howzit 1.2.15 → 1.2.16
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/CHANGELOG.md +12 -0
- data/bin/howzit +193 -2
- data/lib/howzit/buildnote.rb +543 -0
- data/lib/howzit/buildnotes.rb +0 -1
- data/lib/howzit/colors.rb +2 -2
- data/lib/howzit/config.rb +128 -0
- data/lib/howzit/hash.rb +35 -0
- data/lib/howzit/prompt.rb +84 -11
- data/lib/howzit/stringutils.rb +54 -8
- data/lib/howzit/task.rb +22 -0
- data/lib/howzit/topic.rb +233 -0
- data/lib/howzit/util.rb +149 -0
- data/lib/howzit/version.rb +1 -1
- data/lib/howzit.rb +40 -5
- data/spec/ruby_gem_spec.rb +87 -17
- metadata +8 -2
data/lib/howzit/hash.rb
ADDED
@@ -0,0 +1,35 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Hash helpers
|
4
|
+
class ::Hash
|
5
|
+
##
|
6
|
+
## Freeze all values in a hash
|
7
|
+
##
|
8
|
+
## @return Hash with all values frozen
|
9
|
+
##
|
10
|
+
def deep_freeze
|
11
|
+
chilled = {}
|
12
|
+
each do |k, v|
|
13
|
+
chilled[k] = v.is_a?(Hash) ? v.deep_freeze : v.freeze
|
14
|
+
end
|
15
|
+
|
16
|
+
chilled.freeze
|
17
|
+
end
|
18
|
+
|
19
|
+
def deep_freeze!
|
20
|
+
replace deep_thaw.deep_freeze
|
21
|
+
end
|
22
|
+
|
23
|
+
def deep_thaw
|
24
|
+
chilled = {}
|
25
|
+
each do |k, v|
|
26
|
+
chilled[k] = v.is_a?(Hash) ? v.deep_thaw : v.dup
|
27
|
+
end
|
28
|
+
|
29
|
+
chilled.dup
|
30
|
+
end
|
31
|
+
|
32
|
+
def deep_thaw!
|
33
|
+
replace deep_thaw
|
34
|
+
end
|
35
|
+
end
|
data/lib/howzit/prompt.rb
CHANGED
@@ -3,17 +3,90 @@
|
|
3
3
|
module Howzit
|
4
4
|
# Command line prompt utils
|
5
5
|
module Prompt
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
6
|
+
class << self
|
7
|
+
def yn(prompt, default = true)
|
8
|
+
return default if !$stdout.isatty
|
9
|
+
|
10
|
+
system 'stty cbreak'
|
11
|
+
yn = color_single_options(default ? %w[Y n] : %w[y N])
|
12
|
+
$stdout.syswrite "\e[1;37m#{prompt} #{yn}\e[1;37m? \e[0m"
|
13
|
+
res = $stdin.sysread 1
|
14
|
+
res.chomp!
|
15
|
+
puts
|
16
|
+
system 'stty cooked'
|
17
|
+
res =~ /y/i
|
18
|
+
end
|
19
|
+
|
20
|
+
def color_single_options(choices = %w[y n])
|
21
|
+
out = []
|
22
|
+
choices.each do |choice|
|
23
|
+
case choice
|
24
|
+
when /[A-Z]/
|
25
|
+
out.push(Color.template("{bg}#{choice}{xg}"))
|
26
|
+
else
|
27
|
+
out.push(Color.template("{w}#{choice}"))
|
28
|
+
end
|
29
|
+
end
|
30
|
+
Color.template("{g}[#{out.join('/')}{g}]{x}")
|
31
|
+
end
|
32
|
+
|
33
|
+
def options_list(matches)
|
34
|
+
counter = 1
|
35
|
+
puts
|
36
|
+
matches.each do |match|
|
37
|
+
printf("%<counter>2d ) %<option>s\n", counter: counter, option: match)
|
38
|
+
counter += 1
|
39
|
+
end
|
40
|
+
puts
|
41
|
+
end
|
42
|
+
|
43
|
+
def choose(matches)
|
44
|
+
if Util.command_exist?('fzf')
|
45
|
+
settings = [
|
46
|
+
'-0',
|
47
|
+
'-1',
|
48
|
+
'-m',
|
49
|
+
"--height=#{matches.count + 2}",
|
50
|
+
'--header="Use tab to mark multiple selections, enter to display/run"',
|
51
|
+
'--prompt="Select a section > "'
|
52
|
+
]
|
53
|
+
res = `echo #{Shellwords.escape(matches.join("\n"))} | fzf #{settings.join(' ')}`.strip
|
54
|
+
if res.nil? || res.empty?
|
55
|
+
warn 'Cancelled'
|
56
|
+
Process.exit 0
|
57
|
+
end
|
58
|
+
return res.split(/\n/)
|
59
|
+
end
|
60
|
+
|
61
|
+
res = matches[0..9]
|
62
|
+
stty_save = `stty -g`.chomp
|
63
|
+
|
64
|
+
trap('INT') do
|
65
|
+
system('stty', stty_save)
|
66
|
+
exit
|
67
|
+
end
|
68
|
+
|
69
|
+
options_list(matches)
|
70
|
+
|
71
|
+
begin
|
72
|
+
printf("Type 'q' to cancel, enter for first item", res.length)
|
73
|
+
while (line = Readline.readline(': ', true))
|
74
|
+
if line =~ /^[a-z]/i
|
75
|
+
system('stty', stty_save) # Restore
|
76
|
+
exit
|
77
|
+
end
|
78
|
+
line = line == '' ? 1 : line.to_i
|
79
|
+
|
80
|
+
return matches[line - 1] if line.positive? && line <= matches.length
|
81
|
+
|
82
|
+
puts 'Out of range'
|
83
|
+
options_list(matches)
|
84
|
+
end
|
85
|
+
rescue Interrupt
|
86
|
+
system('stty', stty_save)
|
87
|
+
exit
|
88
|
+
end
|
89
|
+
end
|
17
90
|
end
|
18
91
|
end
|
19
92
|
end
|
data/lib/howzit/stringutils.rb
CHANGED
@@ -28,6 +28,19 @@ module Howzit
|
|
28
28
|
end
|
29
29
|
end
|
30
30
|
|
31
|
+
def to_rx
|
32
|
+
case Howzit.options[:matching]
|
33
|
+
when 'exact'
|
34
|
+
/^#{self}$/i
|
35
|
+
when 'beginswith'
|
36
|
+
/^#{self}/i
|
37
|
+
when 'fuzzy'
|
38
|
+
/#{split(//).join('.*?')}/i
|
39
|
+
else
|
40
|
+
/#{self}/i
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
31
44
|
# Just strip out color codes when requested
|
32
45
|
def uncolor
|
33
46
|
gsub(/\e\[[\d;]+m/, '').gsub(/\e\]1337;SetMark/,'')
|
@@ -103,20 +116,15 @@ module Howzit
|
|
103
116
|
end
|
104
117
|
|
105
118
|
def available?
|
106
|
-
|
107
|
-
File.executable?(File.expand_path(self))
|
108
|
-
else
|
109
|
-
system "which #{self}", out: File::NULL
|
110
|
-
end
|
119
|
+
Util.valid_command?(self)
|
111
120
|
end
|
112
121
|
|
113
122
|
def render_template(vars)
|
114
|
-
content = dup
|
115
123
|
vars.each do |k, v|
|
116
|
-
|
124
|
+
gsub!(/\[%#{k}(:.*?)?\]/, v)
|
117
125
|
end
|
118
126
|
|
119
|
-
|
127
|
+
gsub(/\[%(.*?):(.*?)\]/, '\2')
|
120
128
|
end
|
121
129
|
|
122
130
|
def render_template!(vars)
|
@@ -154,6 +162,44 @@ module Howzit
|
|
154
162
|
end
|
155
163
|
data
|
156
164
|
end
|
165
|
+
|
166
|
+
def should_mark_iterm?
|
167
|
+
ENV['TERM_PROGRAM'] =~ /^iTerm/ && !Howzit.options[:run] && !Howzit.options[:paginate]
|
168
|
+
end
|
169
|
+
|
170
|
+
def iterm_marker
|
171
|
+
"\e]1337;SetMark\a" if should_mark_iterm?
|
172
|
+
end
|
173
|
+
|
174
|
+
# Make a fancy title line for the topic
|
175
|
+
def format_header(opts = {})
|
176
|
+
title = dup
|
177
|
+
options = {
|
178
|
+
hr: "\u{254C}",
|
179
|
+
color: '{bg}',
|
180
|
+
border: '{x}',
|
181
|
+
mark: should_mark_iterm?
|
182
|
+
}
|
183
|
+
|
184
|
+
options.merge!(opts)
|
185
|
+
|
186
|
+
case Howzit.options[:header_format]
|
187
|
+
when :block
|
188
|
+
Color.template("#{options[:color]}\u{258C}#{title}#{should_mark_iterm? && options[:mark] ? iterm_marker : ''}{x}")
|
189
|
+
else
|
190
|
+
cols = TTY::Screen.columns
|
191
|
+
|
192
|
+
cols = Howzit.options[:wrap] if (Howzit.options[:wrap]).positive? && cols > Howzit.options[:wrap]
|
193
|
+
title = Color.template("#{options[:border]}#{options[:hr] * 2}( #{options[:color]}#{title}#{options[:border]} )")
|
194
|
+
|
195
|
+
tail = if should_mark_iterm?
|
196
|
+
"#{options[:hr] * (cols - title.uncolor.length - 15)}#{options[:mark] ? iterm_marker : ''}"
|
197
|
+
else
|
198
|
+
options[:hr] * (cols - title.uncolor.length)
|
199
|
+
end
|
200
|
+
Color.template("#{title}#{tail}{x}")
|
201
|
+
end
|
202
|
+
end
|
157
203
|
end
|
158
204
|
end
|
159
205
|
|
data/lib/howzit/task.rb
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Howzit
|
4
|
+
class Task
|
5
|
+
attr_reader :type, :title, :action, :parent
|
6
|
+
|
7
|
+
def initialize(type, title, action, parent = nil)
|
8
|
+
@type = type
|
9
|
+
@title = title
|
10
|
+
@action = action
|
11
|
+
@parent = parent
|
12
|
+
end
|
13
|
+
|
14
|
+
def to_s
|
15
|
+
@title
|
16
|
+
end
|
17
|
+
|
18
|
+
def to_list
|
19
|
+
" * #{@type}: #{@title}"
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
data/lib/howzit/topic.rb
ADDED
@@ -0,0 +1,233 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Howzit
|
4
|
+
# Topic Class
|
5
|
+
class Topic
|
6
|
+
attr_writer :parent
|
7
|
+
|
8
|
+
attr_reader :title, :content, :tasks, :prereqs, :postreqs
|
9
|
+
|
10
|
+
def initialize(title, content)
|
11
|
+
@title = title
|
12
|
+
@content = content
|
13
|
+
@parent = nil
|
14
|
+
@nest_level = 0
|
15
|
+
@tasks = gather_tasks
|
16
|
+
end
|
17
|
+
|
18
|
+
def grep(term)
|
19
|
+
@title =~ /#{term}/i || @content =~ /#{term}/i
|
20
|
+
end
|
21
|
+
|
22
|
+
# Handle run command, execute directives
|
23
|
+
def run(nested: false)
|
24
|
+
output = []
|
25
|
+
tasks = 0
|
26
|
+
if @tasks.count.positive?
|
27
|
+
unless @prereqs.empty?
|
28
|
+
puts @prereqs.join("\n\n")
|
29
|
+
res = Prompt.yn('This task has prerequisites, have they been met?', true)
|
30
|
+
Process.exit 1 unless res
|
31
|
+
|
32
|
+
end
|
33
|
+
|
34
|
+
@tasks.each do |task|
|
35
|
+
if task.type == :block
|
36
|
+
warn Color.template("{bg}Running block {bw}#{title}{x}") if Howzit.options[:log_level] < 2
|
37
|
+
block = task.action
|
38
|
+
script = Tempfile.new('howzit_script')
|
39
|
+
begin
|
40
|
+
script.write(block)
|
41
|
+
script.close
|
42
|
+
File.chmod(0777, script.path)
|
43
|
+
system(%(/bin/sh -c "#{script.path}"))
|
44
|
+
tasks += 1
|
45
|
+
ensure
|
46
|
+
script.close
|
47
|
+
script.unlink
|
48
|
+
end
|
49
|
+
else
|
50
|
+
case task.type
|
51
|
+
when :include
|
52
|
+
matches = Howzit.buildnote.find_topic(task.action)
|
53
|
+
raise "Topic not found: #{task.action}" if matches.empty?
|
54
|
+
|
55
|
+
warn Color.template("{by}Running tasks from {bw}#{matches[0].title}{x}") if Howzit.options[:log_level] < 2
|
56
|
+
output.push(matches[0].run(nested: true))
|
57
|
+
warn Color.template("{by}End include: #{matches[0].tasks.count} tasks") if Howzit.options[:log_level] < 2
|
58
|
+
tasks += matches[0].tasks.count
|
59
|
+
when :run
|
60
|
+
warn Color.template("{bg}Running {bw}#{task.title}{x}") if Howzit.options[:log_level] < 2
|
61
|
+
system(task.action)
|
62
|
+
tasks += 1
|
63
|
+
when :copy
|
64
|
+
warn Color.template("{bg}Copied {bw}#{task.title}{bg} to clipboard{x}") if Howzit.options[:log_level] < 2
|
65
|
+
`echo #{Shellwords.escape(task.action)}'\\c'|pbcopy`
|
66
|
+
tasks += 1
|
67
|
+
when :open
|
68
|
+
os_open(task.action)
|
69
|
+
tasks += 1
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
else
|
74
|
+
warn Color.template("{r}--run: No {br}@directive{xr} found in {bw}#{key}{x}")
|
75
|
+
end
|
76
|
+
output.push(Color.template("{bm}Ran #{tasks} #{tasks == 1 ? 'task' : 'tasks'}{x}")) if Howzit.options[:log_level] < 2 && !nested
|
77
|
+
|
78
|
+
puts postreqs.join("\n\n") unless postreqs.empty?
|
79
|
+
|
80
|
+
output
|
81
|
+
end
|
82
|
+
|
83
|
+
def os_open(command)
|
84
|
+
os = RbConfig::CONFIG['target_os']
|
85
|
+
out = Color.template("{bg}Opening {bw}#{command}")
|
86
|
+
case os
|
87
|
+
when /darwin.*/i
|
88
|
+
warn Color.template("#{out} (macOS){x}") if Howzit.options[:log_level] < 2
|
89
|
+
`open #{Shellwords.escape(command)}`
|
90
|
+
when /mingw|mswin/i
|
91
|
+
warn Color.template("#{out} (Windows){x}") if Howzit.options[:log_level] < 2
|
92
|
+
`start #{Shellwords.escape(command)}`
|
93
|
+
else
|
94
|
+
if 'xdg-open'.available?
|
95
|
+
warn Color.template("#{out} (Linux){x}") if Howzit.options[:log_level] < 2
|
96
|
+
`xdg-open #{Shellwords.escape(command)}`
|
97
|
+
else
|
98
|
+
warn out if Howzit.options[:log_level] < 2
|
99
|
+
warn 'Unable to determine executable for `open`.'
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
# Output a topic with fancy title and bright white text.
|
105
|
+
def print_out(options = {})
|
106
|
+
defaults = { single: false, header: true }
|
107
|
+
opt = defaults.merge(options)
|
108
|
+
|
109
|
+
output = []
|
110
|
+
if opt[:header]
|
111
|
+
output.push(@title.format_header)
|
112
|
+
output.push('')
|
113
|
+
end
|
114
|
+
topic = @content.dup
|
115
|
+
topic.gsub!(/(?mi)^(`{3,})run *([^\n]*)[\s\S]*?\n\1\s*$/, '@@@run \2') unless Howzit.options[:show_all_code]
|
116
|
+
topic.split(/\n/).each do |l|
|
117
|
+
case l
|
118
|
+
when /@(before|after|prereq|end)/
|
119
|
+
next
|
120
|
+
when /@include\((.*?)\)/
|
121
|
+
|
122
|
+
m = Regexp.last_match
|
123
|
+
matches = Howzit.buildnote.find_topic(m[1])
|
124
|
+
unless matches.empty?
|
125
|
+
if opt[:single]
|
126
|
+
title = "From #{matches[0].title}:"
|
127
|
+
color = '{Kyd}'
|
128
|
+
rule = '{kKd}'
|
129
|
+
else
|
130
|
+
title = "Include #{matches[0].title}"
|
131
|
+
color = '{Kyd}'
|
132
|
+
rule = '{kKd}'
|
133
|
+
end
|
134
|
+
unless Howzit.inclusions.include?(matches[0])
|
135
|
+
output.push("#{'> ' * @nest_level}#{title}".format_header({ color: color, hr: '.', border: rule }))
|
136
|
+
end
|
137
|
+
|
138
|
+
if opt[:single]
|
139
|
+
if Howzit.inclusions.include?(matches[0])
|
140
|
+
output.push("#{'> ' * @nest_level}#{title} included above".format_header({
|
141
|
+
color: color, hr: '.', border: rule }))
|
142
|
+
else
|
143
|
+
@nest_level += 1
|
144
|
+
output.concat(matches[0].print_out({ single: true, header: false }))
|
145
|
+
@nest_level -= 1
|
146
|
+
end
|
147
|
+
unless Howzit.inclusions.include?(matches[0])
|
148
|
+
output.push("#{'> ' * @nest_level}...".format_header({ color: color, hr: '.', border: rule }))
|
149
|
+
end
|
150
|
+
end
|
151
|
+
Howzit.inclusions.push(matches[0])
|
152
|
+
end
|
153
|
+
|
154
|
+
when /@(run|copy|open|url|include)\((.*?)\)/
|
155
|
+
m = Regexp.last_match
|
156
|
+
cmd = m[1]
|
157
|
+
obj = m[2]
|
158
|
+
icon = case cmd
|
159
|
+
when 'run'
|
160
|
+
"\u{25B6}"
|
161
|
+
when 'copy'
|
162
|
+
"\u{271A}"
|
163
|
+
when /open|url/
|
164
|
+
"\u{279A}"
|
165
|
+
end
|
166
|
+
|
167
|
+
output.push(Color.template("{bmK}#{icon} {bwK}#{obj.gsub(/\\n/, '\n')}{x}"))
|
168
|
+
when /(`{3,})run *(.*?)$/i
|
169
|
+
m = Regexp.last_match
|
170
|
+
desc = m[2].length.positive? ? "Block: #{m[2]}" : 'Code Block'
|
171
|
+
output.push(Color.template("{bmK}\u{25B6} {bwK}#{desc}{x}\n```"))
|
172
|
+
when /@@@run *(.*?)$/i
|
173
|
+
m = Regexp.last_match
|
174
|
+
desc = m[1].length.positive? ? "Block: #{m[1]}" : 'Code Block'
|
175
|
+
output.push(Color.template("{bmK}\u{25B6} {bwK}#{desc}{x}"))
|
176
|
+
else
|
177
|
+
l.wrap!(Howzit.options[:wrap]) if (Howzit.options[:wrap]).positive?
|
178
|
+
output.push(l)
|
179
|
+
end
|
180
|
+
end
|
181
|
+
output.push('') # FIXME: Is this where the extra line is coming from?
|
182
|
+
end
|
183
|
+
|
184
|
+
private
|
185
|
+
|
186
|
+
def gather_tasks
|
187
|
+
runnable = []
|
188
|
+
@prereqs = @content.scan(/(?<=@before\n).*?(?=\n@end)/im).map(&:strip)
|
189
|
+
@postreqs = @content.scan(/(?<=@after\n).*?(?=\n@end)/im).map(&:strip)
|
190
|
+
|
191
|
+
rx = /(?:@(include|run|copy|open|url)\((.*?)\) *(.*?)(?=$)|(`{3,})run(?: +([^\n]+))?(.*?)\4)/mi
|
192
|
+
directives = @content.scan(rx)
|
193
|
+
|
194
|
+
directives.each do |c|
|
195
|
+
if c[0].nil?
|
196
|
+
title = c[4] ? c[4].strip : ''
|
197
|
+
block = c[5].strip
|
198
|
+
runnable << Howzit::Task.new(:block, title, block)
|
199
|
+
else
|
200
|
+
cmd = c[0]
|
201
|
+
obj = c[1]
|
202
|
+
title = c[3] || obj
|
203
|
+
|
204
|
+
case cmd
|
205
|
+
when /include/i
|
206
|
+
# matches = Howzit.buildnote.find_topic(obj)
|
207
|
+
# unless matches.empty? || Howzit.inclusions.include?(matches[0].title)
|
208
|
+
# tasks = matches[0].tasks.map do |inc|
|
209
|
+
# Howzit.inclusions.push(matches[0].title)
|
210
|
+
# inc.parent = matches[0]
|
211
|
+
# inc
|
212
|
+
# end
|
213
|
+
# runnable.concat(tasks)
|
214
|
+
# end
|
215
|
+
title = c[3] || obj
|
216
|
+
runnable << Howzit::Task.new(:include, title, obj)
|
217
|
+
when /run/i
|
218
|
+
title = c[3] || obj
|
219
|
+
# warn Color.template("{bg}Running {bw}#{obj}{x}") if Howzit.options[:log_level] < 2
|
220
|
+
runnable << Howzit::Task.new(:run, title, obj)
|
221
|
+
when /copy/i
|
222
|
+
# warn Color.template("{bg}Copied {bw}#{obj}{bg} to clipboard{x}") if Howzit.options[:log_level] < 2
|
223
|
+
runnable << Howzit::Task.new(:copy, title, Shellwords.escape(obj))
|
224
|
+
when /open|url/i
|
225
|
+
runnable << Howzit::Task.new(:open, title, obj)
|
226
|
+
end
|
227
|
+
end
|
228
|
+
end
|
229
|
+
|
230
|
+
runnable
|
231
|
+
end
|
232
|
+
end
|
233
|
+
end
|
data/lib/howzit/util.rb
ADDED
@@ -0,0 +1,149 @@
|
|
1
|
+
module Howzit
|
2
|
+
module Util
|
3
|
+
class << self
|
4
|
+
def valid_command?(command)
|
5
|
+
cmd = command.split(' ')[0]
|
6
|
+
command_exist?(cmd)
|
7
|
+
end
|
8
|
+
|
9
|
+
def command_exist?(command)
|
10
|
+
exts = ENV.fetch('PATHEXT', '').split(::File::PATH_SEPARATOR)
|
11
|
+
if Pathname.new(command).absolute?
|
12
|
+
::File.exist?(command) ||
|
13
|
+
exts.any? { |ext| ::File.exist?("#{command}#{ext}") }
|
14
|
+
else
|
15
|
+
ENV.fetch('PATH', '').split(::File::PATH_SEPARATOR).any? do |dir|
|
16
|
+
file = ::File.join(dir, command)
|
17
|
+
::File.exist?(file) ||
|
18
|
+
exts.any? { |ext| ::File.exist?("#{file}#{ext}") }
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
# If either mdless or mdcat are installed, use that for highlighting
|
24
|
+
# markdown
|
25
|
+
def which_highlighter
|
26
|
+
if Howzit.options[:highlighter] =~ /auto/i
|
27
|
+
highlighters = %w[mdcat mdless]
|
28
|
+
highlighters.delete_if(&:nil?).select!(&:available?)
|
29
|
+
return nil if highlighters.empty?
|
30
|
+
|
31
|
+
hl = highlighters.first
|
32
|
+
args = case hl
|
33
|
+
when 'mdless'
|
34
|
+
'--no-pager'
|
35
|
+
end
|
36
|
+
|
37
|
+
[hl, args].join(' ')
|
38
|
+
else
|
39
|
+
hl = Howzit.options[:highlighter].split(/ /)[0]
|
40
|
+
if hl.available?
|
41
|
+
Howzit.options[:highlighter]
|
42
|
+
else
|
43
|
+
warn Color.template("{Rw}Error:{xbw} Specified highlighter (#{Howzit.options[:highlighter]}) not found, switching to auto")
|
44
|
+
Howzit.options[:highlighter] = 'auto'
|
45
|
+
which_highlighter
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
# When pagination is enabled, find the best (in my opinion) option,
|
51
|
+
# favoring environment settings
|
52
|
+
def which_pager
|
53
|
+
if Howzit.options[:pager] =~ /auto/i
|
54
|
+
pagers = [ENV['PAGER'], ENV['GIT_PAGER'],
|
55
|
+
'bat', 'less', 'more', 'pager']
|
56
|
+
pagers.delete_if(&:nil?).select!(&:available?)
|
57
|
+
return nil if pagers.empty?
|
58
|
+
|
59
|
+
pg = pagers.first
|
60
|
+
args = case pg
|
61
|
+
when 'delta'
|
62
|
+
'--pager="less -FXr"'
|
63
|
+
when /^(less|more)$/
|
64
|
+
'-FXr'
|
65
|
+
when 'bat'
|
66
|
+
if Howzit.options[:highlight]
|
67
|
+
'--language Markdown --style plain --pager="less -FXr"'
|
68
|
+
else
|
69
|
+
'--style plain --pager="less -FXr"'
|
70
|
+
end
|
71
|
+
else
|
72
|
+
''
|
73
|
+
end
|
74
|
+
|
75
|
+
[pg, args].join(' ')
|
76
|
+
else
|
77
|
+
pg = Howzit.options[:pager].split(/ /)[0]
|
78
|
+
if pg.available?
|
79
|
+
Howzit.options[:pager]
|
80
|
+
else
|
81
|
+
warn Color.template("{Rw}Error:{xbw} Specified pager (#{Howzit.options[:pager]}) not found, switching to auto")
|
82
|
+
Howzit.options[:pager] = 'auto'
|
83
|
+
which_pager
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
# Paginate the output
|
89
|
+
def page(text)
|
90
|
+
read_io, write_io = IO.pipe
|
91
|
+
|
92
|
+
input = $stdin
|
93
|
+
|
94
|
+
pid = Kernel.fork do
|
95
|
+
write_io.close
|
96
|
+
input.reopen(read_io)
|
97
|
+
read_io.close
|
98
|
+
|
99
|
+
# Wait until we have input before we start the pager
|
100
|
+
IO.select [input]
|
101
|
+
|
102
|
+
pager = which_pager
|
103
|
+
|
104
|
+
begin
|
105
|
+
exec(pager)
|
106
|
+
rescue SystemCallError => e
|
107
|
+
@log.error(e)
|
108
|
+
exit 1
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
read_io.close
|
113
|
+
write_io.write(text)
|
114
|
+
write_io.close
|
115
|
+
|
116
|
+
_, status = Process.waitpid2(pid)
|
117
|
+
|
118
|
+
status.success?
|
119
|
+
end
|
120
|
+
|
121
|
+
# print output to terminal
|
122
|
+
def show(string, opts = {})
|
123
|
+
options = {
|
124
|
+
color: true,
|
125
|
+
highlight: false,
|
126
|
+
wrap: 0
|
127
|
+
}
|
128
|
+
|
129
|
+
options.merge!(opts)
|
130
|
+
|
131
|
+
string = string.uncolor unless options[:color]
|
132
|
+
|
133
|
+
pipes = ''
|
134
|
+
if options[:highlight]
|
135
|
+
hl = which_highlighter
|
136
|
+
pipes = "|#{hl}" if hl
|
137
|
+
end
|
138
|
+
|
139
|
+
output = `echo #{Shellwords.escape(string.strip)}#{pipes}`.strip
|
140
|
+
|
141
|
+
if Howzit.options[:paginate]
|
142
|
+
page(output)
|
143
|
+
else
|
144
|
+
puts output
|
145
|
+
end
|
146
|
+
end
|
147
|
+
end
|
148
|
+
end
|
149
|
+
end
|
data/lib/howzit/version.rb
CHANGED
data/lib/howzit.rb
CHANGED
@@ -1,8 +1,17 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'howzit/version'
|
4
|
+
require_relative 'howzit/prompt'
|
5
|
+
require_relative 'howzit/colors'
|
6
|
+
require_relative 'howzit/stringutils'
|
7
|
+
|
8
|
+
require_relative 'howzit/util'
|
9
|
+
require_relative 'howzit/hash'
|
10
|
+
require_relative 'howzit/config'
|
11
|
+
require_relative 'howzit/task'
|
12
|
+
require_relative 'howzit/topic'
|
13
|
+
require_relative 'howzit/buildnote'
|
14
|
+
|
6
15
|
require 'optparse'
|
7
16
|
require 'shellwords'
|
8
17
|
require 'pathname'
|
@@ -18,3 +27,29 @@ IGNORE_FILE = 'ignore.yaml'
|
|
18
27
|
MATCHING_OPTIONS = %w[partial exact fuzzy beginswith].freeze
|
19
28
|
MULTIPLE_OPTIONS = %w[first best all choose].freeze
|
20
29
|
HEADER_FORMAT_OPTIONS = %w[border block].freeze
|
30
|
+
|
31
|
+
module Howzit
|
32
|
+
class << self
|
33
|
+
attr_accessor :arguments, :cli_args
|
34
|
+
##
|
35
|
+
## Holds a Configuration object with methods and a @settings hash
|
36
|
+
##
|
37
|
+
## @return [Configuration] Configuration object
|
38
|
+
##
|
39
|
+
def config
|
40
|
+
@config ||= Config.new
|
41
|
+
end
|
42
|
+
|
43
|
+
def inclusions
|
44
|
+
@inclusions ||= []
|
45
|
+
end
|
46
|
+
|
47
|
+
def options
|
48
|
+
config.options
|
49
|
+
end
|
50
|
+
|
51
|
+
def buildnote
|
52
|
+
@buildnote ||= BuildNote.new
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|