doing 1.0.87 → 1.0.91
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +15 -12
- data/bin/doing +95 -37
- data/lib/doing/helpers.rb +81 -11
- data/lib/doing/version.rb +1 -1
- data/lib/doing/wwid.rb +165 -37
- data/lib/doing.rb +2 -1
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 365e58ed7377d560d531ce2a1585d115c0a1b73fc3039621a2fef230f52de5dc
|
4
|
+
data.tar.gz: 0efbf78887502113048946df9942e58ad751921b4fc997efd7556ba40e428bb5
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 53fba5968f465d3dc40821360d60f2ff63573b8e5b47e9eb851fbf6592a3a7baea0436efb118d08d4f92e10f035a5f9ee5fa47ae5df65aa6929c40146a0a6f5f
|
7
|
+
data.tar.gz: 6a98c715220355434ed2e05e4582d04ddd7eade5f4588ea4e881a025654ac5e0d90df1f845f73406a50bda14d887540f34254ffe8de50252840a2c070cbb5c75
|
data/README.md
CHANGED
@@ -2,7 +2,7 @@
|
|
2
2
|
|
3
3
|
**A command line tool for remembering what you were doing and tracking what you've done.**
|
4
4
|
|
5
|
-
_If you're one of the
|
5
|
+
_If you're one of the rarev people like me who find this useful, feel free to [buy me some coffee](http://brettterpstra.com/donate/)._
|
6
6
|
|
7
7
|
## Contents
|
8
8
|
|
@@ -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.90<!--END VER-->.
|
31
31
|
|
32
32
|
$ [sudo] gem install doing
|
33
33
|
|
@@ -104,6 +104,7 @@ A basic configuration looks like this:
|
|
104
104
|
html_template:
|
105
105
|
haml:
|
106
106
|
css:
|
107
|
+
markdown:
|
107
108
|
|
108
109
|
|
109
110
|
The config file is stored in `~/.doingrc`, and a skeleton file is created on the first run. Just run `doing` on its own to create the file.
|
@@ -114,7 +115,7 @@ Any options found in a `.doingrc` anywhere in the hierarchy between your current
|
|
114
115
|
|
115
116
|
Possible uses:
|
116
117
|
|
117
|
-
- Define custom HTML output on a per-project basis using the html_template option for custom templates. Customize time tracking reports based on project or client.
|
118
|
+
- Define custom HTML or Markdown output on a per-project basis using the html_template option for custom templates. Customize time tracking reports based on project or client.
|
118
119
|
- Define `default_tags` for a project so that every time you `doing now` from within that project directory or its subfolders, it gets tagged with that project automatically.
|
119
120
|
|
120
121
|
Any part of the configuration can be copied into these local files and modified. You only need to include the parts you want to change or add.
|
@@ -191,7 +192,7 @@ And it outputs:
|
|
191
192
|
12:45pm > I think this thing (doing) is ready to document and distribute
|
192
193
|
$
|
193
194
|
|
194
|
-
You can get pretty clever and include line breaks and other formatting inside of double quotes. If you want
|
195
|
+
You can get pretty clever and include line breaks and other formatting inside of double quotes. If you want multi-line templates, just use `\n` in the template line, and after the next run it will be rewritten as proper YAML automatically.
|
195
196
|
|
196
197
|
For example, this block:
|
197
198
|
|
@@ -300,7 +301,7 @@ Outputs:
|
|
300
301
|
|
301
302
|
![](http://ckyp.us/XKpj+)
|
302
303
|
|
303
|
-
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 for the view in the config.
|
304
|
+
You can also specify a default output format for a view. Most of the optional output formats override the template specification (`html`, `csv`, `json`, `markdown`). If the `view` command is used with the `-o` flag, it will override what's specified for the view in the config.
|
304
305
|
|
305
306
|
### Colors
|
306
307
|
|
@@ -342,13 +343,13 @@ And a few special colors you'll just have to try out to see (or just run `doing
|
|
342
343
|
|
343
344
|
Any time you use one of the foreground colors it will reset the bold and background settings to their default automatically. You can force a reset to default terminal colors using `%default`.
|
344
345
|
|
345
|
-
### HTML Templates
|
346
|
+
### HTML/Markdown Templates
|
346
347
|
|
347
|
-
For commands that provide an HTML output option, you can customize the templates used for markup and CSS. The markup uses [HAML](http://haml.info/), and the styles are pure CSS.
|
348
|
+
For commands that provide an HTML or Markdown output option, you can customize the templates used for markup and CSS. The HTML markup uses [HAML](http://haml.info/), and the styles are pure CSS. The Markdown template uses Ruby ERB templating.
|
348
349
|
|
349
|
-
To export the default configurations for customization, use `doing templates --type=[HAML|CSS]`. This will output to STDOUT where you can pipe it to a file, e.g. `doing templates --type=HAML > my_template.haml`. You can modify the markup, the CSS, or both.
|
350
|
+
To export the default configurations for customization, use `doing templates --type=[HAML|CSS|MARKDOWN]`. This will output to STDOUT where you can pipe it to a file, e.g. `doing templates --type=HAML > my_template.haml`. You can modify the markup, the CSS, or both.
|
350
351
|
|
351
|
-
Once you have
|
352
|
+
Once you have a template file prepared, edit `.doingrc` and look for the `html_template:` section. There are three sub-values, `haml:`, `css:`, and `markdown:`. Add the template paths to the appropriate keys. A tilde may be substituted for your home directory, e.g. `css: ~/styles/doing.css`.
|
352
353
|
|
353
354
|
### Autotagging
|
354
355
|
|
@@ -545,9 +546,11 @@ If you have a use for it, you can use `-o csv` on the show or view commands to o
|
|
545
546
|
|
546
547
|
`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`).
|
547
548
|
|
549
|
+
All of the display commands (view, show, today, yesterday, search) support date ranges in addition to other filtering options. Results can be narrowed to a date/time span using `--before DATE` and `--after DATE`. The `DATE` accepts a natural language string but works best in formats like `10/20/21` or `2021-05-13 3pm` (or shortened to just `5/13 3pm`). For the `today` and `yesterday` commands the `DATE` should just be a time, e.g. `12pm` or `15:00`, and results will be filtered within the timespan for just that day. Both flags are optional, and you can use just `--before` to get everything older than a certain date, or use just `--after` to get everything newer.
|
550
|
+
|
548
551
|
`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`.
|
549
552
|
|
550
|
-
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.
|
553
|
+
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, Markdown, CSV, or JSON.
|
551
554
|
|
552
555
|
##### Modifying the last entry
|
553
556
|
|
@@ -616,7 +619,7 @@ Doing can currently only import tasks from Timing.app reports. If you want to sy
|
|
616
619
|
7. Include short entries if desired
|
617
620
|
8. Export the report to a new file
|
618
621
|
|
619
|
-
Now you can run `doing import --type timing -s SECTION PATH`, where SECTION is the name of the section you want to import the entries to (
|
622
|
+
Now you can run `doing import --type timing -s SECTION PATH`, where SECTION is the name of the section you want to import the entries to (defaults to Currently), and PATH is the path to the JSON file. You can also add a tag (or tags) to all entries, or a custom prefix.
|
620
623
|
|
621
624
|
(`--type timing` is the only option right now, so it doesn't need to be included)
|
622
625
|
|
@@ -716,7 +719,7 @@ Ruby is rife with encoding inconsistencies across platforms and versions. Feel f
|
|
716
719
|
### Support
|
717
720
|
|
718
721
|
|
719
|
-
As a free project, `doing` isn't heavily supported, but you can get support from myself and other users on GitHub. If you run into a
|
722
|
+
As a free project, `doing` isn't heavily supported, but you can get support from myself and other users on GitHub. If you run into a replicable bug in your environment, please [post an issue](https://github.com/ttscoff/doing/issues) and include your platform, OS version, and the result of `ruby -v`, along with a copy/paste of the error message. To get a more verbose error message, try running `GLI_DEBUG=true doing [...]` for a full trace.
|
720
723
|
|
721
724
|
Please try not to email me directly about GitHub projects.
|
722
725
|
|
data/bin/doing
CHANGED
@@ -243,23 +243,25 @@ command :meanwhile do |c|
|
|
243
243
|
end
|
244
244
|
end
|
245
245
|
|
246
|
-
desc 'Output HTML and
|
246
|
+
desc 'Output HTML, CSS, and Markdown (ERB) templates for customization'
|
247
247
|
long_desc %(
|
248
248
|
Templates are printed to STDOUT for piping to a file.
|
249
249
|
Save them and use them in the configuration file under html_template.
|
250
250
|
|
251
251
|
Example `doing template HAML > ~/styles/my_doing.haml`
|
252
252
|
)
|
253
|
-
arg_name 'TYPE', must_match: /^(?:html|haml|css)/i
|
253
|
+
arg_name 'TYPE', must_match: /^(?:html|haml|css|markdown|md|erb)/i
|
254
254
|
command :template do |c|
|
255
255
|
c.action do |_global_options, options, args|
|
256
|
-
exit_now! 'No type specified, use `doing template [HAML|CSS]`' if args.empty?
|
256
|
+
exit_now! 'No type specified, use `doing template [HAML|CSS|MARKDOWN]`' if args.empty?
|
257
257
|
|
258
258
|
case args[0]
|
259
259
|
when /html|haml/i
|
260
260
|
$stdout.puts wwid.haml_template
|
261
261
|
when /css/i
|
262
262
|
$stdout.puts wwid.css_template
|
263
|
+
when /markdown|md|erb/i
|
264
|
+
$stdout.puts wwid.markdown_template
|
263
265
|
else
|
264
266
|
exit_now! 'Invalid type specified, must be HAML or CSS'
|
265
267
|
end
|
@@ -292,10 +294,13 @@ command :select do |c|
|
|
292
294
|
c.arg_name 'SECTION'
|
293
295
|
c.flag %i[m move]
|
294
296
|
|
295
|
-
c.desc 'Initial search query for filtering'
|
297
|
+
c.desc 'Initial search query for filtering. Matching is fuzzy. For exact matching, start query with a single quote, e.g. `--query "\'search"'
|
296
298
|
c.arg_name 'QUERY'
|
297
299
|
c.flag %i[q query]
|
298
300
|
|
301
|
+
c.desc 'Use --no-menu to skip the interactive menu. Use with --query to filter items and act on results automatically. Test with `--output doing` to preview matches.'
|
302
|
+
c.switch %i[menu], negatable: true, default_value: true
|
303
|
+
|
299
304
|
c.desc 'Cancel selected items (add @done without timestamp)'
|
300
305
|
c.switch %i[c cancel], negatable: false, default_value: false
|
301
306
|
|
@@ -311,18 +316,20 @@ command :select do |c|
|
|
311
316
|
c.desc 'Add flag to selected item(s)'
|
312
317
|
c.switch %i[flag], negatable: false, default_value: false
|
313
318
|
|
314
|
-
c.desc 'Perform action without confirmation'
|
319
|
+
c.desc 'Perform action without confirmation.'
|
315
320
|
c.switch %i[force], negatable: false, default_value: false
|
316
321
|
|
317
322
|
c.desc 'Save selected entries to file using --output format'
|
318
323
|
c.arg_name 'FILE'
|
319
324
|
c.flag %i[save_to]
|
320
325
|
|
321
|
-
c.desc 'Output entries to format (doing|taskpaper|csv|html|json|template|timeline)'
|
326
|
+
c.desc 'Output entries to format (doing|taskpaper|csv|html|json|template|timeline|taskpaper|markdown)'
|
322
327
|
c.arg_name 'FORMAT'
|
323
|
-
c.flag %i[o output], must_match: /^(?:doing|taskpaper|html|csv|json|template|timeline)$/i
|
328
|
+
c.flag %i[o output], must_match: /^(?:doing|taskpaper|html|csv|json|template|timeline|taskpaper|markdown|md)$/i
|
324
329
|
|
325
330
|
c.action do |_global_options, options, args|
|
331
|
+
exit_now! "--no-menu requires --query" if !options[:menu] && !options[:query]
|
332
|
+
|
326
333
|
wwid.interactive(options)
|
327
334
|
end
|
328
335
|
end
|
@@ -599,7 +606,7 @@ command :finish do |c|
|
|
599
606
|
c.switch %i[u unfinished], negatable: false, default_value: false
|
600
607
|
|
601
608
|
c.desc %(Auto-generate finish dates from next entry's start time.
|
602
|
-
Automatically generate completion dates 1 minute before next
|
609
|
+
Automatically generate completion dates 1 minute before next item (in any section) began.
|
603
610
|
--auto overrides the --date and --back parameters.)
|
604
611
|
c.switch [:auto], negatable: false, default_value: false
|
605
612
|
|
@@ -733,6 +740,19 @@ command [:again, :resume] do |c|
|
|
733
740
|
end
|
734
741
|
|
735
742
|
desc 'Add tag(s) to last entry'
|
743
|
+
long_desc 'Add (or remove) tags from the last entry, or from multiple entries
|
744
|
+
(with `--count`), entries matching a search (with `--search), or entries
|
745
|
+
containing another tag (with `--tag`).
|
746
|
+
|
747
|
+
When removing tags with `-r`, wildcards are allowed (`*` to match
|
748
|
+
multiple characters, `?` to match a single character). With `--regex`,
|
749
|
+
regular expressions will be interpreted instead of wildcards.
|
750
|
+
|
751
|
+
For all tag removals the match is case insensitive by default, but if
|
752
|
+
the tag search string contains any uppercase letters, the match will
|
753
|
+
become case sensitive automatically.
|
754
|
+
|
755
|
+
Tag name arguments do not need to be prefixed with @.'
|
736
756
|
arg_name 'TAG', :multiple
|
737
757
|
command :tag do |c|
|
738
758
|
c.desc 'Section'
|
@@ -743,6 +763,10 @@ command :tag do |c|
|
|
743
763
|
c.arg_name 'COUNT'
|
744
764
|
c.flag %i[c count], default_value: 1
|
745
765
|
|
766
|
+
c.desc 'Replace existing tag with tag argument, wildcards (*,?) allowed, or use with --regex'
|
767
|
+
c.arg_name 'ORIG_TAG'
|
768
|
+
c.flag %i[rename]
|
769
|
+
|
746
770
|
c.desc 'Don\'t ask permission to tag all entries when count is 0'
|
747
771
|
c.switch %i[force], negatable: false, default_value: false
|
748
772
|
|
@@ -752,6 +776,9 @@ command :tag do |c|
|
|
752
776
|
c.desc 'Remove given tag(s)'
|
753
777
|
c.switch %i[r remove], negatable: false, default_value: false
|
754
778
|
|
779
|
+
c.desc 'Interpret tag string as regular expression (with --remove)'
|
780
|
+
c.switch %i[regex], negatable: false, default_value: false
|
781
|
+
|
755
782
|
c.desc 'Tag last entry (or entries) not marked @done'
|
756
783
|
c.switch %i[u unfinished], negatable: false, default_value: false
|
757
784
|
|
@@ -838,19 +865,26 @@ command :tag do |c|
|
|
838
865
|
exit_now! 'Cancelled' unless res
|
839
866
|
end
|
840
867
|
|
841
|
-
|
842
|
-
|
843
|
-
|
844
|
-
|
845
|
-
|
846
|
-
|
847
|
-
|
848
|
-
|
849
|
-
|
850
|
-
|
851
|
-
|
852
|
-
|
853
|
-
|
868
|
+
options[:count] = count
|
869
|
+
options[:section] = section
|
870
|
+
options[:tag] = search_tags
|
871
|
+
options[:tags] = tags
|
872
|
+
options[:tag_bool] = options[:bool]
|
873
|
+
|
874
|
+
# opts = {
|
875
|
+
# autotag: options[:a],
|
876
|
+
# count: count,
|
877
|
+
# date: options[:date],
|
878
|
+
# iregex: options[:iregex]
|
879
|
+
# remove: options[:r],
|
880
|
+
# search: options[:search],
|
881
|
+
# section: section,
|
882
|
+
# tag: search_tags,
|
883
|
+
# tag_bool: options[:bool],
|
884
|
+
# tags: tags,
|
885
|
+
# unfinished: options[:unfinished]
|
886
|
+
# }
|
887
|
+
wwid.tag_last(options)
|
854
888
|
end
|
855
889
|
end
|
856
890
|
|
@@ -943,9 +977,9 @@ command :show do |c|
|
|
943
977
|
c.desc 'Only show items with recorded time intervals'
|
944
978
|
c.switch [:only_timed], default_value: false, negatable: false
|
945
979
|
|
946
|
-
c.desc 'Output to export format (csv|html|json|template|timeline)'
|
980
|
+
c.desc 'Output to export format (csv|html|json|template|timeline|taskpaper|markdown)'
|
947
981
|
c.arg_name 'FORMAT'
|
948
|
-
c.flag %i[o output], must_match: /^(?:template|html|csv|json|timeline)$/i
|
982
|
+
c.flag %i[o output], must_match: /^(?:template|html|csv|json|timeline|taskpaper|markdown|md)$/i
|
949
983
|
c.action do |_global_options, options, args|
|
950
984
|
tag_filter = false
|
951
985
|
tags = []
|
@@ -1063,9 +1097,9 @@ command [:grep, :search] do |c|
|
|
1063
1097
|
c.arg_name 'DATE_STRING'
|
1064
1098
|
c.flag [:after]
|
1065
1099
|
|
1066
|
-
c.desc 'Output to export format (csv|html|json|template|timeline)'
|
1100
|
+
c.desc 'Output to export format (csv|html|json|template|timeline|taskpaper|markdown)'
|
1067
1101
|
c.arg_name 'FORMAT'
|
1068
|
-
c.flag %i[o output], must_match: /^(?:template|html|csv|json|timeline)$/i
|
1102
|
+
c.flag %i[o output], must_match: /^(?:template|html|csv|json|timeline|taskpaper|markdown|md)$/i
|
1069
1103
|
|
1070
1104
|
c.desc 'Show time intervals on @done tasks'
|
1071
1105
|
c.switch %i[t times], default_value: true, negatable: true
|
@@ -1173,9 +1207,9 @@ command :today do |c|
|
|
1173
1207
|
c.arg_name 'KEY'
|
1174
1208
|
c.flag [:tag_sort], must_match: /^(?:name|time)$/i, default_value: default
|
1175
1209
|
|
1176
|
-
c.desc 'Output to export format (csv|html|json|template|timeline)'
|
1210
|
+
c.desc 'Output to export format (csv|html|json|template|timeline|taskpaper|markdown)'
|
1177
1211
|
c.arg_name 'FORMAT'
|
1178
|
-
c.flag %i[o output], must_match: /^(?:template|html|csv|json|timeline)$/i
|
1212
|
+
c.flag %i[o output], must_match: /^(?:template|html|csv|json|timeline|taskpaper|markdown|md)$/i
|
1179
1213
|
|
1180
1214
|
c.desc 'View entries before specified time (e.g. 8am, 12:30pm, 15:00)'
|
1181
1215
|
c.arg_name 'TIME_STRING'
|
@@ -1221,9 +1255,9 @@ command :on do |c|
|
|
1221
1255
|
c.arg_name 'KEY'
|
1222
1256
|
c.flag [:tag_sort], must_match: /^(?:name|time)$/i, default_value: default
|
1223
1257
|
|
1224
|
-
c.desc 'Output to export format (csv|html|json|template|timeline)'
|
1258
|
+
c.desc 'Output to export format (csv|html|json|template|timeline|taskpaper|markdown)'
|
1225
1259
|
c.arg_name 'FORMAT'
|
1226
|
-
c.flag %i[o output], must_match: /^(?:template|html|csv|json|timeline)$/i
|
1260
|
+
c.flag %i[o output], must_match: /^(?:template|html|csv|json|timeline|taskpaper|markdown|md)$/i
|
1227
1261
|
|
1228
1262
|
c.action do |_global_options, options, args|
|
1229
1263
|
exit_now! 'Missing date argument' if args.empty?
|
@@ -1274,9 +1308,9 @@ command :since do |c|
|
|
1274
1308
|
c.arg_name 'KEY'
|
1275
1309
|
c.flag [:tag_sort], must_match: /^(?:name|time)$/i, default_value: default
|
1276
1310
|
|
1277
|
-
c.desc 'Output to export format (csv|html|json|template|timeline)'
|
1311
|
+
c.desc 'Output to export format (csv|html|json|template|timeline|taskpaper|markdown)'
|
1278
1312
|
c.arg_name 'FORMAT'
|
1279
|
-
c.flag %i[o output], must_match: /^(?:template|html|csv|json|timeline)$/i
|
1313
|
+
c.flag %i[o output], must_match: /^(?:template|html|csv|json|timeline|taskpaper|markdown|md)$/i
|
1280
1314
|
|
1281
1315
|
c.action do |_global_options, options, args|
|
1282
1316
|
exit_now! 'Missing date argument' if args.empty?
|
@@ -1308,9 +1342,9 @@ command :yesterday do |c|
|
|
1308
1342
|
c.arg_name 'NAME'
|
1309
1343
|
c.flag %i[s section], default_value: 'All'
|
1310
1344
|
|
1311
|
-
c.desc 'Output to export format (csv|html|json|template|timeline)'
|
1345
|
+
c.desc 'Output to export format (csv|html|json|template|timeline|taskpaper|markdown)'
|
1312
1346
|
c.arg_name 'FORMAT'
|
1313
|
-
c.flag %i[o output], must_match: /^(?:template|html|csv|json|timeline)$/i
|
1347
|
+
c.flag %i[o output], must_match: /^(?:template|html|csv|json|timeline|taskpaper|markdown|md)$/i
|
1314
1348
|
|
1315
1349
|
c.desc 'Show time intervals on @done tasks'
|
1316
1350
|
c.switch %i[t times], default_value: true, negatable: true
|
@@ -1324,10 +1358,33 @@ command :yesterday do |c|
|
|
1324
1358
|
c.arg_name 'KEY'
|
1325
1359
|
c.flag [:tag_sort], must_match: /^(?:name|time)$/i, default_value: default
|
1326
1360
|
|
1361
|
+
c.desc 'View entries before specified time (e.g. 8am, 12:30pm, 15:00)'
|
1362
|
+
c.arg_name 'TIME_STRING'
|
1363
|
+
c.flag [:before]
|
1364
|
+
|
1365
|
+
c.desc 'View entries after specified time (e.g. 8am, 12:30pm, 15:00)'
|
1366
|
+
c.arg_name 'TIME_STRING'
|
1367
|
+
c.flag [:after]
|
1368
|
+
|
1369
|
+
c.desc 'Tag sort direction (asc|desc)'
|
1370
|
+
c.arg_name 'DIRECTION'
|
1371
|
+
c.flag [:tag_order], must_match: /^(?:a(?:sc)?|d(?:esc)?)$/i
|
1372
|
+
|
1327
1373
|
c.action do |_global_options, options, _args|
|
1374
|
+
tag_order = if options[:tag_order]
|
1375
|
+
options[:tag_order] =~ /^d/i ? 'desc' : 'asc'
|
1376
|
+
else
|
1377
|
+
'asc'
|
1378
|
+
end
|
1328
1379
|
options[:sort_tags] = options[:tag_sort] =~ /^n/i
|
1329
|
-
|
1330
|
-
|
1380
|
+
opt = {
|
1381
|
+
after: options[:after],
|
1382
|
+
before: options[:before],
|
1383
|
+
sort_tags: options[:sort_tags],
|
1384
|
+
tag_order: options[:tag_order],
|
1385
|
+
totals: options[:totals]
|
1386
|
+
}
|
1387
|
+
puts wwid.yesterday(options[:section], options[:times], options[:output], opt).chomp
|
1331
1388
|
end
|
1332
1389
|
end
|
1333
1390
|
|
@@ -1440,9 +1497,9 @@ command :view do |c|
|
|
1440
1497
|
c.arg_name 'COUNT'
|
1441
1498
|
c.flag %i[c count], must_match: /^\d+$/, type: Integer
|
1442
1499
|
|
1443
|
-
c.desc 'Output to export format (csv|html|json|template|timeline)'
|
1500
|
+
c.desc 'Output to export format (csv|html|json|template|timeline|taskpaper|markdown)'
|
1444
1501
|
c.arg_name 'FORMAT'
|
1445
|
-
c.flag %i[o output], must_match: /^(?:template|html|csv|json|timeline)$/i
|
1502
|
+
c.flag %i[o output], must_match: /^(?:template|html|csv|json|timeline|taskpaper|markdown|md)$/i
|
1446
1503
|
|
1447
1504
|
c.desc 'Show time intervals on @done tasks'
|
1448
1505
|
c.switch %i[t times], default_value: true, negatable: true
|
@@ -1904,6 +1961,7 @@ end
|
|
1904
1961
|
post do |global, _command, _options, _args|
|
1905
1962
|
# Use skips_post before a command to skip this
|
1906
1963
|
# block on that command only
|
1964
|
+
|
1907
1965
|
if global[:stdout]
|
1908
1966
|
$stdout.print wwid.results.join("\n")
|
1909
1967
|
else
|
data/lib/doing/helpers.rb
CHANGED
@@ -1,3 +1,20 @@
|
|
1
|
+
##
|
2
|
+
## @brief Date helpers
|
3
|
+
##
|
4
|
+
class ::Time
|
5
|
+
def relative_date
|
6
|
+
if self > Date.today.to_time
|
7
|
+
strftime(' %_I:%M%P')
|
8
|
+
elsif self > (Date.today - 6).to_time
|
9
|
+
strftime('%a %_I:%M%P')
|
10
|
+
elsif self.year == Date.today.year
|
11
|
+
strftime('%m/%d %_I:%M%P')
|
12
|
+
else
|
13
|
+
strftime('%m/%d/%Y %_I:%M%P')
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
1
18
|
##
|
2
19
|
## @brief Hash helpers
|
3
20
|
##
|
@@ -84,29 +101,82 @@ class ::String
|
|
84
101
|
end
|
85
102
|
|
86
103
|
|
104
|
+
##
|
105
|
+
## @brief Remove duplicate tags, leaving only first occurrence
|
106
|
+
##
|
107
|
+
## @return Deduplicated string
|
108
|
+
##
|
109
|
+
def dedup_tags!
|
110
|
+
replace dedup_tags
|
111
|
+
end
|
112
|
+
|
113
|
+
def dedup_tags
|
114
|
+
item = self.dup
|
115
|
+
tags = item.scan(/(?<=^| )(\@(\S+?)(\([^)]+\))?)(?=\b|$)/).uniq
|
116
|
+
tags.each do |tag|
|
117
|
+
found = false
|
118
|
+
item.gsub!(/( |^)#{tag[0]}\b/) do |m|
|
119
|
+
if found
|
120
|
+
''
|
121
|
+
else
|
122
|
+
found = true
|
123
|
+
m
|
124
|
+
end
|
125
|
+
end
|
126
|
+
end
|
127
|
+
item
|
128
|
+
end
|
129
|
+
|
87
130
|
##
|
88
131
|
## @brief Turn raw urls into HTML links
|
89
132
|
##
|
90
133
|
## @param opt (Hash) Additional Options
|
91
134
|
##
|
135
|
+
def link_urls!(opt = {})
|
136
|
+
replace link_urls(opt)
|
137
|
+
end
|
138
|
+
|
92
139
|
def link_urls(opt = {})
|
93
140
|
opt[:format] ||= :html
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
end.gsub(/<(\w+:.*?)>/) do |match|
|
141
|
+
str = self.dup
|
142
|
+
|
143
|
+
if :format == :markdown
|
144
|
+
# Remove <self-linked> formatting
|
145
|
+
str.gsub!(/<(.*?)>/) do |match|
|
100
146
|
m = Regexp.last_match
|
101
|
-
if m[1] =~
|
102
|
-
|
147
|
+
if m[1] =~ /^https?:/
|
148
|
+
m[1]
|
103
149
|
else
|
104
|
-
|
150
|
+
match
|
105
151
|
end
|
106
152
|
end
|
107
|
-
else
|
108
|
-
self
|
109
153
|
end
|
154
|
+
|
155
|
+
# Replace qualified urls
|
156
|
+
str.gsub!(%r{(?mi)(?<!["'\[(\\])((http|https)://)([\w\-_]+(\.[\w\-_]+)+)([\w\-.,@?^=%&:/~+#]*[\w\-@^=%&/~+#])?}) do |_match|
|
157
|
+
m = Regexp.last_match
|
158
|
+
proto = m[1].nil? ? 'http://' : ''
|
159
|
+
case opt[:format]
|
160
|
+
when :html
|
161
|
+
%(<a href="#{proto}#{m[0]}" title="Link to #{m[0]}">[#{m[3]}]</a>)
|
162
|
+
when :markdown
|
163
|
+
"[#{m[0]}](#{proto}#{m[0]})"
|
164
|
+
else
|
165
|
+
m[0]
|
166
|
+
end
|
167
|
+
end
|
168
|
+
|
169
|
+
# Clean up unlinked <urls>
|
170
|
+
str.gsub!(/<(\w+:.*?)>/) do |match|
|
171
|
+
m = Regexp.last_match
|
172
|
+
if m[1] =~ /<a href/
|
173
|
+
match
|
174
|
+
else
|
175
|
+
%(<a href="#{m[1]}" title="Link to #{m[1]}">[link]</a>)
|
176
|
+
end
|
177
|
+
end
|
178
|
+
|
179
|
+
str
|
110
180
|
end
|
111
181
|
end
|
112
182
|
|
data/lib/doing/version.rb
CHANGED
data/lib/doing/wwid.rb
CHANGED
@@ -4,6 +4,7 @@ require 'deep_merge'
|
|
4
4
|
require 'open3'
|
5
5
|
require 'pp'
|
6
6
|
require 'shellwords'
|
7
|
+
require 'erb'
|
7
8
|
|
8
9
|
##
|
9
10
|
## @brief Main "What Was I Doing" methods
|
@@ -104,6 +105,7 @@ class WWID
|
|
104
105
|
@config['html_template'] ||= {}
|
105
106
|
@config['html_template']['haml'] ||= nil
|
106
107
|
@config['html_template']['css'] ||= nil
|
108
|
+
@config['html_template']['markdown'] ||= nil
|
107
109
|
|
108
110
|
@config['templates'] ||= {}
|
109
111
|
@config['templates']['default'] ||= {
|
@@ -233,6 +235,15 @@ class WWID
|
|
233
235
|
end
|
234
236
|
end
|
235
237
|
|
238
|
+
##
|
239
|
+
## @brief Return the contents of the ERB template for Markdown output
|
240
|
+
##
|
241
|
+
## @return (String) ERB template
|
242
|
+
##
|
243
|
+
def markdown_template
|
244
|
+
IO.read(File.join(File.dirname(__FILE__), '../templates/doing-markdown.erb'))
|
245
|
+
end
|
246
|
+
|
236
247
|
##
|
237
248
|
## @brief Return the contents of the HAML template for HTML output
|
238
249
|
##
|
@@ -817,7 +828,7 @@ class WWID
|
|
817
828
|
' | ',
|
818
829
|
item['title']
|
819
830
|
]
|
820
|
-
if
|
831
|
+
if section =~ /^all/i
|
821
832
|
out.concat([
|
822
833
|
' (',
|
823
834
|
item['section'],
|
@@ -834,6 +845,12 @@ class WWID
|
|
834
845
|
'--bind ctrl-a:select-all',
|
835
846
|
%(-q "#{opt[:query]}")
|
836
847
|
]
|
848
|
+
if !opt[:menu]
|
849
|
+
exit_now! "Can't skip menu when no query is provided" unless opt[:query]
|
850
|
+
|
851
|
+
fzf_args.concat([%(--filter="#{opt[:query]}"), '--no-sort'])
|
852
|
+
end
|
853
|
+
|
837
854
|
res = `echo #{Shellwords.escape(options.join("\n"))}|#{fzf} #{fzf_args.join(' ')}`
|
838
855
|
selected = []
|
839
856
|
res.split(/\n/).each do |item|
|
@@ -879,8 +896,7 @@ class WWID
|
|
879
896
|
when /(add|remove) tag/
|
880
897
|
type = action =~ /^add/ ? 'add' : 'remove'
|
881
898
|
if opt[:tag]
|
882
|
-
|
883
|
-
Process.exit 1
|
899
|
+
exit_now! "'add tag' and 'remove tag' can not be used together"
|
884
900
|
end
|
885
901
|
print "#{colors['yellow']}Tag to #{type}: #{colors['reset']}"
|
886
902
|
tag = STDIN.gets
|
@@ -1020,8 +1036,6 @@ class WWID
|
|
1020
1036
|
case opt[:output]
|
1021
1037
|
when /doing/
|
1022
1038
|
options[:template] = '- %date | %title%note'
|
1023
|
-
when /taskpaper/
|
1024
|
-
options[:template] = '- %title @date(%date)%note'
|
1025
1039
|
else
|
1026
1040
|
options[:output] = opt[:output]
|
1027
1041
|
end
|
@@ -1120,7 +1134,12 @@ class WWID
|
|
1120
1134
|
else
|
1121
1135
|
if opt[:sequential]
|
1122
1136
|
next_entry = next_item(item)
|
1123
|
-
|
1137
|
+
|
1138
|
+
if next_entry.nil?
|
1139
|
+
done_date = Time.now
|
1140
|
+
else
|
1141
|
+
done_date = next_entry['date'] - 60
|
1142
|
+
end
|
1124
1143
|
elsif opt[:took]
|
1125
1144
|
if item['date'] + opt[:took] > Time.now
|
1126
1145
|
item['date'] = Time.now - opt[:took]
|
@@ -1141,10 +1160,31 @@ class WWID
|
|
1141
1160
|
title = item['title']
|
1142
1161
|
opt[:tags].each do |tag|
|
1143
1162
|
tag = tag.strip
|
1144
|
-
if opt[:remove]
|
1145
|
-
|
1146
|
-
|
1147
|
-
|
1163
|
+
if opt[:remove] || opt[:rename]
|
1164
|
+
case_sensitive = tag !~ /[A-Z]/
|
1165
|
+
replacement = ''
|
1166
|
+
if opt[:rename]
|
1167
|
+
replacement = tag
|
1168
|
+
tag = opt[:rename]
|
1169
|
+
end
|
1170
|
+
|
1171
|
+
if opt[:regex]
|
1172
|
+
rx_tag = tag.gsub(/\./, '\S')
|
1173
|
+
else
|
1174
|
+
rx_tag = tag.gsub(/\?/, '.').gsub(/\*/, '\S*?')
|
1175
|
+
end
|
1176
|
+
|
1177
|
+
if title =~ / @#{rx_tag}\b/
|
1178
|
+
rx = Regexp.new("(^| )@#{rx_tag}(\\([^)]*\\))?(?=\\b|$)", case_sensitive)
|
1179
|
+
removed_tags = []
|
1180
|
+
title.gsub!(rx) do |mtch|
|
1181
|
+
removed_tags.push(mtch.strip.sub(/\(.*?\)$/, ''))
|
1182
|
+
replacement
|
1183
|
+
end
|
1184
|
+
|
1185
|
+
title.dedup_tags!
|
1186
|
+
|
1187
|
+
@results.push(%(Removed #{removed_tags.join(', ')}: "#{title}" in #{section}))
|
1148
1188
|
end
|
1149
1189
|
elsif title !~ /@#{tag}/
|
1150
1190
|
title.chomp!
|
@@ -1191,6 +1231,15 @@ class WWID
|
|
1191
1231
|
write(@doing_file)
|
1192
1232
|
end
|
1193
1233
|
|
1234
|
+
##
|
1235
|
+
## @brief Move item from current section to
|
1236
|
+
## destination section
|
1237
|
+
##
|
1238
|
+
## @param item The item
|
1239
|
+
## @param section The destination section
|
1240
|
+
##
|
1241
|
+
## @return Updated item
|
1242
|
+
##
|
1194
1243
|
def move_item(item, section)
|
1195
1244
|
old_section = item['section']
|
1196
1245
|
new_item = item.dup
|
@@ -1215,13 +1264,15 @@ class WWID
|
|
1215
1264
|
## @param old_item
|
1216
1265
|
##
|
1217
1266
|
def next_item(old_item)
|
1218
|
-
|
1219
|
-
|
1220
|
-
|
1221
|
-
|
1267
|
+
combined = { 'items' => [] }
|
1268
|
+
@content.each do |_k, v|
|
1269
|
+
combined['items'] += v['items']
|
1270
|
+
end
|
1271
|
+
items = combined['items'].dup.sort_by { |item| item['date'] }.reverse
|
1272
|
+
idx = items.index(old_item)
|
1222
1273
|
|
1223
|
-
if
|
1224
|
-
|
1274
|
+
if idx > 0
|
1275
|
+
items[idx - 1]
|
1225
1276
|
else
|
1226
1277
|
nil
|
1227
1278
|
end
|
@@ -1689,11 +1740,13 @@ class WWID
|
|
1689
1740
|
|
1690
1741
|
# opt[:highlight] ||= true
|
1691
1742
|
section = ''
|
1743
|
+
is_single = true
|
1692
1744
|
if opt[:section].nil?
|
1693
1745
|
section = choose_section
|
1694
1746
|
opt[:section] = @content[section]
|
1695
1747
|
elsif opt[:section].instance_of?(String)
|
1696
1748
|
if opt[:section] =~ /^all$/i
|
1749
|
+
is_single = false
|
1697
1750
|
combined = { 'items' => [] }
|
1698
1751
|
@content.each do |_k, v|
|
1699
1752
|
combined['items'] += v['items']
|
@@ -1780,7 +1833,7 @@ class WWID
|
|
1780
1833
|
|
1781
1834
|
out = ''
|
1782
1835
|
|
1783
|
-
exit_now! 'Unknown output format' if opt[:output] && (opt[:output] !~ /^(template|html|csv|json|timeline)$/i)
|
1836
|
+
exit_now! 'Unknown output format' if opt[:output] && (opt[:output] !~ /^(template|html|csv|json|timeline|markdown|md|taskpaper)$/i)
|
1784
1837
|
|
1785
1838
|
case opt[:output]
|
1786
1839
|
when /^csv$/i
|
@@ -1856,11 +1909,11 @@ class WWID
|
|
1856
1909
|
end
|
1857
1910
|
end
|
1858
1911
|
if opt[:output] == 'json'
|
1859
|
-
|
1912
|
+
puts JSON.pretty_generate({
|
1860
1913
|
'section' => section,
|
1861
1914
|
'items' => items_out,
|
1862
|
-
'timers' => tag_times(format:
|
1863
|
-
}
|
1915
|
+
'timers' => tag_times(format: :json, sort_by_name: opt[:sort_tags], sort_order: opt[:tag_order])
|
1916
|
+
})
|
1864
1917
|
elsif opt[:output] == 'timeline'
|
1865
1918
|
template = <<~EOTEMPLATE
|
1866
1919
|
<!doctype html>
|
@@ -1940,10 +1993,60 @@ class WWID
|
|
1940
1993
|
css_template
|
1941
1994
|
end
|
1942
1995
|
|
1943
|
-
totals = opt[:totals] ? tag_times(format:
|
1996
|
+
totals = opt[:totals] ? tag_times(format: :html, sort_by_name: opt[:sort_tags], sort_order: opt[:tag_order]) : ''
|
1944
1997
|
engine = Haml::Engine.new(template)
|
1945
1998
|
out = engine.render(Object.new,
|
1946
1999
|
{ :@items => items_out, :@page_title => page_title, :@style => style, :@totals => totals })
|
2000
|
+
when /^taskpaper$/i
|
2001
|
+
options = opt
|
2002
|
+
options[:highlight] = false
|
2003
|
+
options[:wrap_width] = 0
|
2004
|
+
options[:tags_color] = false
|
2005
|
+
options[:output] = nil
|
2006
|
+
options[:template] = '- %title @date(%date)%note'
|
2007
|
+
|
2008
|
+
out = list_section(options)
|
2009
|
+
when /^(md|markdown|gfm)$/i
|
2010
|
+
page_title = section
|
2011
|
+
all_items = []
|
2012
|
+
items.each do |i|
|
2013
|
+
if String.method_defined? :force_encoding
|
2014
|
+
title = i['title'].force_encoding('utf-8').link_urls({format: :markdown})
|
2015
|
+
note = i['note'].map { |line| line.force_encoding('utf-8').strip.link_urls({format: :markdown}) } if i['note']
|
2016
|
+
else
|
2017
|
+
title = i['title'].link_urls({format: :markdown})
|
2018
|
+
note = i['note'].map { |line| line.strip.link_urls({format: :markdown}) } if i['note']
|
2019
|
+
end
|
2020
|
+
|
2021
|
+
title = "#{title} @project(#{i['section']})" unless is_single
|
2022
|
+
|
2023
|
+
interval = get_interval(i) if i['title'] =~ /@done\((\d{4}-\d\d-\d\d \d\d:\d\d.*?)\)/ && opt[:times]
|
2024
|
+
interval ||= false
|
2025
|
+
|
2026
|
+
done = i['title'] =~ /(?<= |^)@done/ ? 'x' : ' '
|
2027
|
+
|
2028
|
+
all_items << {
|
2029
|
+
date: i['date'].strftime('%a %-I:%M%p'),
|
2030
|
+
shortdate: i['date'].relative_date,
|
2031
|
+
done: done,
|
2032
|
+
note: note,
|
2033
|
+
section: i['section'],
|
2034
|
+
time: interval,
|
2035
|
+
title: title.strip
|
2036
|
+
}
|
2037
|
+
end
|
2038
|
+
|
2039
|
+
template = if @config['html_template']['markdown'] && File.exist?(File.expand_path(@config['html_template']['markdown']))
|
2040
|
+
IO.read(File.expand_path(@config['html_template']['markdown']))
|
2041
|
+
else
|
2042
|
+
markdown_template
|
2043
|
+
end
|
2044
|
+
|
2045
|
+
totals = opt[:totals] ? tag_times(format: :markdown, sort_by_name: opt[:sort_tags], sort_order: opt[:tag_order]) : ''
|
2046
|
+
|
2047
|
+
mdx = MarkdownExport.new(page_title, all_items, totals)
|
2048
|
+
engine = ERB.new(template)
|
2049
|
+
out = engine.result(mdx.get_binding)
|
1947
2050
|
else
|
1948
2051
|
items.each do |item|
|
1949
2052
|
if opt[:highlight] && item['title'] =~ /@#{@config['marker_tag']}\b/i
|
@@ -1958,7 +2061,9 @@ class WWID
|
|
1958
2061
|
note_lines = item['note'].delete_if do |line|
|
1959
2062
|
line =~ /^\s*$/
|
1960
2063
|
end
|
1961
|
-
note_lines.map! { |line|
|
2064
|
+
note_lines.map! { |line|
|
2065
|
+
"\t#{line.sub(/^\t*(— )?/, '').sub(/^- /, '— ')} "
|
2066
|
+
}
|
1962
2067
|
if opt[:wrap_width]&.positive?
|
1963
2068
|
width = opt[:wrap_width]
|
1964
2069
|
note_lines.map! do |line|
|
@@ -1986,15 +2091,7 @@ class WWID
|
|
1986
2091
|
output.sub!(/%interval/, interval)
|
1987
2092
|
|
1988
2093
|
output.sub!(/%shortdate/) do
|
1989
|
-
|
1990
|
-
item['date'].strftime(' %_I:%M%P')
|
1991
|
-
elsif item['date'] > (Date.today - 6).to_time
|
1992
|
-
item['date'].strftime('%a %_I:%M%P')
|
1993
|
-
elsif item['date'].year == Date.today.year
|
1994
|
-
item['date'].strftime('%m/%d %_I:%M%P')
|
1995
|
-
else
|
1996
|
-
item['date'].strftime('%m/%d/%Y %_I:%M%P')
|
1997
|
-
end
|
2094
|
+
item['date'].relative_date
|
1998
2095
|
end
|
1999
2096
|
|
2000
2097
|
output.sub!(/%title/) do |_m|
|
@@ -2032,7 +2129,7 @@ class WWID
|
|
2032
2129
|
out += "#{output}\n"
|
2033
2130
|
end
|
2034
2131
|
|
2035
|
-
out += tag_times(format:
|
2132
|
+
out += tag_times(format: :text, sort_by_name: opt[:sort_tags], sort_order: opt[:tag_order]) if opt[:totals]
|
2036
2133
|
end
|
2037
2134
|
out
|
2038
2135
|
end
|
@@ -2257,8 +2354,25 @@ class WWID
|
|
2257
2354
|
opt[:totals] ||= false
|
2258
2355
|
opt[:sort_tags] ||= false
|
2259
2356
|
section = guess_section(section)
|
2260
|
-
|
2261
|
-
|
2357
|
+
y = (Time.now - (60 * 60 * 24)).strftime('%Y-%m-%d')
|
2358
|
+
opt[:after] = "#{y} #{opt[:after]}" if opt[:after]
|
2359
|
+
opt[:before] = "#{y} #{opt[:before]}" if opt[:before]
|
2360
|
+
|
2361
|
+
options = {
|
2362
|
+
after: opt[:after],
|
2363
|
+
before: opt[:before],
|
2364
|
+
count: 0,
|
2365
|
+
order: 'asc',
|
2366
|
+
output: output,
|
2367
|
+
section: section,
|
2368
|
+
sort_tags: opt[:sort_tags],
|
2369
|
+
tag_order: opt[:tag_order],
|
2370
|
+
times: times,
|
2371
|
+
totals: opt[:totals],
|
2372
|
+
yesterday: true
|
2373
|
+
}
|
2374
|
+
|
2375
|
+
list_section(options)
|
2262
2376
|
end
|
2263
2377
|
|
2264
2378
|
##
|
@@ -2323,7 +2437,7 @@ class WWID
|
|
2323
2437
|
## @param sort_by_name (Boolean) Sort by name if true, otherwise by time
|
2324
2438
|
## @param sort_order (String) The sort order (asc or desc)
|
2325
2439
|
##
|
2326
|
-
def tag_times(format:
|
2440
|
+
def tag_times(format: :text, sort_by_name: false, sort_order: 'asc')
|
2327
2441
|
return '' if @timers.empty?
|
2328
2442
|
|
2329
2443
|
max = @timers.keys.sort_by { |k| k.length }.reverse[0].length + 1
|
@@ -2338,8 +2452,9 @@ class WWID
|
|
2338
2452
|
end
|
2339
2453
|
|
2340
2454
|
sorted_tags_data.reverse! if sort_order =~ /^asc/i
|
2455
|
+
case format
|
2456
|
+
when :html
|
2341
2457
|
|
2342
|
-
if format == 'html'
|
2343
2458
|
output = <<EOS
|
2344
2459
|
<table>
|
2345
2460
|
<caption id="tagtotals">Tag Totals</caption>
|
@@ -2374,7 +2489,20 @@ EOS
|
|
2374
2489
|
</table>
|
2375
2490
|
EOS
|
2376
2491
|
output + tail
|
2377
|
-
|
2492
|
+
when :markdown
|
2493
|
+
pad = sorted_tags_data.map {|k, v| k }.group_by(&:size).max.last[0].length
|
2494
|
+
output = <<-EOS
|
2495
|
+
| #{' ' * (pad - 7) }project | time |
|
2496
|
+
| #{'-' * (pad - 1)}: | :------- |
|
2497
|
+
EOS
|
2498
|
+
sorted_tags_data.reverse.each do |k, v|
|
2499
|
+
if v > 0
|
2500
|
+
output += "| #{' ' * (pad - k.length)}#{k} | #{'%02d:%02d:%02d' % fmt_time(v)} |\n"
|
2501
|
+
end
|
2502
|
+
end
|
2503
|
+
tail = "[Tag Totals]"
|
2504
|
+
output + tail
|
2505
|
+
when :json
|
2378
2506
|
output = []
|
2379
2507
|
sorted_tags_data.reverse.each do |k, v|
|
2380
2508
|
output << {
|
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.91
|
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-10-
|
11
|
+
date: 2021-10-16 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rake
|