doing 1.0.51 → 1.0.56

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 -9
  3. data/bin/doing +754 -578
  4. data/lib/doing/version.rb +1 -1
  5. data/lib/doing/wwid.rb +221 -86
  6. metadata +6 -6
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 03b16107627feaf0adeafbce9ad45e96fc53df7ba379f59dc72d2970d12642ef
4
- data.tar.gz: 5cd54e6ba005e985b6c86468831d0b83369c5801916a9e8e15e742c86fcccf53
3
+ metadata.gz: 7a0efeeb7339da1578053099b2638ff6ca1e6319520a24b536381ca40a33e330
4
+ data.tar.gz: e6507789ad1e43fce7e1627075fb4fc578f0a6c0630907ef78b469b05c09fc4d
5
5
  SHA512:
6
- metadata.gz: 617cd299b816ad12dc042ec76dbd58e5f19dca7098362ad4cc465d70d5c6dfab0e5ca4e05b82cd9535a85638ae490adea4da502ce4b6f0127e25da8f08b0bc6c
7
- data.tar.gz: c82bb894c5d457d6bde4e6396cb7badffb4db3e6d0d81b75372f98968ec79b9ebfe15548b7040ddb5cc2eb2abfda2e3faaf6ea517c0485878689b50ce1739a79
6
+ metadata.gz: 9914f2d9be6d9fbba69eaf67c316252186c712816e48cfb45acd7e278556f1e7769047b984a6e08fe8af36156d7bddb247f476685dcdbd3a375c28d4a0780172
7
+ data.tar.gz: 53a81b430e70ceebf7f1d284d97a4579d0e29eea714a11380b1fa0dd526c98c2e94b9fef3edbc047e7f65b4df1c631d803840191d961738d2ed78e132ec72ba5
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,6 +29,8 @@ _Side note:_ I actually use the library behind this utility as part of another s
30
29
 
31
30
  ## Installation
32
31
 
32
+ The current version of `doing` is <!--VER-->1.0.55<!--END VER-->.
33
+
33
34
  $ [sudo] gem install doing
34
35
 
35
36
  To install the _latest_ version, use `--pre`:
@@ -67,8 +68,10 @@ A basic configuration looks like this:
67
68
  default_date_format: '%Y-%m-%d %H:%M'
68
69
  marker_tag: flagged
69
70
  marker_color: yellow
71
+ tags_color: boldcyan
70
72
  default_tags: []
71
- editor_app: TextEdit
73
+ editor_app: TaskPaper
74
+ config_editor_app: Sublime Text
72
75
  :include_notes: true
73
76
  views:
74
77
  color:
@@ -137,7 +140,9 @@ You can rename the section that holds your current tasks. By default, this is `C
137
140
 
138
141
  ### Default editors
139
142
 
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.
143
+ 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.
144
+
145
+ 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
146
 
142
147
  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
148
 
@@ -413,6 +418,8 @@ When used with `doing done`, `--back` and `--took` allow time intervals to be ac
413
418
 
414
419
  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
420
 
421
+ `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`.
422
+
416
423
  `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
424
 
418
425
  See `doing help meanwhile` for more options.
@@ -438,9 +445,9 @@ You can finish the last entry containing a specific tag or combination of tags u
438
445
 
439
446
  doing finish --tag=work,project1
440
447
 
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).
448
+ 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
449
 
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.
450
+ 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
451
 
445
452
 
446
453
  ##### Tagging and Autotagging
@@ -492,6 +499,8 @@ This creates a search pattern looking for a string of word characters followed b
492
499
 
493
500
  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
501
 
502
+ 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"`.
503
+
495
504
  #### Displaying entries:
496
505
 
497
506
  show - List all entries
@@ -517,6 +526,18 @@ If you have a use for it, you can use `-o csv` on the show or view commands to o
517
526
 
518
527
  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
528
 
529
+ ##### Modifying the last entry
530
+
531
+ 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.
532
+
533
+ 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.
534
+
535
+ 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).
536
+
537
+ 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.
538
+
539
+ Both `--tag` and `--search` can be constrained to a single section with `-s SECTION`.
540
+
520
541
  #### Views
521
542
 
522
543
  view - Display a user-created view
