doing 2.0.25 → 2.1.0pre

Sign up to get free protection for your applications and to get access to all the features.
Files changed (81) hide show
  1. checksums.yaml +4 -4
  2. data/.yardoc/checksums +18 -15
  3. data/.yardoc/object_types +0 -0
  4. data/.yardoc/objects/root.dat +0 -0
  5. data/CHANGELOG.md +28 -0
  6. data/Gemfile.lock +8 -1
  7. data/README.md +1 -1
  8. data/Rakefile +23 -4
  9. data/bin/doing +205 -127
  10. data/doc/Array.html +354 -1
  11. data/doc/Doing/Color.html +104 -92
  12. data/doc/Doing/Completion.html +216 -0
  13. data/doc/Doing/Configuration.html +340 -5
  14. data/doc/Doing/Content.html +229 -0
  15. data/doc/Doing/Errors/DoingNoTraceError.html +1 -1
  16. data/doc/Doing/Errors/DoingRuntimeError.html +1 -1
  17. data/doc/Doing/Errors/DoingStandardError.html +1 -1
  18. data/doc/Doing/Errors/EmptyInput.html +1 -1
  19. data/doc/Doing/Errors/NoResults.html +1 -1
  20. data/doc/Doing/Errors/PluginException.html +1 -1
  21. data/doc/Doing/Errors/UserCancelled.html +1 -1
  22. data/doc/Doing/Errors/WrongCommand.html +1 -1
  23. data/doc/Doing/Errors.html +1 -1
  24. data/doc/Doing/Hooks.html +1 -1
  25. data/doc/Doing/Item.html +337 -49
  26. data/doc/Doing/Items.html +444 -35
  27. data/doc/Doing/LogAdapter.html +139 -51
  28. data/doc/Doing/Note.html +253 -22
  29. data/doc/Doing/Pager.html +74 -36
  30. data/doc/Doing/Plugins.html +1 -1
  31. data/doc/Doing/Prompt.html +674 -0
  32. data/doc/Doing/Section.html +354 -0
  33. data/doc/Doing/Util.html +57 -1
  34. data/doc/Doing/WWID.html +477 -670
  35. data/doc/Doing/WWIDFile.html +398 -0
  36. data/doc/Doing.html +5 -5
  37. data/doc/GLI/Commands/MarkdownDocumentListener.html +1 -1
  38. data/doc/GLI/Commands.html +1 -1
  39. data/doc/GLI.html +1 -1
  40. data/doc/Hash.html +97 -1
  41. data/doc/Status.html +37 -3
  42. data/doc/String.html +599 -23
  43. data/doc/Symbol.html +3 -3
  44. data/doc/Time.html +1 -1
  45. data/doc/_index.html +22 -1
  46. data/doc/class_list.html +1 -1
  47. data/doc/file.README.html +8 -2
  48. data/doc/index.html +8 -2
  49. data/doc/method_list.html +453 -173
  50. data/doc/top-level-namespace.html +1 -1
  51. data/doing.gemspec +3 -0
  52. data/doing.rdoc +40 -12
  53. data/example_plugin.rb +3 -3
  54. data/lib/completion/_doing.zsh +1 -1
  55. data/lib/completion/doing.bash +8 -8
  56. data/lib/completion/doing.fish +1 -1
  57. data/lib/doing/array.rb +36 -0
  58. data/lib/doing/colors.rb +70 -66
  59. data/lib/doing/completion.rb +6 -0
  60. data/lib/doing/configuration.rb +69 -28
  61. data/lib/doing/hash.rb +37 -0
  62. data/lib/doing/item.rb +77 -12
  63. data/lib/doing/items.rb +125 -0
  64. data/lib/doing/log_adapter.rb +55 -3
  65. data/lib/doing/note.rb +53 -1
  66. data/lib/doing/pager.rb +49 -38
  67. data/lib/doing/plugins/export/markdown_export.rb +4 -4
  68. data/lib/doing/plugins/export/template_export.rb +2 -2
  69. data/lib/doing/plugins/import/calendar_import.rb +4 -4
  70. data/lib/doing/plugins/import/doing_import.rb +5 -7
  71. data/lib/doing/plugins/import/timing_import.rb +3 -3
  72. data/lib/doing/prompt.rb +206 -0
  73. data/lib/doing/section.rb +30 -0
  74. data/lib/doing/string.rb +103 -27
  75. data/lib/doing/util.rb +14 -6
  76. data/lib/doing/version.rb +1 -1
  77. data/lib/doing/wwid.rb +306 -621
  78. data/lib/doing.rb +6 -2
  79. data/lib/examples/plugins/capture_thing_import.rb +162 -0
  80. metadata +73 -5
  81. data/lib/doing/wwidfile.rb +0 -117
