Initial commit.
[todo.git] / todo_cmdline.ml
1 (* Parse the command line. *)
2
3 open CalendarLib
4
5 open Todo_types
6 open Todo_utils
7
8 open Printf
9
10 let usage = "\
11 todo -- A simple to-do list
12
13   todo [--global-options] [subcommand] [--subcommand-options]
14
15 Subcommands:
16   todo list [--all] [--retired]
17               - List all tasks
18   todo today \"description\" [tag[,tag...]]
19               - Add a simple task for today, with an optional list of tags
20   todo idea \"description\" [tag[,tag...]]
21               - Add an long-term idea
22   todo [todo|task] deadline [--estimate nr_days] \"description\" [tag[,tag...]]
23               - Add a to-do task with a deadline and optional time estimate
24   todo retire ID [ID ...]
25               - Retire one or more tasks using the task #ID
26   todo move ID [today | [todo|task] deadline | ideas | retired | unsorted]
27               - Move a task to another list
28   todo tag ID tag [--del tag]
29               - Add or remove tags from a task
30   todo tag-list
31               - List all tags and their usage
32   todo tag-add tag colour
33               - Create a new tag, with a colour
34   todo tag-del tag
35               - Delete a tag (must be unused)
36   todo tag-colour tag colour
37               - Change the colour of a tag
38
39 Tag colours may be:  black  blue  green  cyan  red  purple  yellow
40
41 Examples:
42   todo today \"Eat breakfast\" personal,food
43   todo task 2019-31-12 \"Prepare for next decade\"
44   todo idea \"Write a to-do manager\" work
45   todo list
46
47 Options:"
48
49 (* Parse the command line. *)
50 let parse_command_line () =
51   let print_version_and_exit () =
52     printf "%s %s\n" Todo_config.package_name Todo_config.package_version;
53     exit 0
54   in
55
56   let list_all = ref false in
57   let list_retired = ref false in
58   let tag_del = ref [] in
59   let add_tag_del tag = tag_del := tag :: !tag_del in
60   let todo_estimate = ref None in
61   let set_todo_estimate days =
62     todo_estimate := Some (Calendar.Period.day days)
63   in
64
65   (* Per-subcommand specs. *)
66   let idea_spec =
67     Arg.align [
68     ] in
69   let list_spec =
70     Arg.align [
71       "--all", Arg.Set list_all, " List all tasks including retired";
72       "--retired", Arg.Set list_retired, " List only retired tasks";
73     ] in
74   let move_spec =
75     Arg.align [
76     ] in
77   let retire_spec =
78     Arg.align [
79     ] in
80   let tag_spec =
81     Arg.align [
82       "--del", Arg.String add_tag_del, "tag Remove tag";
83       "--delete", Arg.String add_tag_del, "tag Remove tag";
84       "--remove", Arg.String add_tag_del, "tag Remove tag";
85       "--rm", Arg.String add_tag_del, "tag Remove tag";
86     ] in
87   let tag_add_spec =
88     Arg.align [
89     ] in
90   let tag_colour_spec =
91     Arg.align [
92     ] in
93   let tag_del_spec =
94     Arg.align [
95     ] in
96   let tag_list_spec =
97     Arg.align [
98     ] in
99   let today_spec =
100     Arg.align [
101     ] in
102   let todo_spec =
103     Arg.align [
104       "--estimate", Arg.Int set_todo_estimate, "days Estimated time taken";
105     ] in
106
107   (* This parses the global parameters before the subcommand. *)
108   let global_spec =
109     Arg.align [
110       "--version", Arg.Unit print_version_and_exit, " Display version and exit";
111     ] in
112   let spec = ref global_spec in
113   let subcommand = ref None in
114   let anon_params = ref [] in
115   let anon s =
116     if !subcommand = None then (
117       (* It's the subcommand, so switch the spec. *)
118       let sc =
119         try subcommand_of_string s
120         with Invalid_argument _ ->
121           raise (Arg.Bad "unknown subcommand") in
122       subcommand := Some sc;
123       match sc with
124       | Idea -> spec := idea_spec
125       | List -> spec := list_spec
126       | Move -> spec := move_spec
127       | Retire -> spec := retire_spec
128       | Tag -> spec := tag_spec
129       | Tag_add -> spec := tag_add_spec
130       | Tag_colour -> spec := tag_colour_spec
131       | Tag_del -> spec := tag_del_spec
132       | Tag_list -> spec := tag_list_spec
133       | Today -> spec := today_spec
134       | Todo -> spec := todo_spec
135     )
136     else
137       (* We've seen the subcommand already, just add it to anon_params. *)
138       anon_params := s :: !anon_params
139   in
140   Arg.parse_dynamic spec anon usage;
141
142   (* Return the parsed command line. *)
143   let subcommand =
144     match !subcommand with
145     | None -> error "no subcommand given"
146     | Some c -> c in
147   let anon_params = List.rev !anon_params in
148
149   let list_retired = !list_retired in
150   let list_all = !list_all in
151   let tag_del = List.rev !tag_del in
152   let todo_estimate = !todo_estimate in
153
154   subcommand, anon_params, list_retired, list_all, tag_del, todo_estimate