doing 1.0.93 → 2.0.6.pre
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/AUTHORS +19 -0
- data/CHANGELOG.md +616 -0
- data/COMMANDS.md +1181 -0
- data/Gemfile +2 -0
- data/Gemfile.lock +110 -0
- data/LICENSE +23 -0
- data/README.md +15 -699
- data/Rakefile +79 -0
- data/_config.yml +1 -0
- data/bin/doing +1055 -494
- data/doing.gemspec +34 -0
- data/doing.rdoc +1839 -0
- data/example_plugin.rb +209 -0
- data/generate_completions.sh +5 -0
- data/img/doing-colors.jpg +0 -0
- data/img/doing-printf-wrap-800.jpg +0 -0
- data/img/doing-show-note-formatting-800.jpg +0 -0
- data/lib/completion/_doing.zsh +203 -0
- data/lib/completion/doing.bash +449 -0
- data/lib/completion/doing.fish +329 -0
- data/lib/doing/array.rb +8 -0
- data/lib/doing/cli_status.rb +70 -0
- data/lib/doing/colors.rb +136 -0
- data/lib/doing/configuration.rb +312 -0
- data/lib/doing/errors.rb +109 -0
- data/lib/doing/hash.rb +31 -0
- data/lib/doing/hooks.rb +59 -0
- data/lib/doing/item.rb +155 -0
- data/lib/doing/log_adapter.rb +344 -0
- data/lib/doing/markdown_document_listener.rb +174 -0
- data/lib/doing/note.rb +59 -0
- data/lib/doing/pager.rb +95 -0
- data/lib/doing/plugin_manager.rb +208 -0
- data/lib/doing/plugins/export/csv_export.rb +48 -0
- data/lib/doing/plugins/export/html_export.rb +83 -0
- data/lib/doing/plugins/export/json_export.rb +140 -0
- data/lib/doing/plugins/export/markdown_export.rb +85 -0
- data/lib/doing/plugins/export/taskpaper_export.rb +34 -0
- data/lib/doing/plugins/export/template_export.rb +141 -0
- data/lib/doing/plugins/import/cal_to_json.scpt +0 -0
- data/lib/doing/plugins/import/calendar_import.rb +76 -0
- data/lib/doing/plugins/import/doing_import.rb +144 -0
- data/lib/doing/plugins/import/timing_import.rb +78 -0
- data/lib/doing/string.rb +348 -0
- data/lib/doing/symbol.rb +16 -0
- data/lib/doing/time.rb +18 -0
- data/lib/doing/util.rb +186 -0
- data/lib/doing/version.rb +1 -1
- data/lib/doing/wwid.rb +1868 -2349
- data/lib/doing/wwidfile.rb +117 -0
- data/lib/doing.rb +43 -3
- data/lib/examples/commands/autotag.rb +63 -0
- data/lib/examples/commands/wiki.rb +81 -0
- data/lib/examples/plugins/hooks.rb +22 -0
- data/lib/examples/plugins/say_export.rb +202 -0
- data/lib/examples/plugins/templates/wiki.css +169 -0
- data/lib/examples/plugins/templates/wiki.haml +27 -0
- data/lib/examples/plugins/templates/wiki_index.haml +18 -0
- data/lib/examples/plugins/wiki_export.rb +87 -0
- data/lib/templates/doing-markdown.erb +5 -0
- data/man/doing.1 +964 -0
- data/man/doing.1.html +711 -0
- data/man/doing.1.ronn +600 -0
- data/package-lock.json +3 -0
- data/rdoc_to_mmd.rb +42 -0
- data/rdocfixer.rb +13 -0
- data/scripts/generate_bash_completions.rb +211 -0
- data/scripts/generate_fish_completions.rb +204 -0
- data/scripts/generate_zsh_completions.rb +168 -0
- metadata +82 -7
- data/lib/doing/helpers.rb +0 -191
- data/lib/doing/markdown_export.rb +0 -16
@@ -0,0 +1,344 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Doing
|
4
|
+
##
|
5
|
+
## @brief Log adapter
|
6
|
+
##
|
7
|
+
class LogAdapter
|
8
|
+
attr_writer :logdev, :max_length
|
9
|
+
|
10
|
+
attr_reader :messages, :level, :results
|
11
|
+
|
12
|
+
TOPIC_WIDTH = 12
|
13
|
+
|
14
|
+
LOG_LEVELS = {
|
15
|
+
debug: ::Logger::DEBUG,
|
16
|
+
info: ::Logger::INFO,
|
17
|
+
warn: ::Logger::WARN,
|
18
|
+
error: ::Logger::ERROR
|
19
|
+
}.freeze
|
20
|
+
|
21
|
+
COUNT_KEYS = %i[
|
22
|
+
added_tags
|
23
|
+
removed_tags
|
24
|
+
added
|
25
|
+
updated
|
26
|
+
deleted
|
27
|
+
completed
|
28
|
+
archived
|
29
|
+
moved
|
30
|
+
completed_archived
|
31
|
+
skipped
|
32
|
+
].freeze
|
33
|
+
|
34
|
+
#
|
35
|
+
# @brief Create a new instance of a log writer
|
36
|
+
#
|
37
|
+
# @param level (optional, symbol) the log level
|
38
|
+
#
|
39
|
+
def initialize(level = :info)
|
40
|
+
@messages = []
|
41
|
+
@counters = {}
|
42
|
+
COUNT_KEYS.each { |key| @counters[key] = { tag: [], count: 0 } }
|
43
|
+
@results = []
|
44
|
+
@logdev = $stderr
|
45
|
+
@max_length = `tput cols`.strip.to_i - 5 || 85
|
46
|
+
self.log_level = level
|
47
|
+
end
|
48
|
+
|
49
|
+
#
|
50
|
+
# @brief Set the log level on the writer
|
51
|
+
#
|
52
|
+
# @param level (symbol) the log level
|
53
|
+
#
|
54
|
+
# @return nothing
|
55
|
+
#
|
56
|
+
def log_level=(level)
|
57
|
+
level ||= 'info'
|
58
|
+
level = level.to_s
|
59
|
+
if level.is_a?(String) && level =~ /^([ewid]\w+|[0123])$/
|
60
|
+
level = case level
|
61
|
+
when /^[e0]/
|
62
|
+
:error
|
63
|
+
when /^[w1]/
|
64
|
+
:warn
|
65
|
+
when /^[i2]/
|
66
|
+
:info
|
67
|
+
when /^[d3]/
|
68
|
+
:debug
|
69
|
+
end
|
70
|
+
else
|
71
|
+
level = level.downcase.to_sym
|
72
|
+
end
|
73
|
+
|
74
|
+
@level = level
|
75
|
+
end
|
76
|
+
|
77
|
+
def adjust_verbosity(options = {})
|
78
|
+
if options[:quiet]
|
79
|
+
self.log_level = :error
|
80
|
+
elsif options[:verbose] || options[:debug]
|
81
|
+
self.log_level = :debug
|
82
|
+
end
|
83
|
+
log_now :debug, 'Logging at level:', @level.to_s
|
84
|
+
# log_now :debug, 'Doing Version:', Doing::VERSION
|
85
|
+
end
|
86
|
+
|
87
|
+
def format_counter(key, data)
|
88
|
+
case key
|
89
|
+
when :added_tags
|
90
|
+
['Tagged:', data[:message] || 'added %tags to %count %items']
|
91
|
+
when :removed_tags
|
92
|
+
['Untagged:', data[:message] || 'removed %tags from %count %items']
|
93
|
+
when :added
|
94
|
+
['Added:', data[:message] || 'added %count new %items']
|
95
|
+
when :updated
|
96
|
+
['Updated:', data[:message] || 'updated %count %items']
|
97
|
+
when :deleted
|
98
|
+
['Deleted:', data[:message] || 'deleted %count %items']
|
99
|
+
when :moved
|
100
|
+
['Moved:', data[:message] || 'moved %count %items']
|
101
|
+
when :completed
|
102
|
+
['Completed:', data[:message] || 'completed %count %items']
|
103
|
+
when :archived
|
104
|
+
['Archived:', data[:message] || 'archived %count %items']
|
105
|
+
when :completed_archived
|
106
|
+
['Archived:', data[:message] || 'completed and archived %count %items']
|
107
|
+
when :skipped
|
108
|
+
['Skipped:', data[:message] || '%count %items were unchanged']
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
def total_counters
|
113
|
+
@counters.each do |key, data|
|
114
|
+
next if data[:count].zero?
|
115
|
+
|
116
|
+
count = data[:count]
|
117
|
+
tags = data[:tag] ? data[:tag].uniq.map { |t| "@#{t}".cyan }.join(', ') : 'tags'
|
118
|
+
topic, m = format_counter(key, data)
|
119
|
+
message = m.dup
|
120
|
+
message.sub!(/%count/, count.to_s)
|
121
|
+
message.sub!(/%items/, count == 1 ? 'item' : 'items')
|
122
|
+
message.sub!(/%tags/, tags)
|
123
|
+
write(data[:level], topic, message)
|
124
|
+
end
|
125
|
+
end
|
126
|
+
|
127
|
+
def count(key, level: :info, count: 1, tag: nil, message: nil)
|
128
|
+
raise ArgumentError, 'invalid counter key' unless COUNT_KEYS.include?(key)
|
129
|
+
|
130
|
+
@counters[key][:count] += count
|
131
|
+
@counters[key][:tag].concat(tag).sort.uniq unless tag.nil?
|
132
|
+
@counters[key][:level] ||= level
|
133
|
+
@counters[key][:message] ||= message
|
134
|
+
end
|
135
|
+
|
136
|
+
#
|
137
|
+
# @brief Print a debug message
|
138
|
+
#
|
139
|
+
# @param topic the topic of the message
|
140
|
+
# @param message the message detail
|
141
|
+
#
|
142
|
+
# @return nothing
|
143
|
+
#
|
144
|
+
def debug(topic, message = nil, &block)
|
145
|
+
write(:debug, topic, message, &block)
|
146
|
+
end
|
147
|
+
|
148
|
+
#
|
149
|
+
# @brief Print a message
|
150
|
+
#
|
151
|
+
# @param topic the topic of the message, e.g.
|
152
|
+
# "Configuration file",
|
153
|
+
# "Deprecation", etc.
|
154
|
+
# @param message the message detail
|
155
|
+
#
|
156
|
+
# @return nothing
|
157
|
+
#
|
158
|
+
def info(topic, message = nil, &block)
|
159
|
+
write(:info, topic, message, &block)
|
160
|
+
end
|
161
|
+
|
162
|
+
#
|
163
|
+
# @brief Print a message
|
164
|
+
#
|
165
|
+
# @param topic the topic of the message, e.g.
|
166
|
+
# "Configuration file",
|
167
|
+
# "Deprecation", etc.
|
168
|
+
# @param message the message detail
|
169
|
+
#
|
170
|
+
# @return nothing
|
171
|
+
#
|
172
|
+
def warn(topic, message = nil, &block)
|
173
|
+
write(:warn, topic, message, &block)
|
174
|
+
end
|
175
|
+
|
176
|
+
#
|
177
|
+
# @brief Print an error message
|
178
|
+
#
|
179
|
+
# @param topic the topic of the message, e.g.
|
180
|
+
# "Configuration file",
|
181
|
+
# "Deprecation", etc.
|
182
|
+
# @param message the message detail
|
183
|
+
#
|
184
|
+
# @return nothing
|
185
|
+
#
|
186
|
+
def error(topic, message = nil, &block)
|
187
|
+
write(:error, topic, message, &block)
|
188
|
+
end
|
189
|
+
|
190
|
+
#
|
191
|
+
# @brief Print an error message and immediately
|
192
|
+
# abort the process
|
193
|
+
#
|
194
|
+
# @param topic the topic of the message, e.g.
|
195
|
+
# "Configuration file",
|
196
|
+
# "Deprecation", etc.
|
197
|
+
# @param message the message detail (can be
|
198
|
+
# omitted)
|
199
|
+
#
|
200
|
+
# @return nothing
|
201
|
+
#
|
202
|
+
def abort_with(topic, message = nil, &block)
|
203
|
+
error(topic, message, &block)
|
204
|
+
abort
|
205
|
+
end
|
206
|
+
|
207
|
+
# Internal: Build a topic method
|
208
|
+
#
|
209
|
+
# @param topic the topic of the message, e.g.
|
210
|
+
# "Configuration file",
|
211
|
+
# "Deprecation", etc.
|
212
|
+
# @param message the message detail
|
213
|
+
#
|
214
|
+
# @return the formatted message
|
215
|
+
#
|
216
|
+
def message(topic, message = nil)
|
217
|
+
raise ArgumentError, 'block or message, not both' if block_given? && message
|
218
|
+
|
219
|
+
message = yield if block_given?
|
220
|
+
message = message.to_s.gsub(/\s+/, ' ')
|
221
|
+
|
222
|
+
return topic.ljust(TOPIC_WIDTH) if topic && message.strip.empty?
|
223
|
+
|
224
|
+
topic = formatted_topic(topic, colon: block_given?)
|
225
|
+
message.truncmiddle!(@max_length - TOPIC_WIDTH - 5)
|
226
|
+
out = topic + message
|
227
|
+
out.truncate!(@max_length) if @max_length.positive?
|
228
|
+
messages << out
|
229
|
+
out
|
230
|
+
end
|
231
|
+
|
232
|
+
#
|
233
|
+
# @brief Format the topic
|
234
|
+
#
|
235
|
+
# @param topic the topic of the message, e.g.
|
236
|
+
# "Configuration file",
|
237
|
+
# "Deprecation", etc.
|
238
|
+
# @param colon Separate with a colon?
|
239
|
+
#
|
240
|
+
# @return the formatted topic statement
|
241
|
+
#
|
242
|
+
def formatted_topic(topic, colon: false)
|
243
|
+
if colon
|
244
|
+
"#{topic}: ".rjust(TOPIC_WIDTH)
|
245
|
+
elsif topic =~ /:$/
|
246
|
+
"#{topic} ".rjust(TOPIC_WIDTH)
|
247
|
+
else
|
248
|
+
"#{topic} "
|
249
|
+
end
|
250
|
+
end
|
251
|
+
|
252
|
+
#
|
253
|
+
# @brief Check if the message should be written
|
254
|
+
# given the log level.
|
255
|
+
#
|
256
|
+
# @param level_of_message the Symbol level of
|
257
|
+
# message, one of :debug,
|
258
|
+
# :info, :warn, :error
|
259
|
+
#
|
260
|
+
# @return whether the message should be written.
|
261
|
+
#
|
262
|
+
def write_message?(level_of_message)
|
263
|
+
LOG_LEVELS.fetch(@level) <= LOG_LEVELS.fetch(level_of_message)
|
264
|
+
end
|
265
|
+
|
266
|
+
#
|
267
|
+
# @brief Log a message.
|
268
|
+
#
|
269
|
+
# @param level_of_message the Symbol level of
|
270
|
+
# message, one of :debug,
|
271
|
+
# :info, :warn, :error
|
272
|
+
# @param topic the String topic or full
|
273
|
+
# message
|
274
|
+
# @param message the String message
|
275
|
+
# (optional)
|
276
|
+
# @param block a block containing the
|
277
|
+
# message (optional)
|
278
|
+
#
|
279
|
+
# @return false if the message was not written
|
280
|
+
#
|
281
|
+
def write(level_of_message, topic, message = nil, &block)
|
282
|
+
@results << { level: level_of_message, message: message(topic, message, &block) }
|
283
|
+
true
|
284
|
+
end
|
285
|
+
|
286
|
+
def log_now(level, topic, message = nil, &block)
|
287
|
+
return false unless write_message?(level)
|
288
|
+
|
289
|
+
if @logdev == $stdout
|
290
|
+
@logdev.puts message(topic, message, &block)
|
291
|
+
else
|
292
|
+
@logdev.puts color_message(level, topic, message, &block)
|
293
|
+
end
|
294
|
+
end
|
295
|
+
|
296
|
+
def output_results
|
297
|
+
total_counters
|
298
|
+
|
299
|
+
results = @results.select { |msg| write_message?(msg[:level]) }.uniq
|
300
|
+
|
301
|
+
if @logdev == $stdout
|
302
|
+
$stdout.print results.map {|res| res[:message].uncolor }.join("\n")
|
303
|
+
else
|
304
|
+
results.each do |msg|
|
305
|
+
@logdev.puts color_message(msg[:level], msg[:message])
|
306
|
+
end
|
307
|
+
end
|
308
|
+
end
|
309
|
+
|
310
|
+
private
|
311
|
+
|
312
|
+
def color_message(level, topic, message = nil, &block)
|
313
|
+
colors = Doing::Color
|
314
|
+
message = message(topic, message, &block)
|
315
|
+
prefix = ' '
|
316
|
+
topic_fg = colors.boldcyan
|
317
|
+
message_fg = colors.boldwhite
|
318
|
+
|
319
|
+
case level
|
320
|
+
when :debug
|
321
|
+
prefix = '> '.softpurple
|
322
|
+
topic_fg = colors.softpurple
|
323
|
+
message_fg = colors.white
|
324
|
+
when :warn
|
325
|
+
prefix = '> '.boldyellow
|
326
|
+
topic_fg = colors.boldyellow
|
327
|
+
message_fg = colors.yellow
|
328
|
+
when :error
|
329
|
+
prefix = '!!'.boldred
|
330
|
+
topic_fg = colors.flamingo
|
331
|
+
message_fg = colors.red
|
332
|
+
end
|
333
|
+
|
334
|
+
message.sub!(/^(\s*\S.*?): (.*?)$/) do
|
335
|
+
m = Regexp.last_match
|
336
|
+
msg = m[2] =~ /(\e\[[\d;]+m)/ ? msg : "#{message_fg}#{m[2]}"
|
337
|
+
|
338
|
+
"#{topic_fg}#{m[1]}#{colors.reset}: #{message_fg}#{m[2]}"
|
339
|
+
end
|
340
|
+
|
341
|
+
"#{prefix} #{message.highlight_tags}#{colors.reset}"
|
342
|
+
end
|
343
|
+
end
|
344
|
+
end
|
@@ -0,0 +1,174 @@
|
|
1
|
+
require 'stringio'
|
2
|
+
require 'time'
|
3
|
+
require 'fileutils'
|
4
|
+
|
5
|
+
module GLI
|
6
|
+
module Commands
|
7
|
+
# DocumentListener class for GLI documentation generator
|
8
|
+
class MarkdownDocumentListener
|
9
|
+
def initialize(_global_options, _options, _arguments, app)
|
10
|
+
@exe = app.exe_name
|
11
|
+
if File.exist?('COMMANDS.md') # Back up existing README
|
12
|
+
FileUtils.mv('COMMANDS.md', 'COMMANDS.bak')
|
13
|
+
$stderr.puts "Backing up existing COMMANDS.md"
|
14
|
+
end
|
15
|
+
@io = File.new('COMMANDS.md', 'w')
|
16
|
+
@nest = '#'
|
17
|
+
@arg_name_formatter = GLI::Commands::HelpModules::ArgNameFormatter.new
|
18
|
+
@parent_command = []
|
19
|
+
end
|
20
|
+
|
21
|
+
def beginning
|
22
|
+
end
|
23
|
+
|
24
|
+
# Called when processing has completed
|
25
|
+
def ending
|
26
|
+
if File.exist?('CREDITS.md')
|
27
|
+
@io.puts IO.read('CREDITS.md')
|
28
|
+
@io.puts
|
29
|
+
end
|
30
|
+
|
31
|
+
if File.exist?('AUTHORS.md')
|
32
|
+
@io.puts IO.read('AUTHORS.md')
|
33
|
+
@io.puts
|
34
|
+
end
|
35
|
+
|
36
|
+
if File.exist?('LICENSE.md')
|
37
|
+
@io.puts IO.read('LICENSE.md')
|
38
|
+
@io.puts
|
39
|
+
end
|
40
|
+
@io.puts
|
41
|
+
@io.puts "Documentation generated #{Time.now.strftime('%Y-%m-%d %H:%M')}"
|
42
|
+
@io.puts
|
43
|
+
@io.close
|
44
|
+
end
|
45
|
+
|
46
|
+
# Gives you the program description
|
47
|
+
def program_desc(desc)
|
48
|
+
@io.puts "# #{@exe} CLI"
|
49
|
+
@io.puts
|
50
|
+
@io.puts desc
|
51
|
+
@io.puts
|
52
|
+
end
|
53
|
+
|
54
|
+
def program_long_desc(desc)
|
55
|
+
@io.puts "> #{desc}"
|
56
|
+
@io.puts
|
57
|
+
end
|
58
|
+
|
59
|
+
# Gives you the program version
|
60
|
+
def version(version)
|
61
|
+
@io.puts "*v#{version}*"
|
62
|
+
@io.puts
|
63
|
+
# Hacking in the overview file
|
64
|
+
if File.exist?('OVERVIEW.md')
|
65
|
+
@io.puts IO.read('OVERVIEW.md')
|
66
|
+
@io.puts
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
def options
|
71
|
+
if @nest.size == 1
|
72
|
+
@io.puts "## Global Options"
|
73
|
+
else
|
74
|
+
@io.puts header("Options", 1)
|
75
|
+
end
|
76
|
+
@io.puts
|
77
|
+
end
|
78
|
+
|
79
|
+
# Gives you a flag in the current context
|
80
|
+
def flag(name, aliases, desc, long_desc, default_value, arg_name, must_match, _type)
|
81
|
+
invocations = ([name] + Array(aliases)).map { |_| "`" + add_dashes(_) + "`" }.join(' | ')
|
82
|
+
usage = "#{invocations} #{arg_name || 'arg'}"
|
83
|
+
@io.puts header(usage, 2)
|
84
|
+
@io.puts
|
85
|
+
@io.puts String(desc).strip
|
86
|
+
@io.puts "\n*Default Value:* `#{default_value || 'None'}`\n" unless default_value.nil?
|
87
|
+
@io.puts "\n*Must Match:* `#{must_match.to_s}`\n" unless must_match.nil?
|
88
|
+
cmd_desc = String(long_desc).strip
|
89
|
+
@io.puts "> #{cmd_desc}\n" unless cmd_desc.length == 0
|
90
|
+
@io.puts
|
91
|
+
end
|
92
|
+
|
93
|
+
# Gives you a switch in the current context
|
94
|
+
def switch(name, aliases, desc, long_desc, negatable)
|
95
|
+
if negatable
|
96
|
+
name = "[no-]#{name}" if name.to_s.length > 1
|
97
|
+
aliases = aliases.map { |_| _.to_s.length > 1 ? "[no-]#{_}" : _ }
|
98
|
+
end
|
99
|
+
invocations = ([name] + aliases).map { |_| "`" + add_dashes(_).strip + "`" }.join('|')
|
100
|
+
@io.puts header("#{invocations}", 2)
|
101
|
+
@io.puts
|
102
|
+
@io.puts String(desc).strip
|
103
|
+
cmd_desc = String(long_desc).strip
|
104
|
+
@io.puts "\n> #{cmd_desc}\n" unless cmd_desc.length == 0
|
105
|
+
@io.puts
|
106
|
+
end
|
107
|
+
|
108
|
+
def end_options
|
109
|
+
end
|
110
|
+
|
111
|
+
def commands
|
112
|
+
@io.puts header("Commands", 1)
|
113
|
+
@io.puts
|
114
|
+
increment_nest
|
115
|
+
end
|
116
|
+
|
117
|
+
# Gives you a command in the current context and creates a new context of this command
|
118
|
+
def command(name, aliases, desc, long_desc, arg_name, arg_options)
|
119
|
+
@parent_command.push ([name] + aliases).join('|')
|
120
|
+
arg_name_fmt = @arg_name_formatter.format(arg_name, arg_options, [])
|
121
|
+
arg_name_fmt = " `#{arg_name_fmt.strip}`" if arg_name_fmt
|
122
|
+
@io.puts header("`$ #{@exe}` <mark>`#{@parent_command.join(' ')}`</mark>#{arg_name_fmt}", 1)
|
123
|
+
@io.puts
|
124
|
+
@io.puts "*#{String(desc).strip}*"
|
125
|
+
@io.puts
|
126
|
+
cmd_desc = String(long_desc).strip.split("\n").map { |_| "> #{_}" }.join("\n")
|
127
|
+
@io.puts "#{cmd_desc}\n\n" unless cmd_desc.length == 0
|
128
|
+
increment_nest
|
129
|
+
end
|
130
|
+
|
131
|
+
# Ends a command, and "pops" you back up one context
|
132
|
+
def end_command(_name)
|
133
|
+
@parent_command.pop
|
134
|
+
decrement_nest
|
135
|
+
@io.puts "* * * * * *\n\n" unless @nest.size > 2
|
136
|
+
end
|
137
|
+
|
138
|
+
# Gives you the name of the current command in the current context
|
139
|
+
def default_command(name)
|
140
|
+
@io.puts "#### [Default Command] #{name}" unless name.nil?
|
141
|
+
end
|
142
|
+
|
143
|
+
def end_commands
|
144
|
+
decrement_nest
|
145
|
+
end
|
146
|
+
|
147
|
+
private
|
148
|
+
|
149
|
+
def add_dashes(name)
|
150
|
+
name = "-#{name}"
|
151
|
+
name = "-#{name}" if name.length > 2
|
152
|
+
name
|
153
|
+
end
|
154
|
+
|
155
|
+
def header(content, increment)
|
156
|
+
if @nest.size + increment > 6
|
157
|
+
"**#{content}**"
|
158
|
+
else
|
159
|
+
"#{@nest}#{'#'*increment} #{content}"
|
160
|
+
end
|
161
|
+
end
|
162
|
+
|
163
|
+
def increment_nest(increment=1)
|
164
|
+
@nest = "#{@nest}#{'#'*increment}"
|
165
|
+
end
|
166
|
+
|
167
|
+
def decrement_nest(increment=1)
|
168
|
+
@nest.gsub!(/#{'#'*increment}$/, '')
|
169
|
+
end
|
170
|
+
end
|
171
|
+
end
|
172
|
+
end
|
173
|
+
|
174
|
+
GLI::Commands::Doc::FORMATS['markdown'] = GLI::Commands::MarkdownDocumentListener
|
data/lib/doing/note.rb
ADDED
@@ -0,0 +1,59 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Doing
|
4
|
+
##
|
5
|
+
## @brief This class describes an item note.
|
6
|
+
##
|
7
|
+
class Note < Array
|
8
|
+
def initialize(note = [])
|
9
|
+
super()
|
10
|
+
|
11
|
+
add(note) if note
|
12
|
+
end
|
13
|
+
|
14
|
+
def add(note, replace: false)
|
15
|
+
clear if replace
|
16
|
+
if note.is_a?(String)
|
17
|
+
append_string(note)
|
18
|
+
elsif note.is_a?(Array)
|
19
|
+
append(note)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def append(lines)
|
24
|
+
concat(lines)
|
25
|
+
replace compress
|
26
|
+
end
|
27
|
+
|
28
|
+
def append_string(input)
|
29
|
+
concat(input.split(/\n/).map(&:strip))
|
30
|
+
replace compress
|
31
|
+
end
|
32
|
+
|
33
|
+
def compress!
|
34
|
+
replace compress
|
35
|
+
end
|
36
|
+
|
37
|
+
def compress
|
38
|
+
delete_if { |l| l =~ /^\s*$/ || l =~ /^#/ }
|
39
|
+
end
|
40
|
+
|
41
|
+
def strip_lines!
|
42
|
+
replace strip_lines
|
43
|
+
end
|
44
|
+
|
45
|
+
def strip_lines
|
46
|
+
map(&:strip)
|
47
|
+
end
|
48
|
+
|
49
|
+
def to_s
|
50
|
+
compress.strip_lines.join("\n")
|
51
|
+
end
|
52
|
+
|
53
|
+
def equal?(other)
|
54
|
+
return false unless other.is_a?(Note)
|
55
|
+
|
56
|
+
to_s == other.to_s
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
data/lib/doing/pager.rb
ADDED
@@ -0,0 +1,95 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Doing
|
4
|
+
# Pagination
|
5
|
+
module Pager
|
6
|
+
class << self
|
7
|
+
def paginate
|
8
|
+
@paginate ||= false
|
9
|
+
end
|
10
|
+
|
11
|
+
def paginate=(should_paginate)
|
12
|
+
@paginate = should_paginate
|
13
|
+
end
|
14
|
+
|
15
|
+
def page(text)
|
16
|
+
unless @paginate
|
17
|
+
puts text
|
18
|
+
return
|
19
|
+
end
|
20
|
+
|
21
|
+
read_io, write_io = IO.pipe
|
22
|
+
|
23
|
+
input = $stdin
|
24
|
+
|
25
|
+
pid = Kernel.fork do
|
26
|
+
write_io.close
|
27
|
+
input.reopen(read_io)
|
28
|
+
read_io.close
|
29
|
+
|
30
|
+
# Wait until we have input before we start the pager
|
31
|
+
IO.select [input]
|
32
|
+
|
33
|
+
pager = which_pager
|
34
|
+
Doing.logger.debug('Pager:', "Using #{pager}")
|
35
|
+
begin
|
36
|
+
exec(pager.join(' '))
|
37
|
+
rescue SystemCallError => e
|
38
|
+
raise Errors::DoingStandardError, "Pager error, #{e}"
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
begin
|
43
|
+
read_io.close
|
44
|
+
write_io.write(text)
|
45
|
+
write_io.close
|
46
|
+
rescue SystemCallError => e
|
47
|
+
raise Errors::DoingStandardError, "Pager error, #{e}"
|
48
|
+
end
|
49
|
+
|
50
|
+
_, status = Process.waitpid2(pid)
|
51
|
+
status.success?
|
52
|
+
end
|
53
|
+
|
54
|
+
def which_pager
|
55
|
+
pagers = [ENV['GIT_PAGER'], ENV['PAGER']]
|
56
|
+
|
57
|
+
if Util.exec_available('git')
|
58
|
+
git_pager = `git config --get-all core.pager || true`.split.first
|
59
|
+
git_pager && pagers.push(git_pager)
|
60
|
+
end
|
61
|
+
|
62
|
+
pagers.concat(%w[bat less more pager])
|
63
|
+
|
64
|
+
pagers.select! do |f|
|
65
|
+
if f
|
66
|
+
if f.strip =~ /[ |]/
|
67
|
+
f
|
68
|
+
elsif f == 'most'
|
69
|
+
Doing.logger.warn('most not allowed as pager')
|
70
|
+
false
|
71
|
+
else
|
72
|
+
system "which #{f}", out: File::NULL, err: File::NULL
|
73
|
+
end
|
74
|
+
else
|
75
|
+
false
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
pg = pagers.first
|
80
|
+
args = case pg
|
81
|
+
when /^more$/
|
82
|
+
' -r'
|
83
|
+
when /^less$/
|
84
|
+
' -Xr'
|
85
|
+
when /^bat$/
|
86
|
+
' -p --pager="less -Xr"'
|
87
|
+
else
|
88
|
+
''
|
89
|
+
end
|
90
|
+
|
91
|
+
[pg, args]
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|