doing 2.1.45 → 2.1.48

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (139) hide show
  1. checksums.yaml +4 -4
  2. data/.irbrc +3 -0
  3. data/CHANGELOG.md +32 -0
  4. data/Gemfile.lock +1 -1
  5. data/README.md +1 -1
  6. data/bin/commands/completion.rb +1 -1
  7. data/bin/commands/config.rb +1 -1
  8. data/bin/commands/last.rb +1 -1
  9. data/bin/commands/now.rb +2 -1
  10. data/bin/commands/recent.rb +1 -1
  11. data/bin/commands/reset.rb +6 -4
  12. data/docs/doc/Array.html +1 -1
  13. data/docs/doc/BooleanTermParser/Clause.html +1 -1
  14. data/docs/doc/BooleanTermParser/Operator.html +1 -1
  15. data/docs/doc/BooleanTermParser/Query.html +1 -1
  16. data/docs/doc/BooleanTermParser/QueryParser.html +1 -1
  17. data/docs/doc/BooleanTermParser/QueryTransformer.html +1 -1
  18. data/docs/doc/BooleanTermParser.html +1 -1
  19. data/docs/doc/Doing/ArrayCleanup.html +1 -1
  20. data/docs/doc/Doing/ArrayNestedHash.html +1 -1
  21. data/docs/doc/Doing/ArrayTags.html +1 -1
  22. data/docs/doc/Doing/CSVExport.html +1 -1
  23. data/docs/doc/Doing/CalendarImport.html +1 -1
  24. data/docs/doc/Doing/Change.html +1 -1
  25. data/docs/doc/Doing/Changes.html +1 -1
  26. data/docs/doc/Doing/ChronifyArray.html +1 -1
  27. data/docs/doc/Doing/ChronifyNumeric.html +1 -1
  28. data/docs/doc/Doing/ChronifyString.html +1 -1
  29. data/docs/doc/Doing/Color.html +1 -1
  30. data/docs/doc/Doing/Completion/BashCompletions.html +1 -1
  31. data/docs/doc/Doing/Completion/FishCompletions.html +1 -1
  32. data/docs/doc/Doing/Completion/StringUtils.html +1 -1
  33. data/docs/doc/Doing/Completion/ZshCompletions.html +1 -1
  34. data/docs/doc/Doing/Completion.html +5 -5
  35. data/docs/doc/Doing/Configuration.html +1 -1
  36. data/docs/doc/Doing/DayOneRenderer.html +1 -1
  37. data/docs/doc/Doing/DayoneExport.html +1 -1
  38. data/docs/doc/Doing/DoingExport.html +206 -0
  39. data/docs/doc/Doing/DoingImport.html +1 -1
  40. data/docs/doc/Doing/Entry.html +1 -1
  41. data/docs/doc/Doing/Errors/DoingNoTraceError.html +1 -1
  42. data/docs/doc/Doing/Errors/DoingRuntimeError.html +1 -1
  43. data/docs/doc/Doing/Errors/DoingStandardError.html +1 -1
  44. data/docs/doc/Doing/Errors/EmptyInput.html +1 -1
  45. data/docs/doc/Doing/Errors/HistoryLimitError.html +1 -1
  46. data/docs/doc/Doing/Errors/InvalidPlugin.html +1 -1
  47. data/docs/doc/Doing/Errors/MissingBackupFile.html +1 -1
  48. data/docs/doc/Doing/Errors/NoResults.html +1 -1
  49. data/docs/doc/Doing/Errors/PluginException.html +1 -1
  50. data/docs/doc/Doing/Errors/UserCancelled.html +1 -1
  51. data/docs/doc/Doing/Errors/WrongCommand.html +1 -1
  52. data/docs/doc/Doing/Errors.html +1 -1
  53. data/docs/doc/Doing/HTMLExport.html +1 -1
  54. data/docs/doc/Doing/Hooks.html +1 -1
  55. data/docs/doc/Doing/Item.html +75 -36
  56. data/docs/doc/Doing/ItemDates.html +1 -1
  57. data/docs/doc/Doing/ItemQuery.html +1 -1
  58. data/docs/doc/Doing/ItemState.html +1 -1
  59. data/docs/doc/Doing/ItemTags.html +1 -1
  60. data/docs/doc/Doing/Items.html +129 -1
  61. data/docs/doc/Doing/JSONExport.html +1 -1
  62. data/docs/doc/Doing/JSONImport.html +295 -0
  63. data/docs/doc/Doing/Logger.html +1 -1
  64. data/docs/doc/Doing/MarkdownExport.html +1 -1
  65. data/docs/doc/Doing/Note.html +1 -1
  66. data/docs/doc/Doing/Pager.html +1 -1
  67. data/docs/doc/Doing/Plugins.html +1 -1
  68. data/docs/doc/Doing/Prompt.html +1 -1
  69. data/docs/doc/Doing/PromptChoose.html +1 -1
  70. data/docs/doc/Doing/PromptFZF.html +1 -1
  71. data/docs/doc/Doing/PromptInput.html +1 -1
  72. data/docs/doc/Doing/PromptSTD.html +1 -1
  73. data/docs/doc/Doing/PromptYN.html +1 -1
  74. data/docs/doc/Doing/Section.html +1 -1
  75. data/docs/doc/Doing/StringHighlight.html +1 -1
  76. data/docs/doc/Doing/StringNormalize.html +1 -1
  77. data/docs/doc/Doing/StringQuery.html +1 -1
  78. data/docs/doc/Doing/StringTags.html +1 -1
  79. data/docs/doc/Doing/StringTransform.html +1 -1
  80. data/docs/doc/Doing/StringTruncate.html +1 -1
  81. data/docs/doc/Doing/StringURL.html +1 -1
  82. data/docs/doc/Doing/SymbolNormalize.html +1 -1
  83. data/docs/doc/Doing/TaskPaperExport.html +1 -1
  84. data/docs/doc/Doing/TemplateExport.html +1 -1
  85. data/docs/doc/Doing/TemplateString.html +2 -2
  86. data/docs/doc/Doing/TimingImport.html +1 -1
  87. data/docs/doc/Doing/Types.html +1 -1
  88. data/docs/doc/Doing/Util/Backup.html +1 -1
  89. data/docs/doc/Doing/Util.html +2 -2
  90. data/docs/doc/Doing/Version.html +1 -1
  91. data/docs/doc/Doing/WWID.html +1 -1
  92. data/docs/doc/Doing.html +4 -4
  93. data/docs/doc/FalseClass.html +1 -1
  94. data/docs/doc/GLI/Commands/Help.html +1 -1
  95. data/docs/doc/GLI/Commands/MarkdownDocumentListener.html +1 -1
  96. data/docs/doc/GLI/Commands.html +1 -1
  97. data/docs/doc/GLI.html +1 -1
  98. data/docs/doc/Hash.html +1 -1
  99. data/docs/doc/Numeric.html +1 -1
  100. data/docs/doc/Object.html +1 -1
  101. data/docs/doc/PhraseParser/Operator.html +1 -1
  102. data/docs/doc/PhraseParser/PhraseClause.html +1 -1
  103. data/docs/doc/PhraseParser/Query.html +1 -1
  104. data/docs/doc/PhraseParser/QueryParser.html +1 -1
  105. data/docs/doc/PhraseParser/QueryTransformer.html +1 -1
  106. data/docs/doc/PhraseParser/TermClause.html +1 -1
  107. data/docs/doc/PhraseParser.html +1 -1
  108. data/docs/doc/Status.html +1 -1
  109. data/docs/doc/String.html +64 -2
  110. data/docs/doc/Symbol.html +1 -1
  111. data/docs/doc/Time.html +1 -1
  112. data/docs/doc/TrueClass.html +1 -1
  113. data/docs/doc/_index.html +22 -1
  114. data/docs/doc/class_list.html +1 -1
  115. data/docs/doc/file.README.html +2 -2
  116. data/docs/doc/index.html +2 -2
  117. data/docs/doc/method_list.html +576 -456
  118. data/docs/doc/top-level-namespace.html +1 -1
  119. data/doing.rdoc +20 -20
  120. data/lib/completion/_doing.zsh +31 -31
  121. data/lib/doing/add_options.rb +1 -1
  122. data/lib/doing/completion/fig_completion.rb +121 -0
  123. data/lib/doing/completion.rb +14 -4
  124. data/lib/doing/item/item.rb +7 -8
  125. data/lib/doing/items/items.rb +24 -0
  126. data/lib/doing/plugin_manager.rb +1 -1
  127. data/lib/doing/plugins/export/doing_export.rb +37 -0
  128. data/lib/doing/plugins/export/json_export.rb +16 -2
  129. data/lib/doing/plugins/export/template_export.rb +4 -2
  130. data/lib/doing/plugins/import/doing_import.rb +17 -6
  131. data/lib/doing/plugins/import/json_import.rb +94 -0
  132. data/lib/doing/string/string.rb +9 -0
  133. data/lib/doing/version.rb +1 -1
  134. data/lib/doing/wwid/filetools.rb +3 -2
  135. data/lib/doing/wwid/filter.rb +2 -2
  136. data/lib/doing/wwid/interactive.rb +2 -1
  137. data/lib/doing/wwid/modify.rb +6 -9
  138. data/lib/doing.rb +1 -0
  139. metadata +7 -2
