doing 1.0.70 → 1.0.75
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +28 -2
- data/bin/doing +208 -62
- data/lib/doing/version.rb +1 -1
- data/lib/doing/wwid.rb +235 -28
- metadata +8 -8
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 4acc94f7b7b30e01c77c1f5b026e6b815cd1d55b19d2afa7c9a029a46f201659
|
4
|
+
data.tar.gz: e72ee1110adb862162888690ff17002d8cb17e6810362c46994282293b4c8d4c
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: ff6f4bb0dad93e88beb67b1e96b8d094a6b6b8afb5785d79472c3946e6ac510f8f1d233133972347ba66e256056296d6905753192d7c81152dc2a186f535acda
|
7
|
+
data.tar.gz: 1d7147a5055e593ff5a5b0116ba71525f66d171b042437836c4112ded26a2c68a8b1a1e1b793b19901b0e69e4da24f9759dbdecfaff0e9563e74f92d049a825e
|
data/README.md
CHANGED
@@ -27,7 +27,7 @@ If there's something I want to look at later but doesn't need to be added to a t
|
|
27
27
|
|
28
28
|
## Installation
|
29
29
|
|
30
|
-
The current version of `doing` is <!--VER-->1.0.
|
30
|
+
The current version of `doing` is <!--VER-->1.0.74<!--END VER-->.
|
31
31
|
|
32
32
|
$ [sudo] gem install doing
|
33
33
|
|
@@ -536,7 +536,7 @@ If you have a use for it, you can use `-o csv` on the show or view commands to o
|
|
536
536
|
|
537
537
|
`doing yesterday` is great for stand-ups (thanks to [Sean Collins](https://github.com/sc68cal) for that!). Note that you can show yesterday's activity from an alternate section by using the section name as an argument (e.g. `doing yesterday archive`).
|
538
538
|
|
539
|
-
`doing on` allows for full date ranges and filtering. `doing on saturday`, or `doing on one month to today` will give you ranges. You can use the same terms with the `show` command by adding the `-f` or `--from` flag. `doing show @done --from "monday to friday"` will give you all of your completed items for the last week (assuming it's the weekend).
|
539
|
+
`doing on` allows for full date ranges and filtering. `doing on saturday`, or `doing on one month to today` will give you ranges. You can use the same terms with the `show` command by adding the `-f` or `--from` flag. `doing show @done --from "monday to friday"` will give you all of your completed items for the last week (assuming it's the weekend). There's also `doing since` a simple alias for `doing on PAST_DATE to now`, e.g. `doing since monday`.
|
540
540
|
|
541
541
|
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.
|
542
542
|
|
@@ -617,6 +617,32 @@ Now you can run `doing import --type timing -s SECTION PATH`, where SECTION is t
|
|
617
617
|
# Import to default section (Currently) and prefix entries with '[Imported]'
|
618
618
|
doing import --prefix="[Imported]" "~/Desktop/All Activities.json"
|
619
619
|
|
620
|
+
#### Interactive Usage
|
621
|
+
|
622
|
+
If you have `fzf` installed (<https://github.com/junegunn/fzf>), you can use `doing select` to get a menu of all your items (or items in a given section) which can be searched with fuzzy matching. The menu allows multiple selections to be acted on directly.
|
623
|
+
|
624
|
+
To use the menu, type a search string or use the arrow keys to navigate up and down. Press tab on an entry you'd like to perform an action on. A marker will show up on the left indicating the entry is selected. Repeat the process and select as many entries as needed. When you hit Return, the selection will be passed back to doing. Use Control-A to select all visible entries.
|
625
|
+
|
626
|
+
Doing can perform several functions with this menu. Not all of doing's features are available, but the core functionality you'd need is there, plus you can open the selected entries on one page in your text editor, make changes to them, and when you save and close the entries are updated accordingly. This allows editing of everything from timestamps to tags to notes.
|
627
|
+
|
628
|
+
Run `doing help select` for a list of options:
|
629
|
+
|
630
|
+
-a, --archive - Archive selected items
|
631
|
+
-c, --cancel - Cancel selected items (add @done without timestamp)
|
632
|
+
-d, --delete - Delete selected items
|
633
|
+
-e, --editor - Edit selected item(s)
|
634
|
+
-f, --finish - Add @done with current time to selected item(s)
|
635
|
+
--flag - Add flag to selected item(s)
|
636
|
+
-m, --move=SECTION - Move selected items to section (default: none)
|
637
|
+
-s, --section=SECTION - Select from a specific section (default: none)
|
638
|
+
-t, --tag=TAG - Tag selected entries (default: none)
|
639
|
+
|
640
|
+
For example, `doing select -d -a` would present the menu, and then mark selected entries as @done (with timestamp) and move them to the Archive section.
|
641
|
+
|
642
|
+
Multiple actions can be performed at once by combining options. You can also combine the `--editor` switch with any other options. Other actions will be performed first, then the entries --- with any modifications performed --- will be presented in the editor for tweaking.
|
643
|
+
|
644
|
+
**Note:** when using the `--editor` flag to open selections in your text editor, entries will be separated by `---` lines. These must remain in place for doing to track the changes. You can do anything you want to the entries, modify dates, change text, add notes, etc., as long as you leave the dividers in place. You can even delete an entry entirely, leaving the dividers around the missing line and the entry will be removed from your doing file when you save and exit the editor.
|
645
|
+
|
620
646
|
---
|
621
647
|
|
622
648
|
## Extras
|
data/bin/doing
CHANGED
@@ -79,7 +79,7 @@ command %i[now next] do |c|
|
|
79
79
|
if options[:back]
|
80
80
|
date = wwid.chronify(options[:back])
|
81
81
|
|
82
|
-
|
82
|
+
exit_now! 'Unable to parse date string' if date.nil?
|
83
83
|
else
|
84
84
|
date = Time.now
|
85
85
|
end
|
@@ -87,13 +87,13 @@ command %i[now next] do |c|
|
|
87
87
|
section = wwid.guess_section(options[:s]) || options[:s].cap_first
|
88
88
|
|
89
89
|
if options[:e] || (args.empty? && $stdin.stat.size.zero?)
|
90
|
-
|
90
|
+
exit_now! 'No EDITOR variable defined in environment' if ENV['EDITOR'].nil?
|
91
91
|
|
92
92
|
input = ''
|
93
93
|
input += args.join(' ') unless args.empty?
|
94
94
|
input = wwid.fork_editor(input).strip
|
95
95
|
|
96
|
-
|
96
|
+
exit_now! 'No content' if input.empty?
|
97
97
|
|
98
98
|
title, note = wwid.format_input(input)
|
99
99
|
note.push(options[:n]) if options[:n]
|
@@ -111,7 +111,7 @@ command %i[now next] do |c|
|
|
111
111
|
wwid.add_item(title.cap_first, section, { note: note, back: date, timed: options[:f] })
|
112
112
|
wwid.write(wwid.doing_file)
|
113
113
|
else
|
114
|
-
|
114
|
+
exit_now! 'You must provide content when creating a new entry'
|
115
115
|
end
|
116
116
|
end
|
117
117
|
end
|
@@ -138,7 +138,7 @@ command :note do |c|
|
|
138
138
|
section = wwid.guess_section(options[:s]) || options[:s].cap_first
|
139
139
|
|
140
140
|
if options[:e] || (args.empty? && $stdin.stat.size.zero? && !options[:r])
|
141
|
-
|
141
|
+
exit_now! 'No EDITOR variable defined in environment' if ENV['EDITOR'].nil?
|
142
142
|
|
143
143
|
input = !args.empty? ? args.join(' ') : ''
|
144
144
|
|
@@ -147,11 +147,11 @@ command :note do |c|
|
|
147
147
|
input = prev_input + input
|
148
148
|
|
149
149
|
input = wwid.fork_editor(input).strip
|
150
|
-
|
150
|
+
exit_now! 'No content, cancelled' unless input
|
151
151
|
|
152
152
|
_title, note = wwid.format_input(input)
|
153
153
|
|
154
|
-
|
154
|
+
exit_now! 'No note content' unless note
|
155
155
|
|
156
156
|
wwid.note_last(section, note, replace: true)
|
157
157
|
elsif !args.empty?
|
@@ -165,7 +165,7 @@ command :note do |c|
|
|
165
165
|
elsif options[:r]
|
166
166
|
wwid.note_last(section, [], replace: true)
|
167
167
|
else
|
168
|
-
|
168
|
+
exit_now! 'You must provide content when adding a note'
|
169
169
|
end
|
170
170
|
wwid.write(wwid.doing_file)
|
171
171
|
end
|
@@ -196,7 +196,7 @@ command :meanwhile do |c|
|
|
196
196
|
if options[:back]
|
197
197
|
date = wwid.chronify(options[:back])
|
198
198
|
|
199
|
-
|
199
|
+
exit_now! 'Unable to parse date string' if date.nil?
|
200
200
|
else
|
201
201
|
date = Time.now
|
202
202
|
end
|
@@ -205,7 +205,7 @@ command :meanwhile do |c|
|
|
205
205
|
input = ''
|
206
206
|
|
207
207
|
if options[:e]
|
208
|
-
|
208
|
+
exit_now! 'No EDITOR variable defined in environment' if ENV['EDITOR'].nil?
|
209
209
|
|
210
210
|
input += args.join(' ') unless args.empty?
|
211
211
|
input = wwid.fork_editor(input).strip
|
@@ -243,19 +243,64 @@ long_desc %(
|
|
243
243
|
arg_name 'TYPE', must_match: /^(?:html|haml|css)/i
|
244
244
|
command :template do |c|
|
245
245
|
c.action do |_global_options, options, args|
|
246
|
-
|
246
|
+
exit_now! 'No type specified, use `doing template [HAML|CSS]`' if args.empty?
|
247
247
|
|
248
|
-
case
|
248
|
+
case args[0]
|
249
249
|
when /html|haml/i
|
250
250
|
$stdout.puts wwid.haml_template
|
251
251
|
when /css/i
|
252
252
|
$stdout.puts wwid.css_template
|
253
253
|
else
|
254
|
-
|
254
|
+
exit_now! 'Invalid type specified, must be HAML or CSS'
|
255
255
|
end
|
256
256
|
end
|
257
257
|
end
|
258
258
|
|
259
|
+
desc 'Display an interactive menu to perform operations (requires fzf)'
|
260
|
+
long_desc 'Requires that `fzf` be installed and available in your path. <https://github.com/junegunn/fzf>
|
261
|
+
|
262
|
+
List all entries and select with typeahead fuzzy matching. Multiple selections are allowed, hit tab to add the highlighted entry to the selection. Return processes the selected entries.'
|
263
|
+
command :select do |c|
|
264
|
+
c.desc 'Select from a specific section'
|
265
|
+
c.arg_name 'SECTION'
|
266
|
+
c.flag %i[s section]
|
267
|
+
|
268
|
+
c.desc 'Tag selected entries'
|
269
|
+
c.arg_name 'TAG'
|
270
|
+
c.flag %i[t tag]
|
271
|
+
|
272
|
+
# c.desc 'Add @done to selected item(s), using start time of next item as the finish time'
|
273
|
+
# c.switch %i[a auto], negatable: false, default_value: false
|
274
|
+
|
275
|
+
c.desc 'Archive selected items'
|
276
|
+
c.switch %i[a archive], negatable: false, default_value: false
|
277
|
+
|
278
|
+
c.desc 'Move selected items to section'
|
279
|
+
c.arg_name 'SECTION'
|
280
|
+
c.flag %i[m move]
|
281
|
+
|
282
|
+
c.desc 'Cancel selected items (add @done without timestamp)'
|
283
|
+
c.switch %i[c cancel], negatable: false, default_value: false
|
284
|
+
|
285
|
+
c.desc 'Delete selected items'
|
286
|
+
c.switch %i[d delete], negatable: false, default_value: false
|
287
|
+
|
288
|
+
c.desc 'Edit selected item(s)'
|
289
|
+
c.switch %i[e editor], negatable: false, default_value: false
|
290
|
+
|
291
|
+
c.desc 'Add @done with current time to selected item(s)'
|
292
|
+
c.switch %i[f finish], negatable: false, default_value: false
|
293
|
+
|
294
|
+
c.desc 'Add flag to selected item(s)'
|
295
|
+
c.switch %i[flag], negatable: false, default_value: false
|
296
|
+
|
297
|
+
c.action do |_global_options, options, args|
|
298
|
+
section = options[:section] || 'All'
|
299
|
+
edit = options[:editor]
|
300
|
+
wwid.interactive(options)
|
301
|
+
end
|
302
|
+
end
|
303
|
+
|
259
304
|
desc 'Add an item to the Later section'
|
260
305
|
arg_name 'ENTRY'
|
261
306
|
command :later do |c|
|
@@ -277,17 +322,17 @@ command :later do |c|
|
|
277
322
|
c.action do |_global_options, options, args|
|
278
323
|
if options[:back]
|
279
324
|
date = wwid.chronify(options[:back])
|
280
|
-
|
325
|
+
exit_now! 'Unable to parse date string' if date.nil?
|
281
326
|
else
|
282
327
|
date = Time.now
|
283
328
|
end
|
284
329
|
|
285
330
|
if options[:e] || (args.empty? && $stdin.stat.size.zero?)
|
286
|
-
|
331
|
+
exit_now! 'No EDITOR variable defined in environment' if ENV['EDITOR'].nil?
|
287
332
|
|
288
333
|
input = args.empty? ? '' : args.join(' ')
|
289
334
|
input = wwid.fork_editor(input).strip
|
290
|
-
|
335
|
+
exit_now! 'No content' unless input && !input.empty?
|
291
336
|
|
292
337
|
title, note = wwid.format_input(input)
|
293
338
|
note.push(options[:n]) if options[:n]
|
@@ -304,7 +349,7 @@ command :later do |c|
|
|
304
349
|
wwid.add_item(title.cap_first, 'Later', { note: note, back: date })
|
305
350
|
wwid.write(wwid.doing_file)
|
306
351
|
else
|
307
|
-
|
352
|
+
exit_now! 'You must provide content when creating a new entry'
|
308
353
|
end
|
309
354
|
end
|
310
355
|
end
|
@@ -352,19 +397,19 @@ command %i[done did] do |c|
|
|
352
397
|
|
353
398
|
if options[:took]
|
354
399
|
took = wwid.chronify_qty(options[:took])
|
355
|
-
|
400
|
+
exit_now! 'Unable to parse date string for --took' if took.nil?
|
356
401
|
end
|
357
402
|
|
358
403
|
if options[:back]
|
359
404
|
date = wwid.chronify(options[:back])
|
360
|
-
|
405
|
+
exit_now! 'Unable to parse date string for --back' if date.nil?
|
361
406
|
else
|
362
407
|
date = options[:took] ? Time.now - took : Time.now
|
363
408
|
end
|
364
409
|
|
365
410
|
if options[:at]
|
366
411
|
finish_date = wwid.chronify(options[:at])
|
367
|
-
|
412
|
+
exit_now! 'Unable to parse date string for --at' if finish_date.nil?
|
368
413
|
|
369
414
|
date = options[:took] ? finish_date - took : finish_date
|
370
415
|
elsif options[:took]
|
@@ -382,12 +427,12 @@ command %i[done did] do |c|
|
|
382
427
|
section = wwid.guess_section(options[:s]) || options[:s].cap_first
|
383
428
|
|
384
429
|
if options[:e]
|
385
|
-
|
430
|
+
exit_now! 'No EDITOR variable defined in environment' if ENV['EDITOR'].nil?
|
386
431
|
|
387
432
|
input = ''
|
388
433
|
input += args.join(' ') unless args.empty?
|
389
434
|
input = wwid.fork_editor(input).strip
|
390
|
-
|
435
|
+
exit_now! 'No content' unless input && !input.empty?
|
391
436
|
|
392
437
|
title, note = wwid.format_input(input)
|
393
438
|
title += " @done#{donedate}"
|
@@ -422,7 +467,7 @@ command %i[done did] do |c|
|
|
422
467
|
wwid.add_item(title.cap_first, section.cap_first, { note: note, back: date })
|
423
468
|
wwid.write(wwid.doing_file)
|
424
469
|
else
|
425
|
-
|
470
|
+
exit_now! 'You must provide content when creating a new entry'
|
426
471
|
end
|
427
472
|
end
|
428
473
|
end
|
@@ -468,9 +513,9 @@ command :cancel do |c|
|
|
468
513
|
end
|
469
514
|
end
|
470
515
|
|
471
|
-
|
516
|
+
exit_now! 'Only one argument allowed' if args.length > 1
|
472
517
|
|
473
|
-
|
518
|
+
exit_now! 'Invalid argument (specify number of recent items to mark @done)' unless args.empty? || args[0] =~ /\d+/
|
474
519
|
|
475
520
|
count = args[0] ? args[0].to_i : 1
|
476
521
|
opts = {
|
@@ -535,14 +580,14 @@ command :finish do |c|
|
|
535
580
|
section = wwid.guess_section(options[:s]) || options[:s].cap_first
|
536
581
|
|
537
582
|
unless options[:auto]
|
538
|
-
|
583
|
+
exit_now! '--back and --took cannot be used together' if options[:back] && options[:took]
|
539
584
|
|
540
|
-
|
585
|
+
exit_now! '--search and --tag cannot be used together' if options[:search] && options[:tag]
|
541
586
|
|
542
587
|
if options[:back]
|
543
588
|
date = wwid.chronify(options[:back])
|
544
589
|
|
545
|
-
|
590
|
+
exit_now! 'Unable to parse date string' if date.nil?
|
546
591
|
elsif options[:took]
|
547
592
|
date = wwid.chronify_qty(options[:took])
|
548
593
|
else
|
@@ -566,9 +611,9 @@ command :finish do |c|
|
|
566
611
|
end
|
567
612
|
end
|
568
613
|
|
569
|
-
|
614
|
+
exit_now! 'Only one argument allowed' if args.length > 1
|
570
615
|
|
571
|
-
|
616
|
+
exit_now! 'Invalid argument (specify number of recent items to mark @done)' unless args.length == 0 || args[0] =~ /\d+/
|
572
617
|
|
573
618
|
count = args[0] ? args[0].to_i : 1
|
574
619
|
opts = {
|
@@ -650,6 +695,9 @@ command :tag do |c|
|
|
650
695
|
c.arg_name 'COUNT'
|
651
696
|
c.flag %i[c count], default_value: 1
|
652
697
|
|
698
|
+
c.desc 'Don\'t ask permission to tag all entries when count is 0'
|
699
|
+
c.switch %i[force], negatable: false, default_value: false
|
700
|
+
|
653
701
|
c.desc 'Include current date/time with tag'
|
654
702
|
c.switch %i[d date], negatable: false, default_value: false
|
655
703
|
|
@@ -662,12 +710,48 @@ command :tag do |c|
|
|
662
710
|
c.desc 'Autotag entries based on autotag configuration in ~/.doingrc'
|
663
711
|
c.switch %i[a autotag], negatable: false, default_value: false
|
664
712
|
|
713
|
+
c.desc 'Tag the last X entries containing TAG.
|
714
|
+
Separate multiple tags with comma (--tag=tag1,tag2), combine with --bool'
|
715
|
+
c.arg_name 'TAG'
|
716
|
+
c.flag [:tag]
|
717
|
+
|
718
|
+
c.desc 'Tag entries matching search filter, surround with slashes for regex (e.g. "/query.*/")'
|
719
|
+
c.arg_name 'QUERY'
|
720
|
+
c.flag [:search]
|
721
|
+
|
722
|
+
c.desc 'Boolean (AND|OR|NOT) with which to combine multiple tag filters'
|
723
|
+
c.arg_name 'BOOLEAN'
|
724
|
+
c.flag [:bool], must_match: /(?:and|all|any|or|not|none)/i, default_value: 'AND'
|
725
|
+
|
665
726
|
c.action do |_global_options, options, args|
|
666
|
-
|
727
|
+
exit_now! 'You must specify at least one tag' if args.empty? && !options[:a]
|
728
|
+
|
729
|
+
exit_now! '--search and --tag cannot be used together' if options[:search] && options[:tag]
|
730
|
+
|
731
|
+
section = 'All'
|
732
|
+
|
733
|
+
if options[:section]
|
734
|
+
section = wwid.guess_section(options[:section]) || options[:section].cap_first
|
735
|
+
end
|
667
736
|
|
668
|
-
section = wwid.guess_section(options[:s]) || options[:s].cap_first
|
669
737
|
|
670
|
-
if options[:
|
738
|
+
if options[:tag].nil?
|
739
|
+
search_tags = []
|
740
|
+
else
|
741
|
+
search_tags = options[:tag].split(/ *, */).map { |t| t.strip.sub(/^@/, '') }
|
742
|
+
options[:bool] = case options[:bool]
|
743
|
+
when /(and|all)/i
|
744
|
+
'AND'
|
745
|
+
when /(any|or)/i
|
746
|
+
'OR'
|
747
|
+
when /(not|none)/i
|
748
|
+
'NOT'
|
749
|
+
else
|
750
|
+
'AND'
|
751
|
+
end
|
752
|
+
end
|
753
|
+
|
754
|
+
if options[:autotag]
|
671
755
|
tags = []
|
672
756
|
else
|
673
757
|
tags = if args.join('') =~ /,/
|
@@ -679,10 +763,19 @@ command :tag do |c|
|
|
679
763
|
tags.map! { |tag| tag.sub(/^@/, '').strip }
|
680
764
|
end
|
681
765
|
|
682
|
-
count = options[:
|
766
|
+
count = options[:count].to_i
|
767
|
+
|
768
|
+
if count.zero? && !options[:force]
|
769
|
+
if options[:search]
|
770
|
+
section_q = ' matching your search terms'
|
771
|
+
elsif options[:tag]
|
772
|
+
section_q = ' matching your tag search'
|
773
|
+
elsif section == 'All'
|
774
|
+
section_q = ''
|
775
|
+
else
|
776
|
+
section_q = " in section #{section}"
|
777
|
+
end
|
683
778
|
|
684
|
-
if count.zero?
|
685
|
-
section_q = section == 'All' ? '' : " in section #{section}"
|
686
779
|
|
687
780
|
question = if options[:a]
|
688
781
|
"Are you sure you want to autotag all records#{section_q}"
|
@@ -694,14 +787,18 @@ command :tag do |c|
|
|
694
787
|
|
695
788
|
res = wwid.yn(question, default_response: false)
|
696
789
|
|
697
|
-
|
790
|
+
exit_now! 'Cancelled' unless res
|
698
791
|
end
|
792
|
+
|
699
793
|
opts = {
|
700
794
|
autotag: options[:a],
|
701
795
|
count: count,
|
702
796
|
date: options[:date],
|
703
797
|
remove: options[:r],
|
798
|
+
search: options[:search],
|
704
799
|
section: section,
|
800
|
+
tag: search_tags,
|
801
|
+
tag_bool: options[:bool],
|
705
802
|
tags: tags,
|
706
803
|
unfinished: options[:unfinished]
|
707
804
|
}
|
@@ -768,10 +865,10 @@ command :show do |c|
|
|
768
865
|
c.flag %i[f from]
|
769
866
|
|
770
867
|
c.desc 'Show time intervals on @done tasks'
|
771
|
-
c.switch %i[t times], default_value: true
|
868
|
+
c.switch %i[t times], default_value: true, negatable: true
|
772
869
|
|
773
870
|
c.desc 'Show intervals with totals at the end of output'
|
774
|
-
c.switch [:totals], default_value: false, negatable:
|
871
|
+
c.switch [:totals], default_value: false, negatable: false
|
775
872
|
|
776
873
|
c.desc 'Sort tags by (name|time)'
|
777
874
|
default = 'time'
|
@@ -800,7 +897,7 @@ command :show do |c|
|
|
800
897
|
section = 'All'
|
801
898
|
else
|
802
899
|
section = wwid.guess_section(args[0])
|
803
|
-
|
900
|
+
exit_now! "No such section: #{args[0]}" unless section
|
804
901
|
|
805
902
|
args.shift
|
806
903
|
end
|
@@ -890,10 +987,10 @@ command [:grep, :search] do |c|
|
|
890
987
|
c.flag %i[o output], must_match: /^(?:template|html|csv|json|timeline)$/i
|
891
988
|
|
892
989
|
c.desc 'Show time intervals on @done tasks'
|
893
|
-
c.switch %i[t times], default_value: true
|
990
|
+
c.switch %i[t times], default_value: true, negatable: true
|
894
991
|
|
895
992
|
c.desc 'Show intervals with totals at the end of output'
|
896
|
-
c.switch [:totals], default_value: false, negatable:
|
993
|
+
c.switch [:totals], default_value: false, negatable: false
|
897
994
|
|
898
995
|
c.desc 'Sort tags by (name|time)'
|
899
996
|
default = 'time'
|
@@ -937,10 +1034,10 @@ command :recent do |c|
|
|
937
1034
|
c.flag %i[s section], default_value: 'All'
|
938
1035
|
|
939
1036
|
c.desc 'Show time intervals on @done tasks'
|
940
|
-
c.switch %i[t times], default_value: true
|
1037
|
+
c.switch %i[t times], default_value: true, negatable: true
|
941
1038
|
|
942
1039
|
c.desc 'Show intervals with totals at the end of output'
|
943
|
-
c.switch [:totals], default_value: false, negatable:
|
1040
|
+
c.switch [:totals], default_value: false, negatable: false
|
944
1041
|
|
945
1042
|
c.desc 'Sort tags by (name|time)'
|
946
1043
|
default = 'time'
|
@@ -977,10 +1074,10 @@ command :today do |c|
|
|
977
1074
|
c.flag %i[s section], default_value: 'All'
|
978
1075
|
|
979
1076
|
c.desc 'Show time intervals on @done tasks'
|
980
|
-
c.switch %i[t times], default_value: true
|
1077
|
+
c.switch %i[t times], default_value: true, negatable: true
|
981
1078
|
|
982
1079
|
c.desc 'Show time totals at the end of output'
|
983
|
-
c.switch [:totals], default_value: false, negatable:
|
1080
|
+
c.switch [:totals], default_value: false, negatable: false
|
984
1081
|
|
985
1082
|
c.desc 'Sort tags by (name|time)'
|
986
1083
|
default = 'time'
|
@@ -1012,10 +1109,10 @@ command :on do |c|
|
|
1012
1109
|
c.flag %i[s section], default_value: 'All'
|
1013
1110
|
|
1014
1111
|
c.desc 'Show time intervals on @done tasks'
|
1015
|
-
c.switch %i[t times], default_value: true
|
1112
|
+
c.switch %i[t times], default_value: true, negatable: true
|
1016
1113
|
|
1017
1114
|
c.desc 'Show time totals at the end of output'
|
1018
|
-
c.switch [:totals], default_value: false, negatable:
|
1115
|
+
c.switch [:totals], default_value: false, negatable: false
|
1019
1116
|
|
1020
1117
|
c.desc 'Sort tags by (name|time)'
|
1021
1118
|
default = 'time'
|
@@ -1055,6 +1152,55 @@ command :on do |c|
|
|
1055
1152
|
end
|
1056
1153
|
end
|
1057
1154
|
|
1155
|
+
desc 'List entries since a date'
|
1156
|
+
long_desc %(Date argument can be natural language and are always interpreted as being in the past. "thursday" would be interpreted as "last thursday,"
|
1157
|
+
and "2d" would be interpreted as "two days ago.")
|
1158
|
+
arg_name 'DATE_STRING'
|
1159
|
+
command :since do |c|
|
1160
|
+
c.desc 'Section'
|
1161
|
+
c.arg_name 'NAME'
|
1162
|
+
c.flag %i[s section], default_value: 'All'
|
1163
|
+
|
1164
|
+
c.desc 'Show time intervals on @done tasks'
|
1165
|
+
c.switch %i[t times], default_value: true, negatable: true
|
1166
|
+
|
1167
|
+
c.desc 'Show time totals at the end of output'
|
1168
|
+
c.switch [:totals], default_value: false, negatable: false
|
1169
|
+
|
1170
|
+
c.desc 'Sort tags by (name|time)'
|
1171
|
+
default = 'time'
|
1172
|
+
default = wwid.config['tag_sort'] if wwid.config.key?('tag_sort')
|
1173
|
+
c.arg_name 'KEY'
|
1174
|
+
c.flag [:tag_sort], must_match: /^(?:name|time)$/i, default_value: default
|
1175
|
+
|
1176
|
+
c.desc 'Output to export format (csv|html|json|template|timeline)'
|
1177
|
+
c.arg_name 'FORMAT'
|
1178
|
+
c.flag %i[o output], must_match: /^(?:template|html|csv|json|timeline)$/i
|
1179
|
+
|
1180
|
+
c.action do |_global_options, options, args|
|
1181
|
+
exit_now! 'Missing date argument' if args.empty?
|
1182
|
+
|
1183
|
+
date_string = args.join(' ')
|
1184
|
+
|
1185
|
+
date_string += ' at midnight' unless date_string =~ /(\d:|\d *[ap]m?|midnight|noon)/i
|
1186
|
+
date_string.sub!(/(day) (\d)/, '\1 at \2') if date_string =~ /day \d/
|
1187
|
+
|
1188
|
+
start = wwid.chronify(date_string)
|
1189
|
+
finish = Time.now
|
1190
|
+
|
1191
|
+
exit_now! 'Unrecognized date string' unless start
|
1192
|
+
|
1193
|
+
message = "Date interpreted as #{start} through the current time"
|
1194
|
+
wwid.results.push(message)
|
1195
|
+
|
1196
|
+
options[:t] = true if options[:totals]
|
1197
|
+
options[:sort_tags] = options[:tag_sort] =~ /^n/i
|
1198
|
+
|
1199
|
+
puts wwid.list_date([start, finish], options[:s], options[:t], options[:output],
|
1200
|
+
{ totals: options[:totals], sort_tags: options[:sort_tags] }).chomp
|
1201
|
+
end
|
1202
|
+
end
|
1203
|
+
|
1058
1204
|
desc 'List entries from yesterday'
|
1059
1205
|
command :yesterday do |c|
|
1060
1206
|
c.desc 'Specify a section'
|
@@ -1066,10 +1212,10 @@ command :yesterday do |c|
|
|
1066
1212
|
c.flag %i[o output], must_match: /^(?:template|html|csv|json|timeline)$/i
|
1067
1213
|
|
1068
1214
|
c.desc 'Show time intervals on @done tasks'
|
1069
|
-
c.switch %i[t times], default_value: true
|
1215
|
+
c.switch %i[t times], default_value: true, negatable: true
|
1070
1216
|
|
1071
1217
|
c.desc 'Show time totals at the end of output'
|
1072
|
-
c.switch [:totals], default_value: false, negatable:
|
1218
|
+
c.switch [:totals], default_value: false, negatable: false
|
1073
1219
|
|
1074
1220
|
c.desc 'Sort tags by (name|time)'
|
1075
1221
|
default = 'time'
|
@@ -1106,7 +1252,7 @@ command :last do |c|
|
|
1106
1252
|
c.flag [:search]
|
1107
1253
|
|
1108
1254
|
c.action do |_global_options, options, _args|
|
1109
|
-
|
1255
|
+
exit_now! '--tag and --search cannot be used together' if options[:tag] && options[:search]
|
1110
1256
|
|
1111
1257
|
if options[:tag].nil?
|
1112
1258
|
tags = []
|
@@ -1155,7 +1301,7 @@ desc 'Add a new section to the "doing" file'
|
|
1155
1301
|
arg_name 'SECTION_NAME'
|
1156
1302
|
command :add_section do |c|
|
1157
1303
|
c.action do |_global_options, _options, args|
|
1158
|
-
|
1304
|
+
exit_now! "Section #{args[0]} already exists" if wwid.sections.include?(args[0])
|
1159
1305
|
|
1160
1306
|
wwid.add_section(args[0].cap_first)
|
1161
1307
|
wwid.write(wwid.doing_file)
|
@@ -1196,10 +1342,10 @@ command :view do |c|
|
|
1196
1342
|
c.flag %i[o output], must_match: /^(?:template|html|csv|json|timeline)$/i
|
1197
1343
|
|
1198
1344
|
c.desc 'Show time intervals on @done tasks'
|
1199
|
-
c.switch %i[t times], default_value: true
|
1345
|
+
c.switch %i[t times], default_value: true, negatable: true
|
1200
1346
|
|
1201
1347
|
c.desc 'Show intervals with totals at the end of output'
|
1202
|
-
c.switch [:totals], default_value: false, negatable:
|
1348
|
+
c.switch [:totals], default_value: false, negatable: false
|
1203
1349
|
|
1204
1350
|
c.desc 'Include colors in output'
|
1205
1351
|
c.switch [:color], default_value: true, negatable: true
|
@@ -1211,7 +1357,7 @@ command :view do |c|
|
|
1211
1357
|
c.flag [:tag_sort], must_match: /^(?:name|time)$/i, default_value: default
|
1212
1358
|
|
1213
1359
|
c.desc 'Only show items with recorded time intervals'
|
1214
|
-
c.switch [:only_timed], default_value: false, negatable:
|
1360
|
+
c.switch [:only_timed], default_value: false, negatable: false
|
1215
1361
|
|
1216
1362
|
c.action do |_global_options, options, args|
|
1217
1363
|
title = if args.empty?
|
@@ -1283,7 +1429,7 @@ command :view do |c|
|
|
1283
1429
|
elsif title.instance_of?(FalseClass)
|
1284
1430
|
exit_now! 'Cancelled'
|
1285
1431
|
else
|
1286
|
-
|
1432
|
+
exit_now! "View #{title} not found in config"
|
1287
1433
|
end
|
1288
1434
|
end
|
1289
1435
|
end
|
@@ -1340,7 +1486,7 @@ command :archive do |c|
|
|
1340
1486
|
tags = args.length > 1 ? args[1..].map { |t| t.sub(/^@/, '').strip } : []
|
1341
1487
|
end
|
1342
1488
|
|
1343
|
-
|
1489
|
+
exit_now! '--keep and --count can\'t be used together' if options[:keep] && options[:count]
|
1344
1490
|
|
1345
1491
|
tags.concat(options[:tag].split(/ *, */).map { |t| t.sub(/^@/, '').strip }) if options[:tag]
|
1346
1492
|
|
@@ -1391,7 +1537,7 @@ command :open do |c|
|
|
1391
1537
|
elsif options[:b]
|
1392
1538
|
system %(open -b "#{options[:b]}" "#{File.expand_path(wwid.doing_file)}")
|
1393
1539
|
elsif options[:e]
|
1394
|
-
|
1540
|
+
exit_now! 'No EDITOR variable defined in environment' if ENV['EDITOR'].nil?
|
1395
1541
|
|
1396
1542
|
system %($EDITOR "#{File.expand_path(wwid.doing_file)}")
|
1397
1543
|
elsif wwid.config.key?('editor_app') && !wwid.config['editor_app'].nil?
|
@@ -1401,7 +1547,7 @@ command :open do |c|
|
|
1401
1547
|
end
|
1402
1548
|
|
1403
1549
|
else
|
1404
|
-
|
1550
|
+
exit_now! 'No EDITOR variable defined in environment' if ENV['EDITOR'].nil?
|
1405
1551
|
|
1406
1552
|
system %($EDITOR "#{File.expand_path(wwid.doing_file)}")
|
1407
1553
|
end
|
@@ -1438,13 +1584,13 @@ command :config do |c|
|
|
1438
1584
|
`open -b #{options[:b]} "#{wwid.config_file}"`
|
1439
1585
|
end
|
1440
1586
|
else
|
1441
|
-
|
1587
|
+
exit_now! 'No EDITOR variable defined in environment' if options[:e].nil? && ENV['EDITOR'].nil?
|
1442
1588
|
|
1443
1589
|
editor = options[:e].nil? ? ENV['EDITOR'] : options[:e]
|
1444
1590
|
system %(#{editor} "#{wwid.config_file}")
|
1445
1591
|
end
|
1446
1592
|
else
|
1447
|
-
|
1593
|
+
exit_now! 'No EDITOR variable defined in environment' if options[:e].nil? && ENV['EDITOR'].nil?
|
1448
1594
|
|
1449
1595
|
editor = options[:e].nil? ? ENV['EDITOR'] : options[:e]
|
1450
1596
|
system %(#{editor} "#{wwid.config_file}")
|
@@ -1497,7 +1643,7 @@ command :import do |c|
|
|
1497
1643
|
wwid.write(wwid.doing_file)
|
1498
1644
|
end
|
1499
1645
|
else
|
1500
|
-
|
1646
|
+
exit_now! 'Invalid import type'
|
1501
1647
|
end
|
1502
1648
|
end
|
1503
1649
|
end
|
data/lib/doing/version.rb
CHANGED
data/lib/doing/wwid.rb
CHANGED
@@ -3,6 +3,7 @@
|
|
3
3
|
require 'deep_merge'
|
4
4
|
require 'open3'
|
5
5
|
require 'pp'
|
6
|
+
require 'shellwords'
|
6
7
|
|
7
8
|
##
|
8
9
|
## @brief Main "What Was I Doing" methods
|
@@ -72,7 +73,7 @@ class WWID
|
|
72
73
|
rescue StandardError
|
73
74
|
@config = {}
|
74
75
|
@local_config = {}
|
75
|
-
#
|
76
|
+
# exit_now! "error reading config"
|
76
77
|
end
|
77
78
|
end
|
78
79
|
|
@@ -157,7 +158,7 @@ class WWID
|
|
157
158
|
|
158
159
|
# if ENV['DOING_DEBUG'].to_i == 3
|
159
160
|
# if @config['default_tags'].length > 0
|
160
|
-
#
|
161
|
+
# exit_now! "DEFAULT CONFIG CHANGED"
|
161
162
|
# end
|
162
163
|
# end
|
163
164
|
|
@@ -292,7 +293,7 @@ class WWID
|
|
292
293
|
if $?.exitstatus == 0
|
293
294
|
input = IO.read(tmpfile.path)
|
294
295
|
else
|
295
|
-
|
296
|
+
exit_now! 'Cancelled'
|
296
297
|
end
|
297
298
|
ensure
|
298
299
|
tmpfile.close
|
@@ -310,16 +311,16 @@ class WWID
|
|
310
311
|
# @param input (String) The string to parse
|
311
312
|
#
|
312
313
|
def format_input(input)
|
313
|
-
|
314
|
+
exit_now! 'No content in entry' if input.nil? || input.strip.empty?
|
314
315
|
|
315
316
|
input_lines = input.split(/[\n\r]+/).delete_if {|line| line =~ /^#/ || line =~ /^\s*$/ }
|
316
317
|
title = input_lines[0]&.strip
|
317
|
-
|
318
|
+
exit_now! 'No content in first line' if title.nil? || title.strip.empty?
|
318
319
|
|
319
320
|
note = input_lines.length > 1 ? input_lines[1..-1] : []
|
320
321
|
# If title line ends in a parenthetical, use that as the note
|
321
|
-
if note.empty? && title =~ /\(.*?\)$/
|
322
|
-
title.sub!(/\((.*?)\)$/) do
|
322
|
+
if note.empty? && title =~ /\s+\(.*?\)$/
|
323
|
+
title.sub!(/\s+\((.*?)\)$/) do
|
323
324
|
m = Regexp.last_match
|
324
325
|
note.push(m[1])
|
325
326
|
''
|
@@ -345,7 +346,7 @@ class WWID
|
|
345
346
|
#
|
346
347
|
def chronify(input)
|
347
348
|
now = Time.now
|
348
|
-
|
349
|
+
exit_now! "Invalid time expression #{input.inspect}" if input.to_s.strip == ''
|
349
350
|
|
350
351
|
secs_ago = if input.match(/^(\d+)$/)
|
351
352
|
# plain number, assume minutes
|
@@ -437,7 +438,7 @@ class WWID
|
|
437
438
|
end
|
438
439
|
unless section || guessed
|
439
440
|
alt = guess_view(frag, true)
|
440
|
-
|
441
|
+
exit_now! "Did you mean `doing view #{alt}`?" if alt
|
441
442
|
|
442
443
|
res = yn("Section #{frag} not found, create it", default_response: false)
|
443
444
|
|
@@ -447,7 +448,7 @@ class WWID
|
|
447
448
|
return frag.cap_first
|
448
449
|
end
|
449
450
|
|
450
|
-
|
451
|
+
exit_now! "Unknown section: #{frag}"
|
451
452
|
end
|
452
453
|
section ? section.cap_first : guessed
|
453
454
|
end
|
@@ -517,9 +518,9 @@ class WWID
|
|
517
518
|
unless view || guessed
|
518
519
|
alt = guess_section(frag, guessed: true)
|
519
520
|
if alt
|
520
|
-
|
521
|
+
exit_now! "Did you mean `doing show #{alt}`?"
|
521
522
|
else
|
522
|
-
|
523
|
+
exit_now! "Unknown view: #{frag}"
|
523
524
|
end
|
524
525
|
end
|
525
526
|
view
|
@@ -630,7 +631,7 @@ class WWID
|
|
630
631
|
|
631
632
|
add_tags = opt[:tag] ? opt[:tag].split(/[ ,]+/).map { |t| t.sub(/^@?/, '@') }.join(' ') : ''
|
632
633
|
prefix = opt[:prefix] ? opt[:prefix] : '[Timing.app]'
|
633
|
-
|
634
|
+
exit_now! "File not found" unless File.exist?(File.expand_path(path))
|
634
635
|
|
635
636
|
data = JSON.parse(IO.read(File.expand_path(path)))
|
636
637
|
new_items = []
|
@@ -687,7 +688,7 @@ class WWID
|
|
687
688
|
section = combined['items'].dup.sort_by { |item| item['date'] }.reverse[0]['section']
|
688
689
|
end
|
689
690
|
|
690
|
-
|
691
|
+
exit_now! "Section #{section} not found" unless @content.key?(section)
|
691
692
|
|
692
693
|
last_item = @content[section]['items'].dup.sort_by { |item| item['date'] }.reverse[0]
|
693
694
|
warn "Editing note for #{last_item['title']}"
|
@@ -765,13 +766,138 @@ class WWID
|
|
765
766
|
all_items.max_by { |item| item['date'] }
|
766
767
|
end
|
767
768
|
|
769
|
+
|
770
|
+
##
|
771
|
+
## @brief Display an interactive menu of entries
|
772
|
+
##
|
773
|
+
## @param opt (Hash) Additional options
|
774
|
+
##
|
775
|
+
def interactive(opt = {})
|
776
|
+
exit_now! "Select command requires that fzf be installed" unless exec_available('fzf')
|
777
|
+
|
778
|
+
section = opt[:section] ? guess_section(opt[:section]) : 'All'
|
779
|
+
|
780
|
+
|
781
|
+
if section =~ /^all$/i
|
782
|
+
combined = { 'items' => [] }
|
783
|
+
@content.each do |_k, v|
|
784
|
+
combined['items'] += v['items']
|
785
|
+
end
|
786
|
+
items = combined['items'].dup.sort_by { |item| item['date'] }.reverse
|
787
|
+
else
|
788
|
+
items = @content[section]['items']
|
789
|
+
end
|
790
|
+
|
791
|
+
|
792
|
+
options = items.map.with_index do |item, i|
|
793
|
+
out = [
|
794
|
+
i,
|
795
|
+
') ',
|
796
|
+
item['date'],
|
797
|
+
' | ',
|
798
|
+
item['title'],
|
799
|
+
]
|
800
|
+
if opt[:section] =~ /^all/i
|
801
|
+
out.concat([
|
802
|
+
' (',
|
803
|
+
item['section'],
|
804
|
+
') '
|
805
|
+
])
|
806
|
+
end
|
807
|
+
out.join('')
|
808
|
+
end
|
809
|
+
|
810
|
+
res = `echo #{Shellwords.escape(options.join("\n"))}|fzf -m --bind ctrl-a:select-all`
|
811
|
+
selected = []
|
812
|
+
res.split(/\n/).each do |item|
|
813
|
+
idx = item.match(/^(\d+)\)/)[1].to_i
|
814
|
+
selected.push(items[idx])
|
815
|
+
end
|
816
|
+
|
817
|
+
if selected.empty?
|
818
|
+
@results.push("No selection")
|
819
|
+
return
|
820
|
+
end
|
821
|
+
|
822
|
+
if opt[:delete]
|
823
|
+
res = yn("Delete #{selected.size} items?", default_response: 'y')
|
824
|
+
if res
|
825
|
+
selected.each {|item| delete_item(item) }
|
826
|
+
write(@doing_file)
|
827
|
+
end
|
828
|
+
return
|
829
|
+
end
|
830
|
+
|
831
|
+
if opt[:flag]
|
832
|
+
tag = @config['marker_tag'] || 'flagged'
|
833
|
+
selected.map! {|item| tag_item(item, tag, date: false) }
|
834
|
+
end
|
835
|
+
|
836
|
+
if opt[:finish] || opt[:cancel]
|
837
|
+
tag = 'done'
|
838
|
+
selected.map! {|item| tag_item(item, tag, date: !opt[:cancel])}
|
839
|
+
end
|
840
|
+
|
841
|
+
if opt[:tag]
|
842
|
+
tag = opt[:tag]
|
843
|
+
selected.map! {|item| tag_item(item, tag, date: false)}
|
844
|
+
end
|
845
|
+
|
846
|
+
if opt[:archive] || opt[:move]
|
847
|
+
section = opt[:archive] ? 'Archive' : guess_section(opt[:move])
|
848
|
+
selected.map! {|item| move_item(item, section) }
|
849
|
+
end
|
850
|
+
|
851
|
+
write(@doing_file)
|
852
|
+
|
853
|
+
if opt[:editor]
|
854
|
+
|
855
|
+
editable_items = []
|
856
|
+
|
857
|
+
selected.each do |item|
|
858
|
+
editable = "#{item['date']} | #{item['title']}"
|
859
|
+
old_note = item['note'] ? item['note'].map(&:strip).join("\n") : nil
|
860
|
+
editable += "\n#{old_note}" unless old_note.nil?
|
861
|
+
editable_items << editable
|
862
|
+
end
|
863
|
+
|
864
|
+
new_items = fork_editor(editable_items.map(&:strip).join("\n---\n") + "\n\n# You may delete entries, but leave all divider lines in place").split(/\n---\n/)
|
865
|
+
|
866
|
+
new_items.each_with_index do |new_item, i|
|
867
|
+
|
868
|
+
input_lines = new_item.split(/[\n\r]+/).delete_if {|line| line =~ /^#/ || line =~ /^\s*$/ }
|
869
|
+
title = input_lines[0]&.strip
|
870
|
+
|
871
|
+
if title.nil? || title =~ /^---$/ || title.strip.empty?
|
872
|
+
delete_item(selected[i])
|
873
|
+
else
|
874
|
+
note = input_lines.length > 1 ? input_lines[1..-1] : []
|
875
|
+
|
876
|
+
note.map!(&:strip)
|
877
|
+
note.delete_if { |line| line =~ /^\s*$/ || line =~ /^#/ }
|
878
|
+
|
879
|
+
date = title.match(/^([\d\-: ]+) \| /)[1]
|
880
|
+
title.sub!(/^([\d\-: ]+) \| /, '')
|
881
|
+
|
882
|
+
item = selected[i].dup
|
883
|
+
item['title'] = title
|
884
|
+
item['note'] = note
|
885
|
+
item['date'] = Time.parse(date) || selected[i]['date']
|
886
|
+
update_item(selected[i], item)
|
887
|
+
end
|
888
|
+
end
|
889
|
+
|
890
|
+
write(@doing_file)
|
891
|
+
end
|
892
|
+
end
|
893
|
+
|
768
894
|
##
|
769
895
|
## @brief Tag the last entry or X entries
|
770
896
|
##
|
771
897
|
## @param opt (Hash) Additional Options
|
772
898
|
##
|
773
899
|
def tag_last(opt = {})
|
774
|
-
opt[:section] ||=
|
900
|
+
opt[:section] ||= nil
|
775
901
|
opt[:count] ||= 1
|
776
902
|
opt[:archive] ||= false
|
777
903
|
opt[:tags] ||= ['done']
|
@@ -786,7 +912,11 @@ class WWID
|
|
786
912
|
sec_arr = []
|
787
913
|
|
788
914
|
if opt[:section].nil?
|
789
|
-
|
915
|
+
if opt[:search] || opt[:tag]
|
916
|
+
sec_arr = sections
|
917
|
+
else
|
918
|
+
sec_arr = [@current_section]
|
919
|
+
end
|
790
920
|
elsif opt[:section].instance_of?(String)
|
791
921
|
if opt[:section] =~ /^all$/i
|
792
922
|
if opt[:count] == 1
|
@@ -797,7 +927,11 @@ class WWID
|
|
797
927
|
items = combined['items'].dup.sort_by { |item| item['date'] }.reverse
|
798
928
|
sec_arr.push(items[0]['section'])
|
799
929
|
elsif opt[:count] > 1
|
800
|
-
|
930
|
+
if opt[:search] || opt[:tag]
|
931
|
+
sec_arr = sections
|
932
|
+
else
|
933
|
+
exit_now! 'A count greater than one requires a section to be specified'
|
934
|
+
end
|
801
935
|
else
|
802
936
|
sec_arr = sections
|
803
937
|
end
|
@@ -897,13 +1031,78 @@ class WWID
|
|
897
1031
|
@results.push('Archiving is skipped when operating on all entries') if (opt[:count]).zero?
|
898
1032
|
end
|
899
1033
|
else
|
900
|
-
|
1034
|
+
exit_now! "Section not found: #{section}"
|
901
1035
|
end
|
902
1036
|
end
|
903
1037
|
|
904
1038
|
write(@doing_file)
|
905
1039
|
end
|
906
1040
|
|
1041
|
+
def move_item(item, section)
|
1042
|
+
old_section = item['section']
|
1043
|
+
new_item = item.dup
|
1044
|
+
new_item['section'] = section
|
1045
|
+
|
1046
|
+
section_items = @content[old_section]['items']
|
1047
|
+
section_items.delete(item)
|
1048
|
+
@content[old_section]['items'] = section_items
|
1049
|
+
|
1050
|
+
archive_items = @content[section]['items']
|
1051
|
+
archive_items.push(new_item)
|
1052
|
+
# archive_items = archive_items.sort_by { |item| item['date'] }
|
1053
|
+
@content[section]['items'] = archive_items
|
1054
|
+
|
1055
|
+
@results.push("Entry moved to #{section}: #{new_item['title']}")
|
1056
|
+
return new_item
|
1057
|
+
end
|
1058
|
+
|
1059
|
+
##
|
1060
|
+
## @brief Delete an item from the index
|
1061
|
+
##
|
1062
|
+
## @param old_item
|
1063
|
+
##
|
1064
|
+
def delete_item(old_item)
|
1065
|
+
section = old_item['section']
|
1066
|
+
|
1067
|
+
section_items = @content[section]['items']
|
1068
|
+
deleted = section_items.delete(old_item)
|
1069
|
+
@results.push("Entry deleted: #{deleted['title']}")
|
1070
|
+
@content[section]['items'] = section_items
|
1071
|
+
end
|
1072
|
+
|
1073
|
+
##
|
1074
|
+
## @brief Tag an item from the index
|
1075
|
+
##
|
1076
|
+
## @param old_item (Item) The item to tag
|
1077
|
+
## @param tag (string) The tag to apply
|
1078
|
+
## @param date (Boolean) Include timestamp?
|
1079
|
+
##
|
1080
|
+
def tag_item(old_item, tag, date: false)
|
1081
|
+
title = old_item['title'].dup
|
1082
|
+
done_date = Time.now
|
1083
|
+
if title !~ /@#{tag}/
|
1084
|
+
title.chomp!
|
1085
|
+
if date
|
1086
|
+
title += " @#{tag}(#{done_date.strftime('%F %R')})"
|
1087
|
+
else
|
1088
|
+
title += " @#{tag}"
|
1089
|
+
end
|
1090
|
+
new_item = old_item.dup
|
1091
|
+
new_item['title'] = title
|
1092
|
+
update_item(old_item, new_item)
|
1093
|
+
return new_item
|
1094
|
+
else
|
1095
|
+
@results.push(%(Item already @#{tag}: "#{title}" in #{old_item['section']}))
|
1096
|
+
return old_item
|
1097
|
+
end
|
1098
|
+
end
|
1099
|
+
|
1100
|
+
##
|
1101
|
+
## @brief Update an item in the index with a modified item
|
1102
|
+
##
|
1103
|
+
## @param old_item The old item
|
1104
|
+
## @param new_item The new item
|
1105
|
+
##
|
907
1106
|
def update_item(old_item, new_item)
|
908
1107
|
section = old_item['section']
|
909
1108
|
|
@@ -1002,7 +1201,7 @@ class WWID
|
|
1002
1201
|
section = combined['items'].dup.sort_by { |item| item['date'] }.reverse[0]['section']
|
1003
1202
|
end
|
1004
1203
|
|
1005
|
-
|
1204
|
+
exit_now! "Section #{section} not found" unless @content.key?(section)
|
1006
1205
|
|
1007
1206
|
# sort_section(opt[:section])
|
1008
1207
|
items = @content[section]['items'].dup.sort_by { |item| item['date'] }.reverse
|
@@ -1238,7 +1437,7 @@ class WWID
|
|
1238
1437
|
end
|
1239
1438
|
end
|
1240
1439
|
|
1241
|
-
|
1440
|
+
exit_now! 'Invalid section object' unless opt[:section].instance_of? Hash
|
1242
1441
|
|
1243
1442
|
items = opt[:section]['items'].sort_by { |item| item['date'] }
|
1244
1443
|
|
@@ -1288,7 +1487,7 @@ class WWID
|
|
1288
1487
|
|
1289
1488
|
out = ''
|
1290
1489
|
|
1291
|
-
|
1490
|
+
exit_now! 'Unknown output format' if opt[:output] && (opt[:output] !~ /^(template|html|csv|json|timeline)$/i)
|
1292
1491
|
|
1293
1492
|
case opt[:output]
|
1294
1493
|
when /^csv$/i
|
@@ -1345,13 +1544,13 @@ class WWID
|
|
1345
1544
|
'id' => index + 1,
|
1346
1545
|
'content' => title.strip, #+ " #{note}"
|
1347
1546
|
'title' => title.strip + " (#{'%02d:%02d:%02d' % fmt_time(interval)})",
|
1348
|
-
'start' => i['date'].strftime('%F'),
|
1547
|
+
'start' => i['date'].strftime('%F %T'),
|
1349
1548
|
'type' => 'point'
|
1350
1549
|
}
|
1351
1550
|
|
1352
|
-
if interval && interval > 0
|
1353
|
-
new_item['end'] = end_date.strftime('%F')
|
1354
|
-
new_item['type'] = 'range' if interval > 3600 * 3
|
1551
|
+
if interval && interval.to_i > 0
|
1552
|
+
new_item['end'] = end_date.strftime('%F %T')
|
1553
|
+
new_item['type'] = 'range' if interval.to_i > 3600 * 3
|
1355
1554
|
end
|
1356
1555
|
items_out.push(new_item)
|
1357
1556
|
end
|
@@ -1367,8 +1566,8 @@ class WWID
|
|
1367
1566
|
<!doctype html>
|
1368
1567
|
<html>
|
1369
1568
|
<head>
|
1370
|
-
<link href="
|
1371
|
-
<script src="
|
1569
|
+
<link href="https://unpkg.com/vis-timeline@7.4.9/dist/vis-timeline-graph2d.min.css" rel="stylesheet" type="text/css" />
|
1570
|
+
<script src="https://unpkg.com/vis-timeline@7.4.9/dist/vis-timeline-graph2d.min.js"></script>
|
1372
1571
|
</head>
|
1373
1572
|
<body>
|
1374
1573
|
<div id="mytimeline"></div>
|
@@ -1561,7 +1760,7 @@ class WWID
|
|
1561
1760
|
do_archive(section, destination, { count: count, tags: tags, bool: bool, search: options[:search], label: options[:label] })
|
1562
1761
|
write(doing_file)
|
1563
1762
|
else
|
1564
|
-
|
1763
|
+
exit_now! 'Either source or destination does not exist'
|
1565
1764
|
end
|
1566
1765
|
end
|
1567
1766
|
|
@@ -2005,4 +2204,12 @@ EOS
|
|
2005
2204
|
minutes = (minutes % 60).to_i
|
2006
2205
|
[days, hours, minutes]
|
2007
2206
|
end
|
2207
|
+
|
2208
|
+
def exec_available(cli)
|
2209
|
+
if File.exists?(File.expand_path(cli))
|
2210
|
+
File.executable?(File.expand_path(cli))
|
2211
|
+
else
|
2212
|
+
system "which #{cli}", :out => File::NULL, :err => File::NULL
|
2213
|
+
end
|
2214
|
+
end
|
2008
2215
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: doing
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.0.
|
4
|
+
version: 1.0.75
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Brett Terpstra
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2021-09-
|
11
|
+
date: 2021-09-16 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rake
|
@@ -36,14 +36,14 @@ dependencies:
|
|
36
36
|
requirements:
|
37
37
|
- - "~>"
|
38
38
|
- !ruby/object:Gem::Version
|
39
|
-
version: 6.
|
39
|
+
version: 6.3.1
|
40
40
|
type: :development
|
41
41
|
prerelease: false
|
42
42
|
version_requirements: !ruby/object:Gem::Requirement
|
43
43
|
requirements:
|
44
44
|
- - "~>"
|
45
45
|
- !ruby/object:Gem::Version
|
46
|
-
version: 6.
|
46
|
+
version: 6.3.1
|
47
47
|
- !ruby/object:Gem::Dependency
|
48
48
|
name: aruba
|
49
49
|
requirement: !ruby/object:Gem::Requirement
|
@@ -62,16 +62,16 @@ dependencies:
|
|
62
62
|
name: test-unit
|
63
63
|
requirement: !ruby/object:Gem::Requirement
|
64
64
|
requirements:
|
65
|
-
- - "
|
65
|
+
- - "~>"
|
66
66
|
- !ruby/object:Gem::Version
|
67
|
-
version:
|
67
|
+
version: 3.4.4
|
68
68
|
type: :development
|
69
69
|
prerelease: false
|
70
70
|
version_requirements: !ruby/object:Gem::Requirement
|
71
71
|
requirements:
|
72
|
-
- - "
|
72
|
+
- - "~>"
|
73
73
|
- !ruby/object:Gem::Version
|
74
|
-
version:
|
74
|
+
version: 3.4.4
|
75
75
|
- !ruby/object:Gem::Dependency
|
76
76
|
name: gli
|
77
77
|
requirement: !ruby/object:Gem::Requirement
|