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.
Files changed (73) hide show
  1. checksums.yaml +4 -4
  2. data/AUTHORS +19 -0
  3. data/CHANGELOG.md +616 -0
  4. data/COMMANDS.md +1181 -0
  5. data/Gemfile +2 -0
  6. data/Gemfile.lock +110 -0
  7. data/LICENSE +23 -0
  8. data/README.md +15 -699
  9. data/Rakefile +79 -0
  10. data/_config.yml +1 -0
  11. data/bin/doing +1055 -494
  12. data/doing.gemspec +34 -0
  13. data/doing.rdoc +1839 -0
  14. data/example_plugin.rb +209 -0
  15. data/generate_completions.sh +5 -0
  16. data/img/doing-colors.jpg +0 -0
  17. data/img/doing-printf-wrap-800.jpg +0 -0
  18. data/img/doing-show-note-formatting-800.jpg +0 -0
  19. data/lib/completion/_doing.zsh +203 -0
  20. data/lib/completion/doing.bash +449 -0
  21. data/lib/completion/doing.fish +329 -0
  22. data/lib/doing/array.rb +8 -0
  23. data/lib/doing/cli_status.rb +70 -0
  24. data/lib/doing/colors.rb +136 -0
  25. data/lib/doing/configuration.rb +312 -0
  26. data/lib/doing/errors.rb +109 -0
  27. data/lib/doing/hash.rb +31 -0
  28. data/lib/doing/hooks.rb +59 -0
  29. data/lib/doing/item.rb +155 -0
  30. data/lib/doing/log_adapter.rb +344 -0
  31. data/lib/doing/markdown_document_listener.rb +174 -0
  32. data/lib/doing/note.rb +59 -0
  33. data/lib/doing/pager.rb +95 -0
  34. data/lib/doing/plugin_manager.rb +208 -0
  35. data/lib/doing/plugins/export/csv_export.rb +48 -0
  36. data/lib/doing/plugins/export/html_export.rb +83 -0
  37. data/lib/doing/plugins/export/json_export.rb +140 -0
  38. data/lib/doing/plugins/export/markdown_export.rb +85 -0
  39. data/lib/doing/plugins/export/taskpaper_export.rb +34 -0
  40. data/lib/doing/plugins/export/template_export.rb +141 -0
  41. data/lib/doing/plugins/import/cal_to_json.scpt +0 -0
  42. data/lib/doing/plugins/import/calendar_import.rb +76 -0
  43. data/lib/doing/plugins/import/doing_import.rb +144 -0
  44. data/lib/doing/plugins/import/timing_import.rb +78 -0
  45. data/lib/doing/string.rb +348 -0
  46. data/lib/doing/symbol.rb +16 -0
  47. data/lib/doing/time.rb +18 -0
  48. data/lib/doing/util.rb +186 -0
  49. data/lib/doing/version.rb +1 -1
  50. data/lib/doing/wwid.rb +1868 -2349
  51. data/lib/doing/wwidfile.rb +117 -0
  52. data/lib/doing.rb +43 -3
  53. data/lib/examples/commands/autotag.rb +63 -0
  54. data/lib/examples/commands/wiki.rb +81 -0
  55. data/lib/examples/plugins/hooks.rb +22 -0
  56. data/lib/examples/plugins/say_export.rb +202 -0
  57. data/lib/examples/plugins/templates/wiki.css +169 -0
  58. data/lib/examples/plugins/templates/wiki.haml +27 -0
  59. data/lib/examples/plugins/templates/wiki_index.haml +18 -0
  60. data/lib/examples/plugins/wiki_export.rb +87 -0
  61. data/lib/templates/doing-markdown.erb +5 -0
  62. data/man/doing.1 +964 -0
  63. data/man/doing.1.html +711 -0
  64. data/man/doing.1.ronn +600 -0
  65. data/package-lock.json +3 -0
  66. data/rdoc_to_mmd.rb +42 -0
  67. data/rdocfixer.rb +13 -0
  68. data/scripts/generate_bash_completions.rb +211 -0
  69. data/scripts/generate_fish_completions.rb +204 -0
  70. data/scripts/generate_zsh_completions.rb +168 -0
  71. metadata +82 -7
  72. data/lib/doing/helpers.rb +0 -191
  73. 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
@@ -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