package main import ( "bufio" "bytes" "fmt" "os" "strings" "time" ) type ( DB interface { HistoryOf(IDU) map[IDQ][]Answer Next(IDU, IDQ) time.Time Question(IDQ) Question LastAnswer(IDU, IDQ) (IDA, Answer) Answer(IDA) Answer PushAnswer(IDU, IDQ, Renderable, bool) error UserResolution(IDU) int Close() } Question struct { Q Renderable Solution []Renderable Clues []Renderable Tags []IDT } Answer struct { Q IDQ A Renderable TS int64 Author IDU Pass bool } IDA string IDQ string IDU string IDT string Renderable string ) var ( CMD_PASS = "y" CMD_FAIL = "n" CMD_SKIP = "s" CMD_QUIT = "q" ) func main() { if err := Main(); err != nil { panic(err) } } func Main() error { db, err := NewDB() if err != nil { return err } defer db.Close() user := IDU(os.Getenv("USER")) failed, err := Review(db, user) if err != nil { return err } for len(failed) > 0 { for i := len(failed) - 1; i >= 0; i-- { if _, passed, err := ReviewQ(db, user, failed[i]); err != nil { return err } else if passed == CMD_PASS { return nil } else if passed != CMD_FAIL { failed = append(failed[:i], failed[i+1:]...) } } } return nil } func Review(db DB, user IDU) ([]IDQ, error) { failed := []IDQ{} for q, _ := range db.HistoryOf(user) { if time.Until(db.Next(user, q)) > 0 { continue } response, passed, err := ReviewQ(db, user, q) if err != nil { return nil, err } switch passed { case CMD_QUIT: return nil, nil case CMD_FAIL: failed = append(failed, q) if err := db.PushAnswer(user, q, Renderable(response), false); err != nil { return nil, err } case CMD_PASS: if err := db.PushAnswer(user, q, Renderable(response), true); err != nil { return nil, err } } fmt.Println() } return failed, nil } func ReviewQ(db DB, user IDU, q IDQ) (string, string, error) { question := db.Question(q) fmt.Printf("> Q: %s\n", question.Q.ToString(db.UserResolution(user))) fmt.Printf("\n") fmt.Printf("> %+v\n", question.Tags) var response string for i := range question.Clues { if i == 0 { fmt.Printf("> /clue for a clue\n") } response = readline() if response != "/clue" { break } fmt.Printf("> %s", question.Clues[i].ToString(db.UserResolution(user))) if i+1 < len(question.Clues) { fmt.Printf(" | /clue for another clue") } fmt.Printf("\n") } if len(question.Clues) == 0 || response == "/clue" { response = readline() } if id, _ := db.LastAnswer(user, q); id == "" { } else if lastAnswer := db.Answer(id); lastAnswer.A != "" { fmt.Printf("> Last time, you responded:\n\t%s\n", lastAnswer.A) } fmt.Printf("> Did you pass this time? [%s%s%s%s]\n", strings.ToUpper(CMD_PASS), strings.ToLower(CMD_FAIL), strings.ToLower(CMD_SKIP), strings.ToLower(CMD_QUIT), ) switch strings.ToLower(readline()) { case CMD_SKIP: return response, CMD_SKIP, nil case CMD_FAIL: return response, CMD_FAIL, nil case CMD_QUIT: return response, CMD_QUIT, nil } return response, CMD_PASS, nil } func readline() string { fmt.Printf("\t") reader := bufio.NewReader(os.Stdin) text, _ := reader.ReadString('\n') return strings.TrimSpace(text) } func NewDB() (DB, error) { return newYamlDB(os.Getenv("DB")) } func (q Question) Tagged(tag IDT) bool { for i := range q.Tags { if q.Tags[i] == tag { return true } } return false } func (renderable Renderable) ToString(resolution int) string { s := string(renderable) if !strings.HasPrefix(s, "img:") { return s } buff := bytes.NewBuffer(nil) ViewAt(buff, s[4:], resolution) return string(buff.Bytes()) }