@@ -6,6 +6,7 @@ require_relative 'completion/completion_string'
6
6
  require_relative 'completion/fish_completion'
7
7
  require_relative 'completion/zsh_completion'
8
8
  require_relative 'completion/bash_completion'
9
+ require_relative 'completion/fig_completion'
9
10
 
10
11
  module Doing
11
12
  # Completion script generator
@@ -15,8 +16,8 @@ module Doing
15
16
  COMMAND_RX = /^(?<cmd>[^, \t]+)(?<alias>(?:, [^, \t]+)*)?\s+- (?<desc>.*?)$/.freeze
16
17
 
17
18
  class << self
18
- def get_help_sections(command = '')
19
- res = `doing help #{command}`.strip
19
+ def get_help_sections(command = "")
20
+ res = `doing help #{command}|command cat`.strip
20
21
  scanned = res.scan(SECTIONS_RX)
21
22
  sections = {}
22
23
  scanned.each do |sect|
@@ -85,7 +86,7 @@ module Doing
85
86
  type = normalize_type(type)
86
87
  raise InvalidArgument, 'Unrecognized shell specified' if type == :invalid
87
88
 
88
- return %i[zsh bash fish].each { |t| link_default(t) } if type == :all
89
+ return %i[zsh bash fish fig].each { |t| link_default(t) } if type == :all
89
90
 