@@ -576,11 +597,14 @@ __Fish:__ See the file [`doing.fish`](https://github.com/ttscoff/doing/blob/mast
576
597
 
577
598
  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
599
 
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.
600
+ 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
601
 
581
602
  Point of interest, the LaunchBar Action makes use of the `-o json` flag for outputting JSON to the action's script for parsing.
582
603
 
583
- See <https://brettterpstra.com/projects/doing/> for the download.
604
+ <!--GITHUB-->See <https://brettterpstra.com/projects/doing/> for the download.<!--END GITHUB-->
605
+ <!--JEKYLL
606
+ {% download 117 %}
607
+ -->
584
608
 
585
609
  Evan Lovely has [created an Alfred workflow as well](http://www.evanlovely.com/blog/technology/alfred-for-terpstras-doing/).
586
610
 
@@ -621,7 +645,9 @@ Please try not to email me directly about GitHub projects.
621
645
 
622
646
  Feel free to [poke around](http://github.com/ttscoff/doing/), I'll try to add more comments in the future (and retroactively).
623
647
 
624
- {% donate doing now sending coffee money to Brett. %}
648
+ <!--END README-->
649
+
650
+ PayPal link: [paypal.me/ttscoff](https://paypal.me/ttscoff)
625
651
 
626
652
  ## Changelog
627
653
 
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
429
+
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]
434
433
 
435
- c.action do |global_options,options,args|
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]
481
+
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'
485
+ c.flag [:tag]
462
486
 
463
- c.desc 'Finish the last X entries containing TAG. Separate multiple tags with comma (--tag=tag2,tag2)'
464
- c.flag [:tag], :default_value => []
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]
465
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
@@ -493,192 +523,245 @@ command :finish do |c|
493
523
  end
494
524
  end
495
525
 
496
- if options[:tag]
497
- options[:tag] = options[:tag].split(/,/).map {|tag| tag.strip.sub(/^@/, '') }
498
- if options[:tag_bool] =~ /^(and|or|not)$/i
499
- options[:tag_bool] = options[:tag_bool].upcase
500
- else
501
- options[:tag_bool] = 'AND'
502
- end
503
- end
504
-
505
- if args.length > 1
506
- raise "Only one argument allowed"
507
- elsif args.length == 0 || args[0] =~ /\d+/
508
- count = args[0] ? args[0].to_i : 1
509
- 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] })
526
+ if options[:tag].nil?
527
+ tags = []
510
528
  else
511
- raise "Invalid argument (specify number of recent items to mark @done)"
529
+ tags = options[:tag].split(/ *, */).map { |t| t.strip.sub(/^@/, '') }
530
+ options[:bool] = options[:bool] =~ /^(and|or|not)$/i ? options[:bool].upcase : 'AND'
512
531
  end
532
+
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)
513
551
  end
514
552
  end
515
553
 
516
554
  desc 'Repeat last entry as new entry'
517
- arg_name 'section'
518
555
  command [:again, :resume] do |c|
519
556
  c.desc 'Section'
520
- c.flag [:s, :section], :default_value => "All"
557
+ c.arg_name 'NAME'
558
+ c.flag %i[s section], default_value: 'All'
521
559
 
522
- c.desc 'Note'
523
- c.arg_name 'note_text'
524
- 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]
525
563
 
526
- c.action do |global_options, options, args|
527
- 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)
528
592
  end
529
593
  end
530
594
 
531
- desc 'Tag last entry'
532
- arg_name 'tag1 [tag2...]'
595
+ desc 'Add tag(s) to last entry'
596
+ arg_name 'TAG', :multiple
533
597
  command :tag do |c|
534
598
  c.desc 'Section'
535
- c.flag [:s, :section], :default_value => "All"
599
+ c.arg_name 'SECTION_NAME'
600
+ c.flag %i[s section], default_value: 'All'
536
601
 
537
602
  c.desc 'How many recent entries to tag (0 for all)'
538
- c.flag [:c, :count], :default_value => 1
603
+ c.arg_name 'COUNT'
604
+ c.flag %i[c count], default_value: 1
539
605
 
540
606
  c.desc 'Include current date/time with tag'
541
- c.switch [:d, :date], :negatable => false, :default_value => false
607
+ c.switch %i[d date], negatable: false, default_value: false
542
608
 
543
609
  c.desc 'Remove given tag(s)'
544
- c.switch [:r, :remove], :negatable => false, :default_value => false
610
+ c.switch %i[r remove], negatable: false, default_value: false
545
611
 
546
612
  c.desc 'Autotag entries based on autotag configuration in ~/.doingrc'
547
- c.switch [:a, :autotag], :negatable => false, :default_value => false
613
+ c.switch %i[a autotag], negatable: false, default_value: false
548
614
 
549
- c.action do |global_options,options,args|
550
- if args.length == 0 && !options[:a]
551
- raise "You must specify at least one tag"
552
- else
553
-
554
- 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]
555
617
 
556
- unless options[:a]
557
- if args.join("") =~ /,/
558
- tags = args.join("").split(/,/)
559
- else
560
- tags = args.join(" ").split(" ") # in case tags are quoted as one arg
561
- end
618
+ section = wwid.guess_section(options[:s]) || options[:s].cap_first
562
619
 
563
- tags.map!{|tag| tag.sub(/^@/,'').strip }
564
- else
565
- tags = []
566
- 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
567
628
 
568
- count = options[:c].to_i
629
+ tags.map! { |tag| tag.sub(/^@/, '').strip }
630
+ end
569
631
 
570
- if count == 0
571
- section_q = section == 'All' ? "" : " in section #{section}"
632
+ count = options[:c].to_i
572
633
 
573
- if options[:a]
574
- question = "Are you sure you want to autotag all records#{section_q}"
575
- elsif options[:r]
576
- question = "Are you sure you want to remove #{tags.join(" and ")} from all records#{section_q}"
577
- else
578
- question = "Are you sure you want to add #{tags.join(" and ")} to all records#{section_q}"
579
- end
634
+ if count.zero?
635
+ section_q = section == 'All' ? '' : " in section #{section}"
580
636
 
