├── README.md ├── cmd_add.go ├── cmd_clean.go ├── cmd_delete.go ├── cmd_done.go ├── cmd_list.go ├── cmd_sort.go ├── cmd_undone.go ├── cmd_update.go ├── go.mod ├── go.sum ├── renovate.json └── todo.go /README.md: -------------------------------------------------------------------------------- 1 | # todo 2 | 3 | A simple command-line todo list written in Go. 4 | 5 | ## Usage 6 | 7 | ### List todo 8 | 9 | $ todo list 10 | ☐ 001: Send patch to golang-dev 11 | ☑ 002: Fix bug in vim 12 | ☐ 003: Write new blog entry 13 | 14 | ### Add new todo 15 | 16 | $ todo add Bake cake 17 | $ todo list 18 | ☐ 001: Send patch to golang-dev 19 | ☑ 002: Fix bug in vim 20 | ☐ 003: Write new blog entry 21 | ☐ 004: Bake cake 22 | 23 | ### Update todo 24 | 25 | $ todo update 4 Bake pudding 26 | Task 4 updated with message: Bake pudding 27 | $ todo list 28 | ☐ 001: Send patch to golang-dev 29 | ☑ 002: Fix bug in vim 30 | ☐ 003: Write new blog entry 31 | ☐ 004: Bake pudding 32 | 33 | ### Delete todo 34 | 35 | $ todo delete 3 36 | $ todo list 37 | ☐ 001: Send patch to golang-dev 38 | ☑ 002: Fix bug in vim 39 | ☐ 003: Bake cake 40 | 41 | ### Done todo 42 | 43 | $ todo done 3 44 | $ todo list 45 | ☐ 001: Send patch to golang-dev 46 | ☑ 002: Fix bug in vim 47 | ☑ 003: Bake cake 48 | 49 | ### Undone todo 50 | 51 | $ todo undone 3 52 | $ todo list 53 | ☐ 001: Send patch to golang-dev 54 | ☑ 002: Fix bug in vim 55 | ☐ 003: Bake cake 56 | 57 | ### Sort todo 58 | 59 | $ todo list 60 | ☐ 001: Send patch to golang-dev 61 | ☑ 002: Fix bug in vim 62 | ☐ 003: Bake cake 63 | ☑ 004: Write new blog entry 64 | 65 | $ todo sort 66 | $ todo list 67 | ☑ 001: Fix bug in vim 68 | ☑ 002: Write new blog entry 69 | ☐ 003: Send patch to golang-dev 70 | ☐ 004: Bake cake 71 | 72 | ## Requirements 73 | 74 | - golang 75 | 76 | ## Installation 77 | 78 | go get github.com/mattn/todo 79 | 80 | ## License 81 | 82 | MIT 83 | 84 | ## Author 85 | 86 | Yasuhiro Matsumoto (a.k.a mattn) 87 | -------------------------------------------------------------------------------- /cmd_add.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | "strings" 7 | 8 | "github.com/gonuts/commander" 9 | ) 10 | 11 | func makeCmdAdd(filename string) *commander.Command { 12 | cmdAdd := func(cmd *commander.Command, args []string) error { 13 | if len(args) == 0 { 14 | cmd.Usage() 15 | return nil 16 | } 17 | w, err := os.OpenFile(filename, os.O_APPEND|os.O_CREATE|os.O_RDWR, 0666) 18 | if err != nil { 19 | return err 20 | } 21 | defer w.Close() 22 | task := strings.Join(args, " ") 23 | _, err = fmt.Fprintln(w, task) 24 | fmt.Printf("Task added: %s\n", task) 25 | return err 26 | } 27 | 28 | return &commander.Command{ 29 | Run: cmdAdd, 30 | UsageLine: "add [message]", 31 | Short: "add new todo", 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /cmd_clean.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bufio" 5 | "fmt" 6 | "io" 7 | "os" 8 | "strings" 9 | 10 | "github.com/gonuts/commander" 11 | ) 12 | 13 | func makeCmdClean(filename string) *commander.Command { 14 | cmdClean := func(cmd *commander.Command, args []string) error { 15 | if len(args) != 0 { 16 | cmd.Usage() 17 | return nil 18 | } 19 | w, err := os.Create(filename + "_") 20 | if err != nil { 21 | return err 22 | } 23 | defer w.Close() 24 | f, err := os.Open(filename) 25 | if err != nil { 26 | return err 27 | } 28 | br := bufio.NewReader(f) 29 | for { 30 | b, _, err := br.ReadLine() 31 | if err != nil { 32 | if err != io.EOF { 33 | return err 34 | } 35 | break 36 | } 37 | line := string(b) 38 | if !strings.HasPrefix(line, "-") { 39 | _, err = fmt.Fprintf(w, "%s\n", line) 40 | if err != nil { 41 | return err 42 | } 43 | } 44 | } 45 | f.Close() 46 | w.Close() 47 | err = os.Remove(filename) 48 | if err != nil { 49 | return err 50 | } 51 | return os.Rename(filename+"_", filename) 52 | } 53 | 54 | return &commander.Command{ 55 | Run: cmdClean, 56 | UsageLine: "clean", 57 | Short: "remove all done items", 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /cmd_delete.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bufio" 5 | "fmt" 6 | "io" 7 | "os" 8 | "strconv" 9 | 10 | "github.com/gonuts/commander" 11 | ) 12 | 13 | func makeCmdDelete(filename string) *commander.Command { 14 | cmdDelete := func(cmd *commander.Command, args []string) error { 15 | if len(args) == 0 { 16 | cmd.Usage() 17 | return nil 18 | } 19 | var ids []int 20 | for _, arg := range args { 21 | id, err := strconv.Atoi(arg) 22 | if err != nil { 23 | return err 24 | } 25 | ids = append(ids, id) 26 | } 27 | w, err := os.Create(filename + "_") 28 | if err != nil { 29 | return err 30 | } 31 | defer w.Close() 32 | f, err := os.Open(filename) 33 | if err != nil { 34 | return err 35 | } 36 | br := bufio.NewReader(f) 37 | for n := 1; ; n++ { 38 | b, _, err := br.ReadLine() 39 | if err != nil { 40 | if err != io.EOF { 41 | return err 42 | } 43 | break 44 | } 45 | match := false 46 | for _, id := range ids { 47 | if id == n { 48 | match = true 49 | } 50 | } 51 | if match { 52 | fmt.Printf("Task deleted: %s\n", string(b)) 53 | } else { 54 | _, err = fmt.Fprintf(w, "%s\n", string(b)) 55 | if err != nil { 56 | return err 57 | } 58 | } 59 | } 60 | f.Close() 61 | w.Close() 62 | err = os.Remove(filename) 63 | if err != nil { 64 | return err 65 | } 66 | return os.Rename(filename+"_", filename) 67 | } 68 | 69 | return &commander.Command{ 70 | Run: cmdDelete, 71 | UsageLine: "delete [ID]", 72 | Short: "delete the todo", 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /cmd_done.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bufio" 5 | "fmt" 6 | "io" 7 | "os" 8 | "strconv" 9 | "strings" 10 | 11 | "github.com/gonuts/commander" 12 | ) 13 | 14 | func makeCmdDone(filename string) *commander.Command { 15 | cmdDone := func(cmd *commander.Command, args []string) error { 16 | if len(args) == 0 { 17 | cmd.Usage() 18 | return nil 19 | } 20 | var ids []int 21 | for _, arg := range args { 22 | id, err := strconv.Atoi(arg) 23 | if err != nil { 24 | return err 25 | } 26 | ids = append(ids, id) 27 | } 28 | w, err := os.Create(filename + "_") 29 | if err != nil { 30 | return err 31 | } 32 | defer w.Close() 33 | f, err := os.Open(filename) 34 | if err != nil { 35 | return err 36 | } 37 | br := bufio.NewReader(f) 38 | for n := 1; ; n++ { 39 | b, _, err := br.ReadLine() 40 | if err != nil { 41 | if err != io.EOF { 42 | return err 43 | } 44 | break 45 | } 46 | match := false 47 | for _, id := range ids { 48 | if id == n { 49 | match = true 50 | } 51 | } 52 | line := strings.TrimSpace(string(b)) 53 | if match && !strings.HasPrefix(line, "-") { 54 | _, err = fmt.Fprintf(w, "-%s\n", line) 55 | if err != nil { 56 | return err 57 | } 58 | fmt.Printf("Task done: %s\n", line) 59 | } else { 60 | _, err = fmt.Fprintf(w, "%s\n", line) 61 | if err != nil { 62 | return err 63 | } 64 | } 65 | } 66 | f.Close() 67 | w.Close() 68 | err = os.Remove(filename) 69 | if err != nil { 70 | return err 71 | } 72 | return os.Rename(filename+"_", filename) 73 | } 74 | 75 | return &commander.Command{ 76 | Run: cmdDone, 77 | UsageLine: "done [ID]", 78 | Short: "done the todo", 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /cmd_list.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bufio" 5 | "fmt" 6 | "io" 7 | "os" 8 | "strings" 9 | 10 | "github.com/gonuts/commander" 11 | "github.com/gonuts/flag" 12 | ) 13 | 14 | const ( 15 | doneMark1 = "\u2610" 16 | doneMark2 = "\u2611" 17 | ) 18 | 19 | func makeCmdList(filename string) *commander.Command { 20 | cmdList := func(cmd *commander.Command, args []string) error { 21 | nflag := cmd.Flag.Lookup("n").Value.Get().(bool) 22 | f, err := os.Open(filename) 23 | if err != nil { 24 | return err 25 | } 26 | defer f.Close() 27 | br := bufio.NewReader(f) 28 | n := 1 29 | for { 30 | b, _, err := br.ReadLine() 31 | if err != nil { 32 | if err != io.EOF { 33 | return err 34 | } 35 | break 36 | } 37 | line := string(b) 38 | if strings.HasPrefix(line, "-") { 39 | if !nflag { 40 | fmt.Printf("%s %03d: %s\n", doneMark2, n, strings.TrimSpace(line[1:])) 41 | } 42 | } else { 43 | fmt.Printf("%s %03d: %s\n", doneMark1, n, strings.TrimSpace(line)) 44 | } 45 | n++ 46 | 47 | } 48 | return nil 49 | } 50 | 51 | flg := *flag.NewFlagSet("list", flag.ExitOnError) 52 | flg.Bool("n", false, "only not done") 53 | 54 | return &commander.Command{ 55 | Run: cmdList, 56 | UsageLine: "list [options]", 57 | Short: "show list index", 58 | Flag: flg, 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /cmd_sort.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bufio" 5 | "bytes" 6 | "fmt" 7 | "io" 8 | "os" 9 | "strings" 10 | 11 | "github.com/gonuts/commander" 12 | ) 13 | 14 | func makeCmdSort(filename string) *commander.Command { 15 | cmdSort := func(cmd *commander.Command, args []string) error { 16 | if len(args) != 0 { 17 | cmd.Usage() 18 | return nil 19 | } 20 | var bottom bytes.Buffer 21 | w, err := os.Create(filename + "_") 22 | if err != nil { 23 | return err 24 | } 25 | defer w.Close() 26 | f, err := os.Open(filename) 27 | if err != nil { 28 | return err 29 | } 30 | defer f.Close() 31 | br := bufio.NewReader(f) 32 | for { 33 | b, _, err := br.ReadLine() 34 | if err != nil { 35 | if err != io.EOF { 36 | return err 37 | } 38 | break 39 | } 40 | line := string(b) 41 | if !strings.HasPrefix(line, "-") { 42 | _, err = fmt.Fprintf(&bottom, "%s\n", line) 43 | if err != nil { 44 | return err 45 | } 46 | } else { 47 | _, err = fmt.Fprintf(w, "%s\n", line) 48 | if err != nil { 49 | return err 50 | } 51 | } 52 | } 53 | _, err = bottom.WriteTo(w) 54 | if err != nil { 55 | return err 56 | } 57 | err = os.Remove(filename) 58 | if err != nil { 59 | return err 60 | } 61 | return os.Rename(filename+"_", filename) 62 | } 63 | 64 | return &commander.Command{ 65 | Run: cmdSort, 66 | UsageLine: "sort", 67 | Short: "sorts done to the top and undone to the bottom", 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /cmd_undone.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bufio" 5 | "fmt" 6 | "io" 7 | "os" 8 | "strconv" 9 | "strings" 10 | 11 | "github.com/gonuts/commander" 12 | ) 13 | 14 | func makeCmdUndone(filename string) *commander.Command { 15 | cmdUndone := func(cmd *commander.Command, args []string) error { 16 | if len(args) == 0 { 17 | cmd.Usage() 18 | return nil 19 | } 20 | var ids []int 21 | for _, arg := range args { 22 | id, err := strconv.Atoi(arg) 23 | if err != nil { 24 | return err 25 | } 26 | ids = append(ids, id) 27 | } 28 | w, err := os.Create(filename + "_") 29 | if err != nil { 30 | return err 31 | } 32 | defer w.Close() 33 | f, err := os.Open(filename) 34 | if err != nil { 35 | return err 36 | } 37 | br := bufio.NewReader(f) 38 | for n := 1; ; n++ { 39 | b, _, err := br.ReadLine() 40 | if err != nil { 41 | if err != io.EOF { 42 | return err 43 | } 44 | break 45 | } 46 | match := false 47 | for _, id := range ids { 48 | if id == n { 49 | match = true 50 | } 51 | } 52 | line := strings.TrimSpace(string(b)) 53 | if match && strings.HasPrefix(line, "-") { 54 | _, err = fmt.Fprintf(w, "%s\n", line[1:]) 55 | if err != nil { 56 | return err 57 | } 58 | fmt.Printf("Task undone: %s\n", line[1:]) 59 | } else { 60 | _, err = fmt.Fprintf(w, "%s\n", line) 61 | if err != nil { 62 | return err 63 | } 64 | } 65 | } 66 | f.Close() 67 | w.Close() 68 | err = os.Remove(filename) 69 | if err != nil { 70 | return err 71 | } 72 | return os.Rename(filename+"_", filename) 73 | } 74 | 75 | return &commander.Command{ 76 | Run: cmdUndone, 77 | UsageLine: "undone [ID]", 78 | Short: "undone the todo", 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /cmd_update.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bufio" 5 | "fmt" 6 | "io" 7 | "os" 8 | "strconv" 9 | "strings" 10 | 11 | "github.com/gonuts/commander" 12 | ) 13 | 14 | func makeCmdUpdate(filename string) *commander.Command { 15 | cmdUpdate := func(cmd *commander.Command, args []string) error { 16 | if len(args) < 2 { 17 | cmd.Usage() 18 | return nil 19 | } 20 | 21 | id, err := strconv.Atoi(args[0]) 22 | if err != nil { 23 | return err 24 | } 25 | task := strings.Join(args[1:], " ") 26 | 27 | w, err := os.Create(filename + "_") 28 | if err != nil { 29 | return err 30 | } 31 | defer w.Close() 32 | f, err := os.Open(filename) 33 | if err != nil { 34 | return err 35 | } 36 | br := bufio.NewReader(f) 37 | for n := 1; ; n++ { 38 | b, _, err := br.ReadLine() 39 | if err != nil { 40 | if err != io.EOF { 41 | return err 42 | } 43 | break 44 | } 45 | match := false 46 | if id == n { 47 | match = true 48 | } 49 | 50 | originalTask := string(b) 51 | if match { 52 | hasCompleted := strings.HasPrefix(originalTask, "-") 53 | if hasCompleted { 54 | task = "-" + task 55 | } 56 | _, err = fmt.Fprintf(w, "%s\n", task) 57 | if err != nil { 58 | return err 59 | } 60 | fmt.Printf("Task %d updated with message: %s\n", id, task[1:]) 61 | } else { 62 | _, err = fmt.Fprintf(w, "%s\n", originalTask) 63 | if err != nil { 64 | return err 65 | } 66 | } 67 | } 68 | f.Close() 69 | w.Close() 70 | err = os.Remove(filename) 71 | if err != nil { 72 | return err 73 | } 74 | return os.Rename(filename+"_", filename) 75 | } 76 | 77 | return &commander.Command{ 78 | Run: cmdUpdate, 79 | UsageLine: "update [ID] [message]", 80 | Short: "update todo", 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/mattn/todo 2 | 3 | go 1.12 4 | 5 | require ( 6 | github.com/gonuts/commander v0.1.0 7 | github.com/gonuts/flag v0.1.0 8 | ) 9 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/gonuts/commander v0.1.0 h1:EcDTiVw9oAVORFjQOEOuHQqcl6OXMyTgELocTq6zJ0I= 2 | github.com/gonuts/commander v0.1.0/go.mod h1:qkb5mSlcWodYgo7vs8ulLnXhfinhZsZcm6+H/z1JjgY= 3 | github.com/gonuts/flag v0.1.0 h1:fqMv/MZ+oNGu0i9gp0/IQ/ZaPIDoAZBOBaJoV7viCWM= 4 | github.com/gonuts/flag v0.1.0/go.mod h1:ZTmTGtrSPejTo/SRNhCqwLTmiAgyBdCkLYhHrAoBdz4= 5 | -------------------------------------------------------------------------------- /renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": [ 3 | "config:base" 4 | ] 5 | } 6 | -------------------------------------------------------------------------------- /todo.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | "path/filepath" 7 | 8 | "github.com/gonuts/commander" 9 | ) 10 | 11 | const ( 12 | todoFilename = ".todo" 13 | ) 14 | 15 | func main() { 16 | filename := "" 17 | existCurTodo := false 18 | curDir, err := os.Getwd() 19 | if err == nil { 20 | filename = filepath.Join(curDir, todoFilename) 21 | _, err = os.Stat(filename) 22 | if err == nil { 23 | existCurTodo = true 24 | } 25 | } 26 | if !existCurTodo { 27 | home := os.Getenv("HOME") 28 | if home == "" { 29 | home = os.Getenv("USERPROFILE") 30 | } 31 | filename = filepath.Join(home, todoFilename) 32 | } 33 | command := &commander.Command{ 34 | UsageLine: os.Args[0], 35 | Short: "todo for cli", 36 | } 37 | command.Subcommands = []*commander.Command{ 38 | makeCmdList(filename), 39 | makeCmdAdd(filename), 40 | makeCmdUpdate(filename), 41 | makeCmdDelete(filename), 42 | makeCmdDone(filename), 43 | makeCmdUndone(filename), 44 | makeCmdClean(filename), 45 | makeCmdSort(filename), 46 | } 47 | err = command.Dispatch(os.Args[1:]) 48 | if err != nil { 49 | fmt.Printf("%v\n", err) 50 | os.Exit(1) 51 | } 52 | } 53 | --------------------------------------------------------------------------------