data/lib/doing.rb CHANGED
@@ -7,6 +7,8 @@ require 'pp'
7
7
  require 'csv'
8
8
  require 'tempfile'
9
9
  require 'chronic'
10
+ require 'tty-link'
11
+ require 'tty-which'
10
12
  # require 'amatch'
11
13
  require 'haml'
12
14
  require 'json'
@@ -20,11 +22,13 @@ require 'doing/array'
20
22
  require 'doing/symbol'
21
23
  require 'doing/util'
22
24
  require 'doing/configuration'
23
- require 'doing/item'
25
+ require 'doing/section'
26
+ require 'doing/items'
24
27
  require 'doing/note'
25
- require 'doing/wwidfile'
28
+ require 'doing/item'
26
29
  require 'doing/wwid'
27
30
  require 'doing/log_adapter'
31
+ require 'doing/prompt'
28
32
  require 'doing/errors'
29
33
  require 'doing/hooks'
30
34
  require 'doing/plugin_manager'
@@ -0,0 +1,162 @@
1
+ # frozen_string_literal: true
2
+
3
+ # title: Capture Thing Import
4
+ # description: Import entries from a Capture Thing folder
5
+ # author: Brett Terpstra
6
+ # url: https://brettterpstra.com
7
+ module Doing
8
+ class CaptureThingImport
9
+ require 'time'
10
+ include Doing::Util
11
+ include Doing::Errors
12
+
13
+ def self.settings
14
+ {
15
+ trigger: '^cap(?:ture)?(:?thing)?'
16
+ }
17
+ end
18
+
19
+ ##
20
+ ## Imports a Capture Thing folder
21
+ ##
22
+ ## @param wwid [WWID] WWID object
23
+ ## @param path [String] Path to Capture Thing folder
24
+ ## @param options [Hash] Additional Options
25
+ ##
26
+ def self.import(wwid, path, options: {})
27
+ raise InvalidArgument, 'Path to Capture Thing folder required' if path.nil?
28
+
29
+ path = File.expand_path(path)
30
+
31
+ raise InvalidArgument, 'File not found' unless File.exist?(path)
32
+
33
+ raise InvalidArgument, 'Path is not a directory' unless File.directory?(path)
34
+
35
+ options[:no_overlap] ||= false
36
+ options[:autotag] ||= wwid.auto_tag
37
+
38
+ tags = options[:tag] ? options[:tag].split(/[ ,]+/).map { |t| t.sub(/^@?/, '') } : []
39
+ options[:tag] = nil
40
+ prefix = options[:prefix] || ''
41
+
42
+ @old_items = wwid.content.dup
43
+
44
+ new_items = read_capture_folder(path)
45
+
46
+ total = new_items.count
47
+
48
+ options[:count] = 0
49
+
50
+ new_items = wwid.filter_items(new_items, opt: options)
51
+
52
+ skipped = total - new_items.count
53
+ Doing.logger.debug('Skipped:' , %(#{skipped} items that didn't match filter criteria)) if skipped.positive?
54
+
55
+ imported = []
56
+
57
+ new_items.each do |item|
58
+ next if duplicate?(item)
59
+
60
+ title = "#{prefix} #{item.title}"
61
+ tags.each do |tag|
62
+ if title =~ /\b#{tag}\b/i
63
+ title.sub!(/\b#{tag}\b/i, "@#{tag}")
64
+ else
65
+ title += " @#{tag}"
66
+ end
67
+ end
68
+ title = wwid.autotag(title) if options[:autotag]
69
+ title.gsub!(/ +/, ' ')
70
+ title.strip!
71
+ section = options[:section] || item.section
72
+ section ||= wwid.config['current_section']
73
+
74
+ new_item = Item.new(item.date, title, section)
75
+ new_item.note = item.note
76
+
77
+ imported.push(new_item)
78
+ end
79
+
80
+ dups = new_items.count - imported.count
81
+ Doing.logger.info('Skipped:', %(#{dups} duplicate items)) if dups.positive?
82
+
83
+ imported = wwid.dedup(imported, no_overlap: !options[:overlap])
84
+ overlaps = new_items.count - imported.count - dups
85
+ Doing.logger.debug('Skipped:', "#{overlaps} items with overlapping times") if overlaps.positive?
86
+
87
+ imported.each do |item|
88
+ wwid.content.add_section(item.section) unless wwid.content.section?(item.section)
89
+ wwid.content.push(item)
90
+ end
91
+
92
+ Doing.logger.info('Imported:', "#{imported.count} items")
93
+ end
94
+
95
+ def self.duplicate?(item)
96
+ @old_items.each do |oi|
97
+ return true if item.equal?(oi)
98
+ end
99
+
100
+ false
101
+ end
102
+
103
+ def self.parse_entry(date, entry)
104
+ lines = entry.strip.split(/\n/)
105
+
106
+ return nil if lines.nil?
107
+
108
+ time_line = lines.shift
109
+
110
+ return nil unless time_line =~ /^# (\d+:\d{2} [AP]M)/
111
+
112
+ m = time_line.match(/^# (\d+:\d{2} [AP]M)/)
113
+
114
+ unless m
115
+ Doing.logger.debug("Error parsing time #{time_line}")
116
+ return nil
117
+ end
118
+
119
+ time = m[1]
120
+ entry_date = Time.parse("#{date} #{time}")
121
+
122
+ title = ''
123
+ note = Note.new
124
+ lines.each_with_index do |l, i|
125
+ if l =~ /^-{4,}/
126
+ note.add(lines.slice(i + 1, lines.count - i))
127
+ break
128
+ else
129
+ title += l
130
+ end
131
+ end
132
+
133
+ Item.new(entry_date, title, nil, note)
134
+ end
135
+
136
+ def self.read_capture_folder(path)
137
+ folder = File.expand_path(path)
138
+
139
+ return nil unless File.exist?(folder) && File.directory?(folder)
140
+
141
+ items = []
142
+
143
+ files = Dir.glob('**/*.md', base: folder)
144
+
145
+ files.each do |file|
146
+ date = File.basename(file, '.md').match(/^(\d{4}-\d{2}-\d{2})/)[1]
147
+ input = IO.read(File.join(folder, file))
148
+ input = input.force_encoding('utf-8') if input.respond_to? :force_encoding
149
+ entries = input.split(/^\* \* \* \* \*$/).map(&:strip).delete_if(&:empty?)
150
+
151
+ entries.each do |entry|
152
+ new_entry = parse_entry(date, entry)
153
+ items << new_entry if new_entry
154
+ end
155
+ end
156
+
157
+ items
158
+ end
159
+
160
+ Doing::Plugins.register 'capturething', :import, self
161
+ end
162
+ end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: doing
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.0.25
4
+ version: 2.1.0pre
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-11-24 00:00:00.000000000 Z
11
+ date: 2021-11-27 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: safe_yaml
@@ -166,6 +166,26 @@ dependencies:
166
166
  - - ">="
167
167
  - !ruby/object:Gem::Version
168
168
  version: 3.7.3
169
+ - !ruby/object:Gem::Dependency
170
+ name: tty-spinner
171
+ requirement: !ruby/object:Gem::Requirement
172
+ requirements:
173
+ - - "~>"
174
+ - !ruby/object:Gem::Version
175
+ version: '0.9'
176
+ - - ">="
177
+ - !ruby/object:Gem::Version
178
+ version: 0.9.3
179
+ type: :development
180
+ prerelease: false
181
+ version_requirements: !ruby/object:Gem::Requirement
182
+ requirements:
183
+ - - "~>"
184
+ - !ruby/object:Gem::Version
185
+ version: '0.9'
186
+ - - ">="
187
+ - !ruby/object:Gem::Version
188
+ version: 0.9.3
169
189
  - !ruby/object:Gem::Dependency
170
190
  name: tty-progressbar
171
191
  requirement: !ruby/object:Gem::Requirement
@@ -266,6 +286,46 @@ dependencies:
266
286
  - - ">="
267
287
  - !ruby/object:Gem::Version
268
288
  version: 1.2.1
289
+ - !ruby/object:Gem::Dependency
290
+ name: tty-link
291
+ requirement: !ruby/object:Gem::Requirement
292
+ requirements:
293
+ - - "~>"
294
+ - !ruby/object:Gem::Version
295
+ version: '0.1'
296
+ - - ">="
297
+ - !ruby/object:Gem::Version
298
+ version: 0.1.1
299
+ type: :runtime
300
+ prerelease: false
301
+ version_requirements: !ruby/object:Gem::Requirement
302
+ requirements:
303
+ - - "~>"
304
+ - !ruby/object:Gem::Version
305
+ version: '0.1'
306
+ - - ">="
307
+ - !ruby/object:Gem::Version
308
+ version: 0.1.1
309
+ - !ruby/object:Gem::Dependency
310
+ name: tty-which
311
+ requirement: !ruby/object:Gem::Requirement
312
+ requirements:
313
+ - - "~>"
314
+ - !ruby/object:Gem::Version
315
+ version: '0.5'
316
+ - - ">="
317
+ - !ruby/object:Gem::Version
318
+ version: 0.5.0
319
+ type: :runtime
320
+ prerelease: false
321
+ version_requirements: !ruby/object:Gem::Requirement
322
+ requirements:
323
+ - - "~>"
324
+ - !ruby/object:Gem::Version
325
+ version: '0.5'
326
+ - - ">="
327
+ - !ruby/object:Gem::Version
328
+ version: 0.5.0
269
329
  description: A tool for managing a TaskPaper-like file of recent activites. Perfect
270
330
  for the late-night hacker on too much caffeine to remember what they accomplished
271
331
  at 2 in the morning.
@@ -295,7 +355,9 @@ files:
295
355
  - doc/Array.html
296
356
  - doc/Doing.html
297
357
  - doc/Doing/Color.html
358
+ - doc/Doing/Completion.html
298
359
  - doc/Doing/Configuration.html
360
+ - doc/Doing/Content.html
299
361
  - doc/Doing/Errors.html
300
362
  - doc/Doing/Errors/DoingNoTraceError.html
301
363
  - doc/Doing/Errors/DoingRuntimeError.html
@@ -312,8 +374,11 @@ files:
312
374
  - doc/Doing/Note.html
313
375
  - doc/Doing/Pager.html
314
376
  - doc/Doing/Plugins.html
377
+ - doc/Doing/Prompt.html
378
+ - doc/Doing/Section.html
315
379
  - doc/Doing/Util.html
316
380
  - doc/Doing/WWID.html
381
+ - doc/Doing/WWIDFile.html
317
382
  - doc/GLI.html
318
383
  - doc/GLI/Commands.html
319
384
  - doc/GLI/Commands/MarkdownDocumentListener.html
@@ -360,6 +425,7 @@ files:
360
425
  - lib/doing/hash.rb
361
426
  - lib/doing/hooks.rb
362
427
  - lib/doing/item.rb
428
+ - lib/doing/items.rb
363
429
  - lib/doing/log_adapter.rb
364
430
  - lib/doing/markdown_document_listener.rb
365
431
  - lib/doing/note.rb
@@ -375,15 +441,17 @@ files:
375
441
  - lib/doing/plugins/import/calendar_import.rb
376
442
  - lib/doing/plugins/import/doing_import.rb
377
443
  - lib/doing/plugins/import/timing_import.rb
444
+ - lib/doing/prompt.rb
445
+ - lib/doing/section.rb
378
446
  - lib/doing/string.rb
379
447
  - lib/doing/symbol.rb
380
448
  - lib/doing/time.rb
381
449
  - lib/doing/util.rb
382
450
  - lib/doing/version.rb
383
451
  - lib/doing/wwid.rb
384
- - lib/doing/wwidfile.rb
385
452
  - lib/examples/commands/autotag.rb
386
453
  - lib/examples/commands/wiki.rb
454
+ - lib/examples/plugins/capture_thing_import.rb
387
455
  - lib/examples/plugins/hooks.rb
388
456
  - lib/examples/plugins/say_export.rb
389
457
  - lib/examples/plugins/wiki_export/templates/wiki.css
@@ -512,9 +580,9 @@ required_ruby_version: !ruby/object:Gem::Requirement
512
580
  version: '0'
513
581
  required_rubygems_version: !ruby/object:Gem::Requirement
514
582
  requirements:
515
- - - ">="
583
+ - - ">"
516
584
  - !ruby/object:Gem::Version
517
- version: '0'
585
+ version: 1.3.1
518
586
  requirements: []
519
587
  rubygems_version: 3.2.16
520
588
  signing_key:
@@ -1,117 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Doing
4
- class Items < Array
5
- def from(path)
6
- return [] unless path
7
- path = File.expand_path(path)
8
-
9
- if File.exist?(path) && File.file?(path) && File.stat(path).size.positive?
10
- input = IO.read(File.expand_path(input))
11
- input = input.force_encoding('utf-8') if input.respond_to? :force_encoding
12
- end
13
-
14
- section = 'Uncategorized'
15
- lines = input.split(/[\n\r]/)
16
- current = 0
17
-
18
- lines.each do |line|
19
- next if line =~ /^\s*$/
20
-
21
- if line =~ /^(\S[\S ]+):\s*(@\S+\s*)*$/
22
- section = Regexp.last_match(1)
23
- @sections << { original: line, title: section }
24
- current = 0
25
- elsif line =~ /^\s*- (\d{4}-\d\d-\d\d \d\d:\d\d) \| (.*)/
26
- date = Regexp.last_match(1).strip
27
- title = Regexp.last_match(2).strip
28
- item = Item.new(date, title, section)
29
- @items.push(item)
30
- current += 1
31
- elsif current.zero?
32
- # if content[section][:items].length - 1 == current
33
- @other_content_top.push(line)
34
- elsif line =~ /^\S/
35
- @other_content_bottom.push(line)
36
- else
37
- prev_item = @items[current - 1]
38
- prev_item.note = Note.new unless prev_item.note
39
-
40
- prev_item.note.add(line)
41
- # end
42
- end
43
- end
44
- Hooks.trigger :post_read, self
45
- end
46
-
47
- def section_titles
48
- @sections.map { |s| s[:title] }
49
- end
50
-
51
- ##
52
- ## Adds a section.
53
- ##
54
- ## @param title [String] The new section title
55
- ##
56
- def add_section(title)
57
- if section_titles.include?(title.cap_first)
58
- Doing.logger.debug('Skipped': 'Section already exists')
59
- return
60
- end
61
-
62
- @sections << { original: "#{title}:", title: title }
63
- Doing.logger.info('Added section:', %("#{title.cap_first}"))
64
- end
65
-
66
- ##
67
- ## Attempt to match a string with an existing section
68
- ##
69
- ## @param frag [String] The user-provided string
70
- ## @param guessed [Boolean] already guessed and failed
71
- ##
72
- def guess_section(frag, guessed: false, suggest: false)
73
- return 'All' if frag =~ /^all$/i
74
- frag ||= wwid.config['current_section']
75
-
76
- @sections.each { |sect| return sect[:title].cap_first if frag.downcase == sect[:title].downcase }
77
-
78
- section = false
79
- re = frag.split('').join('.*?')
80
- sections.each do |sect|
81
- next unless sect =~ /#{re}/i
82
-
83
- Doing.logger.debug('Section match:', %(Assuming "#{sect}" from "#{frag}"))
84
- section = sect
85
- break
86
- end
87
-
88
- return section if suggest
89
-
90
- unless section || guessed
91
- alt = WWID.guess_view(frag, guessed: true, suggest: true)
92
- if alt
93
- meant_view = WWID.yn("Did you mean `doing view #{alt}`?", default_response: 'n')
94
- raise Errors::InvalidSection, "Run again with `doing view #{alt}`" if meant_view
95
- end
96
-
97
- res = WWID.yn("Section #{frag} not found, create it", default_response: 'n')
98
-
99
- if res
100
- add_section(frag.cap_first)
101
- WWID.write(@doing_file)
102
- return frag.cap_first
103
- end
104
-
105
- raise Errors::InvalidSection, "Unknown section: #{frag}"
106
- end
107
- section ? section.cap_first : guessed
108
- end
109
-
110
- def section_items(section)
111
- section = guess_section(section)
112
- return @items if section =~ /all/i
113
-
114
- @items.filter { |i| i.section == section }
115
- end
116
- end
117
- end