doing 1.0.54 → 1.0.55

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