581
- 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
582
644
 
583
- unless res
584
- raise "Cancelled"
585
- end
586
- end
645
+ res = wwid.yn(question, default_response: false)
587
646
 
588
- wwid.tag_last({:tags => tags, :count => count, :section => section, :date => options[:date], :remove => options[:r], :autotag => options[:a]})
647
+ raise 'Cancelled' unless res
589
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)
590
658
  end
591
659
  end
592
660
 
593
661
  desc 'Mark last entry as highlighted'
594
662
  command [:mark, :flag] do |c|
595
663
  c.desc 'Section'
596
- 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
597
666
 
598
667
  c.desc 'Remove mark'
599
- c.switch [:r, :remove], :negatable => false, :default_value => false
600
-
668
+ c.switch %i[r remove], negatable: false, default_value: false
601
669
 
602
- c.action do |global_options,options,args|
603
- mark = wwid.config['marker_tag'] || "flagged"
604
- 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] })
605
673
  end
606
674
  end
607
675
 
608
676
  desc 'List all entries'
609
- long_desc 'The argument can be a section name, @tag(s) or both. "pick" or "choose" as an argument will offer a section menu.'
610
- 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]'
611
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
+
612
687
  c.desc 'Tag boolean (AND,OR,NONE)'
613
- 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'
614
690
 
615
691
  c.desc 'Max count to show'
616
- c.flag [:c, :count], :default_value => 0
692
+ c.arg_name 'MAX'
693
+ c.flag %i[c count], default_value: 0
617
694
 
618
695
  c.desc 'Age (oldest/newest)'
619
- c.flag [:a, :age], :default_value => 'newest'
696
+ c.arg_name 'AGE'
697
+ c.flag %i[a age], default_value: 'newest'
620
698
 
621
699
  c.desc 'Sort order (asc/desc)'
622
- 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'
623
702
 
624
- c.desc %{
703
+ c.desc %(
625
704
  Date range to show, or a single day to filter date on.
626
705
  Date range argument should be quoted. Date specifications can be natural language.
627
706
  To specify a range, use "to" or "through": `doing show --from "monday to friday"`
628
- }
629
- c.flag [:f, :from]
707
+ )
708
+ c.arg_name 'DATE_OR_RANGE'
709
+ c.flag %i[f from]
630
710
 
631
711
  c.desc 'Show time intervals on @done tasks'
632
- c.switch [:t, :times], :default_value => true
712
+ c.switch %i[t times], default_value: true
633
713
 
634
714
  c.desc 'Show intervals with totals at the end of output'
635
- c.switch [:totals], :default_value => false, :negatable => true
715
+ c.switch [:totals], default_value: false, negatable: true
636
716
 
637
717
  c.desc 'Sort tags by (name|time)'
638
718
  default = 'time'
639
- if wwid.config.has_key?('tag_sort')
640
- default = wwid.config['tag_sort']
641
- end
642
- 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
643
722
 
644
723
  c.desc 'Only show items with recorded time intervals'
645
- c.switch [:only_timed], :default_value => false, :negatable => false
646
-
647
- c.desc 'Output to export format (csv|html|json)'
648
- c.flag [:o, :output]
649
- c.action do |global_options,options,args|
724
+ c.switch [:only_timed], default_value: false, negatable: false
650
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|
651
730
  tag_filter = false
652
731
  tags = []
653
- if args.length > 0
654
- if args[0] =~ /^all$/i
655
- section = "All"
732
+ if args.length.positive?
733
+ case args[0]
734
+ when /^all$/i
735
+ section = 'All'
656
736
  args.shift
657
- elsif args[0] =~ /^(choose|pick)$/i
737
+ when /^(choose|pick)$/i
658
738
  section = wwid.choose_section
659
739
  args.shift
660
- elsif args[0] =~ /^@/
661
- section = "All"
740
+ when /^@/
741
+ section = 'All'
662
742
  else
663
743
  section = wwid.guess_section(args[0])
664
744
  raise "No such section: #{args[0]}" unless section
745
+
665
746
  args.shift
666
747
  end
667
- if args.length > 0
668
- args.each {|arg|
748
+ if args.length.positive?
749
+ args.each do |arg|
669
750
  if arg =~ /,/
670
- arg.split(/,/).each {|tag|
671
- tags.push(tag.strip.sub(/^@/,''))
672
- }
751
+ arg.split(/,/).each do |tag|
752
+ tags.push(tag.strip.sub(/^@/, ''))
753
+ end
673
754
  else
674
- tags.push(arg.strip.sub(/^@/,''))
755
+ tags.push(arg.strip.sub(/^@/, ''))
675
756
  end
676
- }
757
+ end
677
758
  end
678
759
  else
679
760
  section = wwid.current_section
680
761
  end
681
762
 
763
+ tags.concat(options[:tag].split(/ *, */).map { |t| t.sub(/^@/, '').strip }) if options[:tag]
764
+
682
765
  unless tags.empty?
