doing 1.0.92 → 2.0.5.pre

Sign up to get free protection for your applications and to get access to all the features.
Files changed (73) hide show
  1. checksums.yaml +4 -4
  2. data/AUTHORS +19 -0
  3. data/CHANGELOG.md +596 -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 +1012 -486
  12. data/doing.fish +278 -0
  13. data/doing.gemspec +34 -0
  14. data/doing.rdoc +1759 -0
  15. data/example_plugin.rb +209 -0
  16. data/generate_completions.sh +4 -0
  17. data/img/doing-colors.jpg +0 -0
  18. data/img/doing-printf-wrap-800.jpg +0 -0
  19. data/img/doing-show-note-formatting-800.jpg +0 -0
  20. data/lib/completion/_doing.zsh +151 -0
  21. data/lib/completion/doing.bash +416 -0
  22. data/lib/completion/doing.fish +278 -0
  23. data/lib/doing/array.rb +8 -0
  24. data/lib/doing/cli_status.rb +66 -0
  25. data/lib/doing/colors.rb +136 -0
  26. data/lib/doing/configuration.rb +312 -0
  27. data/lib/doing/errors.rb +102 -0
  28. data/lib/doing/hash.rb +31 -0
  29. data/lib/doing/hooks.rb +59 -0
  30. data/lib/doing/item.rb +155 -0
  31. data/lib/doing/log_adapter.rb +342 -0
  32. data/lib/doing/markdown_document_listener.rb +174 -0
  33. data/lib/doing/note.rb +59 -0
  34. data/lib/doing/pager.rb +95 -0
  35. data/lib/doing/plugin_manager.rb +208 -0
  36. data/lib/doing/plugins/export/csv_export.rb +48 -0
  37. data/lib/doing/plugins/export/html_export.rb +83 -0
  38. data/lib/doing/plugins/export/json_export.rb +140 -0
  39. data/lib/doing/plugins/export/markdown_export.rb +85 -0
  40. data/lib/doing/plugins/export/taskpaper_export.rb +34 -0
  41. data/lib/doing/plugins/export/template_export.rb +141 -0
  42. data/lib/doing/plugins/import/cal_to_json.scpt +0 -0
  43. data/lib/doing/plugins/import/calendar_import.rb +76 -0
  44. data/lib/doing/plugins/import/doing_import.rb +144 -0
  45. data/lib/doing/plugins/import/timing_import.rb +78 -0
  46. data/lib/doing/string.rb +347 -0
  47. data/lib/doing/symbol.rb +16 -0
  48. data/lib/doing/time.rb +18 -0
  49. data/lib/doing/util.rb +186 -0
  50. data/lib/doing/version.rb +1 -1
  51. data/lib/doing/wwid.rb +1868 -2356
  52. data/lib/doing/wwidfile.rb +117 -0
  53. data/lib/doing.rb +44 -4
  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 +210 -0
  69. data/scripts/generate_fish_completions.rb +201 -0
  70. data/scripts/generate_zsh_completions.rb +164 -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,342 @@
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 = [
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 color_message(level, topic, message = nil, &block)
297
+ colors = Doing::Color
298
+ message = message(topic, message, &block)
299
+ prefix = ' '
300
+ topic_fg = colors.boldcyan
301
+ message_fg = colors.boldwhite
302
+
303
+ case level
304
+ when :debug
305
+ prefix = '> '.softpurple
306
+ topic_fg = colors.softpurple
307
+ message_fg = colors.white
308
+ when :warn
309
+ prefix = '> '.boldyellow
310
+ topic_fg = colors.boldyellow
311
+ message_fg = colors.yellow
312
+ when :error
313
+ prefix = '!!'.boldred
314
+ topic_fg = colors.flamingo
315
+ message_fg = colors.red
316
+ end
317
+
318
+ message.sub!(/^(\s*\S.*?): (.*?)$/) do
319
+ m = Regexp.last_match
320
+ msg = m[2] =~ /(\e\[[\d;]+m)/ ? msg : "#{message_fg}#{m[2]}"
321
+
322
+ "#{topic_fg}#{m[1]}#{colors.reset}: #{message_fg}#{m[2]}"
323
+ end
324
+
325
+ "#{prefix} #{message.highlight_tags}#{colors.reset}"
326
+ end
327
+
328
+ def output_results
329
+ total_counters
330
+
331
+ results = @results.select { |msg| write_message?(msg[:level]) }.uniq
332
+
333
+ if @logdev == $stdout
334
+ $stdout.print results.map {|res| res[:message].uncolor }.join("\n")
335
+ else
336
+ results.each do |msg|
337
+ @logdev.puts color_message(msg[:level], msg[:message])
338
+ end
339
+ end
340
+ end
341
+ end
342
+ 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