doing 1.0.52 → 1.0.57

Sign up to get free protection for your applications and to get access to all the features.
Files changed (6) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +35 -13
  3. data/bin/doing +751 -577
  4. data/lib/doing/version.rb +1 -1
  5. data/lib/doing/wwid.rb +221 -86
  6. metadata +2 -2
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: ab98c4f6aa3c2c5d3ccdb29a7b5f78a5f8adc67d12e9628e81b53316a10bc66c
4
- data.tar.gz: dbe47b1b6b15d3d067a34f77b51b5f44b6137bd6f3cd89eccf4dd1909681ad32
3
+ metadata.gz: 749d71c6201256bf524d3ba0f543e11b7bcc421ee97bf951bd003242edba3847
4
+ data.tar.gz: 2ec5d4cade4b81614bf336fe6ddf63b35b9bbe0064c5c910b9f275e7f8e0945f
5
5
  SHA512:
6
- metadata.gz: 31b94d0107fae62ca3f8b791173d3ea36be2ba8a09f6a4c27f1c75ac9f75a3840f83d0ce13eb11ddcaee05d305e35d5f15acd4d285936cf673e34d320b0db8d5
7
- data.tar.gz: 48e17f344c27e38c82272906f082ee78dbb7be36fcc66b76de1c784ad2ab845ff54398c73624a83eaa883f5780264eb026c4a9b6443f60af7bbb55e06d5e1bb7
6
+ metadata.gz: 2b7d5d7f24ff24701ce94d61311cd4b37ae547258a26193e28d16847d49743b5334e1e4c79145be43819fee1ee67f30d75979d7d26447bbd192a8211c2f63d80
7
+ data.tar.gz: 2b0f4326f8b53a6c0da004a323881bbfff3dad10701a19fd7dc10e5f852e22a541644ac13d6a7c860ab0bac5118520df4366272dd7d93376c1adecbe96e2a152
data/README.md CHANGED
@@ -4,7 +4,6 @@
4
4
 
5
5
  _If you're one of the rare people like me who find this useful, feel free to [buy me some coffee](http://brettterpstra.com/donate/)._
6
6
 
7
-
8
7
  ## Contents
9
8
 