90
91
  install_builtin(type)
91
92
 
@@ -98,6 +99,7 @@ module Doing
98
99
 
99
100
  if File.exist?(File.join(default_dir, default_filenames[type]))
100
101
  return unless Doing::Prompt.yn("Update #{type} completion script", default_response: 'n')
102
+
101
103
  end
102
104
 
103
105
  FileUtils.cp(src, default_dir)
@@ -106,6 +108,8 @@ module Doing
106
108
 
107
109
  def normalize_type(type)
108
110
  case type.to_s
111
+ when /^fig/i
112
+ :fig
109
113
  when /^f/i
110
114
  :fish
111
115
  when /^b/i
@@ -123,6 +127,8 @@ module Doing
123
127
 
124
128
  def generate_type(type)
125
129
  generator = case type.to_s
130
+ when /^fig/i
131
+ FigCompletions.new
126
132
  when /^f/i
127
133
  FishCompletions.new
128
134
  when /^b/i
@@ -149,7 +155,7 @@ module Doing
149
155
  end
150
156
 
151
157
  def default_filenames
152
- { zsh: '_doing.zsh', bash: 'doing.bash', fish: 'doing.fish' }
158
+ { zsh: '_doing.zsh', bash: 'doing.bash', fish: 'doing.fish', fig: 'doing.ts' }
153
159
  end
154
160
 
155
161
  def default_file(type)
@@ -188,6 +194,8 @@ module Doing
188
194
  Doing.logger.warn('File written:', 'zsh completions written to lib/completion/_doing.zsh')
189
195
  generate_completion(type: 'bash', file: 'lib/completion/doing.bash', link: false)
190
196
  Doing.logger.warn('File written:', 'bash completions written to lib/completion/doing.bash')
197
+ generate_completion(type: 'fig', file: 'lib/completion/doing.ts', link: false)
198
+ Doing.logger.warn('File written:', 'Fig completions written to lib/completion/doing.ts')
191
199
  end
192
200
 
193
201
  def link_completion_type(type, file)
