doing 1.0.90 → 1.0.91

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: ce98503c379e1c769b36a26ed4f2efc9d1871c60abd67a18152db3862e1664c4
4
- data.tar.gz: a4783a52a345f7676e08b53d9a06e4b5170ab5ff0f0d52e2ac45bcf7d6dc87f9
3
+ metadata.gz: 365e58ed7377d560d531ce2a1585d115c0a1b73fc3039621a2fef230f52de5dc
4
+ data.tar.gz: 0efbf78887502113048946df9942e58ad751921b4fc997efd7556ba40e428bb5
5
5
  SHA512:
6
- metadata.gz: e2166477e4557597bbc5ad81bb94b73a2ec6c1c4ebdb98ac3d7ef860ddb9e6baf1998dd61accc9d336bd59003a77f7486b31edd1c818bab24b9e9ddb44608c7f
7
- data.tar.gz: bfba29c14482747cf3150d31d44ccf43038e41c40876b37ebbb2c8c5e1aeb0dc3c0365125c0ef6a53e79d790893c4670a97e55051b06b3292ec4d2040799f15f
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 rare people like me who find this useful, feel free to [buy me some coffee](http://brettterpstra.com/donate/)._
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.89<!--END VER-->.
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 multiline templates, just use `\n` in the template line, and after the next run it will be rewritten as proper YAML automatically.
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 either or both of the template files, edit `.doingrc` and look for the `html_template:` section. There are two subvalues, `haml:` and `css:`. Add the path to the templates you want to use. A tilde may be substituted for your home directory, e.g. `css: ~/styles/doing.css`.
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 (defauts 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
+ 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 replicatable 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
+ 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 CSS templates for customization'
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]
@@ -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
- opts = {
847
- autotag: options[:a],
848
- count: count,
849
- date: options[:date],
850
- remove: options[:r],
851
- search: options[:search],
852
- section: section,
853
- tag: search_tags,
854
- tag_bool: options[:bool],
855
- tags: tags,
856
- unfinished: options[:unfinished]
857
- }
858
- wwid.tag_last(opts)
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
- if opt[:format] == :html
95
- gsub(%r{(?mi)((http|https)://)([\w\-_]+(\.[\w\-_]+)+)([\w\-.,@?^=%&amp;:/~+#]*[\w\-@^=%&amp;/~+#])?}) do |_match|
96
- m = Regexp.last_match
97
- proto = m[1].nil? ? 'http://' : ''
98
- %(<a href="#{proto}#{m[0]}" title="Link to #{m[0]}">[#{m[3]}]</a>)
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] =~ /<a href/
102
- match
147
+ if m[1] =~ /^https?:/
148
+ m[1]
103
149
  else
104
- %(<a href="#{m[1]}" title="Link to #{m[1]}">[link]</a>)
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\-.,@?^=%&amp;:/~+#]*[\w\-@^=%&amp;/~+#])?}) 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
@@ -1,3 +1,3 @@
1
1
  module Doing
2
- VERSION = '1.0.90'
2
+ VERSION = '1.0.91'
3
3
  end
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
  ##
@@ -1025,8 +1036,6 @@ class WWID
1025
1036
  case opt[:output]
1026
1037
  when /doing/
1027
1038
  options[:template] = '- %date | %title%note'
1028
- when /taskpaper/
1029
- options[:template] = '- %title @date(%date)%note'
1030
1039
  else
1031
1040
  options[:output] = opt[:output]
1032
1041
  end
@@ -1151,10 +1160,31 @@ class WWID
1151
1160
  title = item['title']
1152
1161
  opt[:tags].each do |tag|
1153
1162
  tag = tag.strip
1154
- if opt[:remove]
1155
- if title =~ /@#{tag}\b/
1156
- title.gsub!(/(^| )@#{tag}(\([^)]*\))?/, '')
1157
- @results.push(%(Removed @#{tag}: "#{title}" in #{section}))
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}))
1158
1188
  end
1159
1189
  elsif title !~ /@#{tag}/
1160
1190
  title.chomp!
@@ -1201,6 +1231,15 @@ class WWID
1201
1231
  write(@doing_file)
1202
1232
  end
1203
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
+ ##
1204
1243
  def move_item(item, section)
1205
1244
  old_section = item['section']
1206
1245
  new_item = item.dup
@@ -1701,11 +1740,13 @@ class WWID
1701
1740
 
1702
1741
  # opt[:highlight] ||= true
1703
1742
  section = ''
1743
+ is_single = true
1704
1744
  if opt[:section].nil?
1705
1745
  section = choose_section
1706
1746
  opt[:section] = @content[section]
1707
1747
  elsif opt[:section].instance_of?(String)
1708
1748
  if opt[:section] =~ /^all$/i
1749
+ is_single = false
1709
1750
  combined = { 'items' => [] }
1710
1751
  @content.each do |_k, v|
1711
1752
  combined['items'] += v['items']
@@ -1792,7 +1833,7 @@ class WWID
1792
1833
 
1793
1834
  out = ''
1794
1835
 
1795
- 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)
1796
1837
 
1797
1838
  case opt[:output]
1798
1839
  when /^csv$/i
@@ -1871,7 +1912,7 @@ class WWID
1871
1912
  puts JSON.pretty_generate({
1872
1913
  'section' => section,
1873
1914
  'items' => items_out,
1874
- 'timers' => tag_times(format: 'json', sort_by_name: opt[:sort_tags], sort_order: opt[:tag_order])
1915
+ 'timers' => tag_times(format: :json, sort_by_name: opt[:sort_tags], sort_order: opt[:tag_order])
1875
1916
  })
1876
1917
  elsif opt[:output] == 'timeline'
1877
1918
  template = <<~EOTEMPLATE
@@ -1952,10 +1993,60 @@ class WWID
1952
1993
  css_template
1953
1994
  end
1954
1995
 
1955
- totals = opt[:totals] ? tag_times(format: 'html', sort_by_name: opt[:sort_tags], sort_order: opt[:tag_order]) : ''
1996
+ totals = opt[:totals] ? tag_times(format: :html, sort_by_name: opt[:sort_tags], sort_order: opt[:tag_order]) : ''
1956
1997
  engine = Haml::Engine.new(template)
1957
1998
  out = engine.render(Object.new,
1958
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)
1959
2050
  else
1960
2051
  items.each do |item|
1961
2052
  if opt[:highlight] && item['title'] =~ /@#{@config['marker_tag']}\b/i
@@ -1970,7 +2061,9 @@ class WWID
1970
2061
  note_lines = item['note'].delete_if do |line|
1971
2062
  line =~ /^\s*$/
1972
2063
  end
1973
- note_lines.map! { |line| "\t\t#{line.sub(/^\t*/, '').sub(/^-/, '—')} " }
2064
+ note_lines.map! { |line|
2065
+ "\t#{line.sub(/^\t*(— )?/, '').sub(/^- /, '— ')} "
2066
+ }
1974
2067
  if opt[:wrap_width]&.positive?
1975
2068
  width = opt[:wrap_width]
1976
2069
  note_lines.map! do |line|
@@ -1998,15 +2091,7 @@ class WWID
1998
2091
  output.sub!(/%interval/, interval)
1999
2092
 
2000
2093
  output.sub!(/%shortdate/) do
2001
- if item['date'] > Date.today.to_time
2002
- item['date'].strftime(' %_I:%M%P')
2003
- elsif item['date'] > (Date.today - 6).to_time
2004
- item['date'].strftime('%a %_I:%M%P')
2005
- elsif item['date'].year == Date.today.year
2006
- item['date'].strftime('%m/%d %_I:%M%P')
2007
- else
2008
- item['date'].strftime('%m/%d/%Y %_I:%M%P')
2009
- end
2094
+ item['date'].relative_date
2010
2095
  end
2011
2096
 
2012
2097
  output.sub!(/%title/) do |_m|
@@ -2044,7 +2129,7 @@ class WWID
2044
2129
  out += "#{output}\n"
2045
2130
  end
2046
2131
 
2047
- out += tag_times(format: 'text', sort_by_name: opt[:sort_tags], sort_order: opt[:tag_order]) if opt[:totals]
2132
+ out += tag_times(format: :text, sort_by_name: opt[:sort_tags], sort_order: opt[:tag_order]) if opt[:totals]
2048
2133
  end
2049
2134
  out
2050
2135
  end
@@ -2352,7 +2437,7 @@ class WWID
2352
2437
  ## @param sort_by_name (Boolean) Sort by name if true, otherwise by time
2353
2438
  ## @param sort_order (String) The sort order (asc or desc)
2354
2439
  ##
2355
- def tag_times(format: 'text', sort_by_name: false, sort_order: 'asc')
2440
+ def tag_times(format: :text, sort_by_name: false, sort_order: 'asc')
2356
2441
  return '' if @timers.empty?
2357
2442
 
2358
2443
  max = @timers.keys.sort_by { |k| k.length }.reverse[0].length + 1
@@ -2367,8 +2452,9 @@ class WWID
2367
2452
  end
2368
2453
 
2369
2454
  sorted_tags_data.reverse! if sort_order =~ /^asc/i
2455
+ case format
2456
+ when :html
2370
2457
 
2371
- if format == 'html'
2372
2458
  output = <<EOS
2373
2459
  <table>
2374
2460
  <caption id="tagtotals">Tag Totals</caption>
@@ -2403,7 +2489,20 @@ EOS
2403
2489
  </table>
2404
2490
  EOS
2405
2491
  output + tail
2406
- elsif format == 'json'
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
2407
2506
  output = []
2408
2507
  sorted_tags_data.reverse.each do |k, v|
2409
2508
  output << {
data/lib/doing.rb CHANGED
@@ -9,5 +9,6 @@ require 'chronic'
9
9
  require 'haml'
10
10
  require 'json'
11
11
  require 'doing/helpers'
12
+ require 'doing/markdown_export'
12
13
  require 'doing/wwid'
13
- # require 'doing/markdown_document_listener'
14
+ require 'doing/markdown_document_listener'
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.90
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-14 00:00:00.000000000 Z
11
+ date: 2021-10-16 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rake