doing 2.1.29 → 2.1.33
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/CHANGELOG.md +4962 -0
- data/Dockerfile-2.6 +3 -1
- data/Dockerfile-2.7 +4 -2
- data/Dockerfile-3.0 +3 -1
- data/Gemfile.lock +2 -67
- data/README.md +1 -1
- data/bash_profile +13 -0
- data/bin/commands/again.rb +1 -1
- data/bin/commands/archive.rb +3 -3
- data/bin/commands/cancel.rb +1 -1
- data/bin/commands/changes.rb +2 -2
- data/bin/commands/commands.rb +8 -8
- data/bin/commands/completion.rb +61 -19
- data/bin/commands/config.rb +20 -17
- 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 +4 -4
- data/bin/commands/recent.rb +4 -4
- data/bin/commands/show.rb +8 -8
- data/bin/commands/since.rb +1 -1
- data/bin/commands/tag_dir.rb +27 -3
- data/bin/commands/today.rb +1 -1
- data/bin/commands/view.rb +3 -3
- data/bin/commands/yesterday.rb +2 -2
- data/bin/doing +26 -135
- 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 +3 -3
- 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 +2 -2
- 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/docs/index.md +1 -1
- data/doing.gemspec +24 -23
- data/doing.rdoc +34 -13
- data/example_plugin.rb +7 -5
- data/inputrc +57 -0
- data/lib/completion/_doing.zsh +46 -46
- data/lib/completion/doing.bash +2 -2
- data/lib/completion/doing.fish +4 -4
- data/lib/doing/add_options.rb +117 -0
- data/lib/doing/array/array.rb +16 -0
- data/lib/doing/changelog/changes.rb +8 -6
- data/lib/doing/changelog/version.rb +11 -3
- data/lib/doing/completion/bash_completion.rb +12 -51
- data/lib/doing/completion/fish_completion.rb +17 -53
- data/lib/doing/completion/zsh_completion.rb +21 -54
- data/lib/doing/completion.rb +203 -17
- data/lib/doing/configuration.rb +12 -6
- 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/section.rb +1 -1
- data/lib/doing/string/highlight.rb +3 -4
- data/lib/doing/string/string.rb +8 -0
- data/lib/doing/types.rb +1 -1
- 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 +75 -76
- data/lib/doing.rb +58 -0
- data/lib/examples/commands/wiki.rb +27 -19
- data/lib/helpers/threaded_tests.rb +2 -0
- data/scripts/setting_replace.rb +11 -0
- metadata +107 -102
- data/.yardoc/checksums +0 -29
- data/.yardoc/complete +0 -0
- data/.yardoc/object_types +0 -0
- data/.yardoc/objects/root.dat +0 -0
- data/.yardoc/proxy_types +0 -0
|
@@ -37,7 +37,7 @@ module Doing
|
|
|
37
37
|
end
|
|
38
38
|
|
|
39
39
|
|
|
40
|
-
def compare(other, comp)
|
|
40
|
+
def compare(other, comp, inclusive: false)
|
|
41
41
|
case comp
|
|
42
42
|
when :older
|
|
43
43
|
if @maj <= other.maj
|
|
@@ -46,7 +46,11 @@ module Doing
|
|
|
46
46
|
elsif @maj == other.maj && (other.min.nil? || @min < other.min)
|
|
47
47
|
true
|
|
48
48
|
elsif @maj == other.maj && @min == other.min
|
|
49
|
-
other.patch.nil?
|
|
49
|
+
if other.patch.nil?
|
|
50
|
+
false
|
|
51
|
+
else
|
|
52
|
+
inclusive ? @patch <= other.patch : @patch < other.patch
|
|
53
|
+
end
|
|
50
54
|
else
|
|
51
55
|
false
|
|
52
56
|
end
|
|
@@ -60,7 +64,11 @@ module Doing
|
|
|
60
64
|
elsif @maj == other.maj && (other.min.nil? || @min > other.min)
|
|
61
65
|
true
|
|
62
66
|
elsif @maj == other.maj && @min == other.min
|
|
63
|
-
other.patch.nil?
|
|
67
|
+
if other.patch.nil?
|
|
68
|
+
false
|
|
69
|
+
else
|
|
70
|
+
inclusive ? @patch >= other.patch : @patch > other.patch
|
|
71
|
+
end
|
|
64
72
|
else
|
|
65
73
|
false
|
|
66
74
|
end
|
|
@@ -1,5 +1,8 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
module Doing
|
|
2
4
|
module Completion
|
|
5
|
+
# Generate completions for Bash
|
|
3
6
|
class BashCompletions
|
|
4
7
|
attr_accessor :commands, :global_options
|
|
5
8
|
|
|
@@ -11,7 +14,7 @@ module Doing
|
|
|
11
14
|
@commands.each_with_index do |cmd, i|
|
|
12
15
|
@bar.advance(status: cmd[:commands].first)
|
|
13
16
|
|
|
14
|
-
data = get_help_sections(cmd[:commands].first)
|
|
17
|
+
data = Completion.get_help_sections(cmd[:commands].first)
|
|
15
18
|
|
|
16
19
|
arg = data[:synopsis].join(' ').strip.split(/ /).last
|
|
17
20
|
case arg
|
|
@@ -26,7 +29,7 @@ module Doing
|
|
|
26
29
|
end
|
|
27
30
|
|
|
28
31
|
if data[:command_options]
|
|
29
|
-
options = parse_options(data[:command_options])
|
|
32
|
+
options = Completion.parse_options(data[:command_options])
|
|
30
33
|
out << command_function(cmd[:commands].first, options, type)
|
|
31
34
|
|
|
32
35
|
if first
|
|
@@ -119,56 +122,13 @@ module Doing
|
|
|
119
122
|
[func, logic]
|
|
120
123
|
end
|
|
121
124
|
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
def get_help_sections(command = '')
|
|
125
|
-
res = `doing help #{command}`.strip
|
|
126
|
-
scanned = res.scan(/(?m-i)^([A-Z ]+)\n([\s\S]*?)(?=\n+[A-Z]+|\Z)/)
|
|
127
|
-
sections = {}
|
|
128
|
-
scanned.each do |sect|
|
|
129
|
-
title = sect[0].downcase.strip.gsub(/ +/, '_').to_sym
|
|
130
|
-
content = sect[1].split(/\n/).map(&:strip).delete_if(&:empty?)
|
|
131
|
-
sections[title] = content
|
|
132
|
-
end
|
|
133
|
-
sections
|
|
134
|
-
end
|
|
135
|
-
|
|
136
|
-
def parse_option(option)
|
|
137
|
-
res = option.match(/(?:-(?<short>\w), )?(?:--(?:\[no-\])?(?<long>\w+)(?:=(?<arg>\w+))?)\s+- (?<desc>.*?)$/)
|
|
138
|
-
return nil unless res
|
|
139
|
-
{
|
|
140
|
-
short: res['short'],
|
|
141
|
-
long: res['long'],
|
|
142
|
-
arg: res[:arg],
|
|
143
|
-
description: res['desc'].short_desc
|
|
144
|
-
}
|
|
145
|
-
end
|
|
146
|
-
|
|
147
|
-
def parse_options(options)
|
|
148
|
-
options.map { |opt| parse_option(opt) }
|
|
149
|
-
end
|
|
150
|
-
|
|
151
|
-
def parse_command(command)
|
|
152
|
-
res = command.match(/^(?<cmd>[^, \t]+)(?<alias>(?:, [^, \t]+)*)?\s+- (?<desc>.*?)$/)
|
|
153
|
-
commands = [res['cmd']]
|
|
154
|
-
commands.concat(res['alias'].split(/, /).delete_if(&:empty?)) if res['alias']
|
|
155
|
-
|
|
156
|
-
{
|
|
157
|
-
commands: commands,
|
|
158
|
-
description: res['desc'].short_desc
|
|
159
|
-
}
|
|
160
|
-
end
|
|
161
|
-
|
|
162
|
-
def parse_commands(commands)
|
|
163
|
-
commands.map { |cmd| parse_command(cmd) }
|
|
164
|
-
end
|
|
165
|
-
|
|
166
125
|
def initialize
|
|
167
|
-
data = get_help_sections
|
|
168
|
-
@global_options = parse_options(data[:global_options])
|
|
169
|
-
@commands = parse_commands(data[:commands])
|
|
170
|
-
@bar = TTY::ProgressBar.new("\033[0;0;33mGenerating Bash completions: \033[0;35;40m[:bar] :status\033[0m", total: @commands.count, bar_format: :
|
|
171
|
-
|
|
126
|
+
data = Completion.get_help_sections
|
|
127
|
+
@global_options = Completion.parse_options(data[:global_options])
|
|
128
|
+
@commands = Completion.parse_commands(data[:commands])
|
|
129
|
+
@bar = TTY::ProgressBar.new("\033[0;0;33mGenerating Bash completions: \033[0;35;40m[:bar] :status\033[0m", total: @commands.count + 1, bar_format: :square, hide_cursor: true, status: 'Reading subcommands')
|
|
130
|
+
width = TTY::Screen.columns - 45
|
|
131
|
+
@bar.resize(width)
|
|
172
132
|
end
|
|
173
133
|
|
|
174
134
|
def generate_completions
|
|
@@ -176,6 +136,7 @@ module Doing
|
|
|
176
136
|
out = []
|
|
177
137
|
out << main_function
|
|
178
138
|
out << 'complete -F _doing doing'
|
|
139
|
+
@bar.advance(status: '✅')
|
|
179
140
|
@bar.finish
|
|
180
141
|
out.join("\n")
|
|
181
142
|
end
|
|
@@ -1,5 +1,8 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
module Doing
|
|
2
4
|
module Completion
|
|
5
|
+
# Generate completions for Fish
|
|
3
6
|
class FishCompletions
|
|
4
7
|
|
|
5
8
|
attr_accessor :commands, :global_options
|
|
@@ -137,53 +140,12 @@ module Doing
|
|
|
137
140
|
EOFUNCTIONS
|
|
138
141
|
end
|
|
139
142
|
|
|
140
|
-
def get_help_sections(command = '')
|
|
141
|
-
res = `doing help #{command}`.strip
|
|
142
|
-
scanned = res.scan(/(?m-i)^([A-Z ]+)\n([\s\S]*?)(?=\n+[A-Z]+|\Z)/)
|
|
143
|
-
sections = {}
|
|
144
|
-
scanned.each do |sect|
|
|
145
|
-
title = sect[0].downcase.strip.gsub(/ +/, '_').to_sym
|
|
146
|
-
content = sect[1].split(/\n/).map(&:strip).delete_if(&:empty?)
|
|
147
|
-
sections[title] = content
|
|
148
|
-
end
|
|
149
|
-
sections
|
|
150
|
-
end
|
|
151
|
-
|
|
152
|
-
def parse_option(option)
|
|
153
|
-
res = option.match(/(?:-(?<short>\w), )?(?:--(?:\[no-\])?(?<long>\w+)(?:=(?<arg>\w+))?)\s+- (?<desc>.*?)$/)
|
|
154
|
-
return nil unless res
|
|
155
|
-
|
|
156
|
-
{
|
|
157
|
-
short: res['short'],
|
|
158
|
-
long: res['long'],
|
|
159
|
-
arg: res['arg'],
|
|
160
|
-
description: res['desc'].short_desc
|
|
161
|
-
}
|
|
162
|
-
end
|
|
163
|
-
|
|
164
|
-
def parse_options(options)
|
|
165
|
-
options.map { |opt| parse_option(opt) }
|
|
166
|
-
end
|
|
167
|
-
|
|
168
|
-
def parse_command(command)
|
|
169
|
-
res = command.match(/^(?<cmd>[^, \t]+)(?<alias>(?:, [^, \t]+)*)?\s+- (?<desc>.*?)$/)
|
|
170
|
-
commands = [res['cmd']]
|
|
171
|
-
commands.concat(res['alias'].split(/, /).delete_if(&:empty?)) if res['alias']
|
|
172
|
-
|
|
173
|
-
{
|
|
174
|
-
commands: commands,
|
|
175
|
-
description: res['desc'].short_desc
|
|
176
|
-
}
|
|
177
|
-
end
|
|
178
|
-
|
|
179
|
-
def parse_commands(commands)
|
|
180
|
-
commands.map { |cmd| parse_command(cmd) }
|
|
181
|
-
end
|
|
182
|
-
|
|
183
143
|
def generate_subcommand_completions
|
|
184
144
|
out = []
|
|
185
|
-
@commands.
|
|
186
|
-
|
|
145
|
+
@commands.each do |cmd|
|
|
146
|
+
desc = Shellwords.escape(cmd[:description])
|
|
147
|
+
cmds = cmd[:commands].join(' ')
|
|
148
|
+
out << "complete -xc doing -n '__fish_doing_needs_command' -a '#{cmds}' -d #{desc}"
|
|
187
149
|
end
|
|
188
150
|
|
|
189
151
|
out.join("\n")
|
|
@@ -203,14 +165,14 @@ module Doing
|
|
|
203
165
|
|
|
204
166
|
@commands.each_with_index do |cmd, i|
|
|
205
167
|
@bar.advance(status: cmd[:commands].first)
|
|
206
|
-
data = get_help_sections(cmd[:commands].first)
|
|
168
|
+
data = Completion.get_help_sections(cmd[:commands].first)
|
|
207
169
|
|
|
208
170
|
if data[:synopsis].join(' ').strip.split(/ /).last =~ /(path|file)/i
|
|
209
171
|
out << "complete -c doing -F -n '__fish_doing_using_command #{cmd[:commands].join(" ")}'"
|
|
210
172
|
end
|
|
211
173
|
|
|
212
174
|
if data[:command_options]
|
|
213
|
-
parse_options(data[:command_options]).each do |option|
|
|
175
|
+
Completion.parse_options(data[:command_options]).each do |option|
|
|
214
176
|
next if option.nil?
|
|
215
177
|
|
|
216
178
|
arg = option[:arg] ? '-r' : ''
|
|
@@ -221,7 +183,7 @@ module Doing
|
|
|
221
183
|
need_export.concat(cmd[:commands]) if option[:long] == 'output'
|
|
222
184
|
need_bool.concat(cmd[:commands]) if option[:long] == 'bool'
|
|
223
185
|
need_case.concat(cmd[:commands]) if option[:long] == 'case'
|
|
224
|
-
|
|
186
|
+
need_sort.concat(cmd[:commands]) if option[:long] == 'sort'
|
|
225
187
|
need_tag_sort.concat(cmd[:commands]) if option[:long] == 'tag_sort'
|
|
226
188
|
need_tag_order.concat(cmd[:commands]) if option[:long] == 'tag_order'
|
|
227
189
|
need_age.concat(cmd[:commands]) if option[:long] == 'age'
|
|
@@ -267,11 +229,12 @@ module Doing
|
|
|
267
229
|
end
|
|
268
230
|
|
|
269
231
|
def initialize
|
|
270
|
-
data = get_help_sections
|
|
271
|
-
@global_options = parse_options(data[:global_options])
|
|
272
|
-
@commands = parse_commands(data[:commands])
|
|
273
|
-
@bar = TTY::ProgressBar.new("\033[0;0;33mGenerating Fish completions: \033[0;35;40m[:bar] :status\033[0m", total: @commands.count, bar_format: :
|
|
274
|
-
|
|
232
|
+
data = Completion.get_help_sections
|
|
233
|
+
@global_options = Completion.parse_options(data[:global_options])
|
|
234
|
+
@commands = Completion.parse_commands(data[:commands])
|
|
235
|
+
@bar = TTY::ProgressBar.new("\033[0;0;33mGenerating Fish completions: \033[0;35;40m[:bar] :status\033[0m", total: @commands.count + 1, bar_format: :square, hide_cursor: true, status: 'processing subcommands')
|
|
236
|
+
width = TTY::Screen.columns - 45
|
|
237
|
+
@bar.resize(width)
|
|
275
238
|
end
|
|
276
239
|
|
|
277
240
|
def generate_completions
|
|
@@ -280,6 +243,7 @@ module Doing
|
|
|
280
243
|
out << generate_helpers
|
|
281
244
|
out << generate_subcommand_completions
|
|
282
245
|
out << generate_subcommand_option_completions
|
|
246
|
+
@bar.advance(status: '✅')
|
|
283
247
|
@bar.finish
|
|
284
248
|
out.join("\n")
|
|
285
249
|
end
|
|
@@ -1,7 +1,15 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
module Doing
|
|
2
4
|
module Completion
|
|
3
|
-
class
|
|
5
|
+
class ::String
|
|
6
|
+
def sanitize
|
|
7
|
+
gsub(/'/, '\\\'').gsub(/\[/, '(').gsub(/\]/, ')')
|
|
8
|
+
end
|
|
9
|
+
end
|
|
4
10
|
|
|
11
|
+
# Generate completions for zsh
|
|
12
|
+
class ZshCompletions
|
|
5
13
|
attr_accessor :commands, :global_options
|
|
6
14
|
|
|
7
15
|
def generate_helpers
|
|
@@ -34,53 +42,11 @@ module Doing
|
|
|
34
42
|
}
|
|
35
43
|
|
|
36
44
|
EOFUNCTIONS
|
|
45
|
+
@bar.advance(status: '✅')
|
|
37
46
|
@bar.finish
|
|
38
47
|
out
|
|
39
48
|
end
|
|
40
49
|
|
|
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
|
-
commands = [res['cmd']]
|
|
72
|
-
commands.concat(res['alias'].split(/, /).delete_if(&:empty?)) if res['alias']
|
|
73
|
-
|
|
74
|
-
{
|
|
75
|
-
commands: commands,
|
|
76
|
-
description: res['desc'].short_desc
|
|
77
|
-
}
|
|
78
|
-
end
|
|
79
|
-
|
|
80
|
-
def parse_commands(commands)
|
|
81
|
-
commands.map { |cmd| parse_command(cmd) }
|
|
82
|
-
end
|
|
83
|
-
|
|
84
50
|
def generate_subcommand_completions
|
|
85
51
|
out = []
|
|
86
52
|
@commands.each_with_index do |cmd, i|
|
|
@@ -98,19 +64,19 @@ module Doing
|
|
|
98
64
|
@commands.each_with_index do |cmd, i|
|
|
99
65
|
@bar.advance(status: cmd[:commands].first)
|
|
100
66
|
|
|
101
|
-
data = get_help_sections(cmd[:commands].first)
|
|
67
|
+
data = Completion.get_help_sections(cmd[:commands].first)
|
|
102
68
|
option_arr = []
|
|
103
69
|
|
|
104
70
|
if data[:command_options]
|
|
105
|
-
parse_options(data[:command_options]).each do |option|
|
|
71
|
+
Completion.parse_options(data[:command_options]).each do |option|
|
|
106
72
|
next if option.nil?
|
|
107
73
|
|
|
108
|
-
arg = option[:arg] ?
|
|
74
|
+
arg = option[:arg] ? ":#{option[:arg]}:" : ''
|
|
109
75
|
|
|
110
76
|
option_arr << if option[:short]
|
|
111
|
-
%({-#{option[:short]}
|
|
77
|
+
%({'(--#{option[:long]})-#{option[:short]}','(-#{option[:short]})--#{option[:long]}'}"[#{option[:description].sanitize}]#{arg}")
|
|
112
78
|
else
|
|
113
|
-
%("
|
|
79
|
+
%("--#{option[:long]}[#{option[:description].sanitize}]#{arg}")
|
|
114
80
|
end
|
|
115
81
|
end
|
|
116
82
|
end
|
|
@@ -124,11 +90,12 @@ module Doing
|
|
|
124
90
|
end
|
|
125
91
|
|
|
126
92
|
def initialize
|
|
127
|
-
data = get_help_sections
|
|
128
|
-
@global_options = parse_options(data[:global_options])
|
|
129
|
-
@commands = parse_commands(data[:commands])
|
|
130
|
-
@bar = TTY::ProgressBar.new(" \033[0;0;33mGenerating Zsh completions: \033[0;35;40m[:bar] :status\033[0m", total: @commands.count, bar_format: :
|
|
131
|
-
|
|
93
|
+
data = Completion.get_help_sections
|
|
94
|
+
@global_options = Completion.parse_options(data[:global_options])
|
|
95
|
+
@commands = Completion.parse_commands(data[:commands])
|
|
96
|
+
@bar = TTY::ProgressBar.new(" \033[0;0;33mGenerating Zsh completions: \033[0;35;40m[:bar] :status\033[0m", total: @commands.count + 1, bar_format: :square, hide_cursor: true, status: 'processing subcommands')
|
|
97
|
+
width = TTY::Screen.columns - 45
|
|
98
|
+
@bar.resize(width)
|
|
132
99
|
end
|
|
133
100
|
|
|
134
101
|
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/configuration.rb
CHANGED
|
@@ -156,7 +156,13 @@ module Doing
|
|
|
156
156
|
##
|
|
157
157
|
## @return [String] file path
|
|
158
158
|
##
|
|
159
|
-
def choose_config(create: false)
|
|
159
|
+
def choose_config(create: false, local: false)
|
|
160
|
+
if local && create
|
|
161
|
+
res = File.expand_path('.doingrc')
|
|
162
|
+
FileUtils.touch(res)
|
|
163
|
+
return res
|
|
164
|
+
end
|
|
165
|
+
|
|
160
166
|
return @config_file if @force_answer
|
|
161
167
|
|
|
162
168
|
if @additional_configs&.count&.positive? || create
|
|
@@ -220,9 +226,9 @@ module Doing
|
|
|
220
226
|
return nil unless create
|
|
221
227
|
end
|
|
222
228
|
|
|
223
|
-
resolved = real_path.count.positive? ? "Resolved #{real_path.join('
|
|
229
|
+
resolved = real_path.count.positive? ? "Resolved #{real_path.join('.')}, but " : ''
|
|
224
230
|
Doing.logger.log_now(:warn, "#{resolved}#{path} is unknown")
|
|
225
|
-
new_path = [*real_path, path, *paths].join('
|
|
231
|
+
new_path = [*real_path, path, *paths].join('.')
|
|
226
232
|
Doing.logger.log_now(:warn, "Continuing will create the path #{new_path}")
|
|
227
233
|
res = Prompt.yn('Key path not found, create it?', default_response: true)
|
|
228
234
|
raise InvalidArgument, 'Invalid key path' unless res
|
|
@@ -374,7 +380,7 @@ module Doing
|
|
|
374
380
|
deprecated = true
|
|
375
381
|
config['editors']['default'] ||= config['editor']
|
|
376
382
|
config.delete('editor')
|
|
377
|
-
Doing.logger.debug('Deprecated:', "config key 'editor' is now 'editors
|
|
383
|
+
Doing.logger.debug('Deprecated:', "config key 'editor' is now 'editors.default', please update your config.")
|
|
378
384
|
end
|
|
379
385
|
|
|
380
386
|
if config.key?('config_editor_app') && !config['editors']['config']
|
|
@@ -382,7 +388,7 @@ module Doing
|
|
|
382
388
|
config['editors']['config'] = config['config_editor_app']
|
|
383
389
|
config.delete('config_editor_app')
|
|
384
390
|
Doing.logger.debug('Deprecated:',
|
|
385
|
-
"config key 'config_editor_app' is now 'editors
|
|
391
|
+
"config key 'config_editor_app' is now 'editors.config', please update your config.")
|
|
386
392
|
end
|
|
387
393
|
|
|
388
394
|
if config.key?('editor_app') && !config['editors']['doing_file']
|
|
@@ -390,7 +396,7 @@ module Doing
|
|
|
390
396
|
config['editors']['doing_file'] = config['editor_app']
|
|
391
397
|
config.delete('editor_app')
|
|
392
398
|
Doing.logger.debug('Deprecated:',
|
|
393
|
-
"config key 'editor_app' is now 'editors
|
|
399
|
+
"config key 'editor_app' is now 'editors.doing_file', please update your config.")
|
|
394
400
|
end
|
|
395
401
|
|
|
396
402
|
Doing.logger.warn('Deprecated:', 'outdated keys found, please run `doing config --update`.') if deprecated
|