@@ -197,6 +205,7 @@ module Doing
197
205
  unless dir =~ %r{(\.bash_it/completion|bash_completion/completions)}
198
206
  link_completion(file, ['~/.bash_it/completion/enabled', '/usr/share/bash_completion/completions'], 'doing.bash')
199
207
  end
208
+ when /^fig/i
200
209
  when /^f/i
201
210
  link_completion(file, ['~/.config/fish/completions'], 'doing.fish') unless dir =~ %r{.config/fish/completions}
202
211
  when /^z/i
@@ -214,6 +223,7 @@ module Doing
214
223
 
215
224
  targets.each do |target|
216
225
  next unless File.directory?(File.expand_path(target))
226
+
217
227
  found = true
218
228
 
219
229
  target_file = File.join(File.expand_path(target), filename)
@@ -15,7 +15,7 @@ module Doing
15
15
  include ItemState
16
16
  include ItemTags
17
17
 
18
- attr_accessor :date, :title, :section, :note
18
+ attr_accessor :date, :title, :section, :note, :id
19
19
 
20
20
  # attr_reader :id
21
21
 
@@ -31,19 +31,18 @@ module Doing
31
31
  ## the item belongs
32
32
  ## @param note [Array or String] The note
33
33
  ## (optional)
34
+ ## @param id MD5 identifier
34
35
  ##
35
- def initialize(date, title, section, note = nil)
36
+ def initialize(date, title, section, note = nil, id = nil)
36
37
  @date = date.is_a?(Time) ? date : Time.parse(date)
37
38
  @title = title
38
39
  @section = section
39
40
  @note = Note.new(note)
41
+ @id = id&.valid_id? ? id.strip : gen_id
40
42
  end
41
43
 
42
- # Generate a hash that represents the entry
43
- #
44
- # @return [String] entry hash
45
- def id
46
- @id ||= (@date.to_s + @title + @section).hash
44
+ def gen_id
45
+ Digest::MD5.hexdigest(to_s)
47
46
  end
48
47
 
49
48
  ##
@@ -91,7 +90,7 @@ module Doing
91
90
 
92
91
  # outputs item in Doing file format, including leading tab
93
92
  def to_s
94
- "\t- #{@date.strftime('%Y-%m-%d %H:%M')} | #{@title}#{@note.good? ? "\n#{@note}" : ''}"
93
+ "\t- #{@date.strftime('%Y-%m-%d %H:%M')} | #{@title} <#{@id}>#{@note.good? ? "\n#{@note}" : ''}"
95
94
  end
96
95
 
97
96
  ##
@@ -35,6 +35,30 @@ module Doing
35
35
  includes
36
36
  end
37
37
 
38
+ # Find an item by ID
39
+ #
40
+ # @param id The identifier to match
41
+ #
42
+ def find_id(id)
43
+ select { |item| item.id == id }[0]
44
+ end
45
+
46
+ ##
47
+ ## Return the index for an entry matching ID
48
+ ##
49
+ ## @param id The identifier to match
50
+ ##
51
+ def index_for_id(id)
52
+ i = nil
53
+ each_with_index do |item, idx|
54
+ if item.id == id
55
+ i = idx
56
+ break
57
+ end
58
+ end
59
+ i
60
+ end
61
+
38
62
  # Output sections and items in Doing file format
39
63
  def to_s
40
64
  out = []
@@ -201,7 +201,7 @@ module Doing
201
201
  plugins[type].each do |_, options|
202
202
  pattern << options[:trigger].normalize_trigger
203
203
  end
204
- Regexp.new("^(?:#{pattern.join('|')})$", true)
204
+ Regexp.new("^(?:#{pattern.sort.uniq.join('|')})$", true)
205
205
  end
206
206
 
207
207
  ##