683
766
  tag_filter = {
684
767
  'tags' => tags,
@@ -696,96 +779,127 @@ command :show do |c|
696
779
  start = wwid.chronify(date_string)
697
780
  finish = false
698
781
  end
699
- exit_now! "Unrecognized date string" unless start
700
- dates = [start,finish]
782
+ exit_now! 'Unrecognized date string' unless start
783
+ dates = [start, finish]
701
784
  end
702
785
 
703
786
  options[:t] = true if options[:totals]
704
787
 
705
- options[:sort_tags] = options[:tag_sort] =~ /^n/i
706
-
707
- 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
708
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)
709
807
  end
710
808
  end
711
809
 
712
810
  desc 'Search for entries'
713
- long_desc <<-'EODESC'
714
- 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.
715
813
 
716
- 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/'`
717
815
  EODESC
718
816
 
719
- arg_name 'search_pattern'
817
+ arg_name 'SEARCH_PATTERN'
720
818
  command [:grep, :search] do |c|
721
819
  c.desc 'Section'
722
- c.flag [:s, :section], :default_value => "All"
820
+ c.arg_name 'NAME'
821
+ c.flag %i[s section], default_value: 'All'
723
822
 
724
- c.desc 'Output to export format (csv|html|json)'
725
- 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
726
826
 
727
827
  c.desc 'Show time intervals on @done tasks'
728
- c.switch [:t, :times], :default_value => true
828
+ c.switch %i[t times], default_value: true
729
829
 
730
830
  c.desc 'Show intervals with totals at the end of output'
731
- c.switch [:totals], :default_value => false, :negatable => true
831
+ c.switch [:totals], default_value: false, negatable: true
732
832
 
733
833
  c.desc 'Sort tags by (name|time)'
734
834
  default = 'time'
735
- if wwid.config.has_key?('tag_sort')
736
- default = wwid.config['tag_sort']
737
- end
738
- 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
739
838
 
740
839
  c.desc 'Only show items with recorded time intervals'
741
- c.switch [:only_timed], :default_value => false, :negatable => false
840
+ c.switch [:only_timed], default_value: false, negatable: false
742
841
 
743
- 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
744
844
 
745
845
  section = wwid.guess_section(options[:s]) if options[:s]
746
846
 
747
847
  options[:t] = true if options[:totals]
748
848
  options[:sort_tags] = options[:tag_sort] =~ /^n/i
749
849
 
750
- 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
+ }
751
861
 
862
+ puts wwid.list_section(opts)
752
863
  end
753
864
  end
754
865
 
755
866
  desc 'List recent entries'
756
867
  default_value 10
757
- arg_name 'count'
868
+ arg_name 'COUNT'
758
869
  command :recent do |c|
759
870
  c.desc 'Section'
760
- c.flag [:s, :section], :default_value => "All"
871
+ c.arg_name 'NAME'
872
+ c.flag %i[s section], default_value: 'All'
761
873
 
762
874
  c.desc 'Show time intervals on @done tasks'
763
- c.switch [:t, :times], :default_value => true
875
+ c.switch %i[t times], default_value: true
764
876
 
765
877
  c.desc 'Show intervals with totals at the end of output'
766
- c.switch [:totals], :default_value => false, :negatable => true
878
+ c.switch [:totals], default_value: false, negatable: true
767
879
 
768
880
  c.desc 'Sort tags by (name|time)'
769
881
  default = 'time'
770
- if wwid.config.has_key?('tag_sort')
771
- default = wwid.config['tag_sort']
772
- end
773
- c.flag [:tag_sort], :default_value => default
774
-
775
- 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
776
885
 
886
+ c.action do |global_options, options, args|
777
887
  section = wwid.guess_section(options[:s]) || options[:s].cap_first
778
888
 
779
889
  unless global_options[:version]
780
- if args.length > 0
781
- count = args[0].to_i
782
- else
783
- count = 10
784
- end
890
+ count = args.empty? ? 10 : args[0].to_i
785
891
  options[:t] = true if options[:totals]
786
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
+ }
787
901
 
788
- 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)
789
903
 
790
904
  end
791
905
  end
@@ -794,62 +908,64 @@ end
794
908
  desc 'List entries from today'
795
909
  command :today do |c|
796
910
  c.desc 'Specify a section'
797
- c.arg_name 'section_name'
798
- c.flag [:s, :section], :default_value => 'All'
911
+ c.arg_name 'NAME'
912
+ c.flag %i[s section], default_value: 'All'
799
913
 
800
914
  c.desc 'Show time intervals on @done tasks'
801
- c.switch [:t, :times], :default_value => true
915
+ c.switch %i[t times], default_value: true
802
916
 
803
917
  c.desc 'Show time totals at the end of output'
804
- c.switch [:totals], :default_value => false, :negatable => true
918
+ c.switch [:totals], default_value: false, negatable: true
805
919
 
806
920
  c.desc 'Sort tags by (name|time)'
807
921
  default = 'time'
808
- if wwid.config.has_key?('tag_sort')
809
- default = wwid.config['tag_sort']
810
- end
811
- c.flag [:tag_sort], :default_value => default
812
-
813
- c.desc 'Output to export format (csv|html|json)'
814
- 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
815
925
 
