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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 24b630c81dbd81b43b8572bbc91baabe96063a17c3cb6295a8026e6a3176dd39
4
- data.tar.gz: 04c98ab9c7a9c57e8c7536e7709b57d51b965a76cc315c846c46dca3a0a2105e
3
+ metadata.gz: 3858dec6b009f75173d3d72067a94c330cb826b0e367109c7416b2db19065fa8
4
+ data.tar.gz: 2e05520b1ff9d5da82aba03b1af3ed31ee0a635f3fe42c50b3a515c45337c38b
5
5
  SHA512:
6
- metadata.gz: 3ce4d9a3cd8cd8970cd37e5b90c7154c0b66de06827780c4e38bb4f358d517b8c81d720383cbaa8fa84ff74f56a9f1f60e554f92614cf12e4f1f563092d9d651
7
- data.tar.gz: 5187fbd097d9171ddd3756f9df92e62b0df5309d7e1d2e0aef26551f6641340d65ade5f49f1c9b0a47852e6913c64d5914d334732e15a1ac71f2277edde38fe7
6
+ metadata.gz: 90cb7e647c7956823e73eac6e4884a1ffe26875acd531cc70be6a1b334aa9653213cafbf0830cd69a634579c7fa61553585bbbc95f997a02b0192d093a85be1b
7
+ data.tar.gz: bfba515bc66d5f15ae33d967866e83e2e9e4c887160d0c66269542ee909acf8383742fd0e64f5639013a1ccdc16253664a3c90cd150fd5a67529280473ed1a88
data/.gitignore CHANGED
@@ -41,3 +41,4 @@ Gemfile.lock
41
41
 
42
42
  # unless supporting rvm < 1.11.0 or doing something fancy, ignore this:
43
43
  .rvmrc
44
+ results.log
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 using editor.sh
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 Traverse up parent directories for additional build notes
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 editor.sh
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
@@ -1,4 +1,4 @@
1
- #!/usr/bin/env ruby
1
+ #!/usr/bin/env ruby -W1
2
2
  # frozen_string_literal: true
3
3
 
4
4
  $LOAD_PATH.unshift File.join(__dir__, '..', 'lib')
@@ -6,4 +6,3 @@ require 'howzit'
6
6
 
7
7
  how = Howzit::BuildNotes.new(ARGV)
8
8
  how.process
9
-
@@ -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['GIT_PAGER'], ENV['PAGER'],
44
- 'bat', 'less', 'more', 'cat', 'pager']
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("\e[1;32m#{choice}\e[0;32m")
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
- "\e[0;32m[#{out.join('/')}]\e[0m"
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 = "\e[1;33m#{note_file}\e[1;37m"
165
- res = yn("#{fname} exists and appears to be a build note, continue anyway?", false)
166
- unless res
167
- puts 'Canceled'
168
- Process.exit 0
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
- printf "\e[1;37mProject name \e[0;32m[#{title}]\e[1;37m: \e[0m"
174
- input = STDIN.gets.chomp
175
- title = input unless input.empty?
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
- printf "\e[1;37mProject summary: \e[0m"
179
- input = STDIN.gets.chomp
180
- summary = input unless input.empty?
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
- filename = 'buildnotes.md'
183
- printf "\e[1;37mBuild notes filename (must begin with 'howzit' or 'build')\n\e[0;32m[#{filename}]\e[1;37m: \e[0m"
184
- input = STDIN.gets.chomp
185
- filename = input unless input.empty?
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?(filename)
213
- file = "\e[1;33m#{filename}"
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(filename, 'w') do |f|
233
+ File.open(fname, 'w') do |f|
223
234
  f.puts note