10
9
  - [What and why](#what-and-why)
@@ -16,7 +15,7 @@ _If you're one of the rare people like me who find this useful, feel free to [bu
16
15
  - [Troubleshooting](#troubleshooting)
17
16
  - [Changelog](#changelog)
18
17
 
19
- <!-- end toc -->
18
+ <!--README-->
20
19
 
21
20
  ## What and why
22
21
 
@@ -30,11 +29,9 @@ _Side note:_ I actually use the library behind this utility as part of another s
30
29
 
31
30
  ## Installation
32
31
 
33
- $ [sudo] gem install doing
34
-
35
- To install the _latest_ version, use `--pre`:
32
+ The current version of `doing` is <!--VER-->1.0.56<!--END VER-->.
36
33
 
37
- $ [sudo] gem install --pre doing
34
+ $ [sudo] gem install doing
38
35
 
39
36
  Only use `sudo` if your environment requires it. If you're using the system Ruby on a Mac, for example, it will likely be necessary. If `gem install doing` fails, then run `sudo gem install doing` and provide your administrator password.
40
37
 
@@ -67,8 +64,10 @@ A basic configuration looks like this:
67
64
  default_date_format: '%Y-%m-%d %H:%M'
68
65
  marker_tag: flagged
69
66
  marker_color: yellow
67
+ tags_color: boldcyan
70
68
  default_tags: []
71
- editor_app: TextEdit
69
+ editor_app: TaskPaper
70
+ config_editor_app: Sublime Text
72
71
  :include_notes: true
73
72
  views:
74
73
  color:
@@ -137,7 +136,9 @@ You can rename the section that holds your current tasks. By default, this is `C
137
136
 
138
137
  ### Default editors
139
138
 
140
- The setting `editor_app` only applies to Mac OS X users. It's the default application that the command `doing open` will open your WWID file in. If this is blank, it will be opened by whatever the system default is, or you can use `-a app_name` or `-b bundle_id` to override.
139
+ The setting `editor_app` only applies to Mac OS X users. It's the default application that the command `doing open` will open your WWID file in. Personally, I use `editor_app: TaskPaper`. If this is blank, it will be opened by whatever the system default is for the doing file extension (default is `.md`), or you can use `-a app_name` or `-b bundle_id` to override.
140
+
141
+ You can define a separate app to open the config file in when running `doing config`. The key for this is `config_editor_app`.
141
142
 
142
143
  In the case of the `doing now -e` command, your `$EDITOR` environment variable will be used to complete the entry text and notes. Set it in your `~/.bash_profile` or whatever is appropriate for your system:
143
144
 
@@ -413,6 +414,8 @@ When used with `doing done`, `--back` and `--took` allow time intervals to be ac
413
414
 
414
415
  All of these commands accept a `-e` argument. This opens your command line editor (as defined in the environment variable `$EDITOR`). Add your entry, save the temp file, and close it. The new entry is added. Anything after the first line is included as a note on the entry.
415
416
 
417
+ `doing again` (or `doing resume`) will duplicate the last @done entry (most recently completed) with a new start date (and without the @done tag). To resume the last entry matching specific tags, use `--tag=TAG`. You can specify multiple tags by separating with a comma. Multiple tags are combined with 'AND' by default (all tags must exist on the entry to match), but you can use `--bool=` to set it to 'OR' or 'NOT'. By default the new entry will be added to the same section as the matching entry, but you can specify a section with `--in=SECTION`.
418
+
416
419
  `doing meanwhile` is a special command for creating and finishing tasks that may have other entries come before they're complete. When you create an entry with `doing meanwhile [entry text]`, it will automatically complete the last _@meanwhile_ item (dated _@done_ tag) and add the _@meanwhile_ tag to the new item. This allows time tracking on a more general basis, and still lets you keep track of the smaller things you do while working on an overarching project. The `meanwhile` command accepts `--back [time]` and will backdate the _@done_ tag and start date of the new task at the same time. Running `meanwhile` with no arguments will simply complete the last _@meanwhile_ task.
417
420
 
418
421
  See `doing help meanwhile` for more options.
@@ -438,9 +441,9 @@ You can finish the last entry containing a specific tag or combination of tags u
438
441
 
439
442
  doing finish --tag=work,project1
440
443
 
441
- You can change the boolean using `--tag_bool=OR` (last entry containing any of the specified tags) or `--tag_bool=NOT` (last entry containing none of the tags).
444
+ You can change the boolean using `--bool=OR` (last entry containing any of the specified tags) or `--bool=NOT` (last entry containing none of the tags).
442
445
 
443
- You can also include a `--no-date` switch to add `@done` without a finish date, meaning no time is tracked for the task. `doing cancel` is an alias for this. Like `finish`, `cancel` accepts a count to act on the last X entries, as well as `--archive` and `--section` options.
446
+ You can also include a `--no-date` switch to add `@done` without a finish date, meaning no time is tracked for the task. `doing cancel` is an alias for this. Like `finish`, `cancel` accepts a count to act on the last X entries, as well as `--archive` and `--section` options. `cancel` also accepts the `--tag` and `--bool` flags for tag filtering.
444
447
 
445
448
 
446
449
  ##### Tagging and Autotagging
@@ -492,6 +495,8 @@ This creates a search pattern looking for a string of word characters followed b
492
495
 
493
496
  You can also add notes at the time of entry by using the `-n` or `--note` flag with `doing now`, `doing later`, or `doing done`. If you pass text to any of the creation commands which has multiple lines, everything after the first line break will become the note.
494
497
 
498
+ If a string passed to `now`, `later`, or `done` has a parenthetical at the end, the parenthetical will be removed from the title and its contents added as a note. So `doing now Working on @project1 (Adding some unit tests)` would create an entry titled "Working on @project1" with a note "Adding some unit tests." This is the equivalent of `doing now Working on @project1 -n "Adding some unit tests"`.
499
+
495
500
  #### Displaying entries:
496
501
 
497
502
  show - List all entries
@@ -517,6 +522,18 @@ If you have a use for it, you can use `-o csv` on the show or view commands to o
517
522
 
518
523
  You can also show entries matching a search string with `doing grep` (synonym `doing search`). If you want to search with regular expressions or for an exact match, surround your search query with forward slashes, e.g. `doing search /project name/`. If you pass a search string without slashes, it's treated as a fuzzy search string, meaning matches can be found as long as the characters in the search string are in order and with no more than three other characters between each. By default searches are across all sections, but you can limit it to one with the `-s SECTION_NAME` flag. Searches can be displayed with the default template, or output as HTML, CSV, or JSON.
519
524
 
525
+ ##### Modifying the last entry
526
+
527
+ If you want to make a change to the last entry added, use `doing last -e`. The `-e` flag opens the last entry (including note) in your editor, and when you close your editor, your doing file will be updated with any changes you made to the entry.
528
+
529
+ You can choose the last entry in a specific section by including the `-s` flag, so `doing last -s Later -e` would edit the most recent entry in the Later section.
530
+
531
+ You can also use text search or a tag filter to get an entry earlier than the most recent one. A tag search with `doing last --tag=project1 -e` will edit the last entry tagged `@project1`. Multiple tags can be combined with commas, and you can use `--bool` to specify whether the search is `AND` (matches all tags given), `OR` (matches any tag given), or `NOT` (matches none of the tags).
532
+
533
+ You can edit the last entry that matches a search string with `--search=QUERY`. `QUERY` can either be a raw string, or you can surround it with slashes to search by regex (`doing last --search="/project./" -e`). If the string is raw text, fuzzy matching will be used, so the characters must be in order but can be separated by up to three other characters.
534
+
535
+ Both `--tag` and `--search` can be constrained to a single section with `-s SECTION`.
536
+
520
537
  #### Views
521
538
 
522
539
  view - Display a user-created view
@@ -576,11 +593,14 @@ __Fish:__ See the file [`doing.fish`](https://github.com/ttscoff/doing/blob/mast
576
593
 
577
594
  The LaunchBar action requires that `doing` be available in `/usr/local/bin/doing`. If it's not (because you're using RVM or similar), you'll need to symlink it there. Running the action with Return will show the latest 9 items from Currently, along with any time intervals recorded, and includes a submenu of Timers for each tag.
578
595
 
579
- Pressing Spacebar and typing allows you to add a new entry to currently. You an also trigger a custom show command by typing "show [section/tag]" and hitting return.
596
+ Pressing Spacebar and typing allows you to add a new entry to currently. You an also trigger a custom show command by typing "show [section/tag]" and hitting return. Include any command line flags at the end of the string, and if you add text in parenthesis, it will be processed as a note on the entry.
580
597
 
581
598
  Point of interest, the LaunchBar Action makes use of the `-o json` flag for outputting JSON to the action's script for parsing.
582
599
 
583
- See <https://brettterpstra.com/projects/doing/> for the download.
600
+ <!--GITHUB-->See <https://brettterpstra.com/projects/doing/> for the download.<!--END GITHUB-->
601
+ <!--JEKYLL
602
+ {% download 117 %}
603
+ -->
584
604
 
585
605
  Evan Lovely has [created an Alfred workflow as well](http://www.evanlovely.com/blog/technology/alfred-for-terpstras-doing/).
586
606
 
@@ -621,7 +641,9 @@ Please try not to email me directly about GitHub projects.
621
641
 
622
642
  Feel free to [poke around](http://github.com/ttscoff/doing/), I'll try to add more comments in the future (and retroactively).
623
643
 
624
- {% donate doing now sending coffee money to Brett. %}
644
+ <!--END README-->
645
+
646
+ PayPal link: [paypal.me/ttscoff](https://paypal.me/ttscoff)
625
647
 
626
648
  ## Changelog
627
649
 
data/bin/doing CHANGED
@@ -1,4 +1,6 @@
1
1
  #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
2
4
  $LOAD_PATH.unshift File.join(__dir__, '..', 'lib')
3
5
  require 'gli'
4
6
  require 'doing'
@@ -7,9 +9,9 @@ require 'pp'
7
9
 
8
10
  def class_exists?(class_name)
9
11
  klass = Module.const_get(class_name)
10
- return klass.is_a?(Class)
12
+ klass.is_a?(Class)
11
13
  rescue NameError
12
- return false
14
+ false
13
15
  end
14
16
 
15
17
  if class_exists? 'Encoding'
@@ -17,358 +19,352 @@ if class_exists? 'Encoding'
17
19
  Encoding.default_internal = Encoding::UTF_8 if Encoding.respond_to?('default_internal')
18
20
  end
19
21
 
20
-
21
22
  include GLI::App
22
23
  version Doing::VERSION
23
24
 
24
25
  wwid = WWID.new
25
- if Dir.respond_to?('home')
26
- wwid.user_home = Dir.home
27
- else
28
- wwid.user_home = File.expand_path("~")
29
- end
26
+ wwid.user_home = if Dir.respond_to?('home')
27
+ Dir.home
28
+ else
29
+ File.expand_path('~')
30
+ end
30
31
  wwid.configure
31
32
 
32
-
33
33
  program_desc 'A CLI for a What Was I Doing system'
34
34
 
35
35
  default_command :recent
36
36
  # sort_help :manually
37
37
 
38
38
  desc 'Output notes if included in the template'
39
- default_value true
40
- switch [:notes], :default_value => true, :negatable => true
39
+ switch [:notes], default_value: true, negatable: true
41
40
 
42
41
  desc 'Send results report to STDOUT instead of STDERR'
43
- default_value false
44
- switch [:stdout], :default_value => false, :negatable => false
42
+ switch [:stdout], default_value: false, negatable: false
45
43
 
46
44
  desc 'Exclude auto tags and default tags'
47
- switch [:x, :noauto], :default_value => false
45
+ switch %i[x noauto], default_value: false
48
46
 
49
47
  desc 'Use a specific configuration file'
50
- default_value false
51
- flag [:config_file]
52
-
53
-
54
- # desc 'Wrap notes at X chars (0 for no wrap)'
55
- # flag [:w, :wrapwidth], :must_match => /^\d+$/, :type => Integer
48
+ flag [:config_file], default_value: wwid.config_file
56
49
 
57
50
  desc 'Specify a different doing_file'
58
- flag [:f, :doing_file]
51
+ flag %i[f doing_file]
59
52
 
60
53
  desc 'Add an entry'
61
- arg_name 'entry'
54
+ arg_name 'ENTRY'
62
55
  command [:now, :next] do |c|
63
56
  c.desc 'Section'
64
- c.arg_name 'section_name'
65
- c.flag [:s, :section], :default_value => wwid.current_section
57
+ c.arg_name 'NAME'
58
+ c.flag %i[s section], default_value: wwid.current_section
66
59
 
67
60
  c.desc "Edit entry with #{ENV['EDITOR']}"
68
- c.switch [:e, :editor]
61
+ c.switch %i[e editor], negatable: false, default_value: false
69
62
 
70
63
  c.desc 'Backdate start time [4pm|20m|2h|yesterday noon]'
71
- c.flag [:b, :back]
64
+ c.arg_name 'DATE_STRING'
65
+ c.flag %i[b back]
72
66
 
73
67
  c.desc 'Timed entry, marks last entry in section as @done'
74
- c.switch [:f, :finish_last], :negatable => false, :default_value => false
68
+ c.switch %i[f finish_last], negatable: false, default_value: false
75
69
 
76
70
  c.desc 'Note'
77
- c.arg_name 'note_text'
78
- c.flag [:n, :note]
71
+ c.arg_name 'TEXT'
72
+ c.flag %i[n note]
79
73
 
80
74
  # c.desc "Edit entry with specified app"
81
75
  # c.arg_name 'editor_app'
82
76
  # # c.flag [:a, :app]
83
77
 
84
- c.action do |global_options,options,args|
78
+ c.action do |_global_options, options, args|
85
79
  if options[:back]
86
80
  date = wwid.chronify(options[:back])
87
81
 
88
- raise "Unable to parse date string" if date.nil?
82
+ raise 'Unable to parse date string' if date.nil?
89
83
  else
90
84
  date = Time.now
91
85
  end
92
86
 
93
87
  section = wwid.guess_section(options[:s]) || options[:s].cap_first
94
88
 
95
- if options[:e] || (args.length == 0 && STDIN.stat.size == 0)
96
- raise "No EDITOR variable defined in environment" if ENV['EDITOR'].nil?
97
- input = ""
98
- input += args.join(" ") if args.length > 0
99
- input = wwid.fork_editor(input)
100
- if input
101
- title, note = wwid.format_input(input)
102
- note.push(options[:n]) if options[:n]
103
- wwid.add_item(title.cap_first, section, {:note => note, :back => date, :timed => options[:f]})
104
- wwid.write(wwid.doing_file)
105
- else
106
- raise "No content"
107
- end
89
+ if options[:e] || (args.empty? && $stdin.stat.size.zero?)
90
+ raise 'No EDITOR variable defined in environment' if ENV['EDITOR'].nil?
91
+
92
+ input = ''
93
+ input += args.join(' ') unless args.empty?
94
+ input = wwid.fork_editor(input).strip
95
+
96
+ raise 'No content' if input.empty?
97
+
98
+ title, note = wwid.format_input(input)
99
+ note.push(options[:n]) if options[:n]
100
+ wwid.add_item(title.cap_first, section, { note: note, back: date, timed: options[:f] })
101
+ wwid.write(wwid.doing_file)
102
+ elsif args.length.positive?
103
+ title, note = wwid.format_input(args.join(' '))
104
+ note.push(options[:n]) if options[:n]
105
+ wwid.add_item(title.cap_first, section, { note: note, back: date, timed: options[:f] })
106
+ wwid.write(wwid.doing_file)
107
+ elsif $stdin.stat.size.positive?
108
+ title, note = wwid.format_input($stdin.read)
109
+ note.push(options[:n]) if options[:n]
110
+ wwid.add_item(title.cap_first, section, { note: note, back: date, timed: options[:f] })
111
+ wwid.write(wwid.doing_file)
108
112
  else
109
- if args.length > 0
110
- title, note = wwid.format_input(args.join(" "))
111
- note.push(options[:n]) if options[:n]
112
- wwid.add_item(title.cap_first, section, {:note => note, :back => date, :timed => options[:f]})
113
- wwid.write(wwid.doing_file)
114
- elsif STDIN.stat.size > 0
115
- title, note = wwid.format_input(STDIN.read)
116
- note.push(options[:n]) if options[:n]
117
- wwid.add_item(title.cap_first, section, {:note => note, :back => date, :timed => options[:f]})
118
- wwid.write(wwid.doing_file)
119
- else
120
- raise "You must provide content when creating a new entry"
121
- end
113
+ raise 'You must provide content when creating a new entry'
122
114
  end
123
115
  end
124
116
  end
125
117
 
126
118
  desc 'Add a note to the last entry'
127
- long_desc %{
119
+ long_desc %(
128
120
  If -r is provided with no other arguments, the last note is removed. If new content is specified through arguments or STDIN, any previous note will be replaced with the new one.
129
121
 
130
122
  Use -e to load the last entry in a text editor where you can append a note.
131
- }
132
- arg_name 'note_text'
123
+ )
124
+ arg_name 'NOTE_TEXT'
133
125
  command :note do |c|
134
126
  c.desc 'Section'
135
- c.arg_name 'section_name'
136
- c.flag [:s, :section], :default_value => "All"
127
+ c.arg_name 'NAME'
128
+ c.flag %i[s section], default_value: 'All'
137
129
 
138
130
  c.desc "Edit entry with #{ENV['EDITOR']}"
139
- c.switch [:e, :editor], :negatable => false, :default_value => false
131
+ c.switch %i[e editor], negatable: false, default_value: false
140
132
 
141
133
  c.desc "Replace/Remove last entry's note (default append)"
142
- c.switch [:r, :remove], :negatable => false, :default_value => false
134
+ c.switch %i[r remove], negatable: false, default_value: false
143
135
 
144
- c.action do |global_options,options,args|
136
+ c.action do |_global_options, options, args|
145
137
  section = wwid.guess_section(options[:s]) || options[:s].cap_first
146
138
 
147
- if options[:e] || (args.length == 0 && STDIN.stat.size == 0 && !options[:r])
148
- raise "No EDITOR variable defined in environment" if ENV['EDITOR'].nil?
139
+ if options[:e] || (args.empty? && $stdin.stat.size.zero? && !options[:r])
140
+ raise 'No EDITOR variable defined in environment' if ENV['EDITOR'].nil?
149
141
 
150
- input = args.length > 0 ? args.join(" ") : ""
142
+ input = !args.empty? ? args.join(' ') : ''
151
143
 
152
- prev_input = wwid.last_note(section) || ""
153
- if prev_input.class == Array
154
- prev_input = prev_input.join("\n")
155
- end
144
+ prev_input = wwid.last_note(section) || ''
145
+ prev_input = prev_input.join("\n") if prev_input.instance_of?(Array)
156
146
  input = prev_input + input
157
147
 
158
- input = wwid.fork_editor(input)
159
- if input
160
- title, note = wwid.format_input(input)
161
- if note
162
- wwid.note_last(section, note, replace: true)
163
- else
164
- raise "No note content"
165
- end
166
- else
167
- raise "No content, cancelled"
168
- end
148
+ input = wwid.fork_editor(input).strip
149
+ raise 'No content, cancelled' unless input
150
+
151
+ _title, note = wwid.format_input(input)
152
+
153
+ raise 'No note content' unless note
154
+
155
+ wwid.note_last(section, note, replace: true)
156
+ elsif !args.empty?
157
+ title, note = wwid.format_input(args.join(' '))
158
+ note.insert(0, title)
159
+ wwid.note_last(section, note, replace: options[:r])
160
+ elsif $stdin.stat.size.positive?
161
+ title, note = wwid.format_input($stdin.read)
162
+ note.insert(0, title)
163
+ wwid.note_last(section, note, replace: options[:r])
164
+ elsif options[:r]
165
+ wwid.note_last(section, [], replace: true)
169
166
  else
170
- if args.length > 0
171
- title, note = wwid.format_input(args.join(" "))
172
- note.insert(0, title)
173
- wwid.note_last(section, note, replace: options[:r])
174
- elsif STDIN.stat.size > 0
175
- title, note = wwid.format_input(STDIN.read)
176
- note.insert(0, title)
177
- wwid.note_last(section, note, replace: options[:r])
178
- else
179
- if options[:r]
180
- wwid.note_last(section, [], replace: true)
181
- else
182
- raise "You must provide content when adding a note"
183
- end
184
- end
167
+ raise 'You must provide content when adding a note'
185
168
  end
186
169
  wwid.write(wwid.doing_file)
187
170
  end
188
171
  end
189
172
 
190
173
  desc 'Finish any running @meanwhile tasks and optionally create a new one'
191
- arg_name 'entry'
174
+ arg_name 'ENTRY'
192
175
  command :meanwhile do |c|
193
176
  c.desc 'Section'
194
- c.arg_name 'section_name'
195
- c.flag [:s, :section], :default_value => wwid.current_section
177
+ c.arg_name 'NAME'
178
+ c.flag %i[s section], default_value: wwid.current_section
196
179
 
197
180
  c.desc "Edit entry with #{ENV['EDITOR']}"
198
- c.switch [:e, :editor]
181
+ c.switch %i[e editor], negatable: false, default_value: false
199
182
 
200
- c.desc "Archive previous @meanwhile entry"
201
- c.switch [:a, :archive], :default_value => false
183
+ c.desc 'Archive previous @meanwhile entry'
184
+ c.switch %i[a archive], default_value: false
202
185
 
203
186
  c.desc 'Backdate start date for new entry to date string [4pm|20m|2h|yesterday noon]'
204
- c.flag [:b, :back]
187
+ c.arg_name 'DATE_STRING'
188
+ c.flag %i[b back]
205
189
 
206
190
  c.desc 'Note'
207
- c.arg_name 'note_text'
208
- c.flag [:n, :note]
191
+ c.arg_name 'TEXT'
192
+ c.flag %i[n note]
209
193
 
210
- c.action do |global_options,options,args|
194
+ c.action do |_global_options, options, args|
211
195
  if options[:back]
212
196
  date = wwid.chronify(options[:back])
213
197
 
214
- raise "Unable to parse date string" if date.nil?
198
+ raise 'Unable to parse date string' if date.nil?
215
199
  else
216
200
  date = Time.now
217
201
  end
218
202
 
219
203
  section = wwid.guess_section(options[:s]) || options[:s].cap_first
220
- input = ""
204
+ input = ''
221
205
 
222
206
  if options[:e]
223
- raise "No EDITOR variable defined in environment" if ENV['EDITOR'].nil?
224
- input += args.join(" ") if args.length > 0
225
- input = wwid.fork_editor(input)
207
+ raise 'No EDITOR variable defined in environment' if ENV['EDITOR'].nil?
208
+
209
+ input += args.join(' ') unless args.empty?
210
+ input = wwid.fork_editor(input).strip
211
+ elsif !args.empty?
212
+ input = args.join(' ')
213
+ elsif $stdin.stat.size.positive?
214
+ input = $stdin.read
215
+ end
216
+
217
+ if input && !input.empty?
218
+ input, note = wwid.format_input(input)
226
219
  else
227
- if args.length > 0
228
- input = args.join(" ")
229
- elsif STDIN.stat.size > 0
230
- input = STDIN.read
231
- end
220
+ input = nil
221
+ note = []
222
+ end
223
+
224
+ if options[:n]
225
+ note.push(options[:n])
226
+ elsif note.empty?
227
+ note = nil
232
228
  end
233
- input = false unless input && input.length > 0
234
229
 
235
- note = options[:n] ? options[:n] : false
236
- wwid.stop_start('meanwhile', {:new_item => input, :back => date, :section => section, :archive => options[:a], :note => note})
230
+ wwid.stop_start('meanwhile', { new_item: input, back: date, section: section, archive: options[:a], note: note })
237
231
  wwid.write(wwid.doing_file)
238
232
  end
239
233
  end
240
234
 
241
- desc 'Output HTML templates for customization'
242
- long_desc %{
243
- Templates are printed to STDOUT for piping to a file. Save them and use them in the configuration file under html_template. Example `doing templates --type=HAML > ~/styles/my_doing.haml`
244
- }
245
- command :templates do |c|
246
- c.desc "Type of template to output (HAML|CSS)"
247
- c.arg_name 'template_type'
248
- c.flag [:t, :type]
249
-
250
- c.action do |global_options,options,args|
251
- unless options[:type]
252
- raise "No type specified, use --type=[HAML|CSS]"
235
+ desc 'Output HTML and CSS templates for customization'
236
+ long_desc %(
237
+ Templates are printed to STDOUT for piping to a file.
238
+ Save them and use them in the configuration file under html_template.
239
+
240
+ Example `doing template HAML > ~/styles/my_doing.haml`
241
+ )
242
+ arg_name 'TYPE', must_match: /^(html|haml|css)/i
243
+ command :template do |c|
244
+ c.action do |_global_options, options, args|
245
+ raise 'No type specified, use `doing template [HAML|CSS]`' if args.empty?
246
+
247
+ case options[:t]
248
+ when /html|haml/i
249
+ $stdout.puts wwid.haml_template
250
+ when /css/i
251
+ $stdout.puts wwid.css_template
253
252
  else
254
- if options[:t] =~ /html|haml/i
255
- $stdout.puts wwid.haml_template
256
- elsif options[:t] =~ /css/i
257
- $stdout.puts wwid.css_template
258
- else
259
- raise "Invalid type specified, use --type=[HAML|CSS]"
260
- end
253
+ raise 'Invalid type specified, must be HAML or CSS'
261
254
  end
262
255
  end
263
256
  end
264
257
 
265
-
266
258
  desc 'Add an item to the Later section'
267
- arg_name 'entry'
259
+ arg_name 'ENTRY'
268
260
  command :later do |c|
269
261
  c.desc "Edit entry with #{ENV['EDITOR']}"
270
- c.switch [:e, :editor]
262
+ c.switch %i[e editor], negatable: false, default_value: false
271
263
 
272
- c.desc "Edit entry with specified app"
273
- c.arg_name 'editor_app'
274
- c.flag [:a, :app]
264
+ c.desc 'Edit entry with specified app'
265
+ c.arg_name 'APP'
266
+ c.flag %i[a app]
275
267
 
276
268
  c.desc 'Backdate start time to date string [4pm|20m|2h|yesterday noon]'
277
- c.flag [:b, :back]
269
+ c.arg_name 'DATE_STRING'
270
+ c.flag %i[b back]
278
271
 
279
272
  c.desc 'Note'
280
- c.arg_name 'note_text'
281
- c.flag [:n, :note]
273
+ c.arg_name 'TEXT'
274
+ c.flag %i[n note]
282
275
 
283
- c.action do |global_options,options,args|
276
+ c.action do |_global_options, options, args|
284
277
  if options[:back]
285
278
  date = wwid.chronify(options[:back])
286
- raise "Unable to parse date string" if date.nil?
279
+ raise 'Unable to parse date string' if date.nil?
287
280
  else
288
281
  date = Time.now
289
282
  end
290
283
 
291
- if options[:e] || (args.length == 0 && STDIN.stat.size == 0)
292
- raise "No EDITOR variable defined in environment" if ENV['EDITOR'].nil?
293
- input = ""
294
- input += args.join(" ") if args.length > 0
295
- input = wwid.fork_editor(input)
296
- if input
297
- title, note = wwid.format_input(input)
298
- note.push(options[:n]) if options[:n]
299
- wwid.add_item(title.cap_first, "Later", {:note => note, :back => date})
300
- wwid.write(wwid.doing_file)
301
- else
302
- raise "No content"
303
- end
284
+ if options[:e] || (args.empty? && $stdin.stat.size.zero?)
285
+ raise 'No EDITOR variable defined in environment' if ENV['EDITOR'].nil?
286
+
287
+ input = args.empty? ? '' : args.join(' ')
288
+ input = wwid.fork_editor(input).strip
289
+ raise 'No content' unless input && !input.empty?
290
+
291
+ title, note = wwid.format_input(input)
292
+ note.push(options[:n]) if options[:n]
293
+ wwid.add_item(title.cap_first, 'Later', { note: note, back: date })
294
+ wwid.write(wwid.doing_file)
295
+ elsif !args.empty?
296
+ title, note = wwid.format_input(args.join(' '))
297
+ note.push(options[:n]) if options[:n]
298
+ wwid.add_item(title.cap_first, 'Later', { note: note, back: date })
299
+ wwid.write(wwid.doing_file)
300
+ elsif $stdin.stat.size.positive?
301
+ title, note = wwid.format_input($stdin.read)
302
+ note.push(options[:n]) if options[:n]
303
+ wwid.add_item(title.cap_first, 'Later', { note: note, back: date })
304
+ wwid.write(wwid.doing_file)
304
305
  else
305
- if args.length > 0
306
- title, note = wwid.format_input(args.join(" "))
307
- note.push(options[:n]) if options[:n]
308
- wwid.add_item(title.cap_first, "Later", {:note => note, :back => date})
309
- wwid.write(wwid.doing_file)
310
- elsif STDIN.stat.size > 0
311
- title, note = wwid.format_input(STDIN.read)
312
- note.push(options[:n]) if options[:n]
313
- wwid.add_item(title.cap_first, "Later", {:note => note, :back => date})
314
- wwid.write(wwid.doing_file)
315
- else
316
- raise "You must provide content when creating a new entry"
317
- end
306
+ raise 'You must provide content when creating a new entry'
318
307
  end
319
308
  end
320
309
  end
321
310
 
322
311
  desc 'Add a completed item with @done(date). No argument finishes last entry.'
323
- arg_name 'entry'
312
+ arg_name 'ENTRY'
324
313
  command [:done, :did] do |c|
325
314
  c.desc 'Remove @done tag'
326
- c.switch [:r, :remove], :negatable => false, :default_value => false
315
+ c.switch %i[r remove], negatable: false, default_value: false
327
316
 
328
317
  c.desc 'Include date'
329
- c.switch [:date], :negatable => true, :default_value => true
318
+ c.switch [:date], negatable: true, default_value: true
330
319
 
331
320
  c.desc 'Immediately archive the entry'
332
- c.switch [:a, :archive], :negatable => false, :default_value => false
321
+ c.switch %i[a archive], negatable: false, default_value: false
333
322
 
334
- c.desc 'Set finish date to specific date/time (natural langauge parsed, e.g. --at=1:30pm). If used, ignores --back. Used with --took, backdates start date'
323
+ c.desc %(Set finish date to specific date/time (natural langauge parsed, e.g. --at=1:30pm).
324
+ If used, ignores --back. Used with --took, backdates start date)
325
+ c.arg_name 'DATE_STRING'
335
326
  c.flag [:at]
336
327
 
337
328
  c.desc 'Backdate start date by interval [4pm|20m|2h|yesterday noon]'
338
- c.flag [:b, :back]
329
+ c.arg_name 'DATE_STRING'
330
+ c.flag %i[b back]
339
331
 
340
- c.desc 'Set completion date to start date plus interval (XX[mhd] or HH:MM). If used without the --back option, the start date will be moved back to allow the completion date to be the current time.'
341
- c.flag [:t, :took]
332
+ c.desc %(Set completion date to start date plus interval (XX[mhd] or HH:MM).
333
+ If used without the --back option, the start date will be moved back to allow
334
+ the completion date to be the current time.)
335
+ c.arg_name 'INTERVAL'
336
+ c.flag %i[t took]
342
337
 
343
338
  c.desc 'Section'
344
- c.flag [:s, :section], :default_value => wwid.current_section
339
+ c.arg_name 'NAME'
340
+ c.flag %i[s section], default_value: wwid.current_section
345
341
 
346
342
  c.desc "Edit entry with #{ENV['EDITOR']}"
347
- c.switch [:e, :editor]
343
+ c.switch %i[e editor], negatable: false, default_value: false
348
344
 
349
345
  # c.desc "Edit entry with specified app"
350
346
  # c.arg_name 'editor_app'
351
347
  # # c.flag [:a, :app]
352
348
 
353
- c.action do |global_options,options,args|
349
+ c.action do |_global_options, options, args|
354
350
  took = 0
355
351
 
356
-
357
352
  if options[:took]
358
353
  took = wwid.chronify_qty(options[:took])
359
- raise "Unable to parse date string for --took" if took.nil?
354
+ raise 'Unable to parse date string for --took' if took.nil?
360
355
  end
361
356
 
362
357
  if options[:back]
363
358
  date = wwid.chronify(options[:back])
364
- raise "Unable to parse date string for --back" if date.nil?
359
+ raise 'Unable to parse date string for --back' if date.nil?
365
360
  else
366
361
  date = options[:took] ? Time.now - took : Time.now
367
362
  end
368
363
 
369
364
  if options[:at]
370
365
  finish_date = wwid.chronify(options[:at])
371
- raise "Unable to parse date string for --at" if finish_date.nil?
366
+ raise 'Unable to parse date string for --at' if finish_date.nil?
367
+
372
368
  date = options[:took] ? finish_date - took : finish_date
373
369
  elsif options[:took]
374
370
  finish_date = date + took
@@ -379,113 +375,147 @@ command [:done, :did] do |c|
379
375
  end
380
376
 
381
377
  section = wwid.guess_section(options[:s]) || options[:s].cap_first
382
- donedate = options[:date] ? "(#{finish_date.strftime('%F %R')})" : ""
378
+ donedate = options[:date] ? "(#{finish_date.strftime('%F %R')})" : ''
383
379
 
384
380
  if options[:e]
385
- raise "No EDITOR variable defined in environment" if ENV['EDITOR'].nil?
386
- input = ""
387
- input += args.join(" ") if args.length > 0
388
- input = wwid.fork_editor(input)
389
- if input
390
- title, note = wwid.format_input(input)
391
- title += " @done#{donedate}"
392
- section = "Archive" if options[:a]
393
- wwid.add_item(title.cap_first, section.cap_first, {:note => note, :back => date})
394
- wwid.write(wwid.doing_file)
395
- else
396
- raise "No content"
397
- end
398
- elsif args.length == 0 && STDIN.stat.size == 0
381
+ raise 'No EDITOR variable defined in environment' if ENV['EDITOR'].nil?
382
+
383
+ input = ''
384
+ input += args.join(' ') unless args.empty?
385
+ input = wwid.fork_editor(input).strip
386
+ raise 'No content' unless input && !input.empty?
387
+
388
+ title, note = wwid.format_input(input)
389
+ title += " @done#{donedate}"
390
+ section = 'Archive' if options[:a]
391
+ wwid.add_item(title.cap_first, section.cap_first, { note: note, back: date })
392
+ wwid.write(wwid.doing_file)
393
+ elsif args.empty? && $stdin.stat.size.zero?
399
394
  if options[:r]
400
- wwid.tag_last({:tags => ["done"], :count => 1, :section => section, :remove => true })
395
+ wwid.tag_last({ tags: ['done'], count: 1, section: section, remove: true })
401
396
  else
402
- wwid.tag_last({:tags => ["done"], :count => 1, :section => section, :archive => options[:a], :back => finish_date, :date => options[:date]})
397
+ wwid.tag_last({ tags: ['done'], count: 1, section: section, archive: options[:a], back: finish_date,
398
+ date: options[:date] })
403
399
  end
400
+ elsif !args.empty?
401
+ title, note = wwid.format_input(args.join(' '))
402
+ title.chomp!
403
+ title += " @done#{donedate}"
404
+ section = 'Archive' if options[:a]
405
+ wwid.add_item(title.cap_first, section.cap_first, { note: note, back: date })
406
+ wwid.write(wwid.doing_file)
407
+ elsif $stdin.stat.size.positive?
408
+ title, note = wwid.format_input($stdin.read)
409
+ title += " @done#{donedate}"
410
+ section = options[:a] ? 'Archive' : section
411
+ wwid.add_item(title.cap_first, section.cap_first, { note: note, back: date })
412
+ wwid.write(wwid.doing_file)
404
413
  else
405
- if args.length > 0
406
- title, note = wwid.format_input(args.join(" "))
407
- title.chomp!
408
- title += " @done#{donedate}"
409
- section = "Archive" if options[:a]
410
- wwid.add_item(title.cap_first, section.cap_first, {:note => note, :back => date})
411
- wwid.write(wwid.doing_file)
412
- elsif STDIN.stat.size > 0
413
- title, note = wwid.format_input(STDIN.read)
414
- title += " @done#{donedate}"
415
- section = options[:a] ? "Archive" : section
416
- wwid.add_item(title.cap_first, section.cap_first, {:note => note, :back => date})
417
- wwid.write(wwid.doing_file)
418
- else
419
- raise "You must provide content when creating a new entry"
420
- end
414
+ raise 'You must provide content when creating a new entry'
421
415
  end
422
416
  end
423
417
  end
424
418
 
425
419
  desc 'End last X entries with no time tracked'
426
420
  long_desc 'Adds @done tag without datestamp so no elapsed time is recorded. Alias for `doing finish --no-date`.'
427
- arg_name 'count'
421
+ arg_name 'COUNT'
428
422
  command :cancel do |c|
429
423
  c.desc 'Archive entries'
430
- c.switch [:a, :archive], :negatable => false, :default_value => false
424
+ c.switch %i[a archive], negatable: false, default_value: false
431
425
 
432
426
  c.desc 'Section'
433
- c.flag [:s, :section], :default_value => wwid.current_section
427
+ c.arg_name 'NAME'
428
+ c.flag %i[s section], default_value: wwid.current_section
434
429
 
435
- c.action do |global_options,options,args|
430
+ c.desc 'Cancel the last X entries containing TAG. Separate multiple tags with comma (--tag=tag1,tag2)'
431
+ c.arg_name 'TAG'
432
+ c.flag [:tag]
433
+
434
+ c.desc 'Boolean (AND|OR|NOT) with which to combine multiple tag filters'
435
+ c.arg_name 'BOOLEAN'
436
+ c.flag [:bool], must_match: /^(and|or|not)$/i, default_value: 'AND'
436
437
 
438
+ c.action do |_global_options, options, args|
437
439
  section = wwid.guess_section(options[:s]) || options[:s].cap_first
438
440
 
439
- if args.length > 1
440
- raise "Only one argument allowed"
441
- elsif args.length == 0 || args[0] =~ /\d+/
442
- count = args[0] ? args[0].to_i : 1
443
- wwid.tag_last({:tags => ["done"], :count => count, :section => section, :archive => options[:a], :sequential => false, :date => false })
441
+ if options[:tag].nil?
442
+ tags = []
444
443
  else
445
- raise "Invalid argument (specify number of recent items to mark @done)"
444
+ tags = options[:tag].split(/ *, */).map { |t| t.strip.sub(/^@/, '') }
445
+ options[:bool] = options[:bool] =~ /^(and|or|not)$/i ? options[:bool].upcase : 'AND'
446
446
  end
447
+
448
+ raise 'Only one argument allowed' if args.length > 1
449
+
450
+ raise 'Invalid argument (specify number of recent items to mark @done)' unless args.empty? || args[0] =~ /\d+/
451
+
452
+ count = args[0] ? args[0].to_i : 1
453
+ opts = {
454
+ archive: options[:a],
455
+ count: count,
456
+ date: false,
457
+ section: section,
458
+ sequential: false,
459
+ tag: tags,
460
+ tag_bool: options[:bool],
461
+ tags: ['done']
462
+ }
463
+ wwid.tag_last(opts)
447
464
  end
448
465
  end
449
466
 
450
467
  desc 'Mark last X entries as @done'
451
468
  long_desc 'Marks the last X entries with a @done tag and current date. Does not alter already completed entries.'
452
- arg_name 'count'
469
+ arg_name 'COUNT'
453
470
  command :finish do |c|
454
471
  c.desc 'Include date'
455
- c.switch [:date], :negatable => true, :default_value => true
472
+ c.switch [:date], negatable: true, default_value: true
456
473
 
457
474
  c.desc 'Backdate completed date to date string [4pm|20m|2h|yesterday noon]'
458
- c.flag [:b, :back]
475
+ c.arg_name 'DATE_STRING'
476
+ c.flag %i[b back]
459
477
 
460
478
  c.desc 'Set the completed date to the start date plus XX[hmd]'
461
- c.flag [:t, :took]
479
+ c.arg_name 'INTERVAL'
480
+ c.flag %i[t took]
462
481
 
463
- c.desc 'Finish the last X entries containing TAG. Separate multiple tags with comma (--tag=tag2,tag2)'
482
+ c.desc 'Finish the last X entries containing TAG.
483
+ Separate multiple tags with comma (--tag=tag1,tag2), combine with --bool'
484
+ c.arg_name 'TAG'
464
485
  c.flag [:tag]
465
486
 
487
+ c.desc 'Finish the last X entries matching search filter, surround with slashes for regex (e.g. "/query.*/")'
488
+ c.arg_name 'QUERY'
489
+ c.flag [:search]
490
+
466
491
  c.desc 'Boolean (AND|OR|NOT) with which to combine multiple tag filters'
467
- c.flag [:tag_bool], default_value: 'AND'
492
+ c.arg_name 'BOOLEAN'
493
+ c.flag [:bool], must_match: /^(and|or|not)$/i, default_value: 'AND'
468
494
 
469
- c.desc 'Auto-generate finish dates from next entry\'s start time. Automatically generate completion dates 1 minute before next start date. --auto overrides the --date and --back parameters.'
470
- c.switch [:auto], :negatable => false, :default_value => false
495
+ c.desc %(Auto-generate finish dates from next entry's start time.
496
+ Automatically generate completion dates 1 minute before next start date.
497
+ --auto overrides the --date and --back parameters.)
498
+ c.switch [:auto], negatable: false, default_value: false
471
499
 
472
500
  c.desc 'Archive entries'
473
- c.switch [:a, :archive], :negatable => false, :default_value => false
501
+ c.switch %i[a archive], negatable: false, default_value: false
474
502
 
475
503
  c.desc 'Section'
476
- c.flag [:s, :section], :default_value => wwid.current_section
477
-
478
- c.action do |global_options,options,args|
504
+ c.arg_name 'NAME'
505
+ c.flag %i[s section], default_value: wwid.current_section
479
506
 
507
+ c.action do |_global_options, options, args|
480
508
  section = wwid.guess_section(options[:s]) || options[:s].cap_first
481
509
 
482
510
  unless options[:auto]
483
- raise "--back and --took cannot be used together" if options[:back] and options[:took]
511
+ raise '--back and --took cannot be used together' if options[:back] && options[:took]
512
+
513
+ raise '--search and --tag cannot be used together' if options[:search] && options[:tag]
484
514
 
485
515
  if options[:back]
486
516
  date = wwid.chronify(options[:back])
487
517
 
488
- raise "Unable to parse date string" if date.nil?
518
+ raise 'Unable to parse date string' if date.nil?
489
519
  elsif options[:took]
490
520
  date = wwid.chronify_qty(options[:took])
491
521
  else
@@ -494,193 +524,244 @@ command :finish do |c|
494
524
  end
495
525
 
496
526
  if options[:tag].nil?
497
- options[:tag] = []
527
+ tags = []
498
528
  else
499
- options[:tag] = options[:tag].split(/,/).map {|tag| tag.strip.sub(/^@/, '') }
500
- if options[:tag_bool] =~ /^(and|or|not)$/i
501
- options[:tag_bool] = options[:tag_bool].upcase
502
- else
503
- options[:tag_bool] = 'AND'
504
- end
529
+ tags = options[:tag].split(/ *, */).map { |t| t.strip.sub(/^@/, '') }
530
+ options[:bool] = options[:bool] =~ /^(and|or|not)$/i ? options[:bool].upcase : 'AND'
505
531
  end
506
532
 
507
- if args.length > 1
508
- raise "Only one argument allowed"
509
- elsif args.length == 0 || args[0] =~ /\d+/
510
- count = args[0] ? args[0].to_i : 1
511
- wwid.tag_last({ tags: ["done"],count: count,section: section,archive: options[:a],sequential: options[:auto],date: options[:date], back: date, tag: options[:tag], tag_bool: options[:tag_bool] })
512
- else
513
- raise "Invalid argument (specify number of recent items to mark @done)"
514
- end
533
+ raise 'Only one argument allowed' if args.length > 1
534
+
535
+ raise 'Invalid argument (specify number of recent items to mark @done)' unless args.length == 0 || args[0] =~ /\d+/
536
+
537
+ count = args[0] ? args[0].to_i : 1
538
+ opts = {
539
+ archive: options[:a],
540
+ back: date,
541
+ count: count,
542
+ date: options[:date],
543
+ search: options[:search],
544
+ section: section,
545
+ sequential: options[:auto],
546
+ tag: tags,
547
+ tag_bool: options[:bool],
548
+ tags: ['done']
549
+ }
550
+ wwid.tag_last(opts)
515
551
  end
516
552
  end
517
553
 
518
554
  desc 'Repeat last entry as new entry'
519
- arg_name 'section'
520
555
  command [:again, :resume] do |c|
521
556
  c.desc 'Section'
522
- c.flag [:s, :section], :default_value => "All"
557
+ c.arg_name 'NAME'
558
+ c.flag %i[s section], default_value: 'All'
523
559
 
524
- c.desc 'Note'
525
- c.arg_name 'note_text'
526
- c.flag [:n, :note]
560
+ c.desc 'Add new entry to section (default: same section as repeated entry)'
561
+ c.arg_name 'SECTION_NAME'
562
+ c.flag [:in]
527
563
 
528
- c.action do |global_options, options, args|
529
- wwid.restart_last({ section: options[:s], note: options[:n] })
564
+ c.desc 'Repeat last entry matching tags. Combine multiple tags with a comma.'
565
+ c.arg_name 'TAG'
566
+ c.flag [:tag]
567
+
568
+ c.desc 'Repeat last entry matching search. Surround with
569
+ slashes for regex (e.g. "/query/").'
570
+ c.arg_name 'QUERY'
571
+ c.flag [:search]
572
+
573
+ c.desc 'Boolean used to combine multiple tags'
574
+ c.arg_name 'BOOLEAN'
575
+ c.flag [:bool], must_match: /^(and|or|not)$/i, default_value: 'ALL'
576
+
577
+ c.desc 'Note'
578
+ c.arg_name 'TEXT'
579
+ c.flag %i[n note]
580
+
581
+ c.action do |_global_options, options, _args|
582
+ tags = options[:tag].nil? ? [] : options[:tag].split(/ *, */).map { |t| t.sub(/^@/, '').strip }
583
+ opts = {
584
+ in: options[:in],
585
+ note: options[:n],
586
+ search: options[:search],
587
+ section: options[:s],
588
+ tag: tags,
589
+ tag_bool: options[:bool]
590
+ }
591
+ wwid.restart_last(opts)
530
592
  end
531
593
  end
532
594
 
533
- desc 'Tag last entry'
534
- arg_name 'tag1 [tag2...]'
595
+ desc 'Add tag(s) to last entry'
596
+ arg_name 'TAG', :multiple
535
597
  command :tag do |c|
536
598
  c.desc 'Section'
537
- c.flag [:s, :section], :default_value => "All"
599
+ c.arg_name 'SECTION_NAME'
600
+ c.flag %i[s section], default_value: 'All'
538
601
 
539
602
  c.desc 'How many recent entries to tag (0 for all)'
540
- c.flag [:c, :count], :default_value => 1
603
+ c.arg_name 'COUNT'
604
+ c.flag %i[c count], default_value: 1
541
605
 
542
606
  c.desc 'Include current date/time with tag'
543
- c.switch [:d, :date], :negatable => false, :default_value => false
607
+ c.switch %i[d date], negatable: false, default_value: false
544
608
 
545
609
  c.desc 'Remove given tag(s)'
546
- c.switch [:r, :remove], :negatable => false, :default_value => false
610
+ c.switch %i[r remove], negatable: false, default_value: false
547
611
 
548
612
  c.desc 'Autotag entries based on autotag configuration in ~/.doingrc'
549
- c.switch [:a, :autotag], :negatable => false, :default_value => false
613
+ c.switch %i[a autotag], negatable: false, default_value: false
550
614
 
551
- c.action do |global_options,options,args|
552
- if args.length == 0 && !options[:a]
553
- raise "You must specify at least one tag"
554
- else
555
-
556
- section = wwid.guess_section(options[:s]) || options[:s].cap_first
615
+ c.action do |_global_options, options, args|
616
+ raise 'You must specify at least one tag' if args.empty? && !options[:a]
557
617
 
558
- unless options[:a]
559
- if args.join("") =~ /,/
560
- tags = args.join("").split(/,/)
561
- else
562
- tags = args.join(" ").split(" ") # in case tags are quoted as one arg
563
- end
618
+ section = wwid.guess_section(options[:s]) || options[:s].cap_first
564
619
 
565
- tags.map!{|tag| tag.sub(/^@/,'').strip }
566
- else
567
- tags = []
568
- end
620
+ if options[:a]
621
+ tags = []
622
+ else
623
+ tags = if args.join('') =~ /,/
624
+ args.join('').split(/,/)
625
+ else
626
+ args.join(' ').split(' ') # in case tags are quoted as one arg
627
+ end
569
628
 
570
- count = options[:c].to_i
629
+ tags.map! { |tag| tag.sub(/^@/, '').strip }
630
+ end
571
631
 
572
- if count == 0
573
- section_q = section == 'All' ? "" : " in section #{section}"
632
+ count = options[:c].to_i
574
633
 
575
- if options[:a]
576
- question = "Are you sure you want to autotag all records#{section_q}"
577
- elsif options[:r]
578
- question = "Are you sure you want to remove #{tags.join(" and ")} from all records#{section_q}"
579
- else
580
- question = "Are you sure you want to add #{tags.join(" and ")} to all records#{section_q}"
581
- end
634
+ if count.zero?
635
+ section_q = section == 'All' ? '' : " in section #{section}"
582
636
 
583
- res = wwid.yn(question, default_response: false)
637
+ question = if options[:a]
638
+ "Are you sure you want to autotag all records#{section_q}"
639
+ elsif options[:r]
640
+ "Are you sure you want to remove #{tags.join(' and ')} from all records#{section_q}"
641
+ else
642
+ "Are you sure you want to add #{tags.join(' and ')} to all records#{section_q}"
643
+ end
584
644
 
585
- unless res
586
- raise "Cancelled"
587
- end
588
- end
645
+ res = wwid.yn(question, default_response: false)
589
646
 
590
- wwid.tag_last({:tags => tags, :count => count, :section => section, :date => options[:date], :remove => options[:r], :autotag => options[:a]})
647
+ raise 'Cancelled' unless res
591
648
  end
649
+ opts = {
650
+ autotag: options[:a],
651
+ count: count,
652
+ date: options[:date],
653
+ remove: options[:r],
654
+ section: section,
655
+ tags: tags
656
+ }
657
+ wwid.tag_last(opts)
592
658
  end
593
659
  end
594
660
 
595
661
  desc 'Mark last entry as highlighted'
596
662
  command [:mark, :flag] do |c|
597
663
  c.desc 'Section'
598
- c.flag [:s, :section], :default_value => wwid.current_section
664
+ c.arg_name 'NAME'
665
+ c.flag %i[s section], default_value: wwid.current_section
599
666
 
600
667
  c.desc 'Remove mark'
601
- c.switch [:r, :remove], :negatable => false, :default_value => false
602
-
668
+ c.switch %i[r remove], negatable: false, default_value: false
603
669
 
604
- c.action do |global_options,options,args|
605
- mark = wwid.config['marker_tag'] || "flagged"
606
- wwid.tag_last({:tags => [mark], :section => options[:s], :remove => options[:r]})
670
+ c.action do |_global_options, options, _args|
671
+ mark = wwid.config['marker_tag'] || 'flagged'
672
+ wwid.tag_last({ tags: [mark], section: options[:s], remove: options[:r] })
607
673
  end
608
674
  end
609
675
 
610
676
  desc 'List all entries'
611
- long_desc 'The argument can be a section name, @tag(s) or both. "pick" or "choose" as an argument will offer a section menu.'
612
- arg_name '[section|@tags]'
677
+ long_desc %(
678
+ The argument can be a section name, @tag(s) or both.
679
+ "pick" or "choose" as an argument will offer a section menu.
680
+ )
681
+ arg_name '[SECTION|@TAGS]'
613
682
  command :show do |c|
683
+ c.desc 'Tag filter, combine multiple tags with a comma. Added for compatibility with other commands.'
684
+ c.arg_name 'TAG'
685
+ c.flag [:tag]
686
+
614
687
  c.desc 'Tag boolean (AND,OR,NONE)'
615
- c.flag [:b, :bool], :default_value => "OR"
688
+ c.arg_name 'BOOLEAN'
689
+ c.flag %i[b bool], must_match: /^(and|or|not)$/i, default_value: 'OR'
616
690
 
617
691
  c.desc 'Max count to show'
618
- c.flag [:c, :count], :default_value => 0
692
+ c.arg_name 'MAX'
693
+ c.flag %i[c count], default_value: 0
619
694
 
620
695
  c.desc 'Age (oldest/newest)'
621
- c.flag [:a, :age], :default_value => 'newest'
696
+ c.arg_name 'AGE'
697
+ c.flag %i[a age], default_value: 'newest'
622
698
 
623
699
  c.desc 'Sort order (asc/desc)'
624
- c.flag [:s, :sort], :default_value => 'asc'
700
+ c.arg_name 'ORDER'
701
+ c.flag %i[s sort], must_match: /^(a|d)/i, default_value: 'ASC'
625
702
 
626
- c.desc %{
703
+ c.desc %(
627
704
  Date range to show, or a single day to filter date on.
628
705
  Date range argument should be quoted. Date specifications can be natural language.
629
706
  To specify a range, use "to" or "through": `doing show --from "monday to friday"`
630
- }
631
- c.flag [:f, :from]
707
+ )
708
+ c.arg_name 'DATE_OR_RANGE'
709
+ c.flag %i[f from]
632
710
 
633
711
  c.desc 'Show time intervals on @done tasks'
634
- c.switch [:t, :times], :default_value => true
712
+ c.switch %i[t times], default_value: true
635
713
 
636
714
  c.desc 'Show intervals with totals at the end of output'
637
- c.switch [:totals], :default_value => false, :negatable => true
715
+ c.switch [:totals], default_value: false, negatable: true
638
716
 
639
717
  c.desc 'Sort tags by (name|time)'
640
718
  default = 'time'
641
- if wwid.config.has_key?('tag_sort')
642
- default = wwid.config['tag_sort']
643
- end
644
- c.flag [:tag_sort], :default_value => default
719
+ default = wwid.config['tag_sort'] if wwid.config.key?('tag_sort')
720
+ c.arg_name 'KEY'
721
+ c.flag [:tag_sort], must_match: /^(name|time)/i, default_value: default
645
722
 
646
723
  c.desc 'Only show items with recorded time intervals'
647
- c.switch [:only_timed], :default_value => false, :negatable => false
648
-
649
- c.desc 'Output to export format (csv|html|json)'
650
- c.flag [:o, :output]
651
- c.action do |global_options,options,args|
724
+ c.switch [:only_timed], default_value: false, negatable: false
652
725
 
726
+ c.desc 'Output to export format (csv|html|json|template|timeline)'
727
+ c.arg_name 'FORMAT'
728
+ c.flag %i[o output], must_match: /^(template|html|csv|json|timeline)$/i
729
+ c.action do |_global_options, options, args|
653
730
  tag_filter = false
654
731
  tags = []
655
- if args.length > 0
656
- if args[0] =~ /^all$/i
657
- section = "All"
732
+ if args.length.positive?
733
+ case args[0]
734
+ when /^all$/i
735
+ section = 'All'
658
736
  args.shift
659
- elsif args[0] =~ /^(choose|pick)$/i
737
+ when /^(choose|pick)$/i
660
738
  section = wwid.choose_section
661
739
  args.shift
662
- elsif args[0] =~ /^@/
663
- section = "All"
740
+ when /^@/
741
+ section = 'All'
664
742
  else
665
743
  section = wwid.guess_section(args[0])
666
744
  raise "No such section: #{args[0]}" unless section
745
+
667
746
  args.shift
668
747
  end
669
- if args.length > 0
670
- args.each {|arg|
748
+ if args.length.positive?
749
+ args.each do |arg|
671
750
  if arg =~ /,/
672
- arg.split(/,/).each {|tag|
673
- tags.push(tag.strip.sub(/^@/,''))
674
- }
751
+ arg.split(/,/).each do |tag|
752
+ tags.push(tag.strip.sub(/^@/, ''))
753
+ end
675
754
  else
676
- tags.push(arg.strip.sub(/^@/,''))
755
+ tags.push(arg.strip.sub(/^@/, ''))
677
756
  end
678
- }
757
+ end
679
758
  end
680
759
  else
681
760
  section = wwid.current_section
682
761
  end
683
762
 
763
+ tags.concat(options[:tag].split(/ *, */).map { |t| t.sub(/^@/, '').strip }) if options[:tag]
764
+
684
765
  unless tags.empty?
685
766
  tag_filter = {
686
767
  'tags' => tags,
@@ -698,96 +779,127 @@ command :show do |c|
698
779
  start = wwid.chronify(date_string)
699
780
  finish = false
700
781
  end
701
- exit_now! "Unrecognized date string" unless start
702
- dates = [start,finish]
782
+ exit_now! 'Unrecognized date string' unless start
783
+ dates = [start, finish]
703
784
  end
704
785
 
705
786
  options[:t] = true if options[:totals]
706
787
 
707
- options[:sort_tags] = options[:tag_sort] =~ /^n/i
708
-
709
- puts wwid.list_section({:section => section, :date_filter => dates, :count => options[:c].to_i, :tag_filter => tag_filter, :age => options[:a], :order => options[:s], :output => options[:output], :times => options[:t], :totals => options[:totals], :sort_tags => options[:sort_tags], :highlight => true, :only_timed => options[:only_timed]})
788
+ tags_color = wwid.config.key?('tags_color') ? wwid.config['tags_color'] : nil
710
789
 
790
+ options[:sort_tags] = options[:tag_sort] =~ /^n/i
791
+ opts = {
792
+ age: options[:a],
793
+ count: options[:c].to_i,
794
+ date_filter: dates,
795
+ highlight: true,
796
+ only_timed: options[:only_timed],
797
+ order: options[:s],
798
+ output: options[:output],
799
+ section: section,
800
+ sort_tags: options[:sort_tags],
801
+ tag_filter: tag_filter,
802
+ tags_color: tags_color,
803
+ times: options[:t],
804
+ totals: options[:totals]
805
+ }
806
+ puts wwid.list_section(opts)
711
807
  end
712
808
  end
713
809
 
714
810
  desc 'Search for entries'
715
- long_desc <<-'EODESC'
716
- Search all sections (or limit to a single section) for entries matching text or regular expression. Normal strings are fuzzy matched.
811
+ long_desc <<~'EODESC'
812
+ Search all sections (or limit to a single section) for entries matching text or regular expression. Normal strings are fuzzy matched.
717
813
 
718
- To search with regular expressions, single quote the string and surround with slashes: `doing search '/\bm.*?x\b/'`
814
+ To search with regular expressions, single quote the string and surround with slashes: `doing search '/\bm.*?x\b/'`
719
815
  EODESC
720
816
 
721
- arg_name 'search_pattern'
817
+ arg_name 'SEARCH_PATTERN'
722
818
  command [:grep, :search] do |c|
723
819
  c.desc 'Section'
724
- c.flag [:s, :section], :default_value => "All"
820
+ c.arg_name 'NAME'
821
+ c.flag %i[s section], default_value: 'All'
725
822
 
726
- c.desc 'Output to export format (csv|html|json)'
727
- c.flag [:o, :output]
823
+ c.desc 'Output to export format (csv|html|json|template|timeline)'
824
+ c.arg_name 'FORMAT'
825
+ c.flag %i[o output], must_match: /^(template|html|csv|json|timeline)$/i
728
826
 
729
827
  c.desc 'Show time intervals on @done tasks'
730
- c.switch [:t, :times], :default_value => true
828
+ c.switch %i[t times], default_value: true
731
829
 
732
830
  c.desc 'Show intervals with totals at the end of output'
733
- c.switch [:totals], :default_value => false, :negatable => true
831
+ c.switch [:totals], default_value: false, negatable: true
734
832
 
735
833
  c.desc 'Sort tags by (name|time)'
736
834
  default = 'time'
737
- if wwid.config.has_key?('tag_sort')
738
- default = wwid.config['tag_sort']
739
- end
740
- c.flag [:tag_sort], :default_value => default
835
+ default = wwid.config['tag_sort'] if wwid.config.key?('tag_sort')
836
+ c.arg_name 'KEY'
837
+ c.flag [:tag_sort], must_match: /^(name|time)$/i, default_value: default
741
838
 
742
839
  c.desc 'Only show items with recorded time intervals'
743
- c.switch [:only_timed], :default_value => false, :negatable => false
840
+ c.switch [:only_timed], default_value: false, negatable: false
744
841
 
745
- c.action do |global_options,options,args|
842
+ c.action do |_global_options, options, args|
843
+ tags_color = wwid.config.key?('tags_color') ? wwid.config['tags_color'] : nil
746
844
 
747
845
  section = wwid.guess_section(options[:s]) if options[:s]
748
846
 
749
847
  options[:t] = true if options[:totals]
750
848
  options[:sort_tags] = options[:tag_sort] =~ /^n/i
751
849
 
752
- puts wwid.list_section({:search => args.join(' '), :section => section, :output => options[:output], :times => options[:t], :highlight => true, :totals => options[:totals], :only_timed => options[:only_timed], :sort_tags => options[:sort_tags]})
850
+ opts = {
851
+ highlight: true,
852
+ only_timed: options[:only_timed],
853
+ output: options[:output],
854
+ search: args.join(' '),
855
+ section: section,
856
+ sort_tags: options[:sort_tags],
857
+ tags_color: tags_color,
858
+ times: options[:t],
859
+ totals: options[:totals]
860
+ }
753
861
 
862
+ puts wwid.list_section(opts)
754
863
  end
755
864
  end
756
865
 
757
866
  desc 'List recent entries'
758
867
  default_value 10
759
- arg_name 'count'
868
+ arg_name 'COUNT'
760
869
  command :recent do |c|
761
870
  c.desc 'Section'
762
- c.flag [:s, :section], :default_value => "All"
871
+ c.arg_name 'NAME'
872
+ c.flag %i[s section], default_value: 'All'
763
873
 
764
874
  c.desc 'Show time intervals on @done tasks'
765
- c.switch [:t, :times], :default_value => true
875
+ c.switch %i[t times], default_value: true
766
876
 
767
877
  c.desc 'Show intervals with totals at the end of output'
768
- c.switch [:totals], :default_value => false, :negatable => true
878
+ c.switch [:totals], default_value: false, negatable: true
769
879
 
770
880
  c.desc 'Sort tags by (name|time)'
771
881
  default = 'time'
772
- if wwid.config.has_key?('tag_sort')
773
- default = wwid.config['tag_sort']
774
- end
775
- c.flag [:tag_sort], :default_value => default
776
-
777
- c.action do |global_options,options,args|
882
+ default = wwid.config['tag_sort'] if wwid.config.key?('tag_sort')
883
+ c.arg_name 'KEY'
884
+ c.flag [:tag_sort], must_match: /^(name|time)$/i, default_value: default
778
885
 
886
+ c.action do |global_options, options, args|
779
887
  section = wwid.guess_section(options[:s]) || options[:s].cap_first
780
888
 
781
889
  unless global_options[:version]
782
- if args.length > 0
783
- count = args[0].to_i
784
- else
785
- count = 10
786
- end
890
+ count = args.empty? ? 10 : args[0].to_i
787
891
  options[:t] = true if options[:totals]
788
892
  options[:sort_tags] = options[:tag_sort] =~ /^n/i
893
+ tags_color = wwid.config.key?('tags_color') ? wwid.config['tags_color'] : nil
894
+
895
+ opts = {
896
+ sort_tags: options[:sort_tags],
897
+ tags_color: tags_color,
898
+ times: options[:t],
899
+ totals: options[:totals]
900
+ }
789
901
 
790
- puts wwid.recent(count,section.cap_first,{ :times => options[:t], :totals => options[:totals], :sort_tags => options[:sort_tags] })
902
+ puts wwid.recent(count, section.cap_first, opts)
791
903
 
792
904
  end
793
905
  end
@@ -796,62 +908,64 @@ end
796
908
  desc 'List entries from today'
797
909
  command :today do |c|
798
910
  c.desc 'Specify a section'
799
- c.arg_name 'section_name'
800
- c.flag [:s, :section], :default_value => 'All'
911
+ c.arg_name 'NAME'
912
+ c.flag %i[s section], default_value: 'All'
801
913
 
802
914
  c.desc 'Show time intervals on @done tasks'
803
- c.switch [:t, :times], :default_value => true
915
+ c.switch %i[t times], default_value: true
804
916
 
805
917
  c.desc 'Show time totals at the end of output'
806
- c.switch [:totals], :default_value => false, :negatable => true
918
+ c.switch [:totals], default_value: false, negatable: true
807
919
 
808
920
  c.desc 'Sort tags by (name|time)'
809
921
  default = 'time'
810
- if wwid.config.has_key?('tag_sort')
811
- default = wwid.config['tag_sort']
812
- end
813
- c.flag [:tag_sort], :default_value => default
814
-
815
- c.desc 'Output to export format (csv|html|json)'
816
- c.flag [:o, :output]
922
+ default = wwid.config['tag_sort'] if wwid.config.key?('tag_sort')
923
+ c.arg_name 'KEY'
924
+ c.flag [:tag_sort], must_match: /^(name|time)$/i, default_value: default
817
925
 
818
- c.action do |global_options,options,args|
926
+ c.desc 'Output to export format (csv|html|json|template|timeline)'
927
+ c.arg_name 'FORMAT'
928
+ c.flag %i[o output], must_match: /^(template|html|csv|json|timeline)$/i
819
929
 
930
+ c.action do |_global_options, options, _args|
820
931
  options[:t] = true if options[:totals]
821
932
  options[:sort_tags] = options[:tag_sort] =~ /^n/i
822
933
 
823
- puts wwid.today(options[:t],options[:output],{:totals => options[:totals], :section => options[:s], :sort_tags => options[:sort_tags]}).chomp
824
-
934
+ puts wwid.today(options[:t], options[:output],
935
+ { totals: options[:totals], section: options[:s], sort_tags: options[:sort_tags] }).chomp
825
936
  end
826
937
  end
827
938
 
828
939
  desc 'List entries for a date'
829
- long_desc 'Date argument can be natural language. "thursday" would be interpreted as "last thursday," and "2d" would be interpreted as "two days ago." If you use "to" or "through" between two dates, it will create a range.'
830
- arg_name 'date_string'
940
+ long_desc %(Date argument can be natural language. "thursday" would be interpreted as "last thursday,"
941
+ and "2d" would be interpreted as "two days ago." If you use "to" or "through" between two dates,
942
+ it will create a range.)
943
+ arg_name 'DATE_STRING'
831
944
  command :on do |c|
832
945
  c.desc 'Section'
833
- c.arg_name 'section_name'
834
- c.flag [:s, :section], :default_value => 'All'
946
+ c.arg_name 'NAME'
947
+ c.flag %i[s section], default_value: 'All'
835
948
 
836
949
  c.desc 'Show time intervals on @done tasks'
837
- c.switch [:t, :times], :default_value => true
950
+ c.switch %i[t times], default_value: true
838
951
 
839
952
  c.desc 'Show time totals at the end of output'
840
- c.switch [:totals], :default_value => false, :negatable => true
953
+ c.switch [:totals], default_value: false, negatable: true
841
954
 
842
955
  c.desc 'Sort tags by (name|time)'
843
956
  default = 'time'
844
- if wwid.config.has_key?('tag_sort')
845
- default = wwid.config['tag_sort']
846
- end
847
- c.flag [:tag_sort], :default_value => default
957
+ default = wwid.config['tag_sort'] if wwid.config.key?('tag_sort')
958
+ c.arg_name 'KEY'
959
+ c.flag [:tag_sort], must_match: /^(name|time)$/i, default_value: default
848
960
 
849
- c.desc 'Output to export format (csv|html|json)'
850
- c.flag [:o, :output]
961
+ c.desc 'Output to export format (csv|html|json|template|timeline)'
962
+ c.arg_name 'FORMAT'
963
+ c.flag %i[o output], must_match: /^(template|html|csv|json|timeline)$/i
851
964
 
852
- c.action do |global_options,options,args|
965
+ c.action do |_global_options, options, args|
966
+ exit_now! 'Missing date argument' if args.empty?
853
967
 
854
- date_string = args.join(" ")
968
+ date_string = args.join(' ')
855
969
 
856
970
  if date_string =~ / (to|through|thru) /
857
971
  dates = date_string.split(/ (to|through|thru) /)
@@ -862,7 +976,7 @@ command :on do |c|
862
976
  finish = false
863
977
  end
864
978
 
865
- exit_now! "Unrecognized date string" unless start
979
+ exit_now! 'Unrecognized date string' unless start
866
980
 
867
981
  message = "Date interpreted as #{start}"
868
982
  message += " to #{finish}" if finish
@@ -871,56 +985,86 @@ command :on do |c|
871
985
  options[:t] = true if options[:totals]
872
986
  options[:sort_tags] = options[:tag_sort] =~ /^n/i
873
987
 
874
- puts wwid.list_date([start, finish], options[:s], options[:t], options[:output], {:totals => options[:totals], :sort_tags => options[:sort_tags]}).chomp
875
-
988
+ puts wwid.list_date([start, finish], options[:s], options[:t], options[:output],
989
+ { totals: options[:totals], sort_tags: options[:sort_tags] }).chomp
876
990
  end
877
991
  end
878
992
 
879
993
  desc 'List entries from yesterday'
880
994
  command :yesterday do |c|
881
995
  c.desc 'Specify a section'
882
- c.arg_name 'section_name'
883
- c.flag [:s, :section], :default_value => 'All'
996
+ c.arg_name 'NAME'
997
+ c.flag %i[s section], default_value: 'All'
884
998
 
885
- c.desc 'Output to export format (csv|html|json)'
886
- c.flag [:o, :output]
999
+ c.desc 'Output to export format (csv|html|json|template|timeline)'
1000
+ c.arg_name 'FORMAT'
1001
+ c.flag %i[o output], must_match: /^(template|html|csv|json|timeline)$/i
887
1002
 
888
1003
  c.desc 'Show time intervals on @done tasks'
889
- c.switch [:t, :times], :default_value => true
1004
+ c.switch %i[t times], default_value: true
890
1005
 
891
1006
  c.desc 'Show time totals at the end of output'
892
- c.switch [:totals], :default_value => false, :negatable => true
1007
+ c.switch [:totals], default_value: false, negatable: true
893
1008
 
894
1009
  c.desc 'Sort tags by (name|time)'
895
1010
  default = 'time'
896
- if wwid.config.has_key?('tag_sort')
897
- default = wwid.config['tag_sort']
898
- end
899
- c.flag [:tag_sort], :default_value => default
1011
+ default = wwid.config['tag_sort'] if wwid.config.key?('tag_sort')
1012
+ c.arg_name 'KEY'
1013
+ c.flag [:tag_sort], must_match: /^(name|time)$/i, default_value: default
900
1014
 
901
- c.action do |global_options, options,args|
1015
+ c.action do |_global_options, options, _args|
902
1016
  options[:sort_tags] = options[:tag_sort] =~ /^n/i
903
- puts wwid.yesterday(options[:s],options[:t],options[:o],{:totals => options[:totals], :sort_tags => options[:sort_tags]}).chomp
904
-
1017
+ puts wwid.yesterday(options[:s], options[:t], options[:o],
1018
+ { totals: options[:totals], sort_tags: options[:sort_tags] }).chomp
905
1019
  end
906
1020
  end
907
1021
 
908
- desc 'Show the last entry'
1022
+ desc 'Show the last entry, optionally edit'
909
1023
  command :last do |c|
910
1024
  c.desc 'Specify a section'
911
- c.flag [:s, :section]
1025
+ c.arg_name 'NAME'
1026
+ c.flag %i[s section], default_value: 'All'
1027
+
1028
+ c.desc "Edit entry with #{ENV['EDITOR']}"
1029
+ c.switch %i[e editor], negatable: false, default_value: false
1030
+
1031
+ c.desc 'Tag filter, combine multiple tags with a comma.'
1032
+ c.arg_name 'TAG'
1033
+ c.flag [:tag]
1034
+
1035
+ c.desc 'Tag boolean'
1036
+ c.arg_name 'BOOLEAN'
1037
+ c.flag [:bool], must_match: /(and|or|not)/i, default_value: 'AND'
1038
+
1039
+ c.desc 'Search filter, surround with slashes for regex (/query/)'
1040
+ c.arg_name 'QUERY'
1041
+ c.flag [:search]
912
1042
 
913
- c.action do |global_options,options,args|
914
- puts wwid.last(true,options[:s]).strip
1043
+ c.action do |_global_options, options, _args|
1044
+ raise '--tag and --search cannot be used together' if options[:tag] && options[:search]
1045
+
1046
+ if options[:tag].nil?
1047
+ tags = []
1048
+ else
1049
+ tags = options[:tag].split(/ *, */).map { |t| t.strip.sub(/^@/, '') }
1050
+ options[:bool] = options[:bool] =~ /^(and|or|not)$/i ? options[:bool].upcase : 'AND'
1051
+ end
1052
+
1053
+ if options[:e]
1054
+ wwid.edit_last(section: options[:s], options: { search: options[:search], tag: tags, tag_bool: options[:bool] })
1055
+ else
1056
+ puts wwid.last(times: true, section: options[:s],
1057
+ options: { search: options[:search], tag: tags, tag_bool: options[:bool] }).strip
1058
+ end
915
1059
  end
916
1060
  end
917
1061
 
918
1062
  desc 'List sections'
919
1063
  command :sections do |c|
920
1064
  c.desc 'List in single column'
921
- c.switch [:c, :column], :default_value => false
1065
+ c.switch %i[c column], default_value: false
922
1066
 
923
- c.action do |global_options,options,args|
1067
+ c.action do |_global_options, options, _args|
924
1068
  joiner = options[:c] ? "\n" : "\t"
925
1069
  print wwid.sections.join(joiner)
926
1070
  end
@@ -928,124 +1072,145 @@ end
928
1072
 
929
1073
  desc 'Select a section to display from a menu'
930
1074
  command :choose do |c|
931
- c.action do |global_options,options,args|
1075
+ c.action do |_global_options, _options, _args|
932
1076
  section = wwid.choose_section
933
- puts wwid.list_section({:section => section.cap_first, :count => 0})
1077
+ puts wwid.list_section({ section: section.cap_first, count: 0 })
934
1078
  end
935
1079
  end
936
1080
 
937
1081
  desc 'Add a new section to the "doing" file'
938
- arg_name 'section_name'
1082
+ arg_name 'SECTION_NAME'
939
1083
  command :add_section do |c|
940
- c.action do |global_options,options,args|
941
- unless wwid.sections.include?(args[0])
942
- wwid.add_section(args[0].cap_first)
943
- wwid.write(wwid.doing_file)
944
- else
945
- raise "Section #{args[0]} already exists"
946
- end
1084
+ c.action do |_global_options, _options, args|
1085
+ raise "Section #{args[0]} already exists" if wwid.sections.include?(args[0])
1086
+
1087
+ wwid.add_section(args[0].cap_first)
1088
+ wwid.write(wwid.doing_file)
947
1089
  end
948
1090
  end
949
1091
 
950
1092
  desc 'List available color variables for configuration templates and views'
951
1093
  command :colors do |c|
952
- c.action do |global_options,options,args|
1094
+ c.action do |_global_options, _options, _args|
953
1095
  clrs = wwid.colors
954
1096
  bgs = []
955
1097
  fgs = []
956
- clrs.each {|k,v|
1098
+ clrs.each do |k, v|
957
1099
  if k =~ /bg/
958
1100
  bgs.push("#{v} #{clrs['default']} <-- #{k}")
959
1101
  else
960
1102
  fgs.push("#{v}XXXX#{clrs['default']} <-- #{k}")
961
1103
  end
962
- }
1104
+ end
963
1105
  puts fgs.join("\n")
964
1106
  puts bgs.join("\n")
965
1107
  end
966
1108
  end
967
1109
 
968
1110
  desc 'Display a user-created view'
969
- arg_name 'view_name'
1111
+ arg_name 'VIEW_NAME'
970
1112
  command :view do |c|
971
1113
  c.desc 'Section (override view settings)'
972
- c.flag [:s, :section]
1114
+ c.arg_name 'NAME'
1115
+ c.flag %i[s section]
973
1116
 
974
1117
  c.desc 'Count to display (override view settings)'
975
- c.flag [:c, :count], :must_match => /^\d+$/, :type => Integer
1118
+ c.arg_name 'COUNT'
1119
+ c.flag %i[c count], must_match: /^\d+$/, type: Integer
976
1120
 
977
- c.desc 'Output to export format (csv|html|json)'
978
- c.flag [:o, :output]
1121
+ c.desc 'Output to export format (csv|html|json|template|timeline)'
1122
+ c.arg_name 'FORMAT'
1123
+ c.flag %i[o output], must_match: /^(template|html|csv|json|timeline)$/i
979
1124
 
980
1125
  c.desc 'Show time intervals on @done tasks'
981
- c.switch [:t, :times], :default_value => true
1126
+ c.switch %i[t times], default_value: true
982
1127
 
983
1128
  c.desc 'Show intervals with totals at the end of output'
984
- c.switch [:totals], :default_value => false, :negatable => true
1129
+ c.switch [:totals], default_value: false, negatable: true
1130
+
1131
+ c.desc 'Include colors in output'
1132
+ c.switch [:color], default_value: true, negatable: true
985
1133
 
986
1134
  c.desc 'Sort tags by (name|time)'
987
1135
  default = 'time'
988
- if wwid.config.has_key?('tag_sort')
989
- default = wwid.config['tag_sort']
990
- end
991
- c.flag [:tag_sort], :default_value => default
1136
+ default = wwid.config['tag_sort'] if wwid.config.key?('tag_sort')
1137
+ c.arg_name 'KEY'
1138
+ c.flag [:tag_sort], must_match: /^(name|time)$/i, default_value: default
992
1139
 
993
1140
  c.desc 'Only show items with recorded time intervals'
994
- c.switch [:only_timed], :default_value => false, :negatable => true
1141
+ c.switch [:only_timed], default_value: false, negatable: true
995
1142
 
996
- c.action do |global_options,options,args|
997
- if args.empty?
998
- title = wwid.choose_view
999
- else
1000
- title = wwid.guess_view(args[0])
1001
- end
1143
+ c.action do |_global_options, options, args|
1144
+ title = if args.empty?
1145
+ wwid.choose_view
1146
+ else
1147
+ wwid.guess_view(args[0])
1148
+ end
1002
1149
 
1003
- if options[:s]
1004
- section = wwid.guess_section(options[:s]) || options[:s].cap_first
1005
- end
1150
+ section = wwid.guess_section(options[:s]) || options[:s].cap_first if options[:s]
1006
1151
 
1007
1152
  view = wwid.get_view(title)
1008
1153
  if view
1009
- if (view.has_key?('only_timed') && view['only_timed']) || options[:only_timed]
1010
- only_timed = true
1011
- else
1012
- only_timed = false
1013
- end
1014
-
1015
- template = view.has_key?('template') ? view['template'] : nil
1016
- format = view.has_key?('date_format') ? view['date_format'] : nil
1017
- tags_color = view.has_key?('tags_color') ? view['tags_color'] : nil
1154
+ only_timed = if (view.key?('only_timed') && view['only_timed']) || options[:only_timed]
1155
+ true
1156
+ else
1157
+ false
1158
+ end
1159
+
1160
+ template = view.key?('template') ? view['template'] : nil
1161
+ format = view.key?('date_format') ? view['date_format'] : nil
1162
+ tags_color = view.key?('tags_color') ? view['tags_color'] : nil
1018
1163
  tag_filter = false
1019
- if view.has_key?('tags')
1020
- unless view['tags'].nil? || view['tags'].empty?
1021
- tag_filter = {'tags' => [], 'bool' => "OR"}
1022
- if view['tags'].class == Array
1023
- tag_filter['tags'] = view['tags'].map{|tag| tag.strip }
1024
- else
1025
- tag_filter['tags'] = view['tags'].gsub(/[, ]+/," ").split(" ").map{|tag| tag.strip }
1026
- end
1027
- tag_filter['bool'] = view.has_key?('tags_bool') && !view['tags_bool'].nil? ? view['tags_bool'].upcase : "OR"
1028
- end
1164
+ if view.key?('tags') && !(view['tags'].nil? || view['tags'].empty?)
1165
+ tag_filter = { 'tags' => [], 'bool' => 'OR' }
1166
+ tag_filter['tags'] = if view['tags'].instance_of?(Array)
1167
+ view['tags'].map(&:strip)
1168
+ else
1169
+ view['tags'].gsub(/[, ]+/, ' ').split(' ').map(&:strip)
1170
+ end
1171
+ tag_filter['bool'] = view.key?('tags_bool') && !view['tags_bool'].nil? ? view['tags_bool'].upcase : 'OR'
1029
1172
  end
1030
1173
 
1031
1174
  # If the -o/--output flag was specified, override any default in the view template
1032
- options[:o] ||= view.has_key?('output_format') ? view['output_format'] : "template"
1033
-
1034
- count = options[:c] ? options[:c] : view.has_key?('count') ? view['count'] : 10
1035
- section = options[:s] ? section : view.has_key?('section') ? view['section'] : wwid.current_section
1036
- order = view.has_key?('order') ? view['order'] : "asc"
1175
+ options[:o] ||= view.key?('output_format') ? view['output_format'] : 'template'
1176
+
1177
+ count = if options[:c]
1178
+ options[:c]
1179
+ else
1180
+ view.key?('count') ? view['count'] : 10
1181
+ end
1182
+ section = if options[:s]
1183
+ section
1184
+ else
1185
+ view.key?('section') ? view['section'] : wwid.current_section
1186
+ end
1187
+ order = view.key?('order') ? view['order'] : 'asc'
1037
1188
 
1038
1189
  options[:t] = true if options[:totals]
1039
- options[:output].downcase! if options[:output]
1190
+ options[:output]&.downcase!
1040
1191
  options[:sort_tags] = options[:tag_sort] =~ /^n/i
1041
1192
 
1042
- puts wwid.list_section({:section => section, :count => count, :template => template, :format => format, :order => order, :tag_filter => tag_filter, :output => options[:o], :tags_color => tags_color, :times => options[:t], :highlight => true, :totals => options[:totals], :only_timed => only_timed, :sort_tags => options[:sort_tags] })
1193
+ opts = {
1194
+ count: count,
1195
+ format: format,
1196
+ highlight: options[:color],
1197
+ only_timed: only_timed,
1198
+ order: order,
1199
+ output: options[:o],
1200
+ section: section,
1201
+ sort_tags: options[:sort_tags],
1202
+ tag_filter: tag_filter,
1203
+ tags_color: tags_color,
1204
+ template: template,
1205
+ times: options[:t],
1206
+ totals: options[:totals]
1207
+ }
1208
+
1209
+ puts wwid.list_section(opts)
1210
+ elsif title.instance_of?(FalseClass)
1211
+ exit_now! 'Cancelled'
1043
1212
  else
1044
- if title.class == FalseClass
1045
- exit_now! "Cancelled"
1046
- else
1047
- raise "View #{title} not found in config"
1048
- end
1213
+ raise "View #{title} not found in config"
1049
1214
  end
1050
1215
  end
1051
1216
  end
@@ -1053,124 +1218,135 @@ end
1053
1218
  desc 'List available custom views'
1054
1219
  command :views do |c|
1055
1220
  c.desc 'List in single column'
1056
- c.switch [:c, :column], :default_value => false
1221
+ c.switch %i[c column], default_value: false
1057
1222
 
1058
- c.action do |global_options,options,args|
1223
+ c.action do |_global_options, options, _args|
1059
1224
  joiner = options[:c] ? "\n" : "\t"
1060
1225
  print wwid.views.join(joiner)
1061
1226
  end
1062
1227
  end
1063
1228
 
1064
- desc 'Move entries in between sections'
1065
- arg_name 'section'
1229
+ desc 'Move entries between sections'
1230
+ arg_name 'SECTION_NAME'
1066
1231
  default_value wwid.current_section
1067
1232
  command :archive do |c|
1068
1233
  c.desc 'Count to keep (ignored if archiving by tag)'
1069
- c.flag [:k, :keep], :default_value => 5, :must_match => /^\d+$/, :type => Integer
1234
+ c.arg_name 'COUNT'
1235
+ c.flag %i[k keep], default_value: 5, must_match: /^\d+$/, type: Integer
1070
1236
 
1071
1237
  c.desc 'Move entries to'
1072
- c.flag [:t, :to], :default_value => "Archive"
1238
+ c.arg_name 'SECTION_NAME'
1239
+ c.flag %i[t to], default_value: 'Archive'
1073
1240
 
1074
- c.desc 'Tag boolean'
1075
- c.flag [:b, :bool], :default_value => "AND"
1241
+ c.desc 'Tag filter, combine multiple tags with a comma. Added for compatibility with other commands.'
1242
+ c.arg_name 'TAG'
1243
+ c.flag [:tag]
1076
1244
 
1077
- c.action do |global_options,options,args|
1078
- if args.length > 0
1079
- if args[0] =~ /^@\S+/
1080
- section = "all"
1081
- tags = args.map {|t| t.sub(/^@/,'').strip }
1082
- else
1083
- section = args[0].cap_first
1084
- tags = args.length > 1 ? args[1..-1].map {|t| t.sub(/^@/,'').strip } : nil
1085
- end
1086
- else
1245
+ c.desc 'Tag boolean (AND|OR|NOT)'
1246
+ c.arg_name 'BOOLEAN'
1247
+ c.flag [:bool], must_match: /(and|or|not)/i, default_value: 'AND'
1248
+
1249
+ c.action do |_global_options, options, args|
1250
+ if args.empty?
1087
1251
  section = wwid.current_section
1088
- tags = nil
1252
+ tags = []
1253
+ elsif args[0] =~ /^@\S+/
1254
+ section = 'all'
1255
+ tags = args.map { |t| t.sub(/^@/, '').strip }
1256
+ else
1257
+ section = args[0].cap_first
1258
+ tags = args.length > 1 ? args[1..].map { |t| t.sub(/^@/, '').strip } : nil
1089
1259
  end
1090
- wwid.archive(section,options[:k],options[:t],tags,options[:b])
1260
+
1261
+ tags.concat(options[:tag].split(/ *, */).map { |t| t.sub(/^@/, '').strip }) if options[:tag]
1262
+
1263
+ wwid.archive(section, options[:keep], options[:to], tags, options[:bool])
1091
1264
  end
1092
1265
  end
1093
1266
 
1094
1267
  desc 'Open the "doing" file in an editor'
1268
+ long_desc "`doing open` defaults to using the editor_app setting in #{wwid.config_file} (#{wwid.config.key?('editor_app') ? wwid.config['editor_app'] : 'not set'})"
1095
1269
  command :open do |c|
1096
- if `uname` =~ /Darwin/
1097
- c.desc 'open with app name'
1098
- c.arg_name 'app_name'
1099
- c.flag [:a]
1100
-
1101
- c.desc 'open with app bundle id'
1102
- c.arg_name 'bundle_id'
1103
- c.flag [:b]
1104
- end
1105
- c.desc 'open with $EDITOR'
1106
- c.switch [:e], :negatable => false
1270
+ if `uname` =~ /Darwin/
1271
+ c.desc 'Open with app name'
1272
+ c.arg_name 'APP_NAME'
1273
+ c.flag [:a]
1274
+
1275
+ c.desc 'Open with app bundle id'
1276
+ c.arg_name 'BUNDLE_ID'
1277
+ c.flag [:b]
1278
+ end
1279
+ c.desc "Open with $EDITOR (#{ENV['EDITOR']})"
1280
+ c.switch %i[e editor], negatable: false, default_value: false
1107
1281
 
1108
- c.action do |global_options,options,args|
1282
+ c.action do |_global_options, options, _args|
1109
1283
  params = options.dup
1110
- params.delete_if { |k,v|
1111
- k.class == String || v.nil? || v == false
1112
- }
1284
+ params.delete_if do |k, v|
1285
+ k.instance_of?(String) || v.nil? || v == false
1286
+ end
1113
1287
  if `uname` =~ /Darwin/
1114
- if params.length < 2
1115
1288
  if options[:a]
1116
- system %Q{open -a "#{options[:a]}" "#{File.expand_path(wwid.doing_file)}"}
1289
+ system %(open -a "#{options[:a]}" "#{File.expand_path(wwid.doing_file)}")
1117
1290
  elsif options[:b]
1118
- system %Q{open -b "#{options[:b]}" "#{File.expand_path(wwid.doing_file)}"}
1291
+ system %(open -b "#{options[:b]}" "#{File.expand_path(wwid.doing_file)}")
1119
1292
  elsif options[:e]
1120
- raise "No EDITOR variable defined in environment" if ENV['EDITOR'].nil?
1121
- system %Q{$EDITOR "#{File.expand_path(wwid.doing_file)}"}
1293
+ raise 'No EDITOR variable defined in environment' if ENV['EDITOR'].nil?
1294
+
1295
+ system %($EDITOR "#{File.expand_path(wwid.doing_file)}")
1296
+ elsif wwid.config.key?('editor_app') && !wwid.config['editor_app'].nil?
1297
+ system %(open -a "#{wwid.config['editor_app']}" "#{File.expand_path(wwid.doing_file)}")
1122
1298
  else
1123
- if wwid.config.has_key?('editor_app') && !wwid.config['editor_app'].nil?
1124
- system %Q{open -a "#{wwid.config['editor_app']}" "#{File.expand_path(wwid.doing_file)}"}
1125
- else
1126
- system %Q{open "#{File.expand_path(wwid.doing_file)}"}
1127
- end
1299
+ system %(open "#{File.expand_path(wwid.doing_file)}")
1128
1300
  end
1301
+
1129
1302
  else
1130
- raise "The open command takes a single parameter. #{params.length} specified."
1131
- end
1132
- else
1133
- raise "No EDITOR variable defined in environment" if ENV['EDITOR'].nil?
1134
- system %Q{$EDITOR "#{File.expand_path(wwid.doing_file)}"}
1303
+ raise 'No EDITOR variable defined in environment' if ENV['EDITOR'].nil?
1304
+
1305
+ system %($EDITOR "#{File.expand_path(wwid.doing_file)}")
1135
1306
  end
1136
1307
  end
1137
1308
  end
1138
1309
 
1139
-
1140
1310
  desc 'Edit the configuration file'
1141
1311
  command :config do |c|
1142
1312
  c.desc 'Editor to use'
1143
- c.flag [:e, :editor], :default_value => nil
1313
+ c.arg_name 'EDITOR'
1314
+ c.flag %i[e editor], default_value: nil
1144
1315
 
1145
- if `uname` =~ /Darwins/
1316
+ if `uname` =~ /Darwin/
1146
1317
  c.desc 'Application to use'
1318
+ c.arg_name 'APP_NAME'
1147
1319
  c.flag [:a]
1148
1320
 
1149
- c.desc "Use the editor_app defined in ~/.doingrc (#{wwid.config['editor_app']})"
1150
- c.switch [:x]
1151
-
1152
1321
  c.desc 'Application bundle id to use'
1322
+ c.arg_name 'BUNDLE_ID'
1153
1323
  c.flag [:b]
1324
+
1325
+ c.desc "Use the config_editor_app defined in ~/.doingrc (#{wwid.config.key?('config_editor_app') ? wwid.config['config_editor_app'] : 'config_editor_app not set'})"
1326
+ c.switch [:x]
1154
1327
  end
1155
- c.action do |global_options,options,args|
1156
- if `uname` =~ /Darwins/
1328
+
1329
+ c.action do |_global_options, options, _args|
1330
+ if `uname` =~ /Darwin/
1157
1331
  if options[:x]
1158
- %x{open -a "#{wwid.config['editor_app']}" "#{wwid.config_file}"}
1332
+ `open -a "#{wwid.config['config_editor_app']}" "#{wwid.config_file}"`
1159
1333
  elsif options[:a] || options[:b]
1160
1334
  if options[:a]
1161
- %x{open -a "#{options[:a]}" "#{wwid.config_file}"}
1335
+ `open -a "#{options[:a]}" "#{wwid.config_file}"`
1162
1336
  elsif options[:b]
1163
- %x{open -b #{options[:b]} "#{wwid.config_file}"}
1337
+ `open -b #{options[:b]} "#{wwid.config_file}"`
1164
1338
  end
1165
1339
  else
1166
- raise "No EDITOR variable defined in environment" if options[:e].nil? && ENV['EDITOR'].nil?
1340
+ raise 'No EDITOR variable defined in environment' if options[:e].nil? && ENV['EDITOR'].nil?
1341
+
1167
1342
  editor = options[:e].nil? ? ENV['EDITOR'] : options[:e]
1168
- system %Q{#{editor} "#{wwid.config_file}"}
1343
+ system %(#{editor} "#{wwid.config_file}")
1169
1344
  end
1170
1345
  else
1171
- raise "No EDITOR variable defined in environment" if options[:e].nil? && ENV['EDITOR'].nil?
1346
+ raise 'No EDITOR variable defined in environment' if options[:e].nil? && ENV['EDITOR'].nil?
1347
+
1172
1348
  editor = options[:e].nil? ? ENV['EDITOR'] : options[:e]
1173
- system %Q{#{editor} "#{wwid.config_file}"}
1349
+ system %(#{editor} "#{wwid.config_file}")
1174
1350
  end
1175
1351
  end
1176
1352
  end
@@ -1178,19 +1354,19 @@ end
1178
1354
  desc 'Undo the last change to the doing_file'
1179
1355
  command :undo do |c|
1180
1356
  c.desc 'Specify alternate doing file'
1181
- c.flag [:f, :file], :default_value => wwid.doing_file
1357
+ c.arg_name 'PATH'
1358
+ c.flag %i[f file], default_value: wwid.doing_file
1182
1359
 
1183
- c.action do |global_options,options,args|
1360
+ c.action do |_global_options, options, _args|
1184
1361
  file = options[:f] || wwid.doing_file
1185
1362
  wwid.restore_backup(file)
1186
1363
  end
1187
1364
  end
1188
1365
 
1189
-
1190
- pre do |global,command,options,args|
1191
- if global[:config_file]
1366
+ pre do |global, _command, _options, _args|
1367
+ if global[:config_file] && global[:config_file] != wwid.config_file
1192
1368
  wwid.config_file = global[:config_file]
1193
- wwid.configure({:ignore_local => true})
1369
+ wwid.configure({ ignore_local: true })
1194
1370
  # wwid.results.push("Override config file #{wwid.config_file}")
1195
1371
  end
1196
1372
 
@@ -1204,9 +1380,7 @@ pre do |global,command,options,args|
1204
1380
 
1205
1381
  wwid.config[:include_notes] = false unless global[:notes]
1206
1382
 
1207
- if global[:version]
1208
- $stdout.puts "doing v" + Doing::VERSION
1209
- end
1383
+ $stdout.puts "doing v#{Doing::VERSION}" if global[:version]
1210
1384
 
1211
1385
  # Return true to proceed; false to abort and not call the
1212
1386
  # chosen command
@@ -1215,7 +1389,7 @@ pre do |global,command,options,args|
1215
1389
  true
1216
1390
  end
1217
1391
 
1218
- post do |global,command,options,args|
1392
+ post do |global, _command, _options, _args|
1219
1393
  # Use skips_post before a command to skip this
1220
1394
  # block on that command only
1221
1395
  if global[:stdout]
@@ -1225,7 +1399,7 @@ post do |global,command,options,args|
1225
1399
  end
1226
1400
  end
1227
1401
 
1228
- on_error do |exception|
1402
+ on_error do |_exception|
1229
1403
  # puts exception.message
1230
1404
  true
1231
1405
  end