816
- 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
817
929
 
930
+ c.action do |_global_options, options, _args|
818
931
  options[:t] = true if options[:totals]
819
932
  options[:sort_tags] = options[:tag_sort] =~ /^n/i
820
933
 
821
- puts wwid.today(options[:t],options[:output],{:totals => options[:totals], :section => options[:s], :sort_tags => options[:sort_tags]}).chomp
822
-
934
+ puts wwid.today(options[:t], options[:output],
935
+ { totals: options[:totals], section: options[:s], sort_tags: options[:sort_tags] }).chomp
823
936
  end
824
937
  end
825
938
 
826
939
  desc 'List entries for a date'
827
- 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.'
828
- 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'
829
944
  command :on do |c|
830
945
  c.desc 'Section'
831
- c.arg_name 'section_name'
832
- c.flag [:s, :section], :default_value => 'All'
946
+ c.arg_name 'NAME'
947
+ c.flag %i[s section], default_value: 'All'
833
948
 
834
949
  c.desc 'Show time intervals on @done tasks'
835
- c.switch [:t, :times], :default_value => true
950
+ c.switch %i[t times], default_value: true
836
951
 
837
952
  c.desc 'Show time totals at the end of output'
838
- c.switch [:totals], :default_value => false, :negatable => true
953
+ c.switch [:totals], default_value: false, negatable: true
839
954
 
840
955
  c.desc 'Sort tags by (name|time)'
841
956
  default = 'time'
842
- if wwid.config.has_key?('tag_sort')
843
- default = wwid.config['tag_sort']
844
- end
845
- 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
846
960
 
847
- c.desc 'Output to export format (csv|html|json)'
848
- 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
849
964
 
850
- c.action do |global_options,options,args|
965
+ c.action do |_global_options, options, args|
966
+ exit_now! 'Missing date argument' if args.empty?
851
967
 
852
- date_string = args.join(" ")
968
+ date_string = args.join(' ')
853
969
 
854
970
  if date_string =~ / (to|through|thru) /
855
971
  dates = date_string.split(/ (to|through|thru) /)
@@ -860,7 +976,7 @@ command :on do |c|
860
976
  finish = false
861
977
  end
862
978
 
863
- exit_now! "Unrecognized date string" unless start
979
+ exit_now! 'Unrecognized date string' unless start
864
980
 
865
981
  message = "Date interpreted as #{start}"
866
982
  message += " to #{finish}" if finish
@@ -869,56 +985,86 @@ command :on do |c|
869
985
  options[:t] = true if options[:totals]
870
986
  options[:sort_tags] = options[:tag_sort] =~ /^n/i
871
987
 
872
- puts wwid.list_date([start, finish], options[:s], options[:t], options[:output], {:totals => options[:totals], :sort_tags => options[:sort_tags]}).chomp
873
-
988
+ puts wwid.list_date([start, finish], options[:s], options[:t], options[:output],
989
+ { totals: options[:totals], sort_tags: options[:sort_tags] }).chomp
874
990
  end
875
991
  end
876
992
 
877
993
  desc 'List entries from yesterday'
878
994
  command :yesterday do |c|
879
995
  c.desc 'Specify a section'
880
- c.arg_name 'section_name'
881
- c.flag [:s, :section], :default_value => 'All'
996
+ c.arg_name 'NAME'
997
+ c.flag %i[s section], default_value: 'All'
882
998
 
883
- c.desc 'Output to export format (csv|html|json)'
884
- 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
885
1002
 
886
1003
  c.desc 'Show time intervals on @done tasks'
887
- c.switch [:t, :times], :default_value => true
1004
+ c.switch %i[t times], default_value: true
888
1005
 
889
1006
  c.desc 'Show time totals at the end of output'
890
- c.switch [:totals], :default_value => false, :negatable => true
1007
+ c.switch [:totals], default_value: false, negatable: true
891
1008
 
892
1009
  c.desc 'Sort tags by (name|time)'
893
1010
  default = 'time'
894
- if wwid.config.has_key?('tag_sort')
895
- default = wwid.config['tag_sort']
896
- end
897
- 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
898
1014
 
899
- c.action do |global_options, options,args|
1015
+ c.action do |_global_options, options, _args|
900
1016
  options[:sort_tags] = options[:tag_sort] =~ /^n/i
901
- puts wwid.yesterday(options[:s],options[:t],options[:o],{:totals => options[:totals], :sort_tags => options[:sort_tags]}).chomp
902
-
1017
+ puts wwid.yesterday(options[:s], options[:t], options[:o],
1018
+ { totals: options[:totals], sort_tags: options[:sort_tags] }).chomp
903
1019
  end
904
1020
  end
905
1021
 
906
- desc 'Show the last entry'
1022
+ desc 'Show the last entry, optionally edit'
907
1023
  command :last do |c|
908
1024
  c.desc 'Specify a section'
909
- c.flag [:s, :section]
1025
+ c.arg_name 'NAME'
1026
+ c.flag %i[s section], default_value: 'All'
910
1027
 