@@ -0,0 +1,37 @@
1
+ # frozen_string_literal: true
2
+
3
+ # title: Doing File Export
4
+ # description: Export Doing format data
5
+ # author: Brett Terpstra
6
+ # url: https://brettterpstra.com
7
+ module Doing
8
+ class DoingExport
9
+ def self.settings
10
+ {
11
+ trigger: 'doing'
12
+ }
13
+ end
14
+
15
+ def self.render(wwid, items, variables: {})
16
+ return if items.nil?
17
+
18
+ content = Doing::Items.new
19
+ items.each do |item|
20
+ content.add_section(item.section, log: false)
21
+ content.push(item)
22
+ end
23
+
24
+ out = []
25
+ content.sections.each do |section|
26
+ out.push(section.original)
27
+ is = content.in_section(section.title).sort_by { |i| [i.date, i.title] }
28
+ is.reverse! if Doing.setting('doing_file_sort').normalize_order == :desc
29
+ is.each { |item| out.push(item.to_s) }
30
+ end
31
+
32
+ Doing::Pager.page out.join("\n")
33
+ end
34
+
35
+ Doing::Plugins.register 'doing', :export, self
36
+ end
37
+ end
@@ -15,7 +15,18 @@ module Doing
15
15
  end
16
16
 
17
17
  def self.render(wwid, items, variables: {})
18
- return if items.nil?
18
+ if items.nil? || items.empty?
19
+ return case variables[:options][:output]
20
+ when 'json'
21
+ {
22
+ 'section' => '',
23
+ 'items' => [],
24
+ 'timers' => ""
25
+ }.to_json
26
+ when 'timeline'
27
+ "<html></html>"
28
+ end
29
+ end
19
30
 
20
31
  opt = variables[:options]
21
32
  opt[:output] = case opt[:output]
@@ -25,6 +36,7 @@ module Doing
25
36
  'json'
26
37
  end
27
38
  items_out = []
39
+
28
40
  last_date = items[-1].date + (60 * 60 * 24)
29
41
  max = last_date.strftime('%F')
30
42
  min = items[0].date.strftime('%F')
@@ -51,10 +63,12 @@ module Doing
51
63
  date: i.date,
52
64
  end_date: end_date,
53
65
  title: title.strip, #+ " #{note}"
66
+ section: i.section,
54
67
  note: note.to_s(prefix: ''),
55
68
  time: interval.time_string(format: :clock),
56
69
  duration: duration.time_string(format: :clock),
57
- tags: tags
70
+ tags: tags,
71
+ id: i.id
58
72
  }
59
73
 
60
74
  attributes.each { |attr, val| i[attr.to_sym] = val }
@@ -12,7 +12,7 @@ module Doing
12
12
 
13
13
  def self.settings
14
14
  {
15
- trigger: 'template|doing'
15
+ trigger: 'template'
16
16
  }
17
17
  end
18
18
 
@@ -50,6 +50,8 @@ module Doing
50
50
  note = []
51
51
  end
52
52
 
53
+ placeholders['id'] = item.id
54
+
53
55
  placeholders['tags'] = item.tags
54
56
 
55
57
  placeholders['date'] = item.date.strftime(opt[:format])
@@ -141,6 +143,6 @@ module Doing
141
143
  out
142
144
  end
143
145
 
144
- Doing::Plugins.register ['template', 'doing'], :export, self
146
+ Doing::Plugins.register 'template', :export, self
145
147
  end
146
148
  end
@@ -29,6 +29,7 @@ module Doing
29
29
  exit_now! 'File not found' unless File.exist?(File.expand_path(path))
30
30
 
31
31
  options[:no_overlap] ||= false
32
+
32
33
  options[:autotag] ||= Doing.auto_tag
33
34
 
34
35
  tags = options[:tag] ? options[:tag].split(/[ ,]+/).map { |t| t.sub(/^@?/, '') } : []
