doing 1.0.7pre → 1.0.8pre
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +49 -2
- data/bin/doing +96 -21
- data/lib/doing/version.rb +1 -1
- data/lib/doing/wwid.rb +237 -66
- data/lib/doing.rb +1 -0
- metadata +16 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: fe65d7638817ae91c0b28db474b25e1db315c32d
|
4
|
+
data.tar.gz: 2f3f3b3100bd5b9495be019787201b773d7f9e16
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 945b6b6794717f6dfaa41b180209e28cf5cd219852c2098a862e940cb6989010c3aa79fdec5010bfffc82c43bc177667988d6357427d4f6c936efa26950b77e9
|
7
|
+
data.tar.gz: afcc84a5dd4b5723e0b003aa68c444a44ead9b99b56e7e625508482bdd92982acb4c8dcee2859191b912cc3c0500c1dc5d7d37896c569f657c13314a5ffe0fc7
|
data/README.md
CHANGED
@@ -32,6 +32,10 @@ _Side note:_ I actually use the library behind this utility as part of another s
|
|
32
32
|
|
33
33
|
$ [sudo] gem install doing
|
34
34
|
|
35
|
+
To install the _latest_ version, use `--pre`:
|
36
|
+
|
37
|
+
$ [sudo] gem install --pre doing
|
38
|
+
|
35
39
|
Only use `sudo` if your environment requires it. If you're using the system Ruby on a Mac, for example, it will likely be necessary. If `gem install doing` fails, then run `sudo gem install doing` and provide your administrator password.
|
36
40
|
|
37
41
|
Run `doing config` to open your `~/.doingrc` file in the editor defined in the $EDITOR environment variable. Set up your `doing_file` right away (where you want entries to be stored), and cover the rest after you've read the docs.
|
@@ -120,6 +124,7 @@ The config also contains templates for various command outputs. Include placehol
|
|
120
124
|
- `%shortdate`: a custom date formatter that removes the day/month/year from the entry if they match the current day/month/year
|
121
125
|
- `%note`: Any note in the entry will be included here, a newline and tabs are automatically added.
|
122
126
|
- `%odnote`: The notes with a leading tab removed (outdented note)
|
127
|
+
- `%chompnote`: Notes on one line, beginning and trailing whitespace removed.
|
123
128
|
- `%hr`: a horizontal rule (`-`) the width of the terminal
|
124
129
|
- `%hr_under`: a horizontal rule (`_`) the width of the terminal
|
125
130
|
- `%n`: inserts a newline
|
@@ -256,6 +261,8 @@ Outputs:
|
|
256
261
|
|
257
262
|
![](http://ckyp.us/XKpj+)
|
258
263
|
|
264
|
+
You can also specify a default output format for a view. Most of the optional output formats override the template specification (html, csv, json). If the `view` command is used with the `-o` flag, it will override what's specified in the file.
|
265
|
+
|
259
266
|
## Usage
|
260
267
|
|
261
268
|
doing [global options] command [command options] [arguments...]
|
@@ -298,18 +305,48 @@ All of these commands accept a `-e` argument. This opens your command line edito
|
|
298
305
|
tag - Tag last entry
|
299
306
|
note - Add a note to the last entry
|
300
307
|
|
308
|
+
##### Finishing
|
309
|
+
|
301
310
|
`doing finish` by itself is the same as `doing done` by itself. It adds `@done(timestamp)` to the last entry. It also accepts a numeric argument to complete X number of tasks back in history. Add `-a` to also archive the affected entries.
|
302
311
|
|
303
312
|
`doing finish` also provides an `--auto` flag, which you can use to set the end time of any entry to 1 minute before the start time of the next. Running a command such as `doing finish --auto 10` will go through the last 10 entries and sequentially update any without a `@done` tag with one set to the time just before the next entry in the list.
|
304
313
|
|
305
314
|
As mentioned above, `finish` also accepts `--back "2 hours"` (sets the finish date from time now minus interval) or `--took 30m` (sets the finish date to time started plus interval) so you can accurately add times to completed tasks, even if you don't do it in the moment.
|
306
315
|
|
316
|
+
|
317
|
+
##### Tagging and Autotagging
|
318
|
+
|
307
319
|
`tag` adds one or more tags to the last entry, or specify a count with `-c X`. Tags are specified as basic arguments, separated by spaces. For example:
|
308
320
|
|
309
321
|
doing tag -c 3 client cancelled
|
310
322
|
|
311
323
|
... will mark the last three entries as "@client @cancelled." Add `-r` as a switch to remove the listed tags instead.
|
312
324
|
|
325
|
+
You can optionally define keywords for common tasks and projects in your `.doingrc` file. When these keywords appear in an item title, they'll automatically be converted into @tags. The "whitelist" tags are exact (but case insensitive) matches. You can also define "synonyms" which will add a tag at the end based on keywords associated with it.
|
326
|
+
|
327
|
+
To add autotagging, include a section like this in your `~/.doingrc` file:
|
328
|
+
|
329
|
+
autotag:
|
330
|
+
whitelist:
|
331
|
+
- doing
|
332
|
+
- mindmeister
|
333
|
+
- marked
|
334
|
+
- playing
|
335
|
+
- working
|
336
|
+
- writing
|
337
|
+
synonyms:
|
338
|
+
playing:
|
339
|
+
- hacking
|
340
|
+
- tweaking
|
341
|
+
- toying
|
342
|
+
- messing
|
343
|
+
writing:
|
344
|
+
- blogging
|
345
|
+
- posting
|
346
|
+
- publishing
|
347
|
+
|
348
|
+
##### Annotating
|
349
|
+
|
313
350
|
`note` lets you append a note to the last entry. You can specify a section to grab the last entry from with `-s section_name`. `-e` will open your $EDITOR for typing the note, but you can also just include it on the command line after any flags. You can also pipe a note in on STDIN (`echo "fun stuff"|doing note`). If you don't use the `-r` switch, new notes will be appended to the existing notes, and using the `-e` switch will let you edit and add to an existing note. The `-r` switch will remove/replace a note; if there's new note text passed when using the `-r` switch, it will replace any existing note. If the `-r` switch is used alone, any existing note will be removed.
|
314
351
|
|
315
352
|
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.
|
@@ -331,10 +368,12 @@ Use `-c X` to limit the displayed results. Combine it with `-a newest` or `-a ol
|
|
331
368
|
|
332
369
|
The `show` command can also show the time spent on a task if it has a `@done(date)` tag with the `-t` option. This requires that you include a `%interval` token in template -> default in the config. You can also include `@start(date)` tags, which override the timestamp when calculating the intervals.
|
333
370
|
|
334
|
-
If you have a use for it, you can use
|
371
|
+
If you have a use for it, you can use `-o csv` on the show or view commands to output the results as a comma-separated CSV to STDOUT. Redirect to a file to save it: `doing show all done -o csv > ~/Desktop/done.csv`. You can do the same with `-o json`.
|
335
372
|
|
336
373
|
`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`).
|
337
374
|
|
375
|
+
`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).
|
376
|
+
|
338
377
|
#### Views
|
339
378
|
|
340
379
|
view - Display a user-created view
|
@@ -452,8 +491,16 @@ I'll try to document some of the code structure as I flesh it out. I'm currently
|
|
452
491
|
|
453
492
|
### Changelog
|
454
493
|
|
494
|
+
#### 1.0.8pre
|
495
|
+
|
496
|
+
* JSON output option to view commands
|
497
|
+
* Added autotagging to tag command
|
498
|
+
* date filtering, improved date language
|
499
|
+
* added doing on command
|
500
|
+
* let view templates define output format (csv, json, html, template)
|
501
|
+
* add %chompnote template variable (item note with newlines and extra whitespace stripped)
|
455
502
|
|
456
|
-
#### 1.0.
|
503
|
+
#### 1.0.7pre
|
457
504
|
|
458
505
|
* fix for -v option
|
459
506
|
* Slightly fuzzier searching in the grep command
|
data/bin/doing
CHANGED
@@ -442,25 +442,35 @@ command :tag do |c|
|
|
442
442
|
c.default_value false
|
443
443
|
c.switch [:r,:remove], :negatable => false, :default_value => false
|
444
444
|
|
445
|
+
c.desc 'Autotag entries based on autotag configuration in ~/.doingrc'
|
446
|
+
c.default_value false
|
447
|
+
c.switch [:a,:autotag], :negatable => false, :default_value => false
|
448
|
+
|
445
449
|
c.action do |global_options,options,args|
|
446
|
-
if args.length == 0
|
450
|
+
if args.length == 0 && !options[:a]
|
447
451
|
raise "You must specify at least one tag"
|
448
452
|
else
|
449
453
|
|
450
454
|
section = wwid.guess_section(options[:s]) || options[:s].cap_first
|
451
455
|
|
452
|
-
|
453
|
-
|
456
|
+
unless options[:a]
|
457
|
+
if args.join("") =~ /,/
|
458
|
+
tags = args.join("").split(/,/)
|
459
|
+
else
|
460
|
+
tags = args.join(" ").split(" ") # in case tags are quoted as one arg
|
461
|
+
end
|
462
|
+
|
463
|
+
tags.map!{|tag| tag.sub(/^@/,'').strip }
|
454
464
|
else
|
455
|
-
tags =
|
465
|
+
tags = []
|
456
466
|
end
|
457
467
|
|
458
|
-
tags.map!{|tag| tag.sub(/^@/,'').strip }
|
459
|
-
|
460
468
|
count = options[:c].to_i
|
461
469
|
|
462
470
|
if count == 0
|
463
|
-
if options[:
|
471
|
+
if options[:a]
|
472
|
+
print "Are you sure you want to autotag all records? (y/N) "
|
473
|
+
elsif options[:r]
|
464
474
|
print "Are you sure you want to remove #{tags.join(" and ")} from all records? (y/N) "
|
465
475
|
else
|
466
476
|
print "Are you sure you want to add #{tags.join(" and ")} to all records? (y/N) "
|
@@ -473,7 +483,7 @@ command :tag do |c|
|
|
473
483
|
end
|
474
484
|
end
|
475
485
|
|
476
|
-
wwid.tag_last({:tags => tags, :count => count, :section => section, :date => options[:d], :remove => options[:r]})
|
486
|
+
wwid.tag_last({:tags => tags, :count => count, :section => section, :date => options[:d], :remove => options[:r], :autotag => options[:a]})
|
477
487
|
end
|
478
488
|
end
|
479
489
|
end
|
@@ -495,8 +505,6 @@ command :mark do |c|
|
|
495
505
|
end
|
496
506
|
end
|
497
507
|
|
498
|
-
|
499
|
-
|
500
508
|
desc 'List all entries'
|
501
509
|
long_desc 'The argument can be a section name, tag(s) or both. "pick" or "choose" as an argument will offer a section menu.'
|
502
510
|
arg_name 'section [tags]'
|
@@ -517,8 +525,9 @@ command :show do |c|
|
|
517
525
|
c.default_value 'asc'
|
518
526
|
c.flag [:s,:sort], :default_value => 'asc'
|
519
527
|
|
520
|
-
c.desc '
|
521
|
-
c.
|
528
|
+
c.desc 'Date range to show, or a single day to filter date on.'
|
529
|
+
c.long_desc 'Date range argument should be quoted. Date specifications can be natural language. To specify a range, use "to," or "through,".\n\ndoing show --from "monday to friday"'
|
530
|
+
c.flag [:f,:from]
|
522
531
|
|
523
532
|
c.desc 'Show time intervals on @done tasks'
|
524
533
|
c.default_value true
|
@@ -532,7 +541,10 @@ command :show do |c|
|
|
532
541
|
c.default_value false
|
533
542
|
c.switch [:only_timed], :default_value => false, :negatable => false
|
534
543
|
|
544
|
+
c.desc 'Output to export format (csv|html|json)'
|
545
|
+
c.flag [:o,:output]
|
535
546
|
c.action do |global_options,options,args|
|
547
|
+
|
536
548
|
tag_filter = false
|
537
549
|
tags = []
|
538
550
|
if args.length > 0
|
@@ -552,7 +564,7 @@ command :show do |c|
|
|
552
564
|
if args.length > 0
|
553
565
|
args.each {|arg|
|
554
566
|
if arg =~ /,/
|
555
|
-
|
567
|
+
arg.split(/,/).each {|tag|
|
556
568
|
tags.push(tag.strip.sub(/^@/,''))
|
557
569
|
}
|
558
570
|
else
|
@@ -571,9 +583,23 @@ command :show do |c|
|
|
571
583
|
}
|
572
584
|
end
|
573
585
|
|
586
|
+
if options[:f]
|
587
|
+
date_string = options[:f]
|
588
|
+
if date_string =~ / (to|through|thru|(un)?til|-+) /
|
589
|
+
dates = date_string.split(/ (to|through|thru|(un)?til|-+) /)
|
590
|
+
start = wwid.chronify(dates[0])
|
591
|
+
finish = wwid.chronify(dates[2])
|
592
|
+
else
|
593
|
+
start = wwid.chronify(date_string)
|
594
|
+
finish = false
|
595
|
+
end
|
596
|
+
exit_now! "Unrecognized date string" unless start
|
597
|
+
dates = [start,finish]
|
598
|
+
end
|
599
|
+
|
574
600
|
options[:t] = true if options[:totals]
|
575
601
|
|
576
|
-
puts wwid.list_section({:section => section, :count => options[:c].to_i, :tag_filter => tag_filter, :age => options[:a], :order => options[:s], :output => options[:output], :times => options[:t], :totals => options[:totals], :highlight => true, :only_timed => options[:only_timed]})
|
602
|
+
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], :highlight => true, :only_timed => options[:only_timed]})
|
577
603
|
|
578
604
|
end
|
579
605
|
end
|
@@ -586,7 +612,7 @@ command :grep do |c|
|
|
586
612
|
c.default_value "all"
|
587
613
|
c.flag [:s,:section], :default_value => "All"
|
588
614
|
|
589
|
-
c.desc 'Output to export format (csv|html)'
|
615
|
+
c.desc 'Output to export format (csv|html|json)'
|
590
616
|
c.flag [:o,:output]
|
591
617
|
|
592
618
|
c.desc 'Show time intervals on @done tasks'
|
@@ -656,7 +682,7 @@ command :today do |c|
|
|
656
682
|
c.default_value false
|
657
683
|
c.switch [:totals], :default_value => false, :negatable => true
|
658
684
|
|
659
|
-
c.desc 'Output to export format (csv|html)'
|
685
|
+
c.desc 'Output to export format (csv|html|json)'
|
660
686
|
c.flag [:o,:output]
|
661
687
|
|
662
688
|
c.action do |global_options,options,args|
|
@@ -668,11 +694,52 @@ command :today do |c|
|
|
668
694
|
end
|
669
695
|
end
|
670
696
|
|
697
|
+
desc 'List entries for a date'
|
698
|
+
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.'
|
699
|
+
arg_name 'date_string'
|
700
|
+
command :on do |c|
|
701
|
+
c.desc 'Section'
|
702
|
+
c.arg_name 'section_name'
|
703
|
+
c.default_value 'All'
|
704
|
+
c.flag [:s,:section], :default_value => 'All'
|
705
|
+
|
706
|
+
c.desc 'Show time intervals on @done tasks'
|
707
|
+
c.default_value true
|
708
|
+
c.switch [:t,:times], :default_value => true
|
709
|
+
|
710
|
+
c.desc 'Show time totals at the end of output'
|
711
|
+
c.default_value false
|
712
|
+
c.switch [:totals], :default_value => false, :negatable => true
|
713
|
+
|
714
|
+
c.desc 'Output to export format (csv|html|json)'
|
715
|
+
c.flag [:o,:output]
|
716
|
+
|
717
|
+
c.action do |global_options,options,args|
|
718
|
+
|
719
|
+
date_string = args.join(" ")
|
720
|
+
|
721
|
+
if date_string =~ / (to|through|thru) /
|
722
|
+
dates = date_string.split(/ (to|through|thru) /)
|
723
|
+
start = wwid.chronify(dates[0])
|
724
|
+
finish = wwid.chronify(dates[2])
|
725
|
+
else
|
726
|
+
start = wwid.chronify(date_string)
|
727
|
+
finish = false
|
728
|
+
end
|
729
|
+
exit_now! "Unrecognized date string" unless start
|
730
|
+
|
731
|
+
options[:t] = true if options[:totals]
|
732
|
+
|
733
|
+
puts wwid.list_date([start,finish],options[:s],options[:t],options[:output],{:totals => options[:totals]}).chomp
|
734
|
+
|
735
|
+
end
|
736
|
+
end
|
737
|
+
|
671
738
|
desc 'List entries from yesterday'
|
672
739
|
default_value wwid.current_section
|
673
740
|
arg_name 'section'
|
674
741
|
command :yesterday do |c|
|
675
|
-
c.desc 'Output to export format (csv|html)'
|
742
|
+
c.desc 'Output to export format (csv|html|json)'
|
676
743
|
c.flag [:o,:output]
|
677
744
|
|
678
745
|
c.desc 'Show time intervals on @done tasks'
|
@@ -752,7 +819,7 @@ command :view do |c|
|
|
752
819
|
c.desc 'Count to display (override view settings)'
|
753
820
|
c.flag [:c,:count], :must_match => /^\d+$/, :type => Integer
|
754
821
|
|
755
|
-
c.desc 'Output to export format (csv|html)'
|
822
|
+
c.desc 'Output to export format (csv|html|json)'
|
756
823
|
c.flag [:o,:output]
|
757
824
|
|
758
825
|
c.desc 'Show time intervals on @done tasks'
|
@@ -801,14 +868,22 @@ command :view do |c|
|
|
801
868
|
tag_filter['bool'] = view.has_key?('tags_bool') && !view['tags_bool'].nil? ? view['tags_bool'].upcase : "OR"
|
802
869
|
end
|
803
870
|
end
|
871
|
+
|
872
|
+
# If the -o/--output flag was specified, override any default in the view template
|
873
|
+
options[:o] ||= view.has_key?('output_format') ? view['output_format'] : "template"
|
874
|
+
|
804
875
|
count = options[:c] ? options[:c] : view.has_key?('count') ? view['count'] : 10
|
805
876
|
section = options[:s] ? section : view.has_key?('section') ? view['section'] : wwid.current_section
|
806
877
|
order = view.has_key?('order') ? view['order'] : "asc"
|
807
878
|
options[:t] = true if options[:totals]
|
808
879
|
options[:output].downcase! if options[:output]
|
809
|
-
puts wwid.list_section({:section => section, :count => count, :template => template, :format => format, :order => order, :tag_filter => tag_filter, :output => options[:
|
880
|
+
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 })
|
810
881
|
else
|
811
|
-
|
882
|
+
if title.class == FalseClass
|
883
|
+
exit_now! "Cancelled"
|
884
|
+
else
|
885
|
+
raise "View #{title} not found in config"
|
886
|
+
end
|
812
887
|
end
|
813
888
|
end
|
814
889
|
end
|
@@ -953,7 +1028,7 @@ end
|
|
953
1028
|
|
954
1029
|
pre do |global,command,options,args|
|
955
1030
|
if global[:doing_file]
|
956
|
-
wwid.init_doing_file(
|
1031
|
+
wwid.init_doing_file(global[:doing_file])
|
957
1032
|
else
|
958
1033
|
wwid.init_doing_file
|
959
1034
|
end
|
data/lib/doing/version.rb
CHANGED
data/lib/doing/wwid.rb
CHANGED
@@ -226,22 +226,29 @@ class WWID
|
|
226
226
|
# Returns:
|
227
227
|
# seconds(Integer)
|
228
228
|
def chronify(input)
|
229
|
-
|
230
|
-
|
231
|
-
|
232
|
-
|
233
|
-
|
234
|
-
amt
|
235
|
-
|
236
|
-
|
237
|
-
|
238
|
-
|
239
|
-
|
240
|
-
|
229
|
+
|
230
|
+
had_to_try = Time.parse(input) rescue false
|
231
|
+
|
232
|
+
if had_to_try.class == FalseClass
|
233
|
+
if input =~ /^(\d+)([mhd])?$/i
|
234
|
+
amt = $1
|
235
|
+
type = $2.nil? ? "m" : $2
|
236
|
+
input = case type.downcase
|
237
|
+
when 'm'
|
238
|
+
amt + " minutes ago"
|
239
|
+
when 'h'
|
240
|
+
amt + " hours ago"
|
241
|
+
when 'd'
|
242
|
+
amt + " days ago"
|
243
|
+
else
|
244
|
+
input
|
245
|
+
end
|
241
246
|
end
|
242
|
-
end
|
243
247
|
|
244
|
-
|
248
|
+
Chronic.parse(input, {:context => :past, :ambiguous_time_range => 8})
|
249
|
+
else
|
250
|
+
had_to_try
|
251
|
+
end
|
245
252
|
end
|
246
253
|
|
247
254
|
|
@@ -260,11 +267,11 @@ class WWID
|
|
260
267
|
|
261
268
|
minutes = case type.downcase
|
262
269
|
when 'm'
|
263
|
-
|
270
|
+
amt.to_i
|
264
271
|
when 'h'
|
265
|
-
(
|
272
|
+
(amt.to_f * 60).round
|
266
273
|
when 'd'
|
267
|
-
(
|
274
|
+
(amt.to_f * 60 * 24).round
|
268
275
|
else
|
269
276
|
minutes
|
270
277
|
end
|
@@ -341,7 +348,8 @@ class WWID
|
|
341
348
|
opt[:timed] ||= false
|
342
349
|
|
343
350
|
title = [title.strip.cap_first] + @config['default_tags'].map{|t| '@' + t.sub(/^ *@/,'').chomp}
|
344
|
-
|
351
|
+
title = autotag(title.join(' '))
|
352
|
+
entry = {'title' => title, 'date' => opt[:back]}
|
345
353
|
unless opt[:note] =~ /^\s*$/s
|
346
354
|
entry['note'] = opt[:note]
|
347
355
|
end
|
@@ -382,6 +390,7 @@ class WWID
|
|
382
390
|
opt[:sequential] ||= false
|
383
391
|
opt[:date] ||= false
|
384
392
|
opt[:remove] ||= false
|
393
|
+
opt[:autotag] ||= false
|
385
394
|
opt[:back] ||= Time.now
|
386
395
|
|
387
396
|
|
@@ -408,38 +417,44 @@ class WWID
|
|
408
417
|
count = opt[:count] == 0 ? items.length : opt[:count]
|
409
418
|
items.map! {|item|
|
410
419
|
break if index == count
|
411
|
-
if opt[:sequential]
|
412
|
-
done_date = next_start - 1
|
413
|
-
next_start = item['date']
|
414
|
-
elsif opt[:back].instance_of? Fixnum
|
415
|
-
done_date = item['date'] + opt[:back]
|
416
|
-
else
|
417
|
-
done_date = opt[:back]
|
418
|
-
end
|
419
420
|
|
420
|
-
|
421
|
-
|
422
|
-
|
423
|
-
|
424
|
-
|
425
|
-
|
426
|
-
@results.push("Removed @#{tag}: #{title}")
|
427
|
-
end
|
421
|
+
unless opt[:autotag]
|
422
|
+
if opt[:sequential]
|
423
|
+
done_date = next_start - 1
|
424
|
+
next_start = item['date']
|
425
|
+
elsif opt[:back].instance_of? Fixnum
|
426
|
+
done_date = item['date'] + opt[:back]
|
428
427
|
else
|
429
|
-
|
430
|
-
|
431
|
-
|
432
|
-
|
433
|
-
|
434
|
-
|
428
|
+
done_date = opt[:back]
|
429
|
+
end
|
430
|
+
|
431
|
+
title = item['title']
|
432
|
+
opt[:tags].each {|tag|
|
433
|
+
tag.strip!
|
434
|
+
if opt[:remove]
|
435
|
+
if title =~ /@#{tag}/
|
436
|
+
title.gsub!(/(^| )@#{tag}(\([^\)]*\))?/,'')
|
437
|
+
@results.push("Removed @#{tag}: #{title}")
|
438
|
+
end
|
439
|
+
else
|
440
|
+
unless title =~ /@#{tag}/
|
441
|
+
title.chomp!
|
442
|
+
if opt[:date]
|
443
|
+
title += " @#{tag}(#{done_date.strftime('%F %R')})"
|
444
|
+
else
|
445
|
+
title += " @#{tag}"
|
446
|
+
end
|
447
|
+
@results.push("Added @#{tag}: #{title}")
|
435
448
|
end
|
436
|
-
@results.push("Added @#{tag}: #{title}")
|
437
449
|
end
|
438
|
-
|
439
|
-
|
450
|
+
}
|
451
|
+
item['title'] = title
|
452
|
+
else
|
453
|
+
item['title'] = autotag(item['title'])
|
454
|
+
end
|
440
455
|
|
441
|
-
item['title'] = title
|
442
456
|
index += 1
|
457
|
+
|
443
458
|
item
|
444
459
|
}
|
445
460
|
|
@@ -628,6 +643,7 @@ class WWID
|
|
628
643
|
opt[:totals] ||= false
|
629
644
|
opt[:search] ||= false
|
630
645
|
opt[:only_timed] ||= false
|
646
|
+
opt[:date_filter] ||= []
|
631
647
|
|
632
648
|
# opt[:highlight] ||= true
|
633
649
|
section = ""
|
@@ -655,6 +671,18 @@ class WWID
|
|
655
671
|
|
656
672
|
items = opt[:section]['items'].sort_by{|item| item['date'] }
|
657
673
|
|
674
|
+
if opt[:date_filter].length == 2
|
675
|
+
start_date = opt[:date_filter][0]
|
676
|
+
end_date = opt[:date_filter][1]
|
677
|
+
items.keep_if {|item|
|
678
|
+
if end_date
|
679
|
+
item['date'] >= start_date && item['date'] <= end_date
|
680
|
+
else
|
681
|
+
item['date'].strftime('%F') == start_date.strftime('%F')
|
682
|
+
end
|
683
|
+
}
|
684
|
+
end
|
685
|
+
|
658
686
|
if opt[:tag_filter] && !opt[:tag_filter]['tags'].empty?
|
659
687
|
items.delete_if {|item|
|
660
688
|
if opt[:tag_filter]['bool'] =~ /(AND|ALL)/
|
@@ -717,9 +745,8 @@ class WWID
|
|
717
745
|
out = ""
|
718
746
|
|
719
747
|
if opt[:output]
|
720
|
-
raise "Unknown output format" unless opt[:output] =~ /(html|csv)/
|
748
|
+
raise "Unknown output format" unless opt[:output] =~ /(template|html|csv|json|timeline)/
|
721
749
|
end
|
722
|
-
|
723
750
|
if opt[:output] == "csv"
|
724
751
|
output = [CSV.generate_line(['date','title','note','timer'])]
|
725
752
|
items.each {|i|
|
@@ -735,6 +762,104 @@ class WWID
|
|
735
762
|
output.push(CSV.generate_line([i['date'],i['title'],note,interval]))
|
736
763
|
}
|
737
764
|
out = output.join("")
|
765
|
+
elsif opt[:output] == "json" || opt[:output] == "timeline"
|
766
|
+
|
767
|
+
items_out = []
|
768
|
+
max = items[-1]['date'].strftime('%F')
|
769
|
+
min = items[0]['date'].strftime('%F')
|
770
|
+
items.each_with_index {|i,index|
|
771
|
+
if RUBY_VERSION.to_f > 1.8
|
772
|
+
title = i['title'].force_encoding('utf-8')
|
773
|
+
note = i['note'].map {|line| line.force_encoding('utf-8').strip } if i['note']
|
774
|
+
else
|
775
|
+
title = i['title']
|
776
|
+
note = i['note'].map { |line| line.strip }
|
777
|
+
end
|
778
|
+
|
779
|
+
if i['title'] =~ /@done\((\d{4}-\d\d-\d\d \d\d:\d\d.*?)\)/ && opt[:times]
|
780
|
+
end_date = Time.parse($1)
|
781
|
+
interval = get_interval(i,false)
|
782
|
+
end
|
783
|
+
end_date ||= ""
|
784
|
+
interval ||= 0
|
785
|
+
note ||= ""
|
786
|
+
|
787
|
+
tags = []
|
788
|
+
skip_tags = ['meanwhile', 'done', 'cancelled', 'flagged']
|
789
|
+
i['title'].scan(/@([^\(\s]+)(?:\((.*?)\))?/).each {|tag|
|
790
|
+
tags.push(tag[0]) unless skip_tags.include?(tag[0])
|
791
|
+
}
|
792
|
+
if opt[:output] == "json"
|
793
|
+
items_out << {
|
794
|
+
:date => i['date'],
|
795
|
+
:end_date => end_date,
|
796
|
+
:title => title.strip, #+ " #{note}"
|
797
|
+
:note => note.class == Array ? note.join("\n") : note,
|
798
|
+
:time => "%02d:%02d:%02d" % fmt_time(interval),
|
799
|
+
:tags => tags
|
800
|
+
}
|
801
|
+
elsif opt[:output] == "timeline"
|
802
|
+
new_item = {
|
803
|
+
'id' => index + 1,
|
804
|
+
'content' => title.strip, #+ " #{note}"
|
805
|
+
'title' => title.strip + " (#{"%02d:%02d:%02d" % fmt_time(interval)})",
|
806
|
+
'start' => i['date'].strftime('%F'),
|
807
|
+
'type' => 'point'
|
808
|
+
}
|
809
|
+
|
810
|
+
if interval && interval > 0
|
811
|
+
new_item['end'] = end_date.strftime('%F')
|
812
|
+
if interval > 3600 * 3
|
813
|
+
new_item['type'] = 'range'
|
814
|
+
end
|
815
|
+
end
|
816
|
+
items_out.push(new_item)
|
817
|
+
end
|
818
|
+
}
|
819
|
+
if opt[:output] == "json"
|
820
|
+
out = {
|
821
|
+
'section' => section,
|
822
|
+
'items' => items_out,
|
823
|
+
'timers' => tag_times("json")
|
824
|
+
}.to_json
|
825
|
+
elsif opt[:output] == "timeline"
|
826
|
+
template =<<EOTEMPLATE
|
827
|
+
<!doctype html>
|
828
|
+
<html>
|
829
|
+
<head>
|
830
|
+
<link href="http://visjs.org/dist/vis.css" rel="stylesheet" type="text/css" />
|
831
|
+
<script src="http://visjs.org/dist/vis.js"></script>
|
832
|
+
</head>
|
833
|
+
<body>
|
834
|
+
<div id="mytimeline"></div>
|
835
|
+
|
836
|
+
<script type="text/javascript">
|
837
|
+
// DOM element where the Timeline will be attached
|
838
|
+
var container = document.getElementById('mytimeline');
|
839
|
+
|
840
|
+
// Create a DataSet with data (enables two way data binding)
|
841
|
+
var data = new vis.DataSet(#{items_out.to_json});
|
842
|
+
|
843
|
+
// Configuration for the Timeline
|
844
|
+
var options = {
|
845
|
+
width: '100%',
|
846
|
+
height: '800px',
|
847
|
+
margin: {
|
848
|
+
item: 20
|
849
|
+
},
|
850
|
+
stack: true,
|
851
|
+
min: '#{min}',
|
852
|
+
max: '#{max}'
|
853
|
+
};
|
854
|
+
|
855
|
+
// Create a Timeline
|
856
|
+
var timeline = new vis.Timeline(container, data, options);
|
857
|
+
</script>
|
858
|
+
</body>
|
859
|
+
</html>
|
860
|
+
EOTEMPLATE
|
861
|
+
return template
|
862
|
+
end
|
738
863
|
elsif opt[:output] == "html"
|
739
864
|
page_title = section
|
740
865
|
items_out = []
|
@@ -764,34 +889,34 @@ class WWID
|
|
764
889
|
:time => interval
|
765
890
|
}
|
766
891
|
}
|
892
|
+
|
767
893
|
style = "body{background:#fff;color:#333;font-family:Helvetica,arial,freesans,clean,sans-serif;font-size:16px;line-height:120%;text-align:justify;padding:20px}h1{text-align:left;position:relative;left:220px;margin-bottom:1em}ul{list-style-position:outside;position:relative;left:170px;margin-right:170px;text-align:left}ul li{list-style-type:none;border-left:solid 1px #ccc;padding-left:10px;line-height:2;position:relative}ul li .date{font-size:14px;position:absolute;left:-122px;color:#7d9ca2;text-align:right;width:110px;line-height:2}ul li .tag{color:#999}ul li .note{display:block;color:#666;padding:0 0 0 22px;line-height:1.4;font-size:15px}ul li .note:before{content:'\\25BA';font-weight:300;position:absolute;left:40px;font-size:8px;color:#aaa;line-height:3}ul li:hover .note{display:block}span.time{color:#729953;float:left;position:relative;padding:0 5px;font-size:15px;border-bottom:dashed 1px #ccc;text-align:right;background:#f9fced;margin-right:4px}table td{border-bottom:solid 1px #ddd;height:24px}caption{text-align:left;border-bottom:solid 1px #aaa;margin:10px 0}table{width:400px;margin:50px 0 0 211px}th{padding-bottom:10px}th,td{padding-right:20px}table{max-width:400px;margin:50px 0 0 221px}"
|
768
894
|
template =<<EOT
|
769
895
|
!!!
|
770
896
|
%html
|
771
|
-
|
772
|
-
|
773
|
-
|
774
|
-
|
775
|
-
|
776
|
-
|
777
|
-
|
778
|
-
|
779
|
-
|
780
|
-
|
781
|
-
|
782
|
-
|
783
|
-
|
784
|
-
|
785
|
-
|
786
|
-
|
787
|
-
|
788
|
-
|
789
|
-
|
897
|
+
%head
|
898
|
+
%meta{"charset" => "utf-8"}/
|
899
|
+
%meta{"content" => "IE=edge,chrome=1", "http-equiv" => "X-UA-Compatible"}/
|
900
|
+
%title what are you doing?
|
901
|
+
%style= @style
|
902
|
+
%body
|
903
|
+
%header
|
904
|
+
%h1= @page_title
|
905
|
+
%article
|
906
|
+
%ul
|
907
|
+
- @items.each do |i|
|
908
|
+
%li
|
909
|
+
%span.date= i[:date]
|
910
|
+
= i[:title]
|
911
|
+
- if i[:time] && i[:time] != "00:00:00"
|
912
|
+
%span.time= i[:time]
|
913
|
+
- if i[:note]
|
914
|
+
%span.note= i[:note].map{|n| n.strip }.join('<br>')
|
915
|
+
= @totals
|
790
916
|
EOT
|
791
917
|
totals = opt[:totals] ? tag_times("html") : ""
|
792
918
|
engine = Haml::Engine.new(template)
|
793
919
|
puts engine.render(Object.new, { :@items => items_out, :@page_title => page_title, :@style => style, :@totals => totals })
|
794
|
-
|
795
920
|
else
|
796
921
|
items.each {|item|
|
797
922
|
|
@@ -857,6 +982,7 @@ EOT
|
|
857
982
|
end
|
858
983
|
output.sub!(/%note/,note)
|
859
984
|
output.sub!(/%odnote/,note.gsub(/^\t*/,""))
|
985
|
+
output.sub!(/%chompnote/,note.gsub(/\n+/,' ').gsub(/(^\s*|\s*$)/,'').gsub(/\s+/,' '))
|
860
986
|
output.gsub!(/%hr(_under)?/) do |m|
|
861
987
|
o = ""
|
862
988
|
`tput cols`.to_i.times do
|
@@ -1022,6 +1148,17 @@ EOT
|
|
1022
1148
|
list_section({:section => @current_section, :wrap_width => cfg['wrap_width'], :count => 0, :format => cfg['date_format'], :template => cfg['template'], :order => "asc", :today => true, :times => times, :output => output, :totals => opt[:totals]})
|
1023
1149
|
end
|
1024
1150
|
|
1151
|
+
def list_date(dates,section,times=nil,output=nil,opt={})
|
1152
|
+
opt[:totals] ||= false
|
1153
|
+
section = guess_section(section)
|
1154
|
+
# :date_filter expects an array with start and end date
|
1155
|
+
if dates.class == String
|
1156
|
+
dates = [dates, dates]
|
1157
|
+
end
|
1158
|
+
|
1159
|
+
list_section({:section => section, :count => 0, :order => "asc", :date_filter => dates, :times => times, :output => output, :totals => opt[:totals] })
|
1160
|
+
end
|
1161
|
+
|
1025
1162
|
def yesterday(section,times=nil,output=nil,opt={})
|
1026
1163
|
opt[:totals] ||= false
|
1027
1164
|
section = guess_section(section)
|
@@ -1085,6 +1222,16 @@ EOS
|
|
1085
1222
|
</table>
|
1086
1223
|
EOS
|
1087
1224
|
output + tail
|
1225
|
+
elsif format == "json"
|
1226
|
+
output = []
|
1227
|
+
@timers.delete_if { |k,v| v == 0}.sort_by{|k,v| v }.reverse.each {|k,v|
|
1228
|
+
output << {
|
1229
|
+
'tag' => k,
|
1230
|
+
'seconds' => v,
|
1231
|
+
'formatted' => "%02d:%02d:%02d" % fmt_time(v)
|
1232
|
+
}
|
1233
|
+
}
|
1234
|
+
output
|
1088
1235
|
else
|
1089
1236
|
output = []
|
1090
1237
|
@timers.delete_if { |k,v| v == 0}.sort_by{|k,v| v }.reverse.each {|k,v|
|
@@ -1101,6 +1248,30 @@ EOS
|
|
1101
1248
|
end
|
1102
1249
|
end
|
1103
1250
|
|
1251
|
+
# Uses autotag: configuration to turn keywords into tags for time tracking.
|
1252
|
+
# Does not repeat tags in a title, and only converts the first instance of
|
1253
|
+
# an untagged keyword
|
1254
|
+
def autotag(title)
|
1255
|
+
return unless title
|
1256
|
+
@config['autotag']['whitelist'].each {|tag|
|
1257
|
+
title.sub!(/(?<!@)(#{tag.strip})\b/i,'@\1') unless title =~ /@#{tag}\b/i
|
1258
|
+
}
|
1259
|
+
tail_tags = []
|
1260
|
+
@config['autotag']['synonyms'].each {|tag, v|
|
1261
|
+
v.each {|word|
|
1262
|
+
if title =~ /\b#{word}\b/i
|
1263
|
+
tail_tags.push(tag)
|
1264
|
+
end
|
1265
|
+
}
|
1266
|
+
}
|
1267
|
+
title + tail_tags.uniq.map {|t| '@'+t }.join(' ')
|
1268
|
+
end
|
1269
|
+
|
1270
|
+
def autotag_item(item)
|
1271
|
+
item['title'] = autotag(item['title'])
|
1272
|
+
item
|
1273
|
+
end
|
1274
|
+
|
1104
1275
|
private
|
1105
1276
|
|
1106
1277
|
def get_interval(item, formatted=true)
|
data/lib/doing.rb
CHANGED
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.8pre
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Brett Terpstra
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2014-09-
|
11
|
+
date: 2014-09-28 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rake
|
@@ -120,6 +120,20 @@ dependencies:
|
|
120
120
|
- - '>='
|
121
121
|
- !ruby/object:Gem::Version
|
122
122
|
version: '0'
|
123
|
+
- !ruby/object:Gem::Dependency
|
124
|
+
name: json
|
125
|
+
requirement: !ruby/object:Gem::Requirement
|
126
|
+
requirements:
|
127
|
+
- - ~>
|
128
|
+
- !ruby/object:Gem::Version
|
129
|
+
version: 1.8.1
|
130
|
+
type: :runtime
|
131
|
+
prerelease: false
|
132
|
+
version_requirements: !ruby/object:Gem::Requirement
|
133
|
+
requirements:
|
134
|
+
- - ~>
|
135
|
+
- !ruby/object:Gem::Version
|
136
|
+
version: 1.8.1
|
123
137
|
description: A tool for managing a TaskPaper-like file of recent activites. Perfect
|
124
138
|
for the late-night hacker on too much caffeine to remember what they accomplished
|
125
139
|
at 2 in the morning.
|