224
- puts "Build notes for #{title} written to #{filename}"
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: '1;32',
233
- border: '0',
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 = "\e[#{options[:border]}m#{options[:hr] * 2}( \e[#{options[:color]}m#{title}\e[#{options[:border]}m )"
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}\e[0m"
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 = "\e[1;32mOpening \e[3;37m#{command}"
265
+ out = Color.template("{bg}Opening {bw}#{command}")
255
266
  case os
256
267
  when /darwin.*/i
257
- warn "#{out} (macOS)\e[0m" if @options[:log_level] < 2
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)\e[0m" if @options[:log_level] < 2
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)\e[0m" if @options[:log_level] < 2
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 "\e[1;32mRunning block \e[3;37m#{title}\e[0m" if @options[:log_level] < 2
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 "\e[1;33mTasks from \e[3;37m#{matches[0]} already included, skipping\e[0m" if @options[:log_level] < 2
336
+ warn Color.template("{by}Tasks from {bw}#{matches[0]} already included, skipping{x}") if @options[:log_level] < 2
326
337
  else
327
- warn "\e[1;33mIncluding tasks from \e[3;37m#{matches[0]}\e[0m" if @options[:log_level] < 2
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 "\e[1;33mEnd include \e[3;37m#{matches[0]}\e[0m" if @options[:log_level] < 2
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 "\e[1;32mRunning \e[3;37m#{obj}\e[0m" if @options[:log_level] < 2
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 "\e[1;32mCopied \e[3;37m#{obj}\e[1;32m to clipboard\e[0m" if @options[:log_level] < 2
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 "\e[0;31m--run: No \e[1;31m@directive\e[0;31;40m found in \e[1;37m#{key}\e[0m"
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 = '33;40'
375
- rule = '30;40'
387
+ color = '{Kyd}'
388
+ rule = '{kKd}'
376
389
  else
377
390
  title = "Include #{matches[0]}"
378
- color = '33;40'
379
- rule = '0'
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("\e[1;35;40m#{icon} \e[3;37;40m#{obj}\e[0m")
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("\e[1;35;40m\u{25B6} \e[3;37;40m#{desc}\e[0m\n```")
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("\e[1;35;40m\u{25B6} \e[3;37;40m#{desc}\e[0m")
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("\e[1;32mTopics:\e[0m\n")
460
+ output.push(Color.template("{bg}Topics:{x}\n"))
448
461
  topics.each_key do |title|
449
- output.push("- \e[1;37m#{title}\e[0m")
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(filename, truncate = 0)
472
+ def get_note_title(truncate = 0)
460
473
  title = nil
461
- help = IO.read(filename).strip
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
- filename.sub(/(\.\w+)?$/, '')
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(%(\e[1;32m"Runnable" Topics:\e[0m\n))
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("- \e[1;37m#{title}\e[0m")
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 %(\e[0;31mERROR: Missing required metadata key from template '\e[1;37m#{File.basename(template, '.md')}\e[0;31m'\e[0m)
535
- warn %(\e[0;31mPlease define \e[1;33m#{req.downcase}\e[0;31m in build notes\e[0m)
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
- m = Regexp.last_match
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
- grep: nil,
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 #{File.basename(ENV['EDITOR'])}") do
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] = true
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('--edit-config', "Edit configuration file using #{File.basename(ENV['EDITOR'])}") do
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 "\e[7;30;45mtemplate: \e[7;33;40m#{template}\e[0m"
809
- puts "\e[1;30m[\e[1;37mtasks\e[1;30m]──────────────────────────────────────┐\e[0m"
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 " \e[1;30m│\e[1;37m-\e[0m \e[1;36;40m#{template}:#{topic.sub(/^.*?:/, '')}\e[0m"
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| "*\e[1;37m#{m}\e[0;37m" } if metadata.key?('required')
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 "\e[1;30m[\e[1;34mmeta\e[1;30m]───────────────────────────────────────┤\e[0m"
820
- puts " \e[1;30m│\e[1;37m \e[0;37m#{meta.join(", ")}\e[0m"
877
+ puts Color.template("{bk}[{bl}meta{bk}]───────────────────────────────────────┤{x}")
878
+ puts Color.template(" {bk}│ {xw}#{meta.join(", ")}{x}")
821
879
  end
822
- puts " \e[1;30m└───────────────────────────────────────────┘\e[0m"
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
- res = `echo #{Shellwords.escape(matches.join("\n"))} | fzf -0 -1 --height #{matches.count + 2} --prompt 'Select a section > '`.strip
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(note_file, 20)
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(note_file)
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: '1;37;40' })
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
- topic_match = nil
1190
+ topic_matches = []
1119
1191
  if @options[:grep]
1120
- topic_match = choose(grep_topics(@options[:grep]))
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
- topic_match = choose(topics.keys)
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 = @cli_args.join(' ').strip.downcase
1127
- matches = match_topic(search)
1205
+ search.each do |s|
1206
+ matches = match_topic(s)
1128
1207
 
1129
- if matches.empty?
1130
- output.push(%(\e[0;31mERROR: No topic match found for \e[1;33m#{search}\e[0m\n))
1131
- if !@options[:show_all_on_error]
1132
- show(output.join("\n"), { color: true, highlight: false, paginate: false, wrap: 0 })
1133
- Process.exit 1
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
- elsif matches.length == 1
1136
- topic_match = matches[0]
1137
- else
1138
- topic_match = choose(matches)
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 topic_match
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"
@@ -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/,'')
@@ -2,5 +2,5 @@
2
2
  # Primary module for this gem.
3
3
  module Howzit
4
4
  # Current Howzit version.
5
- VERSION = '1.2.11'.freeze
5
+ VERSION = '1.2.14'.freeze
6
6
  end
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
@@ -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.11
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-01 00:00:00.000000000 Z
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