@@ -47,6 +48,7 @@ module Doing
47
48
  Doing.logger.debug('Skipped:', %(#{skipped} items that didn't match filter criteria)) if skipped.positive?
48
49
 
49
50
  imported = []
51
+ updated = 0
50
52
 
51
53
  new_items.each do |item|
52
54
  next if duplicate?(item)
@@ -65,8 +67,7 @@ module Doing
65
67
  section = options[:section] || item.section
66
68
  section ||= Doing.setting('current_section')
67
69
 
68
- new_item = Item.new(item.date, title, section)
69
- new_item.note = item.note
70
+ new_item = Item.new(item.date, title, section, item.note, item.id)
70
71
 
71
72
  is_match = true
72
73
 
@@ -79,13 +80,21 @@ module Doing
79
80
  is_match = options[:not] ? !is_match : is_match
80
81
  end
81
82
 
82
- imported.push(new_item) if is_match
83
+ if wwid.content.find_id(new_item.id)
84
+ old_index = wwid.content.index_for_id(new_item.id)
85
+ old_item = wwid.content[old_index].clone
86
+ wwid.content[old_index] = new_item
87
+ Hooks.trigger :post_entry_updated, self, new_item, old_item
88
+ updated += 1
89
+ else
90
+ imported.push(new_item) if is_match
91
+ end
83
92
  end
84
93
 
85
94
  dups = new_items.count - imported.count
86
95
  Doing.logger.info('Skipped:', %(#{dups} duplicate items)) if dups.positive?
87
96
 
88
- imported = wwid.dedup(imported, no_overlap: !options[:overlap])
97
+ imported = wwid.dedup(imported, no_overlap: options[:no_overlap])
89
98
  overlaps = new_items.count - imported.count - dups
90
99
  Doing.logger.debug('Skipped:', "#{overlaps} items with overlapping times") if overlaps.positive?
91
100
 
@@ -96,6 +105,7 @@ module Doing
96
105
  Hooks.trigger :post_entry_added, self, item
97
106
  end
98
107
 
108
+ Doing.logger.info('Updated:', %(#{updated} items))
99
109
  Doing.logger.info('Imported:', "#{imported.count} items")
100
110
  end
101
111
 
@@ -128,10 +138,11 @@ module Doing
128
138
  when /^(\S[\S ]+):(\s+@[\w\-_.]+(?= |$))*\s*$/
129
139
  section = Regexp.last_match(1)
130
140
  current = 0
131
- when /^\s*- (\d{4}-\d\d-\d\d \d\d:\d\d) \| (.*)/
141
+ when /^\s*- (\d{4}-\d\d-\d\d \d\d:\d\d) \| (.*?)(?: <([a-z0-9]{32})>)? *$/
132
142
  date = Regexp.last_match(1).strip
133
143
  title = Regexp.last_match(2).strip
134
- item = Item.new(date, title, section)
144
+ id = Regexp.last_match(3)
145
+ item = Item.new(date, title, section, nil, id)
135
146
  items.push(item)
136
147
  current += 1
137
148
  when /^\S/
@@ -0,0 +1,94 @@
1
+ # frozen_string_literal: true
2
+
3
+ # title: JSON Import
4
+ # description: Import entries from a Doing JSON export
5
+ # author: Brett Terpstra
6
+ # url: https://brettterpstra.com
7
+ module Doing
8
+ # JSON importer
9
+ class JSONImport
10
+ include Doing::Util
11
+
12
+ def self.settings
13
+ {
14
+ trigger: 'json'
15
+ }
16
+ end
17
+
18
+ ##
19
+ ## Imports a Timing report
20
+ ##
21
+ ## @param wwid [WWID] The wwid object
22
+ ## @param path [String] Path to JSON report
23
+ ## file
24
+ ## @param options [Hash] Additional Options
25
+ ##
26
+ def self.import(wwid, path, options: {})
27
+ exit_now! 'Path to JSON export required' if path.nil?
28
+ options[:no_overlap] ||= false
29
+ options[:autotag] ||= Doing.auto_tag
30
+
31
+ exit_now! 'File not found' unless File.exist?(File.expand_path(path))
32
+
33
+ updated = 0
34
+ added = 0
35
+ skipped = 0
36
+
37
+ data = JSON.parse(IO.read(File.expand_path(path)))
38
+ new_items = []
39
+ new_section = options[:section] || Doing.setting('current_section')
40
+
41
+ data['items'].each do |entry|
42
+ title = entry['title']
43
+ date = Time.parse(entry['date'])
44
+ date ||= entry['date'].chronify
45
+ note = Doing::Note.new(entry['note'])
46
+ section = if entry['section'].empty?
47
+ new_section
48
+ else
49
+ entry['section']
50
+ end
51
+ id = entry.key?('id') ? entry['id'] : nil
52
+
53
+ new_item = Doing::Item.new(date, title, section, note, id)
54
+
55
+ is_match = true
56
+
57
+ if options[:search]
58
+ is_match = new_item.search(options[:search], case_type: options[:case], negate: options[:not])
59
+ end
60
+
61
+ if is_match && options[:date_filter]
62
+ is_match = start_time > options[:date_filter][0] && start_time < options[:date_filter][1]
63
+ is_match = options[:not] ? !is_match : is_match
64
+ end
65
+
66
+ unless is_match
67
+ skipped += 1
68
+ next
69
+
70
+ end
71
+
72
+ if wwid.content.find_id(new_item.id)
73
+ old_index = wwid.content.index_for_id(entry['id'])
74
+ old_item = wwid.content[old_index].clone
75
+ wwid.content[old_index] = new_item
76
+ Hooks.trigger :post_entry_updated, self, new_item, old_item
77
+ updated += 1
78
+ else
79
+ Hooks.trigger :pre_entry_add, self, item
80
+ wwid.content << new_entry
81
+ Hooks.trigger :post_entry_added, self, item
82
+ added += 1
83
+ end
84
+ end
85
+ total = new_items.count
86
+ skipped = data.count - total
87
+ Doing.logger.debug('Skipped:', %(#{skipped} items)) if skipped.positive?
88
+ Doing.logger.info('Updated:', %(#{updated} items))
89
+ Doing.logger.info('Imported:', %(#{added} new items to #{new_section}))
90
+ end
91
+
92
+ Doing::Plugins.register 'json', :import, self
93
+ end
94
+ end
@@ -16,6 +16,15 @@ class ::String
16
16
  include Doing::StringTruncate
17
17
  include Doing::StringURL
18
18
 
19
+ ##
20
+ ## Test if string is a valid 32-character MD5 id
21
+ ##
22
+ ## @return [Boolean] string is valid identifier
23
+ ##
24
+ def valid_id?
25
+ strip =~ /^[a-z0-9]{32}$/ ? true : false
26
+ end
27
+
19
28
  ##
20
29
  ## Force UTF-8 encoding if available
21
30
  ##
data/lib/doing/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module Doing
2
- VERSION = '2.1.45'
2
+ VERSION = '2.1.48'
3
3
  end
@@ -40,7 +40,7 @@ module Doing
40
40
  if line =~ /^(\S[\S ]+):\s*(@[\w\-_.]+\s*)*$/
41
41
  section = Regexp.last_match(1)
42
42
  @content.add_section(Section.new(section, original: line), log: false)
43
- elsif line =~ /^\s*- (\d{4}-\d\d-\d\d \d\d:\d\d) \| (.*)/
43
+ elsif line =~ /^\s*- (\d{4}-\d\d-\d\d \d\d:\d\d) \| (.*?)(?: +<([a-z0-9]{32})>)? *$/
44
44
  if section.nil?
45
45
  section = 'Uncategorized'
46
46
  @content.add_section(Section.new(section, original: 'Uncategorized:'), log: false)
@@ -48,7 +48,8 @@ module Doing
48
48
 
49
49
  date = Regexp.last_match(1).strip
50
50
  title = Regexp.last_match(2).strip
51
- item = Item.new(date, title, section)
51
+ id = Regexp.last_match(3) || nil
52
+ item = Item.new(date, title, section, [], id)
52
53
  @content.push(item)
53
54
  elsif @content.count.zero?
54
55
  # if content[section].items.length - 1 == current
@@ -146,8 +146,8 @@ module Doing
146
146
  end
147
147
 
148
148
  if keep && opt[:time_filter][0] || opt[:time_filter][1]
149
- opt[:time_filter][0] = '00:00' if opt[:time_filter][0] =~ /12 *am/i
150
- opt[:time_filter][1] = '00:00' if opt[:time_filter][1] =~ /12 *am/i
149
+ opt[:time_filter].map! { |v| v =~ /(12 *am|midnight)/i ? '00:00' : v }
150
+
151
151
  start_string = if opt[:time_filter][0].nil?
152
152
  "#{item.date.strftime('%Y-%m-%d')} 00:00"
153
153
  else
@@ -355,7 +355,8 @@ module Doing
355
355
  def verify_duration(date, finish_date, title: nil)
356
356
  max_elapsed = Doing.setting('interaction.confirm_longer_than', 0)
357
357
  max_elapsed = max_elapsed.chronify_qty if max_elapsed.is_a?(String)
358
- date = date.chronify(guess: :end, context: :today) if finish_date.is_a?(String)
358
+ date = date.chronify(guess: :end, context: :today) if date.is_a?(String)
359
+ finish_date = finish_date.chronify(guess: :end, context: :today) if finish_date.is_a?(String)
359
360
 
360
361
  elapsed = finish_date - date
361
362
 
@@ -39,7 +39,8 @@ module Doing
39
39
 
40
40
  if opt[:done] && entry.should_finish?
41
41
  if entry.should_time?
42
- entry.tag('done', value: opt[:done])
42
+ finish = opt[:done].is_a?(String) ? opt[:done].chronify(guess: :end, context: :today) : opt[:done]
43
+ entry.tag('done', value: finish)
43
44
  else
44
45
  entry.tag('done')
45
46
  end
@@ -47,15 +48,11 @@ module Doing
47
48
 
48
49
  entry.note = note
49
50
 
50
- items = @content.clone
51
51
  if opt[:timed]
52
- items.reverse!
53
- items.each_with_index do |i, x|
54
- next if i.title =~ / @done/
55
-
56
- finish_date = verify_duration(i.date, opt[:back], title: i.title)
57
- items[x].tag('done', value: finish_date.strftime('%F %R'))
58
- break
52
+ last_item = last_entry({ section: section })
53
+ if last_item.tags?(['done'], :not)
54
+ finish_date = verify_duration(last_item.date, opt[:back], title: last_item.title)
55
+ last_item.tag('done', value: finish_date.strftime('%F %R'))
59
56
  end
60
57
  end
61
58
 
data/lib/doing.rb CHANGED
@@ -16,6 +16,7 @@ require 'json'
16
16
  require 'logger'
17
17
  require 'safe_yaml/load'
18
18
  require 'fcntl'
19
+ require 'digest'
19
20
 
20
21
  require 'chronic'
21
22
  require 'tty-link'
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.1.45
4
+ version: 2.1.48
5
5
  platform: ruby
6
6
  authors:
7
7
  - Brett Terpstra
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2022-03-22 00:00:00.000000000 Z
11
+ date: 2022-05-11 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: github-markup
@@ -510,6 +510,7 @@ files:
510
510
  - docs/doc/Doing/Content.html
511
511
  - docs/doc/Doing/DayOneRenderer.html
512
512
  - docs/doc/Doing/DayoneExport.html
513
+ - docs/doc/Doing/DoingExport.html
513
514
  - docs/doc/Doing/DoingImport.html
514
515
  - docs/doc/Doing/Entry.html
515
516
  - docs/doc/Doing/Errors.html
@@ -533,6 +534,7 @@ files:
533
534
  - docs/doc/Doing/ItemTags.html
534
535
  - docs/doc/Doing/Items.html
535
536
  - docs/doc/Doing/JSONExport.html
537
+ - docs/doc/Doing/JSONImport.html
536
538
  - docs/doc/Doing/LogAdapter.html
537
539
  - docs/doc/Doing/Logger.html
538
540
  - docs/doc/Doing/MarkdownExport.html
@@ -642,6 +644,7 @@ files:
642
644
  - lib/doing/completion.rb
643
645
  - lib/doing/completion/bash_completion.rb
644
646
  - lib/doing/completion/completion_string.rb
647
+ - lib/doing/completion/fig_completion.rb
645
648
  - lib/doing/completion/fish_completion.rb
646
649
  - lib/doing/completion/zsh_completion.rb
647
650
  - lib/doing/configuration.rb
@@ -669,6 +672,7 @@ files:
669
672
  - lib/doing/plugin_manager.rb
670
673
  - lib/doing/plugins/export/csv_export.rb
671
674
  - lib/doing/plugins/export/dayone_export.rb
675
+ - lib/doing/plugins/export/doing_export.rb
672
676
  - lib/doing/plugins/export/html_export.rb
673
677
  - lib/doing/plugins/export/json_export.rb
674
678
  - lib/doing/plugins/export/markdown_export.rb
@@ -677,6 +681,7 @@ files:
677
681
  - lib/doing/plugins/import/cal_to_json.scpt
678
682
  - lib/doing/plugins/import/calendar_import.rb
679
683
  - lib/doing/plugins/import/doing_import.rb
684
+ - lib/doing/plugins/import/json_import.rb
680
685
  - lib/doing/plugins/import/timing_import.rb
681
686
  - lib/doing/prompt/choose.rb
682
687
  - lib/doing/prompt/fzf.rb