911
- c.action do |global_options,options,args|
912
- 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
913
1059
  end
914
1060
  end
915
1061
 
916
1062
  desc 'List sections'
917
1063
  command :sections do |c|
918
1064
  c.desc 'List in single column'
919
- c.switch [:c, :column], :default_value => false
1065
+ c.switch %i[c column], default_value: false
920
1066
 
921
- c.action do |global_options,options,args|
1067
+ c.action do |_global_options, options, _args|
922
1068
  joiner = options[:c] ? "\n" : "\t"
923
1069
  print wwid.sections.join(joiner)
924
1070
  end
@@ -926,124 +1072,145 @@ end
926
1072
 
927
1073
  desc 'Select a section to display from a menu'
928
1074
  command :choose do |c|
929
- c.action do |global_options,options,args|
1075
+ c.action do |_global_options, _options, _args|
930
1076
  section = wwid.choose_section
931
- puts wwid.list_section({:section => section.cap_first, :count => 0})
1077
+ puts wwid.list_section({ section: section.cap_first, count: 0 })
932
1078
  end
933
1079
  end
934
1080
 
935
1081
  desc 'Add a new section to the "doing" file'
936
- arg_name 'section_name'
1082
+ arg_name 'SECTION_NAME'
937
1083
  command :add_section do |c|
938
- c.action do |global_options,options,args|
939
- unless wwid.sections.include?(args[0])
940
- wwid.add_section(args[0].cap_first)
941
- wwid.write(wwid.doing_file)
942
- else
943
- raise "Section #{args[0]} already exists"
944
- 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)
945
1089
  end
946
1090
  end
947
1091
 
948
1092
  desc 'List available color variables for configuration templates and views'
949
1093
  command :colors do |c|
950
- c.action do |global_options,options,args|
1094
+ c.action do |_global_options, _options, _args|
951
1095
  clrs = wwid.colors
952
1096
  bgs = []
953
1097
  fgs = []
954
- clrs.each {|k,v|
1098
+ clrs.each do |k, v|
955
1099
  if k =~ /bg/
956
1100
  bgs.push("#{v} #{clrs['default']} <-- #{k}")
957
1101
  else
958
1102
  fgs.push("#{v}XXXX#{clrs['default']} <-- #{k}")
959
1103
  end
960
- }
1104
+ end
961
1105
  puts fgs.join("\n")
962
1106
  puts bgs.join("\n")
963
1107
  end
964
1108
  end
965
1109
 
966
1110
  desc 'Display a user-created view'
967
- arg_name 'view_name'
1111
+ arg_name 'VIEW_NAME'
968
1112
  command :view do |c|
969
1113
  c.desc 'Section (override view settings)'
970
- c.flag [:s, :section]
1114
+ c.arg_name 'NAME'
1115
+ c.flag %i[s section]
971
1116
 
972
1117
  c.desc 'Count to display (override view settings)'
973
- 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
974
1120
 
975
- c.desc 'Output to export format (csv|html|json)'
976
- 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
977
1124
 
978
1125
  c.desc 'Show time intervals on @done tasks'
979
- c.switch [:t, :times], :default_value => true
1126
+ c.switch %i[t times], default_value: true
980
1127
 
981
1128
  c.desc 'Show intervals with totals at the end of output'
982
- 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
983
1133
 
984
1134
  c.desc 'Sort tags by (name|time)'
985
1135
  default = 'time'
986
- if wwid.config.has_key?('tag_sort')
987
- default = wwid.config['tag_sort']
988
- end
989
- 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
990
1139
 
991
1140
  c.desc 'Only show items with recorded time intervals'
992
- c.switch [:only_timed], :default_value => false, :negatable => true
1141
+ c.switch [:only_timed], default_value: false, negatable: true
993
1142
 
994
- c.action do |global_options,options,args|
995
- if args.empty?
996
- title = wwid.choose_view
997
- else
998
- title = wwid.guess_view(args[0])
999
- 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
1000
1149
 
1001
- if options[:s]
1002
- section = wwid.guess_section(options[:s]) || options[:s].cap_first
1003
- end
1150
+ section = wwid.guess_section(options[:s]) || options[:s].cap_first if options[:s]
1004
1151
 
1005
1152
  view = wwid.get_view(title)
1006
1153
  if view
1007
- if (view.has_key?('only_timed') && view['only_timed']) || options[:only_timed]
1008
- only_timed = true
1009
- else
1010
- only_timed = false
1011
- end
1012
-
1013
- template = view.has_key?('template') ? view['template'] : nil
1014
- format = view.has_key?('date_format') ? view['date_format'] : nil
1015
- 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
1016
1163
  tag_filter = false
1017
- if view.has_key?('tags')
1018
- unless view['tags'].nil? || view['tags'].empty?
1019
- tag_filter = {'tags' => [], 'bool' => "OR"}
1020
- if view['tags'].class == Array
1021
- tag_filter['tags'] = view['tags'].map{|tag| tag.strip }
1022
- else
1023
- tag_filter['tags'] = view['tags'].gsub(/[, ]+/," ").split(" ").map{|tag| tag.strip }
1024
- end
1025
- tag_filter['bool'] = view.has_key?('tags_bool') && !view['tags_bool'].nil? ? view['tags_bool'].upcase : "OR"
1026
- 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'
1027
1172
  end
