doing 1.0.93 → 2.0.6.pre
Sign up to get free protection for your applications and to get access to all the features.
- 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
|