howzit 1.2.11 → 1.2.14
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.gitignore +1 -0
- data/CHANGELOG.md +27 -0
- data/README.md +15 -5
- data/Rakefile +16 -0
- data/bin/howzit +1 -2
- data/lib/howzit/buildnotes.rb +200 -112
- data/lib/howzit/colors.rb +323 -0
- data/lib/howzit/prompt.rb +3 -0
- data/lib/howzit/stringutils.rb +25 -0
- data/lib/howzit/version.rb +1 -1
- data/lib/howzit.rb +2 -0
- data/spec/ruby_gem_spec.rb +29 -0
- metadata +3 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 3858dec6b009f75173d3d72067a94c330cb826b0e367109c7416b2db19065fa8
|
4
|
+
data.tar.gz: 2e05520b1ff9d5da82aba03b1af3ed31ee0a635f3fe42c50b3a515c45337c38b
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 90cb7e647c7956823e73eac6e4884a1ffe26875acd531cc70be6a1b334aa9653213cafbf0830cd69a634579c7fa61553585bbbc95f997a02b0192d093a85be1b
|
7
|
+
data.tar.gz: bfba515bc66d5f15ae33d967866e83e2e9e4c887160d0c66269542ee909acf8383742fd0e64f5639013a1ccdc16253664a3c90cd150fd5a67529280473ed1a88
|
data/.gitignore
CHANGED
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,30 @@
|
|
1
|
+
### 1.2.14
|
2
|
+
|
3
|
+
2022-08-02 11:01
|
4
|
+
|
5
|
+
#### NEW
|
6
|
+
|
7
|
+
- Config option and flag to determine how to handle multiple results (first, best, all, choose)
|
8
|
+
- --config-get and --config-set flags for working with config options
|
9
|
+
|
10
|
+
#### IMPROVED
|
11
|
+
|
12
|
+
- Allow multiple selections when using fzf
|
13
|
+
- Clean up newlines in output
|
14
|
+
|
15
|
+
### 1.2.13
|
16
|
+
|
17
|
+
2022-08-01 20:50
|
18
|
+
|
19
|
+
### 1.2.12
|
20
|
+
|
21
|
+
2022-08-01 16:23
|
22
|
+
|
23
|
+
#### IMPROVED
|
24
|
+
|
25
|
+
- Replace ANSI escape codes with color template system
|
26
|
+
- When @including an external file, if the file doesn't contain any level 2+ headers, import it as plain text.
|
27
|
+
|
1
28
|
### 1.2.11
|
2
29
|
|
3
30
|
2022-08-01 08:23
|
data/README.md
CHANGED
@@ -24,11 +24,10 @@ Howzit is a tool that allows you to keep Markdown-formatted notes about a projec
|
|
24
24
|
|
25
25
|
## Getting Started
|
26
26
|
|
27
|
-
Howzit is a simple, self-contained script (at least until I get stupid and make a gem out of it).
|
28
|
-
|
29
27
|
### Prerequisites
|
30
28
|
|
31
29
|
- Ruby 2.4+ (It probably works on older Rubys, but is untested prior to 2.4.1.)
|
30
|
+
- Optional: if [`fzf`](https://github.com/junegunn/fzf) is available, it will be used for handling multiple choice selections
|
32
31
|
- Optional: if [`bat`](https://github.com/sharkdp/bat) is available it will page with that
|
33
32
|
- Optional: [`mdless`](https://github.com/ttscoff/mdless) or [`mdcat`](https://github.com/lunaryorn/mdcat) for formatting output
|
34
33
|
|
@@ -200,6 +199,10 @@ Include a topic name to see just that topic, or no argument to display all.
|
|
200
199
|
|
201
200
|
howzit build
|
202
201
|
|
202
|
+
You can combine multiple topic searches by separating with a comma. When multiple results are returned, the `:multiple_results:` configuration determines how they're handled.
|
203
|
+
|
204
|
+
howzit build,deploy
|
205
|
+
|
203
206
|
Use `-l` to list all topics.
|
204
207
|
|
205
208
|
howzit -l
|
@@ -216,12 +219,15 @@ Other options:
|
|
216
219
|
|
217
220
|
Options:
|
218
221
|
-c, --create Create a skeleton build note in the current working directory
|
219
|
-
-e, --edit Edit buildnotes file in current working directory
|
222
|
+
-e, --edit Edit buildnotes file in current working directory
|
223
|
+
using $EDITOR
|
220
224
|
--grep PATTERN Display sections matching a search pattern
|
221
225
|
-L, --list-completions List topics for completion
|
222
226
|
-l, --list List available topics
|
223
227
|
-m, --matching TYPE Topics matching type
|
224
228
|
(partial, exact, fuzzy, beginswith)
|
229
|
+
--multiple TYPE Multiple result handling
|
230
|
+
(first, all, choose)
|
225
231
|
-R, --list-runnable List topics containing @ directives (verbose)
|
226
232
|
-r, --run Execute @run, @open, and/or @copy commands for given topic
|
227
233
|
-s, --select Select topic from menu
|
@@ -229,10 +235,10 @@ Other options:
|
|
229
235
|
-t, --title Output title with build notes
|
230
236
|
-q, --quiet Silence info message
|
231
237
|
--verbose Show all messages
|
232
|
-
-u, --upstream
|
238
|
+
-u, --[no-]upstream Traverse up parent directories for additional build notes
|
233
239
|
--show-code Display the content of fenced run blocks
|
234
240
|
-w, --wrap COLUMNS Wrap to specified width (default 80, 0 to disable)
|
235
|
-
--edit-config Edit configuration file using
|
241
|
+
--edit-config Edit configuration file using default $EDITOR
|
236
242
|
--title-only Output title only
|
237
243
|
--templates List available templates
|
238
244
|
--[no-]color Colorize output (default on)
|
@@ -240,6 +246,7 @@ Other options:
|
|
240
246
|
--[no-]pager Paginate output (default on)
|
241
247
|
-h, --help Display this screen
|
242
248
|
-v, --version Display version number
|
249
|
+
--default Answer all prompts with default response
|
243
250
|
|
244
251
|
|
245
252
|
## Configuration
|
@@ -257,6 +264,7 @@ Some of the command line options can be set as defaults. The first time you run
|
|
257
264
|
:matching: partial
|
258
265
|
:include_upstream: false
|
259
266
|
:log_level: 1
|
267
|
+
:multiple_matches: choose
|
260
268
|
|
261
269
|
If `:color:` is false, output will not be colored, and markdown highlighting will be bypassed.
|
262
270
|
|
@@ -272,6 +280,8 @@ If `:include_upstream:` is true, build note files in parent directories will be
|
|
272
280
|
|
273
281
|
Set `:log_level:` to 0 for debug messages, or 3 to suppress superfluous info messages.
|
274
282
|
|
283
|
+
`:multiple_matches:` determines how howzit will handle cases where a search results in multiple matches. It can be set to "first" (first match in notes), "best" (shortest topic match), "all" (display all results), or "choose" (displays a menu of results). Default is "choose." When grepping for results, only "all" or "choose" are valid, if the default is something else, "choose" will be used. Can be overridden with the `--multiple TYPE` flag.
|
284
|
+
|
275
285
|
### Matching
|
276
286
|
|
277
287
|
All matching is case insensitive. This setting can be overridden by the `--matching TYPE` flag on the command line.
|
data/Rakefile
CHANGED
@@ -18,3 +18,19 @@ RuboCop::RakeTask.new do |t|
|
|
18
18
|
end
|
19
19
|
|
20
20
|
YARD::Rake::YardocTask.new
|
21
|
+
|
22
|
+
desc 'Development version check'
|
23
|
+
task :ver do
|
24
|
+
gver = `git ver`
|
25
|
+
cver = IO.read(File.join(File.dirname(__FILE__), 'CHANGELOG.md')).match(/^#+ (\d+\.\d+\.\d+(\w+)?)/)[1]
|
26
|
+
res = `grep VERSION lib/howzit/version.rb`
|
27
|
+
version = res.match(/VERSION *= *['"](\d+\.\d+\.\d+(\w+)?)/)[1]
|
28
|
+
puts "git tag: #{gver}"
|
29
|
+
puts "version.rb: #{version}"
|
30
|
+
puts "changelog: #{cver}"
|
31
|
+
end
|
32
|
+
|
33
|
+
desc 'Changelog version check'
|
34
|
+
task :cver do
|
35
|
+
puts IO.read(File.join(File.dirname(__FILE__), 'CHANGELOG.md')).match(/^#+ (\d+\.\d+\.\d+(\w+)?)/)[1]
|
36
|
+
end
|
data/bin/howzit
CHANGED
data/lib/howzit/buildnotes.rb
CHANGED
@@ -2,8 +2,9 @@ module Howzit
|
|
2
2
|
# Primary Class for this module
|
3
3
|
class BuildNotes
|
4
4
|
include Prompt
|
5
|
+
include Color
|
5
6
|
|
6
|
-
attr_accessor :cli_args, :arguments, :metadata
|
7
|
+
attr_accessor :cli_args, :options, :arguments, :metadata
|
7
8
|
|
8
9
|
def topics
|
9
10
|
@topics ||= read_help
|
@@ -40,8 +41,8 @@ module Howzit
|
|
40
41
|
# favoring environment settings
|
41
42
|
def which_pager
|
42
43
|
if @options[:pager] =~ /auto/i
|
43
|
-
pagers = [ENV['
|
44
|
-
'bat', 'less', 'more', '
|
44
|
+
pagers = [ENV['PAGER'], ENV['GIT_PAGER'],
|
45
|
+
'bat', 'less', 'more', 'pager']
|
45
46
|
pagers.delete_if(&:nil?).select!(&:available?)
|
46
47
|
return nil if pagers.empty?
|
47
48
|
|
@@ -123,7 +124,7 @@ module Howzit
|
|
123
124
|
pipes = "|#{hl}" if hl
|
124
125
|
end
|
125
126
|
|
126
|
-
output = `echo #{Shellwords.escape(string.strip)}#{pipes}
|
127
|
+
output = `echo #{Shellwords.escape(string.strip)}#{pipes}`.strip
|
127
128
|
|
128
129
|
if @options[:paginate]
|
129
130
|
page(output)
|
@@ -145,12 +146,12 @@ module Howzit
|
|
145
146
|
choices.each do |choice|
|
146
147
|
case choice
|
147
148
|
when /[A-Z]/
|
148
|
-
out.push("
|
149
|
+
out.push(Color.template("{bg}#{choice}{xg}"))
|
149
150
|
else
|
150
|
-
out.push(choice)
|
151
|
+
out.push(Color.template("{w}#{choice}"))
|
151
152
|
end
|
152
153
|
end
|
153
|
-
"
|
154
|
+
Color.template("{g}[#{out.join('/')}{g}]{x}")
|
154
155
|
end
|
155
156
|
|
156
157
|
# Create a buildnotes skeleton
|
@@ -159,30 +160,40 @@ module Howzit
|
|
159
160
|
warn "\nCanceled"
|
160
161
|
exit!
|
161
162
|
end
|
163
|
+
default = !$stdout.isatty || @options[:default]
|
162
164
|
# First make sure there isn't already a buildnotes file
|
163
165
|
if note_file
|
164
|
-
fname = "
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
166
|
+
fname = Color.template("{by}#{note_file}{bw}")
|
167
|
+
unless default
|
168
|
+
res = yn("#{fname} exists and appears to be a build note, continue anyway?", false)
|
169
|
+
unless res
|
170
|
+
puts 'Canceled'
|
171
|
+
Process.exit 0
|
172
|
+
end
|
169
173
|
end
|
170
174
|
end
|
171
175
|
|
172
176
|
title = File.basename(Dir.pwd)
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
+
if default
|
178
|
+
input = title
|
179
|
+
else
|
180
|
+
printf Color.template("{bw}Project name {xg}[#{title}]{bw}: {x}")
|
181
|
+
input = $stdin.gets.chomp
|
182
|
+
title = input unless input.empty?
|
183
|
+
end
|
177
184
|
summary = ''
|
178
|
-
|
179
|
-
|
180
|
-
|
185
|
+
unless default
|
186
|
+
printf Color.template('{bw}Project summary: {x}')
|
187
|
+
input = $stdin.gets.chomp
|
188
|
+
summary = input unless input.empty?
|
189
|
+
end
|
181
190
|
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
191
|
+
fname = 'buildnotes.md'
|
192
|
+
unless default
|
193
|
+
printf Color.template("{bw}Build notes filename (must begin with 'howzit' or 'build')\n{xg}[#{fname}]{bw}: {x}")
|
194
|
+
input = $stdin.gets.chomp
|
195
|
+
fname = input unless input.empty?
|
196
|
+
end
|
186
197
|
|
187
198
|
note = <<~EOBUILDNOTES
|
188
199
|
# #{title}
|
@@ -209,8 +220,8 @@ module Howzit
|
|
209
220
|
|
210
221
|
EOBUILDNOTES
|
211
222
|
|
212
|
-
if File.exist?(
|
213
|
-
file = "
|
223
|
+
if File.exist?(fname) && !default
|
224
|
+
file = Color.template("{by}#{fname}")
|
214
225
|
res = yn("Are you absolutely sure you want to overwrite #{file}", false)
|
215
226
|
|
216
227
|
unless res
|
@@ -219,9 +230,9 @@ module Howzit
|
|
219
230
|
end
|
220
231
|
end
|
221
232
|
|
222
|
-
File.open(
|
233
|
+
File.open(fname, 'w') do |f|
|
223
234
|
f.puts note
|
224
|
-
puts "Build notes for #{title} written to #{
|
235
|
+
puts Color.template("{by}Build notes for #{title} written to #{fname}")
|
225
236
|
end
|
226
237
|
end
|
227
238
|
|
@@ -229,8 +240,8 @@ module Howzit
|
|
229
240
|
def format_header(title, opts = {})
|
230
241
|
options = {
|
231
242
|
hr: "\u{254C}",
|
232
|
-
color: '
|
233
|
-
border: '
|
243
|
+
color: '{bg}',
|
244
|
+
border: '{x}',
|
234
245
|
mark: false
|
235
246
|
}
|
236
247
|
|
@@ -239,29 +250,29 @@ module Howzit
|
|
239
250
|
cols = TTY::Screen.columns
|
240
251
|
|
241
252
|
cols = @options[:wrap] if (@options[:wrap]).positive? && cols > @options[:wrap]
|
242
|
-
title = "
|
253
|
+
title = Color.template("#{options[:border]}#{options[:hr] * 2}( #{options[:color]}#{title}#{options[:border]} )")
|
243
254
|
|
244
255
|
tail = if should_mark_iterm?
|
245
256
|
"#{options[:hr] * (cols - title.uncolor.length - 15)}#{options[:mark] ? iterm_marker : ''}"
|
246
257
|
else
|
247
258
|
options[:hr] * (cols - title.uncolor.length)
|
248
259
|
end
|
249
|
-
"#{title}#{tail}
|
260
|
+
Color.template("#{title}#{tail}{x}")
|
250
261
|
end
|
251
262
|
|
252
263
|
def os_open(command)
|
253
264
|
os = RbConfig::CONFIG['target_os']
|
254
|
-
out = "
|
265
|
+
out = Color.template("{bg}Opening {bw}#{command}")
|
255
266
|
case os
|
256
267
|
when /darwin.*/i
|
257
|
-
warn "#{out} (macOS)
|
268
|
+
warn Color.template("#{out} (macOS){x}") if @options[:log_level] < 2
|
258
269
|
`open #{Shellwords.escape(command)}`
|
259
270
|
when /mingw|mswin/i
|
260
|
-
warn "#{out} (Windows)
|
271
|
+
warn Color.template("#{out} (Windows){x}") if @options[:log_level] < 2
|
261
272
|
`start #{Shellwords.escape(command)}`
|
262
273
|
else
|
263
274
|
if 'xdg-open'.available?
|
264
|
-
warn "#{out} (Linux)
|
275
|
+
warn Color.template("#{out} (Linux){x}") if @options[:log_level] < 2
|
265
276
|
`xdg-open #{Shellwords.escape(command)}`
|
266
277
|
else
|
267
278
|
warn out if @options[:log_level] < 2
|
@@ -300,7 +311,7 @@ module Howzit
|
|
300
311
|
directives.each do |c|
|
301
312
|
if c[0].nil?
|
302
313
|
title = c[3] ? c[3].strip : ''
|
303
|
-
warn "
|
314
|
+
warn Color.template("{bg}Running block {bw}#{title}{x}") if @options[:log_level] < 2
|
304
315
|
block = c[4].strip
|
305
316
|
script = Tempfile.new('howzit_script')
|
306
317
|
begin
|
@@ -322,18 +333,18 @@ module Howzit
|
|
322
333
|
warn "No topic match for @include(#{search})"
|
323
334
|
else
|
324
335
|
if @included.include?(matches[0])
|
325
|
-
warn "
|
336
|
+
warn Color.template("{by}Tasks from {bw}#{matches[0]} already included, skipping{x}") if @options[:log_level] < 2
|
326
337
|
else
|
327
|
-
warn "
|
338
|
+
warn Color.template("{by}Including tasks from {bw}#{matches[0]}{x}") if @options[:log_level] < 2
|
328
339
|
process_topic(matches[0], true)
|
329
|
-
warn "
|
340
|
+
warn Color.template("{by}End include {bw}#{matches[0]}{x}") if @options[:log_level] < 2
|
330
341
|
end
|
331
342
|
end
|
332
343
|
when /run/i
|
333
|
-
warn "
|
344
|
+
warn Color.template("{bg}Running {bw}#{obj}{x}") if @options[:log_level] < 2
|
334
345
|
system(obj)
|
335
346
|
when /copy/i
|
336
|
-
warn "
|
347
|
+
warn Color.template("{bg}Copied {bw}#{obj}{bg} to clipboard{x}") if @options[:log_level] < 2
|
337
348
|
`echo #{Shellwords.escape(obj)}'\\c'|pbcopy`
|
338
349
|
when /open|url/i
|
339
350
|
os_open(obj)
|
@@ -341,11 +352,13 @@ module Howzit
|
|
341
352
|
end
|
342
353
|
end
|
343
354
|
else
|
344
|
-
warn "
|
355
|
+
warn Color.template("{r}--run: No {br}@directive{xr} found in {bw}#{key}{x}")
|
345
356
|
end
|
346
357
|
output.push("Ran #{tasks} #{tasks == 1 ? 'task' : 'tasks'}") if @options[:log_level] < 2
|
347
358
|
|
348
|
-
puts postreqs.join("\n\n")
|
359
|
+
puts postreqs.join("\n\n") unless postreqs.empty?
|
360
|
+
|
361
|
+
output
|
349
362
|
end
|
350
363
|
|
351
364
|
# Output a topic with fancy title and bright white text.
|
@@ -371,12 +384,12 @@ module Howzit
|
|
371
384
|
unless matches.empty?
|
372
385
|
if opt[:single]
|
373
386
|
title = "From #{matches[0]}:"
|
374
|
-
color = '
|
375
|
-
rule = '
|
387
|
+
color = '{Kyd}'
|
388
|
+
rule = '{kKd}'
|
376
389
|
else
|
377
390
|
title = "Include #{matches[0]}"
|
378
|
-
color = '
|
379
|
-
rule = '
|
391
|
+
color = '{Kyd}'
|
392
|
+
rule = '{kKd}'
|
380
393
|
end
|
381
394
|
output.push(format_header("#{'> ' * @nest_level}#{title}", { color: color, hr: '.', border: rule })) unless @included.include?(matches[0])
|
382
395
|
|
@@ -405,15 +418,15 @@ module Howzit
|
|
405
418
|
when /open|url/
|
406
419
|
"\u{279A}"
|
407
420
|
end
|
408
|
-
output.push("
|
421
|
+
output.push(Color.template("{bmK}#{icon} {bwK}#{obj}{x}"))
|
409
422
|
when /(`{3,})run *(.*?)$/i
|
410
423
|
m = Regexp.last_match
|
411
424
|
desc = m[2].length.positive? ? "Block: #{m[2]}" : 'Code Block'
|
412
|
-
output.push("\
|
425
|
+
output.push(Color.template("{bmK}\u{25B6} {bwK}#{desc}{x}\n```"))
|
413
426
|
when /@@@run *(.*?)$/i
|
414
427
|
m = Regexp.last_match
|
415
428
|
desc = m[1].length.positive? ? "Block: #{m[1]}" : 'Code Block'
|
416
|
-
output.push("\
|
429
|
+
output.push(Color.template("{bmK}\u{25B6} {bwK}#{desc}{x}"))
|
417
430
|
else
|
418
431
|
l.wrap!(@options[:wrap]) if (@options[:wrap]).positive?
|
419
432
|
output.push(l)
|
@@ -438,15 +451,15 @@ module Howzit
|
|
438
451
|
else
|
439
452
|
output_topic(key, {single: single})
|
440
453
|
end
|
441
|
-
output.nil? ? '' : output.join("\n")
|
454
|
+
output.nil? ? '' : output.join("\n").strip
|
442
455
|
end
|
443
456
|
|
444
457
|
# Output a list of topic titles
|
445
458
|
def list_topics
|
446
459
|
output = []
|
447
|
-
output.push("\
|
460
|
+
output.push(Color.template("{bg}Topics:{x}\n"))
|
448
461
|
topics.each_key do |title|
|
449
|
-
output.push("-
|
462
|
+
output.push(Color.template("- {bw}#{title}{x}"))
|
450
463
|
end
|
451
464
|
output.join("\n")
|
452
465
|
end
|
@@ -456,14 +469,14 @@ module Howzit
|
|
456
469
|
topics.keys.join("\n")
|
457
470
|
end
|
458
471
|
|
459
|
-
def get_note_title(
|
472
|
+
def get_note_title(truncate = 0)
|
460
473
|
title = nil
|
461
|
-
help = IO.read(
|
474
|
+
help = IO.read(note_file).strip
|
462
475
|
title = help.match(/(?:^(\S.*?)(?=\n==)|^# ?(.*?)$)/)
|
463
476
|
title = if title
|
464
477
|
title[1].nil? ? title[2] : title[1]
|
465
478
|
else
|
466
|
-
|
479
|
+
note_file.sub(/(\.\w+)?$/, '')
|
467
480
|
end
|
468
481
|
|
469
482
|
title && truncate.positive? ? title.trunc(truncate) : title
|
@@ -486,7 +499,7 @@ module Howzit
|
|
486
499
|
|
487
500
|
def list_runnable
|
488
501
|
output = []
|
489
|
-
output.push(%(
|
502
|
+
output.push(Color.template(%({bg}"Runnable" Topics:{x}\n)))
|
490
503
|
topics.each do |title, sect|
|
491
504
|
s_out = []
|
492
505
|
lines = sect.split(/\n/)
|
@@ -507,7 +520,7 @@ module Howzit
|
|
507
520
|
end
|
508
521
|
end
|
509
522
|
unless s_out.empty?
|
510
|
-
output.push("-
|
523
|
+
output.push(Color.template("- {bw}#{title}{x}"))
|
511
524
|
output.push(s_out.join("\n"))
|
512
525
|
end
|
513
526
|
end
|
@@ -531,8 +544,8 @@ module Howzit
|
|
531
544
|
required = t_meta['required'].strip.split(/\s*,\s*/)
|
532
545
|
required.each do |req|
|
533
546
|
unless @metadata.keys.include?(req.downcase)
|
534
|
-
warn %(
|
535
|
-
warn %(
|
547
|
+
warn Color.template(%({xr}ERROR: Missing required metadata key from template '{bw}#{File.basename(template, '.md')}{xr}'{x}))
|
548
|
+
warn Color.template(%({xr}Please define {by}#{req.downcase}{xr} in build notes{x}))
|
536
549
|
Process.exit 1
|
537
550
|
end
|
538
551
|
end
|
@@ -581,6 +594,24 @@ module Howzit
|
|
581
594
|
template_topics
|
582
595
|
end
|
583
596
|
|
597
|
+
def include_file(m)
|
598
|
+
file = File.expand_path(m[1])
|
599
|
+
|
600
|
+
return m[0] unless File.exist?(file)
|
601
|
+
|
602
|
+
content = IO.read(file)
|
603
|
+
home = ENV['HOME']
|
604
|
+
short_path = File.dirname(file.sub(/^#{home}/, '~'))
|
605
|
+
prefix = "#{short_path}/#{File.basename(file)}:"
|
606
|
+
parts = content.split(/^##+/)
|
607
|
+
parts.shift
|
608
|
+
if parts.empty?
|
609
|
+
content
|
610
|
+
else
|
611
|
+
"## #{parts.join('## ')}".gsub(/^(##+ *)(?=\S)/, "\\1#{prefix}")
|
612
|
+
end
|
613
|
+
end
|
614
|
+
|
584
615
|
# Read in the build notes file and output a hash of "Title" => contents
|
585
616
|
def read_help_file(path = nil)
|
586
617
|
filename = path.nil? ? note_file : path
|
@@ -588,21 +619,7 @@ module Howzit
|
|
588
619
|
help = IO.read(filename)
|
589
620
|
|
590
621
|
help.gsub!(/@include\((.*?)\)/) do
|
591
|
-
|
592
|
-
file = File.expand_path(m[1])
|
593
|
-
if File.exist?(file)
|
594
|
-
content = IO.read(file)
|
595
|
-
home = ENV['HOME']
|
596
|
-
short_path = File.dirname(file.sub(/^#{home}/, '~'))
|
597
|
-
prefix = "#{short_path}:"
|
598
|
-
parts = content.split(/^##+/)
|
599
|
-
parts.shift
|
600
|
-
content = '## ' + parts.join('## ')
|
601
|
-
content.gsub!(/^(##+ *)(?=\S)/, "\\1#{prefix}")
|
602
|
-
content
|
603
|
-
else
|
604
|
-
m[0]
|
605
|
-
end
|
622
|
+
include_file(Regexp.last_match)
|
606
623
|
end
|
607
624
|
|
608
625
|
template_topics = get_template_topics(help)
|
@@ -674,6 +691,7 @@ module Howzit
|
|
674
691
|
end
|
675
692
|
|
676
693
|
def initialize(args = [])
|
694
|
+
Color.coloring = $stdout.isatty
|
677
695
|
flags = {
|
678
696
|
run: false,
|
679
697
|
list_topics: false,
|
@@ -683,7 +701,9 @@ module Howzit
|
|
683
701
|
title_only: false,
|
684
702
|
choose: false,
|
685
703
|
quiet: false,
|
686
|
-
verbose: false
|
704
|
+
verbose: false,
|
705
|
+
default: false,
|
706
|
+
grep: nil
|
687
707
|
}
|
688
708
|
|
689
709
|
defaults = {
|
@@ -698,7 +718,7 @@ module Howzit
|
|
698
718
|
show_all_on_error: false,
|
699
719
|
include_upstream: false,
|
700
720
|
show_all_code: false,
|
701
|
-
|
721
|
+
multiple_matches: 'choose',
|
702
722
|
log_level: 1 # 0: debug, 1: info, 2: warn, 3: error
|
703
723
|
}
|
704
724
|
|
@@ -727,7 +747,7 @@ module Howzit
|
|
727
747
|
end
|
728
748
|
|
729
749
|
opts.on('-e', '--edit', "Edit buildnotes file in current working directory
|
730
|
-
using
|
750
|
+
using $EDITOR") do
|
731
751
|
edit_note
|
732
752
|
Process.exit 0
|
733
753
|
end
|
@@ -750,6 +770,11 @@ module Howzit
|
|
750
770
|
@options[:matching] = c
|
751
771
|
end
|
752
772
|
|
773
|
+
opts.on('--multiple TYPE', MULTIPLE_OPTIONS,
|
774
|
+
'Multiple result handling', "(#{MULTIPLE_OPTIONS.join(', ')}, default choose)") do |c|
|
775
|
+
@options[:multiple_matches] = c.to_sym
|
776
|
+
end
|
777
|
+
|
753
778
|
opts.on('-R', '--list-runnable', 'List topics containing @ directives (verbose)') do
|
754
779
|
@options[:list_runnable] = true
|
755
780
|
end
|
@@ -779,8 +804,8 @@ module Howzit
|
|
779
804
|
@options[:log_level] = 0
|
780
805
|
end
|
781
806
|
|
782
|
-
opts.on('-u', '--upstream', 'Traverse up parent directories for additional build notes') do
|
783
|
-
@options[:include_upstream] =
|
807
|
+
opts.on('-u', '--[no-]upstream', 'Traverse up parent directories for additional build notes') do |p|
|
808
|
+
@options[:include_upstream] = p
|
784
809
|
end
|
785
810
|
|
786
811
|
opts.on('--show-code', 'Display the content of fenced run blocks') do
|
@@ -791,7 +816,40 @@ module Howzit
|
|
791
816
|
@options[:wrap] = w.to_i
|
792
817
|
end
|
793
818
|
|
794
|
-
opts.on('--
|
819
|
+
opts.on('--config-get [KEY]', 'Display the configuration settings or setting for a specific key') do |k|
|
820
|
+
|
821
|
+
if k.nil?
|
822
|
+
config.sort_by { |key, _| key }.each do |key, val|
|
823
|
+
print "#{key}: "
|
824
|
+
p val
|
825
|
+
end
|
826
|
+
else
|
827
|
+
k.sub!(/^:/, '')
|
828
|
+
if config.key?(k.to_sym)
|
829
|
+
puts config[k.to_sym]
|
830
|
+
else
|
831
|
+
puts "Key #{k} not found"
|
832
|
+
end
|
833
|
+
end
|
834
|
+
Process.exit 0
|
835
|
+
end
|
836
|
+
|
837
|
+
opts.on('--config-set KEY=VALUE', 'Set a config value (must be a valid key)') do |key|
|
838
|
+
raise 'Argument must be KEY=VALUE' unless key =~ /\S=\S/
|
839
|
+
|
840
|
+
k, v = key.split(/=/)
|
841
|
+
k.sub!(/^:/, '')
|
842
|
+
|
843
|
+
if config.key?(k.to_sym)
|
844
|
+
config[k.to_sym] = v.to_config_value(config[k.to_sym])
|
845
|
+
else
|
846
|
+
puts "Key #{k} not found"
|
847
|
+
end
|
848
|
+
write_config(config)
|
849
|
+
Process.exit 0
|
850
|
+
end
|
851
|
+
|
852
|
+
opts.on('--edit-config', "Edit configuration file using default $EDITOR") do
|
795
853
|
edit_config(defaults)
|
796
854
|
Process.exit 0
|
797
855
|
end
|
@@ -805,21 +863,21 @@ module Howzit
|
|
805
863
|
Dir.chdir(template_folder)
|
806
864
|
Dir.glob('*.md').each do |file|
|
807
865
|
template = File.basename(file, '.md')
|
808
|
-
puts "
|
809
|
-
puts "
|
866
|
+
puts Color.template("{Mk}template:{Yk}#{template}{x}")
|
867
|
+
puts Color.template("{bk}[{bl}tasks{bk}]──────────────────────────────────────┐{x}")
|
810
868
|
metadata = file.extract_metadata
|
811
869
|
topics = read_help_file(file)
|
812
870
|
topics.each_key do |topic|
|
813
|
-
puts "
|
871
|
+
puts Color.template(" {bk}│{bw}-{x} {bcK}#{template}:#{topic.sub(/^.*?:/, '')}{x}")
|
814
872
|
end
|
815
873
|
if metadata.size > 0
|
816
874
|
meta = []
|
817
|
-
meta << metadata['required'].split(/\s*,\s*/).map {|m| "
|
875
|
+
meta << metadata['required'].split(/\s*,\s*/).map {|m| "*{bw}#{m}{xw}" } if metadata.key?('required')
|
818
876
|
meta << metadata['optional'].split(/\s*,\s*/).map {|m| "#{m}" } if metadata.key?('optional')
|
819
|
-
puts "
|
820
|
-
puts "
|
877
|
+
puts Color.template("{bk}[{bl}meta{bk}]───────────────────────────────────────┤{x}")
|
878
|
+
puts Color.template(" {bk}│ {xw}#{meta.join(", ")}{x}")
|
821
879
|
end
|
822
|
-
puts "
|
880
|
+
puts Color.template(" {bk}└───────────────────────────────────────────┘{x}")
|
823
881
|
end
|
824
882
|
Process.exit 0
|
825
883
|
end
|
@@ -846,8 +904,14 @@ module Howzit
|
|
846
904
|
puts "Howzit v#{VERSION}"
|
847
905
|
Process.exit 0
|
848
906
|
end
|
907
|
+
|
908
|
+
opts.on('--default', 'Answer all prompts with default response') do
|
909
|
+
@options[:default] = true
|
910
|
+
end
|
849
911
|
end.parse!(args)
|
850
912
|
|
913
|
+
@options[:multiple_matches] = @options[:multiple_matches].to_sym
|
914
|
+
|
851
915
|
@cli_args = args
|
852
916
|
end
|
853
917
|
|
@@ -980,12 +1044,20 @@ module Howzit
|
|
980
1044
|
|
981
1045
|
def choose(matches)
|
982
1046
|
if command_exist?('fzf')
|
983
|
-
|
1047
|
+
settings = [
|
1048
|
+
'-0',
|
1049
|
+
'-1',
|
1050
|
+
'-m',
|
1051
|
+
"--height=#{matches.count + 2}",
|
1052
|
+
'--header="Use tab to mark multiple selections, enter to display/run"',
|
1053
|
+
'--prompt="Select a section > "'
|
1054
|
+
]
|
1055
|
+
res = `echo #{Shellwords.escape(matches.join("\n"))} | fzf #{settings.join(' ')}`.strip
|
984
1056
|
if res.nil? || res.empty?
|
985
1057
|
warn 'Cancelled'
|
986
1058
|
Process.exit 0
|
987
1059
|
end
|
988
|
-
return res
|
1060
|
+
return res.split(/\n/)
|
989
1061
|
end
|
990
1062
|
|
991
1063
|
res = matches[0..9]
|
@@ -1034,7 +1106,7 @@ module Howzit
|
|
1034
1106
|
File.join(config_dir, 'templates')
|
1035
1107
|
end
|
1036
1108
|
|
1037
|
-
def create_config
|
1109
|
+
def create_config(defaults)
|
1038
1110
|
dir, file = [config_dir, config_file]
|
1039
1111
|
unless File.directory?(dir)
|
1040
1112
|
warn "Creating config directory at #{dir}"
|
@@ -1049,7 +1121,7 @@ module Howzit
|
|
1049
1121
|
end
|
1050
1122
|
|
1051
1123
|
def load_config(defaults)
|
1052
|
-
file = create_config
|
1124
|
+
file = create_config(defaults)
|
1053
1125
|
config = YAML.load(IO.read(file))
|
1054
1126
|
newconfig = config ? defaults.merge(config) : defaults
|
1055
1127
|
write_config(newconfig)
|
@@ -1083,13 +1155,13 @@ module Howzit
|
|
1083
1155
|
end
|
1084
1156
|
|
1085
1157
|
if @options[:title_only]
|
1086
|
-
out = get_note_title(
|
1158
|
+
out = get_note_title(20)
|
1087
1159
|
$stdout.print(out.strip)
|
1088
1160
|
Process.exit(0)
|
1089
|
-
elsif @options[:output_title]
|
1090
|
-
title = get_note_title
|
1161
|
+
elsif @options[:output_title] && !@options[:run]
|
1162
|
+
title = get_note_title
|
1091
1163
|
if title && !title.empty?
|
1092
|
-
header = format_header(title, { hr: "\u{2550}", color: '
|
1164
|
+
header = format_header(title, { hr: "\u{2550}", color: '{bwK}' })
|
1093
1165
|
output.push("#{header}\n")
|
1094
1166
|
end
|
1095
1167
|
end
|
@@ -1115,33 +1187,49 @@ module Howzit
|
|
1115
1187
|
Process.exit(0)
|
1116
1188
|
end
|
1117
1189
|
|
1118
|
-
|
1190
|
+
topic_matches = []
|
1119
1191
|
if @options[:grep]
|
1120
|
-
|
1192
|
+
matches = grep_topics(@options[:grep])
|
1193
|
+
case @options[:multiple_matches]
|
1194
|
+
when :all
|
1195
|
+
topic_matches.concat(matches.sort)
|
1196
|
+
else
|
1197
|
+
topic_matches.concat(choose(matches))
|
1198
|
+
end
|
1121
1199
|
elsif @options[:choose]
|
1122
|
-
|
1200
|
+
topic_matches.concat(choose(topics.keys))
|
1123
1201
|
# If there are arguments use those to search for a matching topic
|
1124
1202
|
elsif !@cli_args.empty?
|
1203
|
+
search = @cli_args.join(' ').strip.downcase.split(/ *, */).map(&:strip)
|
1125
1204
|
|
1126
|
-
search
|
1127
|
-
|
1205
|
+
search.each do |s|
|
1206
|
+
matches = match_topic(s)
|
1128
1207
|
|
1129
|
-
|
1130
|
-
|
1131
|
-
|
1132
|
-
|
1133
|
-
|
1208
|
+
if matches.empty?
|
1209
|
+
output.push(Color.template(%({bR}ERROR:{xr} No topic match found for {bw}#{s}{x}\n)))
|
1210
|
+
else
|
1211
|
+
case @options[:multiple_matches]
|
1212
|
+
when :first
|
1213
|
+
topic_matches.push(matches[0])
|
1214
|
+
when :best
|
1215
|
+
topic_matches.push(matches.sort.min_by(&:length))
|
1216
|
+
when :all
|
1217
|
+
topic_matches.concat(matches)
|
1218
|
+
else
|
1219
|
+
topic_matches.concat(choose(matches))
|
1220
|
+
end
|
1134
1221
|
end
|
1135
|
-
|
1136
|
-
|
1137
|
-
|
1138
|
-
|
1222
|
+
end
|
1223
|
+
|
1224
|
+
if topic_matches.empty? && !@options[:show_all_on_error]
|
1225
|
+
show(output.join("\n"), { color: true, highlight: false, paginate: false, wrap: 0 })
|
1226
|
+
Process.exit 1
|
1139
1227
|
end
|
1140
1228
|
end
|
1141
1229
|
|
1142
|
-
if
|
1230
|
+
if !topic_matches.empty?
|
1143
1231
|
# If we found a match
|
1144
|
-
output.push(process_topic(topic_match, @options[:run], true))
|
1232
|
+
topic_matches.each { |topic_match| output.push(process_topic(topic_match, @options[:run], true)) }
|
1145
1233
|
else
|
1146
1234
|
# If there's no argument or no match found, output all
|
1147
1235
|
topics.each_key { |k| output.push(process_topic(k, false, false)) }
|
@@ -0,0 +1,323 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Cribbed from <https://github.com/flori/term-ansicolor>
|
4
|
+
module Howzit
|
5
|
+
# Terminal output color functions.
|
6
|
+
module Color
|
7
|
+
ESCAPE_REGEX = /(?<=\[)(?:(?:(?:[349]|10)[0-9]|[0-9])?;?)+(?=m)/.freeze
|
8
|
+
# All available color names. Available as methods and string extensions.
|
9
|
+
#
|
10
|
+
# @example Use a color as a method. Color reset will be added to end of string.
|
11
|
+
# Color.yellow('This text is yellow') => "\e[33mThis text is yellow\e[0m"
|
12
|
+
#
|
13
|
+
# @example Use a color as a string extension. Color reset added automatically.
|
14
|
+
# 'This text is green'.green => "\e[1;32mThis text is green\e[0m"
|
15
|
+
#
|
16
|
+
# @example Send a text string as a color
|
17
|
+
# Color.send('red') => "\e[31m"
|
18
|
+
ATTRIBUTES = [
|
19
|
+
[:clear, 0], # String#clear is already used to empty string in Ruby 1.9
|
20
|
+
[:reset, 0], # synonym for :clear
|
21
|
+
[:bold, 1],
|
22
|
+
[:dark, 2],
|
23
|
+
[:italic, 3], # not widely implemented
|
24
|
+
[:underline, 4],
|
25
|
+
[:underscore, 4], # synonym for :underline
|
26
|
+
[:blink, 5],
|
27
|
+
[:rapid_blink, 6], # not widely implemented
|
28
|
+
[:negative, 7], # no reverse because of String#reverse
|
29
|
+
[:concealed, 8],
|
30
|
+
[:strikethrough, 9], # not widely implemented
|
31
|
+
[:strike, 9], # not widely implemented
|
32
|
+
[:black, 30],
|
33
|
+
[:red, 31],
|
34
|
+
[:green, 32],
|
35
|
+
[:yellow, 33],
|
36
|
+
[:blue, 34],
|
37
|
+
[:magenta, 35],
|
38
|
+
[:purple, 35],
|
39
|
+
[:cyan, 36],
|
40
|
+
[:white, 37],
|
41
|
+
[:bgblack, 40],
|
42
|
+
[:bgred, 41],
|
43
|
+
[:bggreen, 42],
|
44
|
+
[:bgyellow, 43],
|
45
|
+
[:bgblue, 44],
|
46
|
+
[:bgmagenta, 45],
|
47
|
+
[:bgpurple, 45],
|
48
|
+
[:bgcyan, 46],
|
49
|
+
[:bgwhite, 47],
|
50
|
+
[:boldblack, 90],
|
51
|
+
[:boldred, 91],
|
52
|
+
[:boldgreen, 92],
|
53
|
+
[:boldyellow, 93],
|
54
|
+
[:boldblue, 94],
|
55
|
+
[:boldmagenta, 95],
|
56
|
+
[:boldpurple, 95],
|
57
|
+
[:boldcyan, 96],
|
58
|
+
[:boldwhite, 97],
|
59
|
+
[:boldbgblack, 100],
|
60
|
+
[:boldbgred, 101],
|
61
|
+
[:boldbggreen, 102],
|
62
|
+
[:boldbgyellow, 103],
|
63
|
+
[:boldbgblue, 104],
|
64
|
+
[:boldbgmagenta, 105],
|
65
|
+
[:boldbgpurple, 105],
|
66
|
+
[:boldbgcyan, 106],
|
67
|
+
[:boldbgwhite, 107],
|
68
|
+
[:softpurple, '0;35;40'],
|
69
|
+
[:hotpants, '7;34;40'],
|
70
|
+
[:knightrider, '7;30;40'],
|
71
|
+
[:flamingo, '7;31;47'],
|
72
|
+
[:yeller, '1;37;43'],
|
73
|
+
[:whiteboard, '1;30;47'],
|
74
|
+
[:chalkboard, '1;37;40'],
|
75
|
+
[:led, '0;32;40'],
|
76
|
+
[:redacted, '0;30;40'],
|
77
|
+
[:alert, '1;31;43'],
|
78
|
+
[:error, '1;37;41'],
|
79
|
+
[:default, '0;39']
|
80
|
+
].map(&:freeze).freeze
|
81
|
+
|
82
|
+
ATTRIBUTE_NAMES = ATTRIBUTES.transpose.first
|
83
|
+
|
84
|
+
# Returns true if Howzit::Color supports the +feature+.
|
85
|
+
#
|
86
|
+
# The feature :clear, that is mixing the clear color attribute into String,
|
87
|
+
# is only supported on ruby implementations, that do *not* already
|
88
|
+
# implement the String#clear method. It's better to use the reset color
|
89
|
+
# attribute instead.
|
90
|
+
def support?(feature)
|
91
|
+
case feature
|
92
|
+
when :clear
|
93
|
+
!String.instance_methods(false).map(&:to_sym).include?(:clear)
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
# Template coloring
|
98
|
+
class ::String
|
99
|
+
##
|
100
|
+
## Extract the longest valid %color name from a string.
|
101
|
+
##
|
102
|
+
## Allows %colors to bleed into other text and still
|
103
|
+
## be recognized, e.g. %greensomething still finds
|
104
|
+
## %green.
|
105
|
+
##
|
106
|
+
## @return [String] a valid color name
|
107
|
+
##
|
108
|
+
def validate_color
|
109
|
+
valid_color = nil
|
110
|
+
compiled = ''
|
111
|
+
normalize_color.split('').each do |char|
|
112
|
+
compiled += char
|
113
|
+
valid_color = compiled if Color.attributes.include?(compiled.to_sym) || compiled =~ /^([fb]g?)?#([a-f0-9]{6})$/i
|
114
|
+
end
|
115
|
+
|
116
|
+
valid_color
|
117
|
+
end
|
118
|
+
|
119
|
+
##
|
120
|
+
## Normalize a color name, removing underscores,
|
121
|
+
## replacing "bright" with "bold", and converting
|
122
|
+
## bgbold to boldbg
|
123
|
+
##
|
124
|
+
## @return [String] Normalized color name
|
125
|
+
##
|
126
|
+
def normalize_color
|
127
|
+
gsub(/_/, '').sub(/bright/i, 'bold').sub(/bgbold/, 'boldbg')
|
128
|
+
end
|
129
|
+
|
130
|
+
# Get the calculated ANSI color at the end of the
|
131
|
+
# string
|
132
|
+
#
|
133
|
+
# @return ANSI escape sequence to match color
|
134
|
+
#
|
135
|
+
def last_color_code
|
136
|
+
m = scan(ESCAPE_REGEX)
|
137
|
+
|
138
|
+
em = ['0']
|
139
|
+
fg = nil
|
140
|
+
bg = nil
|
141
|
+
rgbf = nil
|
142
|
+
rgbb = nil
|
143
|
+
|
144
|
+
m.each do |c|
|
145
|
+
case c
|
146
|
+
when '0'
|
147
|
+
em = ['0']
|
148
|
+
fg, bg, rgbf, rgbb = nil
|
149
|
+
when /^[34]8/
|
150
|
+
case c
|
151
|
+
when /^3/
|
152
|
+
fg = nil
|
153
|
+
rgbf = c
|
154
|
+
when /^4/
|
155
|
+
bg = nil
|
156
|
+
rgbb = c
|
157
|
+
end
|
158
|
+
else
|
159
|
+
c.split(/;/).each do |i|
|
160
|
+
x = i.to_i
|
161
|
+
if x <= 9
|
162
|
+
em << x
|
163
|
+
elsif x >= 30 && x <= 39
|
164
|
+
rgbf = nil
|
165
|
+
fg = x
|
166
|
+
elsif x >= 40 && x <= 49
|
167
|
+
rgbb = nil
|
168
|
+
bg = x
|
169
|
+
elsif x >= 90 && x <= 97
|
170
|
+
rgbf = nil
|
171
|
+
fg = x
|
172
|
+
elsif x >= 100 && x <= 107
|
173
|
+
rgbb = nil
|
174
|
+
bg = x
|
175
|
+
end
|
176
|
+
end
|
177
|
+
end
|
178
|
+
end
|
179
|
+
|
180
|
+
escape = "\e[#{em.join(';')}m"
|
181
|
+
escape += "\e[#{rgbb}m" if rgbb
|
182
|
+
escape += "\e[#{rgbf}m" if rgbf
|
183
|
+
escape + "\e[#{[fg, bg].delete_if(&:nil?).join(';')}m"
|
184
|
+
end
|
185
|
+
end
|
186
|
+
|
187
|
+
class << self
|
188
|
+
# Returns true if the coloring function of this module
|
189
|
+
# is switched on, false otherwise.
|
190
|
+
def coloring?
|
191
|
+
@coloring
|
192
|
+
end
|
193
|
+
|
194
|
+
attr_writer :coloring
|
195
|
+
|
196
|
+
##
|
197
|
+
## Enables colored output
|
198
|
+
##
|
199
|
+
## @example Turn color on or off based on TTY
|
200
|
+
## Howzit::Color.coloring = STDOUT.isatty
|
201
|
+
def coloring
|
202
|
+
@coloring ||= true
|
203
|
+
end
|
204
|
+
|
205
|
+
##
|
206
|
+
## Convert a template string to a colored string.
|
207
|
+
## Colors are specified with single letters inside
|
208
|
+
## curly braces. Uppercase changes background color.
|
209
|
+
##
|
210
|
+
## w: white, k: black, g: green, l: blue, y: yellow, c: cyan,
|
211
|
+
## m: magenta, r: red, b: bold, u: underline, i: italic,
|
212
|
+
## x: reset (remove background, color, emphasis)
|
213
|
+
##
|
214
|
+
## @example Convert a templated string
|
215
|
+
## Color.template('{Rwb}Warning:{x} {w}you look a little {g}ill{x}')
|
216
|
+
##
|
217
|
+
## @param input [String, Array] The template
|
218
|
+
## string. If this is an array, the
|
219
|
+
## elements will be joined with a
|
220
|
+
## space.
|
221
|
+
##
|
222
|
+
## @return [String] Colorized string
|
223
|
+
##
|
224
|
+
def template(input)
|
225
|
+
input = input.join(' ') if input.is_a? Array
|
226
|
+
input.gsub!(/%/, '%%')
|
227
|
+
fmt = input.gsub(/\{(\w+)\}/) do
|
228
|
+
Regexp.last_match(1).split('').map { |c| "%<#{c}>s" }.join('')
|
229
|
+
end
|
230
|
+
|
231
|
+
colors = { w: white, k: black, g: green, l: blue,
|
232
|
+
y: yellow, c: cyan, m: magenta, r: red,
|
233
|
+
W: bgwhite, K: bgblack, G: bggreen, L: bgblue,
|
234
|
+
Y: bgyellow, C: bgcyan, M: bgmagenta, R: bgred,
|
235
|
+
d: dark, b: bold, u: underline, i: italic, x: reset }
|
236
|
+
|
237
|
+
format(fmt, colors)
|
238
|
+
end
|
239
|
+
end
|
240
|
+
|
241
|
+
ATTRIBUTES.each do |c, v|
|
242
|
+
new_method = <<-EOSCRIPT
|
243
|
+
def #{c}(string = nil)
|
244
|
+
result = ''
|
245
|
+
result << "\e[#{v}m" if Howzit::Color.coloring?
|
246
|
+
if block_given?
|
247
|
+
result << yield
|
248
|
+
elsif string.respond_to?(:to_str)
|
249
|
+
result << string.to_str
|
250
|
+
elsif respond_to?(:to_str)
|
251
|
+
result << to_str
|
252
|
+
else
|
253
|
+
return result #only switch on
|
254
|
+
end
|
255
|
+
result << "\e[0m" if Howzit::Color.coloring?
|
256
|
+
result
|
257
|
+
end
|
258
|
+
EOSCRIPT
|
259
|
+
|
260
|
+
module_eval(new_method)
|
261
|
+
|
262
|
+
next unless c =~ /bold/
|
263
|
+
|
264
|
+
# Accept brightwhite in addition to boldwhite
|
265
|
+
new_method = <<-EOSCRIPT
|
266
|
+
def #{c.to_s.sub(/bold/, 'bright')}(string = nil)
|
267
|
+
result = ''
|
268
|
+
result << "\e[#{v}m" if Howzit::Color.coloring?
|
269
|
+
if block_given?
|
270
|
+
result << yield
|
271
|
+
elsif string.respond_to?(:to_str)
|
272
|
+
result << string.to_str
|
273
|
+
elsif respond_to?(:to_str)
|
274
|
+
result << to_str
|
275
|
+
else
|
276
|
+
return result #only switch on
|
277
|
+
end
|
278
|
+
result << "\e[0m" if Howzit::Color.coloring?
|
279
|
+
result
|
280
|
+
end
|
281
|
+
EOSCRIPT
|
282
|
+
|
283
|
+
module_eval(new_method)
|
284
|
+
end
|
285
|
+
|
286
|
+
def rgb(hex)
|
287
|
+
is_bg = hex.match(/^bg?#/) ? true : false
|
288
|
+
hex_string = hex.sub(/^([fb]g?)?#/, '')
|
289
|
+
|
290
|
+
parts = hex_string.match(/(?<r>..)(?<g>..)(?<b>..)/)
|
291
|
+
t = []
|
292
|
+
%w[r g b].each do |e|
|
293
|
+
t << parts[e].hex
|
294
|
+
end
|
295
|
+
color =
|
296
|
+
"\e[#{is_bg ? '48' : '38'};2;#{t.join(';')}m"
|
297
|
+
end
|
298
|
+
|
299
|
+
# Regular expression that is used to scan for ANSI-sequences while
|
300
|
+
# uncoloring strings.
|
301
|
+
COLORED_REGEXP = /\e\[(?:(?:[349]|10)[0-7]|[0-9])?m/.freeze
|
302
|
+
|
303
|
+
# Returns an uncolored version of the string, that is all
|
304
|
+
# ANSI-sequences are stripped from the string.
|
305
|
+
def uncolor(string = nil) # :yields:
|
306
|
+
if block_given?
|
307
|
+
yield.to_str.gsub(COLORED_REGEXP, '')
|
308
|
+
elsif string.respond_to?(:to_str)
|
309
|
+
string.to_str.gsub(COLORED_REGEXP, '')
|
310
|
+
elsif respond_to?(:to_str)
|
311
|
+
to_str.gsub(COLORED_REGEXP, '')
|
312
|
+
else
|
313
|
+
''
|
314
|
+
end
|
315
|
+
end
|
316
|
+
|
317
|
+
# Returns an array of all Howzit::Color attributes as symbols.
|
318
|
+
def attributes
|
319
|
+
ATTRIBUTE_NAMES
|
320
|
+
end
|
321
|
+
extend self
|
322
|
+
end
|
323
|
+
end
|
data/lib/howzit/prompt.rb
CHANGED
@@ -1,8 +1,11 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module Howzit
|
4
|
+
# Command line prompt utils
|
4
5
|
module Prompt
|
5
6
|
def yn(prompt, default = true)
|
7
|
+
return default if !$stdout.isatty
|
8
|
+
|
6
9
|
system 'stty cbreak'
|
7
10
|
yn = color_single_options(default ? %w[Y n] : %w[y N])
|
8
11
|
$stdout.syswrite "\e[1;37m#{prompt} #{yn}\e[1;37m? \e[0m"
|
data/lib/howzit/stringutils.rb
CHANGED
@@ -3,6 +3,31 @@
|
|
3
3
|
module Howzit
|
4
4
|
# String Extensions
|
5
5
|
module StringUtils
|
6
|
+
# Convert a string to a valid YAML value
|
7
|
+
def to_config_value(orig_value = nil)
|
8
|
+
if orig_value
|
9
|
+
case orig_value.class.to_s
|
10
|
+
when /Integer/
|
11
|
+
to_i
|
12
|
+
when /(True|False)Class/
|
13
|
+
self =~ /^(t(rue)?|y(es)?|1)$/i ? true : false
|
14
|
+
else
|
15
|
+
self
|
16
|
+
end
|
17
|
+
else
|
18
|
+
case self
|
19
|
+
when /^[0-9]+$/
|
20
|
+
to_i
|
21
|
+
when /^(t(rue)?|y(es)?)$/i
|
22
|
+
true
|
23
|
+
when /^(f(alse)?|n(o)?)$/i
|
24
|
+
false
|
25
|
+
else
|
26
|
+
self
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
6
31
|
# Just strip out color codes when requested
|
7
32
|
def uncolor
|
8
33
|
gsub(/\e\[[\d;]+m/, '').gsub(/\e\]1337;SetMark/,'')
|
data/lib/howzit/version.rb
CHANGED
data/lib/howzit.rb
CHANGED
@@ -1,5 +1,6 @@
|
|
1
1
|
require 'howzit/version'
|
2
2
|
require 'howzit/prompt'
|
3
|
+
require 'howzit/colors'
|
3
4
|
require 'howzit/buildnotes'
|
4
5
|
require 'howzit/stringutils'
|
5
6
|
require 'optparse'
|
@@ -15,3 +16,4 @@ CONFIG_DIR = '~/.config/howzit'
|
|
15
16
|
CONFIG_FILE = 'howzit.yaml'
|
16
17
|
IGNORE_FILE = 'ignore.yaml'
|
17
18
|
MATCHING_OPTIONS = %w[partial exact fuzzy beginswith].freeze
|
19
|
+
MULTIPLE_OPTIONS = %w[first best all choose].freeze
|
data/spec/ruby_gem_spec.rb
CHANGED
@@ -9,3 +9,32 @@ describe Howzit::BuildNotes do
|
|
9
9
|
end
|
10
10
|
end
|
11
11
|
end
|
12
|
+
|
13
|
+
describe Howzit::BuildNotes do
|
14
|
+
Dir.chdir('spec')
|
15
|
+
how = Howzit::BuildNotes.new(['--no-upstream', '--default'])
|
16
|
+
how.create_note
|
17
|
+
subject { how }
|
18
|
+
|
19
|
+
describe ".note_file" do
|
20
|
+
it "locates a build note file" do
|
21
|
+
expect(subject.note_file).not_to be_empty
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
describe ".grep_topics" do
|
26
|
+
it "finds editable" do
|
27
|
+
expect(subject.grep_topics('editable')).to include('File Structure')
|
28
|
+
expect(subject.grep_topics('editable')).not_to include('Build')
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
describe ".list_topic_titles" do
|
33
|
+
it "finds 4 topics" do
|
34
|
+
expect(subject.topics.keys.count).to eq 4
|
35
|
+
end
|
36
|
+
it "outputs a newline-separated string" do
|
37
|
+
expect(subject.list_topic_titles.scan(/\n/).count).to eq 3
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: howzit
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.2.
|
4
|
+
version: 1.2.14
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Brett Terpstra
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2022-08-
|
11
|
+
date: 2022-08-02 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -269,6 +269,7 @@ files:
|
|
269
269
|
- lib/.rubocop.yml
|
270
270
|
- lib/howzit.rb
|
271
271
|
- lib/howzit/buildnotes.rb
|
272
|
+
- lib/howzit/colors.rb
|
272
273
|
- lib/howzit/prompt.rb
|
273
274
|
- lib/howzit/stringutils.rb
|
274
275
|
- lib/howzit/version.rb
|