doing 2.1.30 → 2.1.31pre
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 +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
|