1028
1173
 
1029
1174
  # If the -o/--output flag was specified, override any default in the view template
1030
- options[:o] ||= view.has_key?('output_format') ? view['output_format'] : "template"
1031
-
1032
- count = options[:c] ? options[:c] : view.has_key?('count') ? view['count'] : 10
1033
- section = options[:s] ? section : view.has_key?('section') ? view['section'] : wwid.current_section
1034
- 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'
1035
1188
 
1036
1189
  options[:t] = true if options[:totals]
1037
- options[:output].downcase! if options[:output]
1190
+ options[:output]&.downcase!
1038
1191
  options[:sort_tags] = options[:tag_sort] =~ /^n/i
1039
1192
 
1040
- 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'
1041
1212
  else
1042
- if title.class == FalseClass
1043
- exit_now! "Cancelled"
1044
- else
1045
- raise "View #{title} not found in config"
1046
- end
1213
+ raise "View #{title} not found in config"
1047
1214
  end
1048
1215
  end
1049
1216
  end
@@ -1051,124 +1218,135 @@ end
1051
1218
  desc 'List available custom views'
1052
1219
  command :views do |c|
1053
1220
  c.desc 'List in single column'
1054
- c.switch [:c, :column], :default_value => false
1221
+ c.switch %i[c column], default_value: false
1055
1222
 
1056
- c.action do |global_options,options,args|
1223
+ c.action do |_global_options, options, _args|
1057
1224
  joiner = options[:c] ? "\n" : "\t"
1058
1225
  print wwid.views.join(joiner)
1059
1226
  end
1060
1227
  end
1061
1228
 
1062
- desc 'Move entries in between sections'
1063
- arg_name 'section'
1229
+ desc 'Move entries between sections'
1230
+ arg_name 'SECTION_NAME'
1064
1231
  default_value wwid.current_section
1065
1232
  command :archive do |c|
1066
1233
  c.desc 'Count to keep (ignored if archiving by tag)'
1067
- 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
1068
1236
 
1069
1237
  c.desc 'Move entries to'
1070
- c.flag [:t, :to], :default_value => "Archive"
1238
+ c.arg_name 'SECTION_NAME'
1239
+ c.flag %i[t to], default_value: 'Archive'
1071
1240
 
1072
- c.desc 'Tag boolean'
1073
- 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]
1074
1244
 
1075
- c.action do |global_options,options,args|
1076
- if args.length > 0
1077
- if args[0] =~ /^@\S+/
1078
- section = "all"
1079
- tags = args.map {|t| t.sub(/^@/,'').strip }
1080
- else
1081
- section = args[0].cap_first
1082
- tags = args.length > 1 ? args[1..-1].map {|t| t.sub(/^@/,'').strip } : nil
1083
- end
1084
- 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?
1085
1251
  section = wwid.current_section
1086
- 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
1087
1259
  end
1088
- 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])
1089
1264
  end
1090
1265
  end
1091
1266
 
1092
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'})"
1093
1269
  command :open do |c|
1094
- if `uname` =~ /Darwin/
1095
- c.desc 'open with app name'
1096
- c.arg_name 'app_name'
1097
- c.flag [:a]
1098
-
1099
- c.desc 'open with app bundle id'
1100
- c.arg_name 'bundle_id'
1101
- c.flag [:b]
1102
- end
1103
- c.desc 'open with $EDITOR'
1104
- 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
1105
1281
 
1106
- c.action do |global_options,options,args|
1282
+ c.action do |_global_options, options, _args|
1107
1283
  params = options.dup
1108
- params.delete_if { |k,v|
1109
- k.class == String || v.nil? || v == false
1110
- }
1284
+ params.delete_if do |k, v|
1285
+ k.instance_of?(String) || v.nil? || v == false
1286
+ end
1111
1287
  if `uname` =~ /Darwin/
1112
- if params.length < 2
1113
1288
  if options[:a]
1114
- system %Q{open -a "#{options[:a]}" "#{File.expand_path(wwid.doing_file)}"}
1289
+ system %(open -a "#{options[:a]}" "#{File.expand_path(wwid.doing_file)}")
1115
1290
  elsif options[:b]
1116
- system %Q{open -b "#{options[:b]}" "#{File.expand_path(wwid.doing_file)}"}
1291
+ system %(open -b "#{options[:b]}" "#{File.expand_path(wwid.doing_file)}")
1117
1292
  elsif options[:e]
1118
- raise "No EDITOR variable defined in environment" if ENV['EDITOR'].nil?
1119
- 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)}")
1120
1298
  else
1121
- if wwid.config.has_key?('editor_app') && !wwid.config['editor_app'].nil?
1122
- system %Q{open -a "#{wwid.config['editor_app']}" "#{File.expand_path(wwid.doing_file)}"}
1123
- else
1124
- system %Q{open "#{File.expand_path(wwid.doing_file)}"}
1125
- end
1299
+ system %(open "#{File.expand_path(wwid.doing_file)}")
1126
1300
  end
