doing 2.1.30 → 2.1.31pre
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.irbrc +1 -0
- data/.yardoc/checksums +9 -8
- data/.yardoc/object_types +0 -0
- data/.yardoc/objects/root.dat +0 -0
- data/CHANGELOG.md +4923 -0
- data/Gemfile.lock +1 -1
- data/README.md +1 -1
- data/bin/commands/again.rb +1 -1
- data/bin/commands/archive.rb +3 -3
- data/bin/commands/cancel.rb +1 -1
- data/bin/commands/commands.rb +8 -8
- data/bin/commands/completion.rb +61 -19
- data/bin/commands/config.rb +9 -9
- data/bin/commands/done.rb +1 -1
- data/bin/commands/flag.rb +1 -1
- data/bin/commands/grep.rb +5 -5
- data/bin/commands/last.rb +1 -1
- data/bin/commands/meanwhile.rb +1 -1
- data/bin/commands/now.rb +1 -1
- data/bin/commands/on.rb +1 -1
- data/bin/commands/open.rb +1 -1
- data/bin/commands/recent.rb +4 -4
- data/bin/commands/show.rb +8 -8
- data/bin/commands/since.rb +1 -1
- data/bin/commands/today.rb +1 -1
- data/bin/commands/view.rb +3 -3
- data/bin/commands/yesterday.rb +2 -2
- data/bin/doing +22 -133
- 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/Color.html +1 -1
- data/docs/doc/Doing/Completion.html +324 -4
- data/docs/doc/Doing/Configuration.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/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/Hooks.html +1 -1
- data/docs/doc/Doing/Item.html +125 -1
- data/docs/doc/Doing/Items.html +1 -1
- data/docs/doc/Doing/LogAdapter.html +1 -1
- data/docs/doc/Doing/Note.html +109 -3
- 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/Section.html +1 -1
- data/docs/doc/Doing/TemplateString.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 +1 -1
- data/docs/doc/Doing/WWID.html +6 -6
- data/docs/doc/Doing.html +2 -2
- 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/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 +1 -1
- 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 +3 -1
- data/docs/doc/file.README.html +2 -2
- data/docs/doc/index.html +2 -2
- data/docs/doc/method_list.html +337 -241
- data/docs/doc/top-level-namespace.html +105 -1
- data/doing.rdoc +25 -9
- data/example_plugin.rb +7 -5
- data/lib/completion/_doing.zsh +2 -2
- data/lib/completion/doing.bash +2 -2
- data/lib/completion/doing.fish +2 -3
- data/lib/doing/add_options.rb +117 -0
- data/lib/doing/array/array.rb +16 -0
- data/lib/doing/completion/bash_completion.rb +12 -51
- data/lib/doing/completion/fish_completion.rb +16 -52
- data/lib/doing/completion/zsh_completion.rb +12 -56
- data/lib/doing/completion.rb +203 -17
- data/lib/doing/item.rb +21 -3
- data/lib/doing/items.rb +5 -5
- data/lib/doing/note.rb +24 -8
- data/lib/doing/plugins/export/dayone_export.rb +8 -6
- data/lib/doing/plugins/export/html_export.rb +4 -4
- data/lib/doing/plugins/export/json_export.rb +19 -20
- data/lib/doing/plugins/export/markdown_export.rb +2 -2
- data/lib/doing/plugins/export/template_export.rb +4 -4
- data/lib/doing/plugins/import/calendar_import.rb +1 -1
- data/lib/doing/plugins/import/doing_import.rb +1 -1
- data/lib/doing/plugins/import/timing_import.rb +1 -1
- data/lib/doing/string/highlight.rb +3 -4
- data/lib/doing/string/string.rb +8 -0
- data/lib/doing/util.rb +1 -1
- data/lib/doing/util_backup.rb +12 -12
- data/lib/doing/version.rb +1 -1
- data/lib/doing/wwid.rb +76 -77
- data/lib/doing.rb +57 -0
- data/lib/examples/commands/wiki.rb +27 -19
- data/scripts/setting_replace.rb +11 -0
- metadata +6 -4
@@ -1,7 +1,9 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module Doing
|
2
4
|
module Completion
|
5
|
+
# Generate completions for zsh
|
3
6
|
class ZshCompletions
|
4
|
-
|
5
7
|
attr_accessor :commands, :global_options
|
6
8
|
|
7
9
|
def generate_helpers
|
@@ -34,58 +36,11 @@ module Doing
|
|
34
36
|
}
|
35
37
|
|
36
38
|
EOFUNCTIONS
|
39
|
+
@bar.advance(status: '✅')
|
37
40
|
@bar.finish
|
38
41
|
out
|
39
42
|
end
|
40
43
|
|
41
|
-
def get_help_sections(command = '')
|
42
|
-
res = `doing help #{command}`.strip
|
43
|
-
scanned = res.scan(/(?m-i)^([A-Z ]+)\n([\s\S]*?)(?=\n+[A-Z]+|\Z)/)
|
44
|
-
sections = {}
|
45
|
-
scanned.each do |sect|
|
46
|
-
title = sect[0].downcase.strip.gsub(/ +/, '_').to_sym
|
47
|
-
content = sect[1].split(/\n/).map(&:strip).delete_if(&:empty?)
|
48
|
-
sections[title] = content
|
49
|
-
end
|
50
|
-
sections
|
51
|
-
end
|
52
|
-
|
53
|
-
def parse_option(option)
|
54
|
-
res = option.match(/(?:-(?<short>\w), )?(?:--(?:\[no-\])?(?<long>\w+)(?:=(?<arg>\w+))?)\s+- (?<desc>.*?)$/)
|
55
|
-
return nil unless res
|
56
|
-
|
57
|
-
{
|
58
|
-
short: res['short'],
|
59
|
-
long: res['long'],
|
60
|
-
arg: res[:arg],
|
61
|
-
description: res['desc'].short_desc
|
62
|
-
}
|
63
|
-
end
|
64
|
-
|
65
|
-
def parse_options(options)
|
66
|
-
options.map { |opt| parse_option(opt) }
|
67
|
-
end
|
68
|
-
|
69
|
-
def parse_command(command)
|
70
|
-
res = command.match(/^(?<cmd>[^, \t]+)(?<alias>(?:, [^, \t]+)*)?\s+- (?<desc>.*?)$/)
|
71
|
-
if res.nil?
|
72
|
-
Doing.logger.error('Completion:', "Error parsing #{command}")
|
73
|
-
return nil
|
74
|
-
|
75
|
-
end
|
76
|
-
commands = [res['cmd']]
|
77
|
-
commands.concat(res['alias'].split(/, /).delete_if(&:empty?)) if res['alias']
|
78
|
-
|
79
|
-
{
|
80
|
-
commands: commands,
|
81
|
-
description: res['desc'].short_desc
|
82
|
-
}
|
83
|
-
end
|
84
|
-
|
85
|
-
def parse_commands(commands)
|
86
|
-
commands.map { |cmd| parse_command(cmd) }
|
87
|
-
end
|
88
|
-
|
89
44
|
def generate_subcommand_completions
|
90
45
|
out = []
|
91
46
|
@commands.each_with_index do |cmd, i|
|
@@ -103,11 +58,11 @@ module Doing
|
|
103
58
|
@commands.each_with_index do |cmd, i|
|
104
59
|
@bar.advance(status: cmd[:commands].first)
|
105
60
|
|
106
|
-
data = get_help_sections(cmd[:commands].first)
|
61
|
+
data = Completion.get_help_sections(cmd[:commands].first)
|
107
62
|
option_arr = []
|
108
63
|
|
109
64
|
if data[:command_options]
|
110
|
-
parse_options(data[:command_options]).each do |option|
|
65
|
+
Completion.parse_options(data[:command_options]).each do |option|
|
111
66
|
next if option.nil?
|
112
67
|
|
113
68
|
arg = option[:arg] ? '=' : ''
|
@@ -129,11 +84,12 @@ module Doing
|
|
129
84
|
end
|
130
85
|
|
131
86
|
def initialize
|
132
|
-
data = get_help_sections
|
133
|
-
@global_options = parse_options(data[:global_options])
|
134
|
-
@commands = parse_commands(data[:commands])
|
135
|
-
@bar = TTY::ProgressBar.new(" \033[0;0;33mGenerating Zsh completions: \033[0;35;40m[:bar] :status\033[0m", total: @commands.count, bar_format: :
|
136
|
-
|
87
|
+
data = Completion.get_help_sections
|
88
|
+
@global_options = Completion.parse_options(data[:global_options])
|
89
|
+
@commands = Completion.parse_commands(data[:commands])
|
90
|
+
@bar = TTY::ProgressBar.new(" \033[0;0;33mGenerating Zsh completions: \033[0;35;40m[:bar] :status\033[0m", total: @commands.count + 1, bar_format: :block, hide_cursor: true, status: 'processing subcommands')
|
91
|
+
width = TTY::Screen.columns - 45
|
92
|
+
@bar.resize(width)
|
137
93
|
end
|
138
94
|
|
139
95
|
def generate_completions
|
data/lib/doing/completion.rb
CHANGED
@@ -11,25 +11,118 @@ require 'bash_completion'
|
|
11
11
|
module Doing
|
12
12
|
# Completion script generator
|
13
13
|
module Completion
|
14
|
+
OPTIONS_RX = /(?:-(?<short>\w), )?(?:--(?:\[no-\])?(?<long>\w+)(?:=(?<arg>\w+))?)\s+- (?<desc>.*?)$/.freeze
|
15
|
+
SECTIONS_RX = /(?m-i)^([A-Z ]+)\n([\s\S]*?)(?=\n+[A-Z]+|\Z)/.freeze
|
16
|
+
COMMAND_RX = /^(?<cmd>[^, \t]+)(?<alias>(?:, [^, \t]+)*)?\s+- (?<desc>.*?)$/.freeze
|
17
|
+
|
14
18
|
class << self
|
19
|
+
def get_help_sections(command = '')
|
20
|
+
res = `doing help #{command}`.strip
|
21
|
+
scanned = res.scan(SECTIONS_RX)
|
22
|
+
sections = {}
|
23
|
+
scanned.each do |sect|
|
24
|
+
title = sect[0].downcase.strip.gsub(/ +/, '_').to_sym
|
25
|
+
content = sect[1].split(/\n/).map(&:strip).delete_if(&:empty?)
|
26
|
+
sections[title] = content
|
27
|
+
end
|
28
|
+
sections
|
29
|
+
end
|
30
|
+
|
31
|
+
def parse_option(option)
|
32
|
+
res = option.match(OPTIONS_RX)
|
33
|
+
return nil unless res
|
34
|
+
|
35
|
+
{
|
36
|
+
short: res['short'],
|
37
|
+
long: res['long'],
|
38
|
+
arg: res['arg'],
|
39
|
+
description: res['desc'].short_desc
|
40
|
+
}
|
41
|
+
end
|
42
|
+
|
43
|
+
def parse_options(options)
|
44
|
+
options.map { |opt| parse_option(opt) }
|
45
|
+
end
|
46
|
+
|
47
|
+
def parse_command(command)
|
48
|
+
res = command.match(COMMAND_RX)
|
49
|
+
commands = [res['cmd']]
|
50
|
+
commands.concat(res['alias'].split(/, /).delete_if(&:empty?)) if res['alias']
|
51
|
+
|
52
|
+
{
|
53
|
+
commands: commands,
|
54
|
+
description: res['desc'].short_desc
|
55
|
+
}
|
56
|
+
end
|
57
|
+
|
58
|
+
def parse_commands(commands)
|
59
|
+
commands.map { |cmd| parse_command(cmd) }
|
60
|
+
end
|
61
|
+
|
15
62
|
# Generate a completion script and output to file or
|
16
63
|
# stdout
|
17
64
|
#
|
18
65
|
# @param type [String] shell to generate for (zsh|bash|fish)
|
19
66
|
# @param file [String] Path to save to, or 'stdout'
|
20
67
|
#
|
21
|
-
def generate_completion(type: 'zsh', file:
|
22
|
-
if type =~ /^all$/i
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
68
|
+
def generate_completion(type: 'zsh', file: :default, link: true)
|
69
|
+
return generate_all if type =~ /^all$/i
|
70
|
+
|
71
|
+
file = file == :default ? default_file(type) : file
|
72
|
+
file = validate_target(file)
|
73
|
+
result = generate_type(type)
|
74
|
+
|
75
|
+
if file =~ /^stdout$/i
|
76
|
+
$stdout.puts result
|
77
|
+
else
|
78
|
+
File.open(file, 'w') { |f| f.puts result }
|
79
|
+
Doing.logger.warn('File written:', "#{type} completions written to #{file}")
|
80
|
+
|
81
|
+
link_completion_type(type, file) if link
|
31
82
|
end
|
83
|
+
end
|
84
|
+
|
85
|
+
def link_default(type)
|
86
|
+
type = normalize_type(type)
|
87
|
+
raise InvalidArgument, 'Unrecognized shell specified' if type == :invalid
|
88
|
+
|
89
|
+
return %i[zsh bash fish].each { |t| link_default(t) } if type == :all
|
32
90
|
|
91
|
+
install_builtin(type)
|
92
|
+
|
93
|
+
link_completion_type(type, File.join(default_dir, default_filenames[type]))
|
94
|
+
end
|
95
|
+
|
96
|
+
def install_builtin(type)
|
97
|
+
FileUtils.mkdir_p(default_dir)
|
98
|
+
src = File.expand_path(File.join(File.dirname(__FILE__), '..', 'completion', default_filenames[type]))
|
99
|
+
|
100
|
+
if File.exist?(File.join(default_dir, default_filenames[type]))
|
101
|
+
return unless Doing::Prompt.yn("Update #{type} completion script", default_response: 'n')
|
102
|
+
end
|
103
|
+
|
104
|
+
FileUtils.cp(src, default_dir)
|
105
|
+
Doing.logger.warn('File written:', "#{type} completions saved to #{default_file(type)}")
|
106
|
+
end
|
107
|
+
|
108
|
+
def normalize_type(type)
|
109
|
+
case type.to_s
|
110
|
+
when /^f/i
|
111
|
+
:fish
|
112
|
+
when /^b/i
|
113
|
+
:bash
|
114
|
+
when /^z/i
|
115
|
+
:zsh
|
116
|
+
when /^a/i
|
117
|
+
:all
|
118
|
+
else
|
119
|
+
:invalid
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
private
|
124
|
+
|
125
|
+
def generate_type(type)
|
33
126
|
generator = case type.to_s
|
34
127
|
when /^f/i
|
35
128
|
FishCompletions.new
|
@@ -39,16 +132,109 @@ module Doing
|
|
39
132
|
ZshCompletions.new
|
40
133
|
end
|
41
134
|
|
42
|
-
|
135
|
+
generator.generate_completions
|
136
|
+
end
|
43
137
|
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
138
|
+
def validate_target(file)
|
139
|
+
unless file =~ /stdout/i
|
140
|
+
file = validate_file(file)
|
141
|
+
|
142
|
+
validate_dir(file)
|
143
|
+
end
|
144
|
+
|
145
|
+
file
|
146
|
+
end
|
147
|
+
|
148
|
+
def default_dir
|
149
|
+
File.expand_path('~/.local/share/doing/completion')
|
150
|
+
end
|
151
|
+
|
152
|
+
def default_filenames
|
153
|
+
{ zsh: '_doing.zsh', bash: 'doing.bash', fish: 'doing.fish' }
|
154
|
+
end
|
155
|
+
|
156
|
+
def default_file(type)
|
157
|
+
type = normalize_type(type)
|
158
|
+
|
159
|
+
File.join(default_dir, default_filenames[type])
|
160
|
+
end
|
161
|
+
|
162
|
+
def validate_file(file)
|
163
|
+
file = File.expand_path(file)
|
164
|
+
if File.exist?(file)
|
165
|
+
res = Doing::Prompt.yn("Overwrite #{file}", default_response: 'y')
|
166
|
+
raise UserCancelled unless res
|
167
|
+
|
168
|
+
FileUtils.rm(file) if res
|
169
|
+
end
|
170
|
+
file
|
171
|
+
end
|
172
|
+
|
173
|
+
def validate_dir(file)
|
174
|
+
dir = File.dirname(file)
|
175
|
+
unless File.directory?(dir)
|
176
|
+
res = Doing::Prompt.yn("#{dir} doesn't exist, create it", default_response: 'y')
|
177
|
+
raise UserCancelled unless res
|
178
|
+
|
179
|
+
FileUtils.mkdir_p(dir)
|
180
|
+
end
|
181
|
+
dir
|
182
|
+
end
|
183
|
+
|
184
|
+
def generate_all
|
185
|
+
Doing.logger.log_now(:warn, 'Generating:', 'all completion types, will use default paths')
|
186
|
+
generate_completion(type: 'fish', file: 'lib/completion/doing.fish', link: false)
|
187
|
+
Doing.logger.warn('File written:', 'fish completions written to lib/completion/doing.fish')
|
188
|
+
generate_completion(type: 'zsh', file: 'lib/completion/_doing.zsh', link: false)
|
189
|
+
Doing.logger.warn('File written:', 'zsh completions written to lib/completion/_doing.zsh')
|
190
|
+
generate_completion(type: 'bash', file: 'lib/completion/doing.bash', link: false)
|
191
|
+
Doing.logger.warn('File written:', 'bash completions written to lib/completion/doing.bash')
|
192
|
+
end
|
193
|
+
|
194
|
+
def link_completion_type(type, file)
|
195
|
+
dir = File.dirname(file)
|
196
|
+
case type.to_s
|
197
|
+
when /^b/i
|
198
|
+
unless dir =~ %r{(\.bash_it/completion|bash_completion/completions)}
|
199
|
+
link_completion(file, ['~/.bash_it/completion/enabled', '/usr/share/bash_completion/completions'], 'doing.bash')
|
49
200
|
end
|
50
|
-
|
201
|
+
when /^f/i
|
202
|
+
link_completion(file, ['~/.config/fish/completions'], 'doing.fish') unless dir =~ %r{.config/fish/completions}
|
203
|
+
when /^z/i
|
204
|
+
unless dir =~ %r{(\.oh-my-zsh/completions|share/site-functions)}
|
205
|
+
link_completion(file, ['~/.oh-my-zsh/completions', '/usr/local/share/zsh/site-functions'], '_doing.zsh')
|
206
|
+
end
|
207
|
+
end
|
208
|
+
end
|
209
|
+
|
210
|
+
def link_completion(file, targets, filename)
|
211
|
+
return if targets.map { |t| File.expand_path(t) }.include?(File.dirname(file))
|
212
|
+
|
213
|
+
found = false
|
214
|
+
linked = false
|
215
|
+
|
216
|
+
targets.each do |target|
|
217
|
+
next unless File.directory?(File.expand_path(target))
|
218
|
+
found = true
|
219
|
+
|
220
|
+
target_file = File.join(File.expand_path(target), filename)
|
221
|
+
next unless Doing::Prompt.yn("Create link to #{target_file}", default_response: 'n')
|
222
|
+
|
223
|
+
FileUtils.ln_s(File.expand_path(file), target_file, force: true)
|
224
|
+
Doing.logger.warn('File linked:', "#{File.expand_path(file)} -> #{target_file}")
|
225
|
+
linked = true
|
226
|
+
break
|
227
|
+
end
|
228
|
+
|
229
|
+
return if linked
|
230
|
+
|
231
|
+
unless found
|
232
|
+
$stdout.puts 'No known auto-load directory found for specified shell'.red
|
233
|
+
$stdout.puts "Looked for #{targets.join(', ')}, found no existing directory".yellow
|
51
234
|
end
|
235
|
+
$stdout.puts 'If you don\'t want to autoload completions'.yellow
|
236
|
+
$stdout.puts 'you can source the script directly in your shell\'s startup file:'.yellow
|
237
|
+
$stdout.puts %(source "#{file}").boldwhite
|
52
238
|
end
|
53
239
|
end
|
54
240
|
end
|
data/lib/doing/item.rb
CHANGED
@@ -272,7 +272,7 @@ module Doing
|
|
272
272
|
end
|
273
273
|
|
274
274
|
def highlight_search(search, distance: nil, negate: false, case_type: nil)
|
275
|
-
prefs = Doing.
|
275
|
+
prefs = Doing.setting('search', {})
|
276
276
|
matching = prefs.fetch('matching', 'pattern').normalize_matching
|
277
277
|
distance ||= prefs.fetch('distance', 3).to_i
|
278
278
|
case_type ||= prefs.fetch('case', 'smart').normalize_case
|
@@ -311,7 +311,7 @@ module Doing
|
|
311
311
|
## @return [Boolean] matches search criteria
|
312
312
|
##
|
313
313
|
def search(search, distance: nil, negate: false, case_type: nil)
|
314
|
-
prefs = Doing.
|
314
|
+
prefs = Doing.setting('search', {})
|
315
315
|
matching = prefs.fetch('matching', 'pattern').normalize_matching
|
316
316
|
distance ||= prefs.fetch('distance', 3).to_i
|
317
317
|
case_type ||= prefs.fetch('case', 'smart').normalize_case
|
@@ -354,6 +354,24 @@ module Doing
|
|
354
354
|
negate ? !matches : matches
|
355
355
|
end
|
356
356
|
|
357
|
+
##
|
358
|
+
## Test if item has a @done tag
|
359
|
+
##
|
360
|
+
## @return [Boolean] true item has @done tag
|
361
|
+
##
|
362
|
+
def finished?
|
363
|
+
tags?('done')
|
364
|
+
end
|
365
|
+
|
366
|
+
##
|
367
|
+
## Test if item does not contain @done tag
|
368
|
+
##
|
369
|
+
## @return [Boolean] true if item is missing @done tag
|
370
|
+
##
|
371
|
+
def unfinished?
|
372
|
+
tags?('done', negate: true)
|
373
|
+
end
|
374
|
+
|
357
375
|
##
|
358
376
|
## Test if item is included in never_finish config and
|
359
377
|
## thus should not receive a @done tag
|
@@ -436,7 +454,7 @@ module Doing
|
|
436
454
|
private
|
437
455
|
|
438
456
|
def should?(key)
|
439
|
-
config = Doing.
|
457
|
+
config = Doing.settings
|
440
458
|
return true unless config[key].is_a?(Array)
|
441
459
|
|
442
460
|
config[key].each do |tag|
|
data/lib/doing/items.rb
CHANGED
@@ -131,9 +131,9 @@ module Doing
|
|
131
131
|
out = []
|
132
132
|
@sections.each do |section|
|
133
133
|
out.push(section.original)
|
134
|
-
items = in_section(section.title).sort_by
|
135
|
-
items.reverse! if Doing.
|
136
|
-
items.each { |item| out.push(item.to_s)}
|
134
|
+
items = in_section(section.title).sort_by(&:date)
|
135
|
+
items.reverse! if Doing.setting('doing_file_sort').normalize_order == :desc
|
136
|
+
items.each { |item| out.push(item.to_s) }
|
137
137
|
end
|
138
138
|
|
139
139
|
out.join("\n")
|
@@ -141,8 +141,8 @@ module Doing
|
|
141
141
|
|
142
142
|
# @private
|
143
143
|
def inspect
|
144
|
-
|
144
|
+
sections = @sections.map { |s| "<Section:#{s.title} #{in_section(s.title).count} items>" }.join(', ')
|
145
|
+
"#<Doing::Items #{count} items, #{@sections.count} sections: #{sections}>"
|
145
146
|
end
|
146
|
-
|
147
147
|
end
|
148
148
|
end
|
data/lib/doing/note.rb
CHANGED
@@ -5,7 +5,6 @@ module Doing
|
|
5
5
|
## This class describes an item note.
|
6
6
|
##
|
7
7
|
class Note < Array
|
8
|
-
|
9
8
|
##
|
10
9
|
## Initializes a new note
|
11
10
|
##
|
@@ -28,9 +27,10 @@ module Doing
|
|
28
27
|
##
|
29
28
|
def add(note, replace: false)
|
30
29
|
clear if replace
|
31
|
-
|
30
|
+
case note
|
31
|
+
when String
|
32
32
|
append_string(note)
|
33
|
-
|
33
|
+
when Array
|
34
34
|
append(note)
|
35
35
|
end
|
36
36
|
end
|
@@ -55,7 +55,7 @@ module Doing
|
|
55
55
|
## @return [Array] Stripped note
|
56
56
|
##
|
57
57
|
def strip_lines
|
58
|
-
map(&:strip)
|
58
|
+
Note.new(map(&:strip))
|
59
59
|
end
|
60
60
|
|
61
61
|
def strip_lines!
|
@@ -64,8 +64,24 @@ module Doing
|
|
64
64
|
|
65
65
|
##
|
66
66
|
## Note as multi-line string
|
67
|
-
|
68
|
-
|
67
|
+
##
|
68
|
+
## @param prefix [String] prefix for each line (default two tabs, TaskPaper format)
|
69
|
+
##
|
70
|
+
def to_s(prefix: "\t\t")
|
71
|
+
compress.strip_lines.map { |l| "#{prefix}#{l}" }.join("\n")
|
72
|
+
end
|
73
|
+
|
74
|
+
##
|
75
|
+
## Returns note as a single line, newlines separated by
|
76
|
+
## space
|
77
|
+
##
|
78
|
+
## @return [String] Line representation of the Note.
|
79
|
+
##
|
80
|
+
## @param separator The separator with which to
|
81
|
+
## join multiple lines
|
82
|
+
##
|
83
|
+
def to_line(separator: ' ')
|
84
|
+
compress.strip_lines.join(separator)
|
69
85
|
end
|
70
86
|
|
71
87
|
# @private
|
@@ -94,7 +110,7 @@ module Doing
|
|
94
110
|
## @param lines [Array] Array of strings
|
95
111
|
##
|
96
112
|
def append(lines)
|
97
|
-
concat(lines)
|
113
|
+
concat(lines.utf8)
|
98
114
|
replace compress
|
99
115
|
end
|
100
116
|
|
@@ -105,7 +121,7 @@ module Doing
|
|
105
121
|
## newlines will be split
|
106
122
|
##
|
107
123
|
def append_string(input)
|
108
|
-
concat(input.split(/\n/).map(&:strip))
|
124
|
+
concat(input.utf8.split(/\n/).map(&:strip))
|
109
125
|
replace compress
|
110
126
|
end
|
111
127
|
end
|
@@ -45,7 +45,9 @@ module Doing
|
|
45
45
|
|
46
46
|
def self.render(wwid, items, variables: {})
|
47
47
|
|
48
|
-
return
|
48
|
+
return unless items.good?
|
49
|
+
|
50
|
+
config = Doing.settings
|
49
51
|
|
50
52
|
opt = variables[:options]
|
51
53
|
trigger = opt[:output]
|
@@ -78,7 +80,7 @@ module Doing
|
|
78
80
|
title = "#{title} @section(#{i.section})" unless variables[:is_single]
|
79
81
|
|
80
82
|
tags.concat(i.tag_array).sort!.uniq!
|
81
|
-
flagged = day_flagged = true if i.tags?(
|
83
|
+
flagged = day_flagged = true if i.tags?(config['marker_tag'])
|
82
84
|
|
83
85
|
interval = wwid.get_interval(i, record: true) if i.title =~ /@done\((\d{4}-\d\d-\d\d \d\d:\d\d.*?)\)/ && opt[:times]
|
84
86
|
interval ||= false
|
@@ -123,8 +125,8 @@ module Doing
|
|
123
125
|
end
|
124
126
|
|
125
127
|
|
126
|
-
template = if
|
127
|
-
IO.read(File.expand_path(
|
128
|
+
template = if config['export_templates']['dayone'] && File.exist?(File.expand_path(config['export_templates']['dayone']))
|
129
|
+
IO.read(File.expand_path(config['export_templates']['dayone']))
|
128
130
|
else
|
129
131
|
self.template('dayone')
|
130
132
|
end
|
@@ -144,8 +146,8 @@ module Doing
|
|
144
146
|
starred: hsh[:starred])
|
145
147
|
end
|
146
148
|
when :entries
|
147
|
-
entry_template = if
|
148
|
-
IO.read(File.expand_path(
|
149
|
+
entry_template = if config['export_templates']['dayone_entry'] && File.exist?(File.expand_path(config['export_templates']['dayone_entry']))
|
150
|
+
IO.read(File.expand_path(config['export_templates']['dayone_entry']))
|
149
151
|
else
|
150
152
|
self.template('dayone-entry')
|
151
153
|
end
|
@@ -58,14 +58,14 @@ module Doing
|
|
58
58
|
}
|
59
59
|
end
|
60
60
|
|
61
|
-
template = if
|
62
|
-
IO.read(File.expand_path(
|
61
|
+
template = if Doing.setting('export_templates.haml') && File.exist?(File.expand_path(Doing.setting('export_templates.haml')))
|
62
|
+
IO.read(File.expand_path(Doing.setting('export_templates.haml')))
|
63
63
|
else
|
64
64
|
self.template('html')
|
65
65
|
end
|
66
66
|
|
67
|
-
style = if
|
68
|
-
IO.read(File.expand_path(
|
67
|
+
style = if Doing.setting('export_templates.css') && File.exist?(File.expand_path(Doing.setting('export_templates.css')))
|
68
|
+
IO.read(File.expand_path(Doing.setting('export_templates.css')))
|
69
69
|
else
|
70
70
|
self.template('css')
|
71
71
|
end
|
@@ -29,16 +29,12 @@ module Doing
|
|
29
29
|
max = last_date.strftime('%F')
|
30
30
|
min = items[0].date.strftime('%F')
|
31
31
|
items.each_with_index do |i, index|
|
32
|
-
|
33
|
-
|
34
|
-
note = i.note.map { |line| line.force_encoding('utf-8').strip } if i.note
|
35
|
-
else
|
36
|
-
title = i.title
|
37
|
-
note = i.note.map { |line| line.strip } if i.note
|
38
|
-
end
|
32
|
+
title = i.title.utf8
|
33
|
+
note = i.note.utf8
|
39
34
|
|
40
35
|
end_date = i.end_date || ''
|
41
36
|
interval = wwid.get_interval(i, formatted: false) || 0
|
37
|
+
duration = i.duration || 0
|
42
38
|
note ||= ''
|
43
39
|
|
44
40
|
tags = []
|
@@ -49,14 +45,15 @@ module Doing
|
|
49
45
|
attributes[tag[0]] = tag[1] if tag[1]
|
50
46
|
end
|
51
47
|
|
52
|
-
|
53
|
-
|
48
|
+
case opt[:output]
|
49
|
+
when 'json'
|
54
50
|
i = {
|
55
51
|
date: i.date,
|
56
52
|
end_date: end_date,
|
57
53
|
title: title.strip, #+ " #{note}"
|
58
|
-
note: note.
|
54
|
+
note: note.to_s(prefix: ''),
|
59
55
|
time: interval.time_string(format: :clock),
|
56
|
+
duration: duration.time_string(format: :clock),
|
60
57
|
tags: tags
|
61
58
|
}
|
62
59
|
|
@@ -64,7 +61,7 @@ module Doing
|
|
64
61
|
|
65
62
|
items_out << i
|
66
63
|
|
67
|
-
|
64
|
+
when 'timeline'
|
68
65
|
new_item = {
|
69
66
|
'id' => index + 1,
|
70
67
|
'content' => title.strip, #+ " #{note}"
|
@@ -74,26 +71,28 @@ module Doing
|
|
74
71
|
'style' => 'color:#4c566b;background-color:#d8dee9;'
|
75
72
|
}
|
76
73
|
|
77
|
-
|
78
|
-
if interval && interval.to_i > 0
|
74
|
+
if interval.to_i&.positive?
|
79
75
|
new_item['end'] = end_date.strftime('%F %T')
|
80
76
|
if interval.to_i > 3600
|
81
77
|
new_item['type'] = 'range'
|
82
78
|
new_item['style'] = 'color:white;background-color:#a2bf8a;'
|
83
79
|
end
|
84
80
|
end
|
85
|
-
new_item['style'] = 'color:white;background-color:#f7921e;' if i.tags?(
|
81
|
+
new_item['style'] = 'color:white;background-color:#f7921e;' if i.tags?(Doing.setting('marker_tag'))
|
86
82
|
items_out.push(new_item)
|
87
83
|
end
|
88
84
|
end
|
89
|
-
|
85
|
+
case opt[:output]
|
86
|
+
when 'json'
|
90
87
|
Doing.logger.debug('JSON Export:', "#{items_out.count} items output to JSON")
|
91
88
|
JSON.pretty_generate({
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
89
|
+
'section' => variables[:page_title],
|
90
|
+
'items' => items_out,
|
91
|
+
'timers' => wwid.tag_times(format: :json,
|
92
|
+
sort_by: opt[:sort_tags],
|
93
|
+
sort_order: opt[:tag_order])
|
94
|
+
})
|
95
|
+
when 'timeline'
|
97
96
|
template = <<~EOTEMPLATE
|
98
97
|
<!doctype html>
|
99
98
|
<html>
|
@@ -66,8 +66,8 @@ module Doing
|
|
66
66
|
}
|
67
67
|
end
|
68
68
|
|
69
|
-
template = if
|
70
|
-
IO.read(File.expand_path(
|
69
|
+
template = if Doing.setting('export_templates.markdown') && File.exist?(File.expand_path(Doing.setting('export_templates.markdown')))
|
70
|
+
IO.read(File.expand_path(Doing.setting('export_templates.markdown')))
|
71
71
|
else
|
72
72
|
self.template(nil)
|
73
73
|
end
|