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.
- checksums.yaml +4 -4
- data/.irbrc +3 -0
- data/CHANGELOG.md +32 -0
- data/Gemfile.lock +1 -1
- data/README.md +1 -1
- data/bin/commands/completion.rb +1 -1
- data/bin/commands/config.rb +1 -1
- data/bin/commands/last.rb +1 -1
- data/bin/commands/now.rb +2 -1
- data/bin/commands/recent.rb +1 -1
- data/bin/commands/reset.rb +6 -4
- data/docs/doc/Array.html +1 -1
- data/docs/doc/BooleanTermParser/Clause.html +1 -1
- data/docs/doc/BooleanTermParser/Operator.html +1 -1
- data/docs/doc/BooleanTermParser/Query.html +1 -1
- data/docs/doc/BooleanTermParser/QueryParser.html +1 -1
- data/docs/doc/BooleanTermParser/QueryTransformer.html +1 -1
- data/docs/doc/BooleanTermParser.html +1 -1
- data/docs/doc/Doing/ArrayCleanup.html +1 -1
- data/docs/doc/Doing/ArrayNestedHash.html +1 -1
- data/docs/doc/Doing/ArrayTags.html +1 -1
- data/docs/doc/Doing/CSVExport.html +1 -1
- data/docs/doc/Doing/CalendarImport.html +1 -1
- data/docs/doc/Doing/Change.html +1 -1
- data/docs/doc/Doing/Changes.html +1 -1
- data/docs/doc/Doing/ChronifyArray.html +1 -1
- data/docs/doc/Doing/ChronifyNumeric.html +1 -1
- data/docs/doc/Doing/ChronifyString.html +1 -1
- data/docs/doc/Doing/Color.html +1 -1
- data/docs/doc/Doing/Completion/BashCompletions.html +1 -1
- data/docs/doc/Doing/Completion/FishCompletions.html +1 -1
- data/docs/doc/Doing/Completion/StringUtils.html +1 -1
- data/docs/doc/Doing/Completion/ZshCompletions.html +1 -1
- data/docs/doc/Doing/Completion.html +5 -5
- data/docs/doc/Doing/Configuration.html +1 -1
- data/docs/doc/Doing/DayOneRenderer.html +1 -1
- data/docs/doc/Doing/DayoneExport.html +1 -1
- data/docs/doc/Doing/DoingExport.html +206 -0
- data/docs/doc/Doing/DoingImport.html +1 -1
- data/docs/doc/Doing/Entry.html +1 -1
- data/docs/doc/Doing/Errors/DoingNoTraceError.html +1 -1
- data/docs/doc/Doing/Errors/DoingRuntimeError.html +1 -1
- data/docs/doc/Doing/Errors/DoingStandardError.html +1 -1
- data/docs/doc/Doing/Errors/EmptyInput.html +1 -1
- data/docs/doc/Doing/Errors/HistoryLimitError.html +1 -1
- data/docs/doc/Doing/Errors/InvalidPlugin.html +1 -1
- data/docs/doc/Doing/Errors/MissingBackupFile.html +1 -1
- data/docs/doc/Doing/Errors/NoResults.html +1 -1
- data/docs/doc/Doing/Errors/PluginException.html +1 -1
- data/docs/doc/Doing/Errors/UserCancelled.html +1 -1
- data/docs/doc/Doing/Errors/WrongCommand.html +1 -1
- data/docs/doc/Doing/Errors.html +1 -1
- data/docs/doc/Doing/HTMLExport.html +1 -1
- data/docs/doc/Doing/Hooks.html +1 -1
- data/docs/doc/Doing/Item.html +75 -36
- data/docs/doc/Doing/ItemDates.html +1 -1
- data/docs/doc/Doing/ItemQuery.html +1 -1
- data/docs/doc/Doing/ItemState.html +1 -1
- data/docs/doc/Doing/ItemTags.html +1 -1
- data/docs/doc/Doing/Items.html +129 -1
- data/docs/doc/Doing/JSONExport.html +1 -1
- data/docs/doc/Doing/JSONImport.html +295 -0
- data/docs/doc/Doing/Logger.html +1 -1
- data/docs/doc/Doing/MarkdownExport.html +1 -1
- data/docs/doc/Doing/Note.html +1 -1
- data/docs/doc/Doing/Pager.html +1 -1
- data/docs/doc/Doing/Plugins.html +1 -1
- data/docs/doc/Doing/Prompt.html +1 -1
- data/docs/doc/Doing/PromptChoose.html +1 -1
- data/docs/doc/Doing/PromptFZF.html +1 -1
- data/docs/doc/Doing/PromptInput.html +1 -1
- data/docs/doc/Doing/PromptSTD.html +1 -1
- data/docs/doc/Doing/PromptYN.html +1 -1
- data/docs/doc/Doing/Section.html +1 -1
- data/docs/doc/Doing/StringHighlight.html +1 -1
- data/docs/doc/Doing/StringNormalize.html +1 -1
- data/docs/doc/Doing/StringQuery.html +1 -1
- data/docs/doc/Doing/StringTags.html +1 -1
- data/docs/doc/Doing/StringTransform.html +1 -1
- data/docs/doc/Doing/StringTruncate.html +1 -1
- data/docs/doc/Doing/StringURL.html +1 -1
- data/docs/doc/Doing/SymbolNormalize.html +1 -1
- data/docs/doc/Doing/TaskPaperExport.html +1 -1
- data/docs/doc/Doing/TemplateExport.html +1 -1
- data/docs/doc/Doing/TemplateString.html +2 -2
- data/docs/doc/Doing/TimingImport.html +1 -1
- data/docs/doc/Doing/Types.html +1 -1
- data/docs/doc/Doing/Util/Backup.html +1 -1
- data/docs/doc/Doing/Util.html +2 -2
- data/docs/doc/Doing/Version.html +1 -1
- data/docs/doc/Doing/WWID.html +1 -1
- data/docs/doc/Doing.html +4 -4
- data/docs/doc/FalseClass.html +1 -1
- data/docs/doc/GLI/Commands/Help.html +1 -1
- data/docs/doc/GLI/Commands/MarkdownDocumentListener.html +1 -1
- data/docs/doc/GLI/Commands.html +1 -1
- data/docs/doc/GLI.html +1 -1
- data/docs/doc/Hash.html +1 -1
- data/docs/doc/Numeric.html +1 -1
- data/docs/doc/Object.html +1 -1
- data/docs/doc/PhraseParser/Operator.html +1 -1
- data/docs/doc/PhraseParser/PhraseClause.html +1 -1
- data/docs/doc/PhraseParser/Query.html +1 -1
- data/docs/doc/PhraseParser/QueryParser.html +1 -1
- data/docs/doc/PhraseParser/QueryTransformer.html +1 -1
- data/docs/doc/PhraseParser/TermClause.html +1 -1
- data/docs/doc/PhraseParser.html +1 -1
- data/docs/doc/Status.html +1 -1
- data/docs/doc/String.html +64 -2
- data/docs/doc/Symbol.html +1 -1
- data/docs/doc/Time.html +1 -1
- data/docs/doc/TrueClass.html +1 -1
- data/docs/doc/_index.html +22 -1
- data/docs/doc/class_list.html +1 -1
- data/docs/doc/file.README.html +2 -2
- data/docs/doc/index.html +2 -2
- data/docs/doc/method_list.html +576 -456
- data/docs/doc/top-level-namespace.html +1 -1
- data/doing.rdoc +20 -20
- data/lib/completion/_doing.zsh +31 -31
- data/lib/doing/add_options.rb +1 -1
- data/lib/doing/completion/fig_completion.rb +121 -0
- data/lib/doing/completion.rb +14 -4
- data/lib/doing/item/item.rb +7 -8
- data/lib/doing/items/items.rb +24 -0
- data/lib/doing/plugin_manager.rb +1 -1
- data/lib/doing/plugins/export/doing_export.rb +37 -0
- data/lib/doing/plugins/export/json_export.rb +16 -2
- data/lib/doing/plugins/export/template_export.rb +4 -2
- data/lib/doing/plugins/import/doing_import.rb +17 -6
- data/lib/doing/plugins/import/json_import.rb +94 -0
- data/lib/doing/string/string.rb +9 -0
- data/lib/doing/version.rb +1 -1
- data/lib/doing/wwid/filetools.rb +3 -2
- data/lib/doing/wwid/filter.rb +2 -2
- data/lib/doing/wwid/interactive.rb +2 -1
- data/lib/doing/wwid/modify.rb +6 -9
- data/lib/doing.rb +1 -0
- metadata +7 -2
data/lib/doing/completion.rb
CHANGED
|
@@ -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)
|
data/lib/doing/item/item.rb
CHANGED
|
@@ -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
|
-
|
|
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}
|
|
93
|
+
"\t- #{@date.strftime('%Y-%m-%d %H:%M')} | #{@title} <#{@id}>#{@note.good? ? "\n#{@note}" : ''}"
|
|
95
94
|
end
|
|
96
95
|
|
|
97
96
|
##
|
data/lib/doing/items/items.rb
CHANGED
|
@@ -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 = []
|
data/lib/doing/plugin_manager.rb
CHANGED
|
@@ -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
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
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:
|
|
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
|
-
|
|
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
|
data/lib/doing/string/string.rb
CHANGED
|
@@ -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
data/lib/doing/wwid/filetools.rb
CHANGED
|
@@ -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
|
-
|
|
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
|
data/lib/doing/wwid/filter.rb
CHANGED
|
@@ -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]
|
|
150
|
-
|
|
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
|
|
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
|
|
data/lib/doing/wwid/modify.rb
CHANGED
|
@@ -39,7 +39,8 @@ module Doing
|
|
|
39
39
|
|
|
40
40
|
if opt[:done] && entry.should_finish?
|
|
41
41
|
if entry.should_time?
|
|
42
|
-
|
|
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
|
-
|
|
53
|
-
|
|
54
|
-
|
|
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
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.
|
|
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-
|
|
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
|