doing 1.0.89 → 1.0.93
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +13 -12
- data/bin/doing +63 -33
- data/lib/doing/helpers.rb +81 -11
- data/lib/doing/markdown_export.rb +16 -0
- data/lib/doing/version.rb +1 -1
- data/lib/doing/wwid.rb +137 -33
- data/lib/doing.rb +1 -0
- metadata +3 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 380b90b3b544c9473bf4d73309178d7701a87f783ccc57eb8541f337d2e569f9
|
4
|
+
data.tar.gz: 967d8fe4ece694713d1a3f739b39bb7c2f5507f19a80e6245eb996f7e2bdbc31
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: bd18bac215da59834c896b443d9645bc0d2bc99c37296574feb7c5cba5e683b5bb9d253b16f320cfd4e7ed0de8bab5cd12bd4eeaec42098d5be36652ebd1cae1
|
7
|
+
data.tar.gz: 50947a9b30c7da30905da2bc1dd194f9489a172f379cf734f3f5a1fdae9df0c13f8a0655ca73ea054a44d5fa86d4958c339a0d15a8864d1e5b43ba7693711715
|
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.92<!--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
|
|
@@ -549,7 +550,7 @@ All of the display commands (view, show, today, yesterday, search) support date
|
|
549
550
|
|
550
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`.
|
551
552
|
|
552
|
-
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.
|
553
554
|
|
554
555
|
##### Modifying the last entry
|
555
556
|
|
@@ -618,7 +619,7 @@ Doing can currently only import tasks from Timing.app reports. If you want to sy
|
|
618
619
|
7. Include short entries if desired
|
619
620
|
8. Export the report to a new file
|
620
621
|
|
621
|
-
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.
|
622
623
|
|
623
624
|
(`--type timing` is the only option right now, so it doesn't need to be included)
|
624
625
|
|
@@ -718,7 +719,7 @@ Ruby is rife with encoding inconsistencies across platforms and versions. Feel f
|
|
718
719
|
### Support
|
719
720
|
|
720
721
|
|
721
|
-
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.
|
722
723
|
|
723
724
|
Please try not to email me directly about GitHub projects.
|
724
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
|
@@ -321,9 +323,9 @@ command :select do |c|
|
|
321
323
|
c.arg_name 'FILE'
|
322
324
|
c.flag %i[save_to]
|
323
325
|
|
324
|
-
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)'
|
325
327
|
c.arg_name 'FORMAT'
|
326
|
-
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
|
327
329
|
|
328
330
|
c.action do |_global_options, options, args|
|
329
331
|
exit_now! "--no-menu requires --query" if !options[:menu] && !options[:query]
|
@@ -604,7 +606,7 @@ command :finish do |c|
|
|
604
606
|
c.switch %i[u unfinished], negatable: false, default_value: false
|
605
607
|
|
606
608
|
c.desc %(Auto-generate finish dates from next entry's start time.
|
607
|
-
Automatically generate completion dates 1 minute before next
|
609
|
+
Automatically generate completion dates 1 minute before next item (in any section) began.
|
608
610
|
--auto overrides the --date and --back parameters.)
|
609
611
|
c.switch [:auto], negatable: false, default_value: false
|
610
612
|
|
@@ -738,6 +740,19 @@ command [:again, :resume] do |c|
|
|
738
740
|
end
|
739
741
|
|
740
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 @.'
|
741
756
|
arg_name 'TAG', :multiple
|
742
757
|
command :tag do |c|
|
743
758
|
c.desc 'Section'
|
@@ -748,6 +763,10 @@ command :tag do |c|
|
|
748
763
|
c.arg_name 'COUNT'
|
749
764
|
c.flag %i[c count], default_value: 1
|
750
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
|
+
|
751
770
|
c.desc 'Don\'t ask permission to tag all entries when count is 0'
|
752
771
|
c.switch %i[force], negatable: false, default_value: false
|
753
772
|
|
@@ -757,6 +776,9 @@ command :tag do |c|
|
|
757
776
|
c.desc 'Remove given tag(s)'
|
758
777
|
c.switch %i[r remove], negatable: false, default_value: false
|
759
778
|
|
779
|
+
c.desc 'Interpret tag string as regular expression (with --remove)'
|
780
|
+
c.switch %i[regex], negatable: false, default_value: false
|
781
|
+
|
760
782
|
c.desc 'Tag last entry (or entries) not marked @done'
|
761
783
|
c.switch %i[u unfinished], negatable: false, default_value: false
|
762
784
|
|
@@ -843,19 +865,26 @@ command :tag do |c|
|
|
843
865
|
exit_now! 'Cancelled' unless res
|
844
866
|
end
|
845
867
|
|
846
|
-
|
847
|
-
|
848
|
-
|
849
|
-
|
850
|
-
|
851
|
-
|
852
|
-
|
853
|
-
|
854
|
-
|
855
|
-
|
856
|
-
|
857
|
-
|
858
|
-
|
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)
|
859
888
|
end
|
860
889
|
end
|
861
890
|
|
@@ -948,9 +977,9 @@ command :show do |c|
|
|
948
977
|
c.desc 'Only show items with recorded time intervals'
|
949
978
|
c.switch [:only_timed], default_value: false, negatable: false
|
950
979
|
|
951
|
-
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)'
|
952
981
|
c.arg_name 'FORMAT'
|
953
|
-
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
|
954
983
|
c.action do |_global_options, options, args|
|
955
984
|
tag_filter = false
|
956
985
|
tags = []
|
@@ -1068,9 +1097,9 @@ command [:grep, :search] do |c|
|
|
1068
1097
|
c.arg_name 'DATE_STRING'
|
1069
1098
|
c.flag [:after]
|
1070
1099
|
|
1071
|
-
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)'
|
1072
1101
|
c.arg_name 'FORMAT'
|
1073
|
-
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
|
1074
1103
|
|
1075
1104
|
c.desc 'Show time intervals on @done tasks'
|
1076
1105
|
c.switch %i[t times], default_value: true, negatable: true
|
@@ -1178,9 +1207,9 @@ command :today do |c|
|
|
1178
1207
|
c.arg_name 'KEY'
|
1179
1208
|
c.flag [:tag_sort], must_match: /^(?:name|time)$/i, default_value: default
|
1180
1209
|
|
1181
|
-
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)'
|
1182
1211
|
c.arg_name 'FORMAT'
|
1183
|
-
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
|
1184
1213
|
|
1185
1214
|
c.desc 'View entries before specified time (e.g. 8am, 12:30pm, 15:00)'
|
1186
1215
|
c.arg_name 'TIME_STRING'
|
@@ -1226,9 +1255,9 @@ command :on do |c|
|
|
1226
1255
|
c.arg_name 'KEY'
|
1227
1256
|
c.flag [:tag_sort], must_match: /^(?:name|time)$/i, default_value: default
|
1228
1257
|
|
1229
|
-
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)'
|
1230
1259
|
c.arg_name 'FORMAT'
|
1231
|
-
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
|
1232
1261
|
|
1233
1262
|
c.action do |_global_options, options, args|
|
1234
1263
|
exit_now! 'Missing date argument' if args.empty?
|
@@ -1279,9 +1308,9 @@ command :since do |c|
|
|
1279
1308
|
c.arg_name 'KEY'
|
1280
1309
|
c.flag [:tag_sort], must_match: /^(?:name|time)$/i, default_value: default
|
1281
1310
|
|
1282
|
-
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)'
|
1283
1312
|
c.arg_name 'FORMAT'
|
1284
|
-
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
|
1285
1314
|
|
1286
1315
|
c.action do |_global_options, options, args|
|
1287
1316
|
exit_now! 'Missing date argument' if args.empty?
|
@@ -1313,9 +1342,9 @@ command :yesterday do |c|
|
|
1313
1342
|
c.arg_name 'NAME'
|
1314
1343
|
c.flag %i[s section], default_value: 'All'
|
1315
1344
|
|
1316
|
-
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)'
|
1317
1346
|
c.arg_name 'FORMAT'
|
1318
|
-
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
|
1319
1348
|
|
1320
1349
|
c.desc 'Show time intervals on @done tasks'
|
1321
1350
|
c.switch %i[t times], default_value: true, negatable: true
|
@@ -1468,9 +1497,9 @@ command :view do |c|
|
|
1468
1497
|
c.arg_name 'COUNT'
|
1469
1498
|
c.flag %i[c count], must_match: /^\d+$/, type: Integer
|
1470
1499
|
|
1471
|
-
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)'
|
1472
1501
|
c.arg_name 'FORMAT'
|
1473
|
-
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
|
1474
1503
|
|
1475
1504
|
c.desc 'Show time intervals on @done tasks'
|
1476
1505
|
c.switch %i[t times], default_value: true, negatable: true
|
@@ -1932,6 +1961,7 @@ end
|
|
1932
1961
|
post do |global, _command, _options, _args|
|
1933
1962
|
# Use skips_post before a command to skip this
|
1934
1963
|
# block on that command only
|
1964
|
+
|
1935
1965
|
if global[:stdout]
|
1936
1966
|
$stdout.print wwid.results.join("\n")
|
1937
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
|
|
@@ -0,0 +1,16 @@
|
|
1
|
+
##
|
2
|
+
## @brief Creates Markdown export
|
3
|
+
##
|
4
|
+
class MarkdownExport
|
5
|
+
attr_accessor :items, :page_title, :totals
|
6
|
+
|
7
|
+
def initialize(page_title, items, totals)
|
8
|
+
@page_title = page_title
|
9
|
+
@items = items
|
10
|
+
@totals = totals
|
11
|
+
end
|
12
|
+
|
13
|
+
def get_binding
|
14
|
+
binding()
|
15
|
+
end
|
16
|
+
end
|
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
|
##
|
@@ -841,7 +852,6 @@ class WWID
|
|
841
852
|
end
|
842
853
|
|
843
854
|
res = `echo #{Shellwords.escape(options.join("\n"))}|#{fzf} #{fzf_args.join(' ')}`
|
844
|
-
`echo '#{Shellwords.escape(options.join("\n"))}|#{fzf} #{fzf_args.join(' ')}' >> test.txt`
|
845
855
|
selected = []
|
846
856
|
res.split(/\n/).each do |item|
|
847
857
|
idx = item.match(/^(\d+)\)/)[1].to_i
|
@@ -886,8 +896,7 @@ class WWID
|
|
886
896
|
when /(add|remove) tag/
|
887
897
|
type = action =~ /^add/ ? 'add' : 'remove'
|
888
898
|
if opt[:tag]
|
889
|
-
|
890
|
-
Process.exit 1
|
899
|
+
exit_now! "'add tag' and 'remove tag' can not be used together"
|
891
900
|
end
|
892
901
|
print "#{colors['yellow']}Tag to #{type}: #{colors['reset']}"
|
893
902
|
tag = STDIN.gets
|
@@ -1027,8 +1036,6 @@ class WWID
|
|
1027
1036
|
case opt[:output]
|
1028
1037
|
when /doing/
|
1029
1038
|
options[:template] = '- %date | %title%note'
|
1030
|
-
when /taskpaper/
|
1031
|
-
options[:template] = '- %title @date(%date)%note'
|
1032
1039
|
else
|
1033
1040
|
options[:output] = opt[:output]
|
1034
1041
|
end
|
@@ -1127,7 +1134,12 @@ class WWID
|
|
1127
1134
|
else
|
1128
1135
|
if opt[:sequential]
|
1129
1136
|
next_entry = next_item(item)
|
1130
|
-
|
1137
|
+
|
1138
|
+
if next_entry.nil?
|
1139
|
+
done_date = Time.now
|
1140
|
+
else
|
1141
|
+
done_date = next_entry['date'] - 60
|
1142
|
+
end
|
1131
1143
|
elsif opt[:took]
|
1132
1144
|
if item['date'] + opt[:took] > Time.now
|
1133
1145
|
item['date'] = Time.now - opt[:took]
|
@@ -1148,10 +1160,31 @@ class WWID
|
|
1148
1160
|
title = item['title']
|
1149
1161
|
opt[:tags].each do |tag|
|
1150
1162
|
tag = tag.strip
|
1151
|
-
if opt[:remove]
|
1152
|
-
|
1153
|
-
|
1154
|
-
|
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}))
|
1155
1188
|
end
|
1156
1189
|
elsif title !~ /@#{tag}/
|
1157
1190
|
title.chomp!
|
@@ -1198,6 +1231,15 @@ class WWID
|
|
1198
1231
|
write(@doing_file)
|
1199
1232
|
end
|
1200
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
|
+
##
|
1201
1243
|
def move_item(item, section)
|
1202
1244
|
old_section = item['section']
|
1203
1245
|
new_item = item.dup
|
@@ -1222,13 +1264,15 @@ class WWID
|
|
1222
1264
|
## @param old_item
|
1223
1265
|
##
|
1224
1266
|
def next_item(old_item)
|
1225
|
-
|
1226
|
-
|
1227
|
-
|
1228
|
-
|
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)
|
1229
1273
|
|
1230
|
-
if
|
1231
|
-
|
1274
|
+
if idx > 0
|
1275
|
+
items[idx - 1]
|
1232
1276
|
else
|
1233
1277
|
nil
|
1234
1278
|
end
|
@@ -1696,11 +1740,13 @@ class WWID
|
|
1696
1740
|
|
1697
1741
|
# opt[:highlight] ||= true
|
1698
1742
|
section = ''
|
1743
|
+
is_single = true
|
1699
1744
|
if opt[:section].nil?
|
1700
1745
|
section = choose_section
|
1701
1746
|
opt[:section] = @content[section]
|
1702
1747
|
elsif opt[:section].instance_of?(String)
|
1703
1748
|
if opt[:section] =~ /^all$/i
|
1749
|
+
is_single = false
|
1704
1750
|
combined = { 'items' => [] }
|
1705
1751
|
@content.each do |_k, v|
|
1706
1752
|
combined['items'] += v['items']
|
@@ -1787,7 +1833,7 @@ class WWID
|
|
1787
1833
|
|
1788
1834
|
out = ''
|
1789
1835
|
|
1790
|
-
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)
|
1791
1837
|
|
1792
1838
|
case opt[:output]
|
1793
1839
|
when /^csv$/i
|
@@ -1866,7 +1912,7 @@ class WWID
|
|
1866
1912
|
puts JSON.pretty_generate({
|
1867
1913
|
'section' => section,
|
1868
1914
|
'items' => items_out,
|
1869
|
-
'timers' => tag_times(format:
|
1915
|
+
'timers' => tag_times(format: :json, sort_by_name: opt[:sort_tags], sort_order: opt[:tag_order])
|
1870
1916
|
})
|
1871
1917
|
elsif opt[:output] == 'timeline'
|
1872
1918
|
template = <<~EOTEMPLATE
|
@@ -1947,10 +1993,60 @@ class WWID
|
|
1947
1993
|
css_template
|
1948
1994
|
end
|
1949
1995
|
|
1950
|
-
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]) : ''
|
1951
1997
|
engine = Haml::Engine.new(template)
|
1952
1998
|
out = engine.render(Object.new,
|
1953
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)
|
1954
2050
|
else
|
1955
2051
|
items.each do |item|
|
1956
2052
|
if opt[:highlight] && item['title'] =~ /@#{@config['marker_tag']}\b/i
|
@@ -1965,7 +2061,9 @@ class WWID
|
|
1965
2061
|
note_lines = item['note'].delete_if do |line|
|
1966
2062
|
line =~ /^\s*$/
|
1967
2063
|
end
|
1968
|
-
note_lines.map! { |line|
|
2064
|
+
note_lines.map! { |line|
|
2065
|
+
"\t#{line.sub(/^\t*(— )?/, '').sub(/^- /, '— ')} "
|
2066
|
+
}
|
1969
2067
|
if opt[:wrap_width]&.positive?
|
1970
2068
|
width = opt[:wrap_width]
|
1971
2069
|
note_lines.map! do |line|
|
@@ -1993,15 +2091,7 @@ class WWID
|
|
1993
2091
|
output.sub!(/%interval/, interval)
|
1994
2092
|
|
1995
2093
|
output.sub!(/%shortdate/) do
|
1996
|
-
|
1997
|
-
item['date'].strftime(' %_I:%M%P')
|
1998
|
-
elsif item['date'] > (Date.today - 6).to_time
|
1999
|
-
item['date'].strftime('%a %_I:%M%P')
|
2000
|
-
elsif item['date'].year == Date.today.year
|
2001
|
-
item['date'].strftime('%m/%d %_I:%M%P')
|
2002
|
-
else
|
2003
|
-
item['date'].strftime('%m/%d/%Y %_I:%M%P')
|
2004
|
-
end
|
2094
|
+
item['date'].relative_date
|
2005
2095
|
end
|
2006
2096
|
|
2007
2097
|
output.sub!(/%title/) do |_m|
|
@@ -2039,7 +2129,7 @@ class WWID
|
|
2039
2129
|
out += "#{output}\n"
|
2040
2130
|
end
|
2041
2131
|
|
2042
|
-
out += tag_times(format:
|
2132
|
+
out += tag_times(format: :text, sort_by_name: opt[:sort_tags], sort_order: opt[:tag_order]) if opt[:totals]
|
2043
2133
|
end
|
2044
2134
|
out
|
2045
2135
|
end
|
@@ -2347,7 +2437,7 @@ class WWID
|
|
2347
2437
|
## @param sort_by_name (Boolean) Sort by name if true, otherwise by time
|
2348
2438
|
## @param sort_order (String) The sort order (asc or desc)
|
2349
2439
|
##
|
2350
|
-
def tag_times(format:
|
2440
|
+
def tag_times(format: :text, sort_by_name: false, sort_order: 'asc')
|
2351
2441
|
return '' if @timers.empty?
|
2352
2442
|
|
2353
2443
|
max = @timers.keys.sort_by { |k| k.length }.reverse[0].length + 1
|
@@ -2362,8 +2452,9 @@ class WWID
|
|
2362
2452
|
end
|
2363
2453
|
|
2364
2454
|
sorted_tags_data.reverse! if sort_order =~ /^asc/i
|
2455
|
+
case format
|
2456
|
+
when :html
|
2365
2457
|
|
2366
|
-
if format == 'html'
|
2367
2458
|
output = <<EOS
|
2368
2459
|
<table>
|
2369
2460
|
<caption id="tagtotals">Tag Totals</caption>
|
@@ -2398,7 +2489,20 @@ EOS
|
|
2398
2489
|
</table>
|
2399
2490
|
EOS
|
2400
2491
|
output + tail
|
2401
|
-
|
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
|
2402
2506
|
output = []
|
2403
2507
|
sorted_tags_data.reverse.each do |k, v|
|
2404
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.93
|
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
|
@@ -166,6 +166,7 @@ files:
|
|
166
166
|
- bin/doing
|
167
167
|
- lib/doing.rb
|
168
168
|
- lib/doing/helpers.rb
|
169
|
+
- lib/doing/markdown_export.rb
|
169
170
|
- lib/doing/version.rb
|
170
171
|
- lib/doing/wwid.rb
|
171
172
|
- lib/helpers/fuzzyfilefinder
|