1301
+
1127
1302
  else
1128
- raise "The open command takes a single parameter. #{params.length} specified."
1129
- end
1130
- else
1131
- raise "No EDITOR variable defined in environment" if ENV['EDITOR'].nil?
1132
- 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)}")
1133
1306
  end
1134
1307
  end
1135
1308
  end
1136
1309
 
1137
-
1138
1310
  desc 'Edit the configuration file'
1139
1311
  command :config do |c|
1140
1312
  c.desc 'Editor to use'
1141
- c.flag [:e, :editor], :default_value => nil
1313
+ c.arg_name 'EDITOR'
1314
+ c.flag %i[e editor], default_value: nil
1142
1315
 
1143
- if `uname` =~ /Darwins/
1316
+ if `uname` =~ /Darwin/
1144
1317
  c.desc 'Application to use'
1318
+ c.arg_name 'APP_NAME'
1145
1319
  c.flag [:a]
1146
1320
 
1147
- c.desc "Use the editor_app defined in ~/.doingrc (#{wwid.config['editor_app']})"
1148
- c.switch [:x]
1149
-
1150
1321
  c.desc 'Application bundle id to use'
1322
+ c.arg_name 'BUNDLE_ID'
1151
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]
1152
1327
  end
1153
- c.action do |global_options,options,args|
1154
- if `uname` =~ /Darwins/
1328
+
1329
+ c.action do |_global_options, options, _args|
1330
+ if `uname` =~ /Darwin/
1155
1331
  if options[:x]
1156
- %x{open -a "#{wwid.config['editor_app']}" "#{wwid.config_file}"}
1332
+ `open -a "#{wwid.config['config_editor_app']}" "#{wwid.config_file}"`
1157
1333
  elsif options[:a] || options[:b]
1158
1334
  if options[:a]
1159
- %x{open -a "#{options[:a]}" "#{wwid.config_file}"}
1335
+ `open -a "#{options[:a]}" "#{wwid.config_file}"`
1160
1336
  elsif options[:b]
1161
- %x{open -b #{options[:b]} "#{wwid.config_file}"}
1337
+ `open -b #{options[:b]} "#{wwid.config_file}"`
1162
1338
  end
1163
1339
  else
1164
- 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
+
1165
1342
  editor = options[:e].nil? ? ENV['EDITOR'] : options[:e]
1166
- system %Q{#{editor} "#{wwid.config_file}"}
1343
+ system %(#{editor} "#{wwid.config_file}")
1167
1344
  end
1168
1345
  else
1169
- 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
+
1170
1348
  editor = options[:e].nil? ? ENV['EDITOR'] : options[:e]
1171
- system %Q{#{editor} "#{wwid.config_file}"}
1349
+ system %(#{editor} "#{wwid.config_file}")
1172
1350
  end
1173
1351
  end
1174
1352
  end
@@ -1176,19 +1354,19 @@ end
1176
1354
  desc 'Undo the last change to the doing_file'
1177
1355
  command :undo do |c|
1178
1356
  c.desc 'Specify alternate doing file'
1179
- 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
1180
1359
 
1181
- c.action do |global_options,options,args|
1360
+ c.action do |_global_options, options, _args|
1182
1361
  file = options[:f] || wwid.doing_file
1183
1362
  wwid.restore_backup(file)
1184
1363
  end
1185
1364
  end
1186
1365
 
1187
-
1188
- pre do |global,command,options,args|
1189
- if global[:config_file]
1366
+ pre do |global, _command, _options, _args|
1367
+ if global[:config_file] && global[:config_file] != wwid.config_file
1190
1368
  wwid.config_file = global[:config_file]
1191
- wwid.configure({:ignore_local => true})
1369
+ wwid.configure({ ignore_local: true })
1192
1370
  # wwid.results.push("Override config file #{wwid.config_file}")
1193
1371
  end
1194
1372
 
@@ -1202,9 +1380,7 @@ pre do |global,command,options,args|
1202
1380
 
1203
1381
  wwid.config[:include_notes] = false unless global[:notes]
1204
1382
 
1205
- if global[:version]
1206
- $stdout.puts "doing v" + Doing::VERSION
1207
- end
1383
+ $stdout.puts "doing v#{Doing::VERSION}" if global[:version]
1208
1384
 
1209
1385
  # Return true to proceed; false to abort and not call the
1210
1386
  # chosen command
@@ -1213,7 +1389,7 @@ pre do |global,command,options,args|
1213
1389
  true
1214
1390
  end
1215
1391
 
1216
- post do |global,command,options,args|
1392
+ post do |global, _command, _options, _args|
1217
1393
  # Use skips_post before a command to skip this
1218
1394
  # block on that command only
1219
1395
  if global[:stdout]
@@ -1223,7 +1399,7 @@ post do |global,command,options,args|
1223
1399
  end
1224
1400
  end
1225
1401
 
1226
- on_error do |exception|
1402
+ on_error do |_exception|
1227
1403
  # puts exception.message
1228
1404
  true
1229
1405
  end