howzit 2.1.18 → 2.1.21
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 +66 -0
- data/bin/howzit +2 -2
- data/howzit.gemspec +3 -1
- data/lib/howzit/buildnote.rb +263 -94
- data/lib/howzit/prompt.rb +19 -7
- data/lib/howzit/run_report.rb +51 -0
- data/lib/howzit/stringutils.rb +3 -1
- data/lib/howzit/task.rb +21 -2
- data/lib/howzit/topic.rb +24 -1
- data/lib/howzit/util.rb +5 -1
- data/lib/howzit/version.rb +1 -1
- data/lib/howzit.rb +11 -2
- data/spec/buildnote_spec.rb +59 -3
- data/spec/cli_spec.rb +1 -1
- data/spec/run_report_spec.rb +35 -0
- data/spec/spec_helper.rb +14 -0
- data/spec/topic_spec.rb +5 -2
- metadata +6 -15
- data/.editorconfig +0 -9
- data/.github/FUNDING.yml +0 -2
- data/.gitignore +0 -45
- data/.howzit.taskpaper.bak +0 -27
- data/.irbrc +0 -12
- data/.rspec +0 -2
- data/.rubocop.yml +0 -48
- data/.travis.yml +0 -17
- data/.yardopts +0 -6
- data/lib/.rubocop.yml +0 -1
- data/spec/.rubocop.yml +0 -4
data/lib/howzit/buildnote.rb
CHANGED
|
@@ -57,7 +57,7 @@ module Howzit
|
|
|
57
57
|
## @param template [String] The template title
|
|
58
58
|
##
|
|
59
59
|
def edit_template(template)
|
|
60
|
-
file = template.sub(/(\.md)?$/i,
|
|
60
|
+
file = template.sub(/(\.md)?$/i, '.md')
|
|
61
61
|
file = File.join(Howzit.config.template_folder, file)
|
|
62
62
|
edit_template_file(file)
|
|
63
63
|
end
|
|
@@ -70,9 +70,39 @@ module Howzit
|
|
|
70
70
|
def find_topic(term = nil)
|
|
71
71
|
return @topics if term.nil?
|
|
72
72
|
|
|
73
|
+
rx = term.to_rx
|
|
74
|
+
|
|
73
75
|
@topics.filter do |topic|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
+
title = topic.title.downcase.sub(/ *\(.*?\) *$/, '')
|
|
77
|
+
match = title =~ rx
|
|
78
|
+
|
|
79
|
+
if !match && term =~ /[,:]/
|
|
80
|
+
normalized = title.gsub(/\s*([,:])\s*/, '\1')
|
|
81
|
+
match = normalized =~ rx
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
match
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
##
|
|
89
|
+
## Find a topic with an exact whole-word match
|
|
90
|
+
##
|
|
91
|
+
## @param term [String] The search term
|
|
92
|
+
##
|
|
93
|
+
## @return [Array] Array of topics that exactly match the term
|
|
94
|
+
##
|
|
95
|
+
def find_topic_exact(term = nil)
|
|
96
|
+
return [] if term.nil?
|
|
97
|
+
|
|
98
|
+
@topics.filter do |topic|
|
|
99
|
+
title = topic.title.downcase.sub(/ *\(.*?\) *$/, '').strip
|
|
100
|
+
# Split both the title and search term into words
|
|
101
|
+
title_words = title.split
|
|
102
|
+
search_words = term.split
|
|
103
|
+
|
|
104
|
+
# Check if all search words match the title words exactly (case-insensitive)
|
|
105
|
+
search_words.map(&:downcase) == title_words.map(&:downcase)
|
|
76
106
|
end
|
|
77
107
|
end
|
|
78
108
|
|
|
@@ -84,7 +114,7 @@ module Howzit
|
|
|
84
114
|
title = "#{title} project notes"
|
|
85
115
|
url = "[#{title}](file://#{note_file})"
|
|
86
116
|
Util.os_copy(url)
|
|
87
|
-
Howzit.console.info(
|
|
117
|
+
Howzit.console.info('Link copied to clipboard.')
|
|
88
118
|
end
|
|
89
119
|
|
|
90
120
|
##
|
|
@@ -117,9 +147,7 @@ module Howzit
|
|
|
117
147
|
def list_topics
|
|
118
148
|
@topics.map do |topic|
|
|
119
149
|
title = topic.title
|
|
120
|
-
unless topic.named_args.empty?
|
|
121
|
-
title += "(#{topic.named_args.keys.join(", ")})"
|
|
122
|
-
end
|
|
150
|
+
title += "(#{topic.named_args.keys.join(', ')})" unless topic.named_args.empty?
|
|
123
151
|
title
|
|
124
152
|
end
|
|
125
153
|
end
|
|
@@ -143,13 +171,11 @@ module Howzit
|
|
|
143
171
|
def list_runnable_completions
|
|
144
172
|
output = []
|
|
145
173
|
@topics.each do |topic|
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
output.push(title)
|
|
152
|
-
end
|
|
174
|
+
next unless topic.tasks.count.positive?
|
|
175
|
+
|
|
176
|
+
title = topic.title
|
|
177
|
+
title += "(#{topic.named_args.keys.join(', ')})" unless topic.named_args.empty?
|
|
178
|
+
output.push(title)
|
|
153
179
|
end
|
|
154
180
|
output.join("\n")
|
|
155
181
|
end
|
|
@@ -172,9 +198,7 @@ module Howzit
|
|
|
172
198
|
next if s_out.empty?
|
|
173
199
|
|
|
174
200
|
title = topic.title
|
|
175
|
-
unless topic.named_args.empty?
|
|
176
|
-
title += " {dy}({xy}#{topic.named_args.keys.join(", ")}{dy}){x}"
|
|
177
|
-
end
|
|
201
|
+
title += " {dy}({xy}#{topic.named_args.keys.join(', ')}{dy}){x}" unless topic.named_args.empty?
|
|
178
202
|
|
|
179
203
|
output.push("- {g}#{title}{x}".c)
|
|
180
204
|
output.push(s_out.join("\n"))
|
|
@@ -199,7 +223,7 @@ module Howzit
|
|
|
199
223
|
## @param prompt [Boolean] confirm file creation?
|
|
200
224
|
##
|
|
201
225
|
def create_template_file(file, prompt: false)
|
|
202
|
-
trap(
|
|
226
|
+
trap('SIGINT') do
|
|
203
227
|
Howzit.console.info "\nCancelled"
|
|
204
228
|
exit!
|
|
205
229
|
end
|
|
@@ -211,7 +235,7 @@ module Howzit
|
|
|
211
235
|
Process.exit 0 unless res
|
|
212
236
|
end
|
|
213
237
|
|
|
214
|
-
title = File.basename(file,
|
|
238
|
+
title = File.basename(file, '.md')
|
|
215
239
|
|
|
216
240
|
note = <<~EOBUILDNOTES
|
|
217
241
|
# #{title}
|
|
@@ -223,12 +247,12 @@ module Howzit
|
|
|
223
247
|
if File.exist?(file) && !default
|
|
224
248
|
file = "{by}#{file}".c
|
|
225
249
|
unless Prompt.yn("Are you sure you want to overwrite #{file}", default: false)
|
|
226
|
-
Howzit.console.info(
|
|
250
|
+
Howzit.console.info('Cancelled')
|
|
227
251
|
Process.exit 0
|
|
228
252
|
end
|
|
229
253
|
end
|
|
230
254
|
|
|
231
|
-
File.open(file,
|
|
255
|
+
File.open(file, 'w') do |f|
|
|
232
256
|
f.puts note
|
|
233
257
|
Howzit.console.info("{by}Template {bw}#{title}{by} written to {bw}#{file}{x}".c)
|
|
234
258
|
end
|
|
@@ -243,7 +267,7 @@ module Howzit
|
|
|
243
267
|
|
|
244
268
|
# Create a buildnotes skeleton
|
|
245
269
|
def create_note(prompt: false)
|
|
246
|
-
trap(
|
|
270
|
+
trap('SIGINT') do
|
|
247
271
|
Howzit.console.info "\nCancelled"
|
|
248
272
|
exit!
|
|
249
273
|
end
|
|
@@ -251,7 +275,7 @@ module Howzit
|
|
|
251
275
|
default = !$stdout.isatty || Howzit.options[:default]
|
|
252
276
|
|
|
253
277
|
if prompt && !default
|
|
254
|
-
res = Prompt.yn(
|
|
278
|
+
res = Prompt.yn('No build notes file found, create one?', default: true)
|
|
255
279
|
Process.exit 0 unless res
|
|
256
280
|
end
|
|
257
281
|
|
|
@@ -274,14 +298,14 @@ module Howzit
|
|
|
274
298
|
input = $stdin.gets.chomp
|
|
275
299
|
title = input unless input.empty?
|
|
276
300
|
end
|
|
277
|
-
summary =
|
|
301
|
+
summary = ''
|
|
278
302
|
unless default
|
|
279
|
-
printf
|
|
303
|
+
printf '{bw}Project summary: {x}'.c
|
|
280
304
|
input = $stdin.gets.chomp
|
|
281
305
|
summary = input unless input.empty?
|
|
282
306
|
end
|
|
283
307
|
|
|
284
|
-
fname =
|
|
308
|
+
fname = 'buildnotes.md'
|
|
285
309
|
unless default
|
|
286
310
|
printf "{bw}Build notes filename (must begin with 'howzit' or 'build')\n{xg}[#{fname}]{bw}: {x}".c
|
|
287
311
|
input = $stdin.gets.chomp
|
|
@@ -316,12 +340,12 @@ module Howzit
|
|
|
316
340
|
if File.exist?(fname) && !default
|
|
317
341
|
file = "{by}#{fname}".c
|
|
318
342
|
unless Prompt.yn("Are you absolutely sure you want to overwrite #{file}", default: false)
|
|
319
|
-
Howzit.console.info(
|
|
343
|
+
Howzit.console.info('Canceled')
|
|
320
344
|
Process.exit 0
|
|
321
345
|
end
|
|
322
346
|
end
|
|
323
347
|
|
|
324
|
-
File.open(fname,
|
|
348
|
+
File.open(fname, 'w') do |f|
|
|
325
349
|
f.puts note
|
|
326
350
|
Howzit.console.info("{by}Build notes for {bw}#{title}{by} written to {bw}#{fname}{x}".c)
|
|
327
351
|
end
|
|
@@ -345,6 +369,70 @@ module Howzit
|
|
|
345
369
|
|
|
346
370
|
private
|
|
347
371
|
|
|
372
|
+
def topic_search_terms_from_cli
|
|
373
|
+
args = Howzit.cli_args || []
|
|
374
|
+
raw = args.join(' ').strip
|
|
375
|
+
return [] if raw.empty?
|
|
376
|
+
|
|
377
|
+
smart_split_topics(raw).map { |term| term.strip.downcase }.reject(&:empty?)
|
|
378
|
+
end
|
|
379
|
+
|
|
380
|
+
def smart_split_topics(raw)
|
|
381
|
+
segments, separators = segments_and_separators_for(raw)
|
|
382
|
+
return segments if separators.empty?
|
|
383
|
+
|
|
384
|
+
combined = []
|
|
385
|
+
current = segments.shift || ''
|
|
386
|
+
|
|
387
|
+
separators.each_with_index do |separator, idx|
|
|
388
|
+
next_segment = segments[idx] || ''
|
|
389
|
+
if keep_separator_with_current?(current, separator, next_segment)
|
|
390
|
+
current = "#{current}#{separator}#{next_segment}"
|
|
391
|
+
else
|
|
392
|
+
combined << current
|
|
393
|
+
current = next_segment
|
|
394
|
+
end
|
|
395
|
+
end
|
|
396
|
+
|
|
397
|
+
combined << current
|
|
398
|
+
combined
|
|
399
|
+
end
|
|
400
|
+
|
|
401
|
+
def segments_and_separators_for(raw)
|
|
402
|
+
segments = []
|
|
403
|
+
separators = []
|
|
404
|
+
current = String.new
|
|
405
|
+
|
|
406
|
+
raw.each_char do |char|
|
|
407
|
+
if char =~ /[,:]/
|
|
408
|
+
segments << current
|
|
409
|
+
separators << char
|
|
410
|
+
current = String.new
|
|
411
|
+
else
|
|
412
|
+
current << char
|
|
413
|
+
end
|
|
414
|
+
end
|
|
415
|
+
|
|
416
|
+
segments << current
|
|
417
|
+
[segments, separators]
|
|
418
|
+
end
|
|
419
|
+
|
|
420
|
+
def keep_separator_with_current?(current, separator, next_segment)
|
|
421
|
+
candidate = "#{current}#{separator}#{next_segment}"
|
|
422
|
+
normalized_candidate = normalize_separator_string(candidate)
|
|
423
|
+
return false if normalized_candidate.empty?
|
|
424
|
+
|
|
425
|
+
@topics.any? do |topic|
|
|
426
|
+
normalize_separator_string(topic.title).start_with?(normalized_candidate)
|
|
427
|
+
end
|
|
428
|
+
end
|
|
429
|
+
|
|
430
|
+
def normalize_separator_string(string)
|
|
431
|
+
return '' if string.nil?
|
|
432
|
+
|
|
433
|
+
string.downcase.gsub(/\s+/, ' ').strip.gsub(/\s*([,:])\s*/, '\1')
|
|
434
|
+
end
|
|
435
|
+
|
|
348
436
|
##
|
|
349
437
|
## Import the contents of a filename as new topics
|
|
350
438
|
##
|
|
@@ -357,15 +445,15 @@ module Howzit
|
|
|
357
445
|
return mtch[0] unless File.exist?(file)
|
|
358
446
|
|
|
359
447
|
content = Util.read_file(file)
|
|
360
|
-
home = ENV[
|
|
361
|
-
short_path = File.dirname(file.sub(/^#{home}/,
|
|
448
|
+
home = ENV['HOME']
|
|
449
|
+
short_path = File.dirname(file.sub(/^#{home}/, '~'))
|
|
362
450
|
prefix = "#{short_path}/#{File.basename(file)}:"
|
|
363
451
|
parts = content.split(/^##+/)
|
|
364
452
|
parts.shift
|
|
365
453
|
if parts.empty?
|
|
366
454
|
content
|
|
367
455
|
else
|
|
368
|
-
"## #{parts.join(
|
|
456
|
+
"## #{parts.join('## ')}".gsub(/^(##+ *)(?=\S)/, "\\1#{prefix}")
|
|
369
457
|
end
|
|
370
458
|
end
|
|
371
459
|
|
|
@@ -382,15 +470,15 @@ module Howzit
|
|
|
382
470
|
|
|
383
471
|
t_meta = t_leader.metadata
|
|
384
472
|
|
|
385
|
-
return unless t_meta.key?(
|
|
473
|
+
return unless t_meta.key?('required')
|
|
386
474
|
|
|
387
|
-
required = t_meta[
|
|
475
|
+
required = t_meta['required'].strip.split(/\s*,\s*/)
|
|
388
476
|
required.each do |req|
|
|
389
477
|
next if @metadata.keys.include?(req.downcase)
|
|
390
478
|
|
|
391
479
|
Howzit.console.error %({bRw}ERROR:{xbr} Missing required metadata key from template '{bw}#{File.basename(
|
|
392
|
-
|
|
393
|
-
|
|
480
|
+
template, '.md'
|
|
481
|
+
)}{xr}'{x}).c
|
|
394
482
|
Howzit.console.error %({br}Please define {by}#{req.downcase}{xr} in build notes{x}).c
|
|
395
483
|
Process.exit 1
|
|
396
484
|
end
|
|
@@ -408,8 +496,8 @@ module Howzit
|
|
|
408
496
|
subtopics = nil
|
|
409
497
|
|
|
410
498
|
if template =~ /\[(.*?)\]$/
|
|
411
|
-
subtopics = Regexp.last_match[1].split(/\s*\|\s*/).map { |t| t.gsub(/\*/,
|
|
412
|
-
template.sub!(/\[.*?\]$/,
|
|
499
|
+
subtopics = Regexp.last_match[1].split(/\s*\|\s*/).map { |t| t.gsub(/\*/, '.*?') }
|
|
500
|
+
template.sub!(/\[.*?\]$/, '').strip
|
|
413
501
|
end
|
|
414
502
|
|
|
415
503
|
[template, subtopics]
|
|
@@ -429,7 +517,7 @@ module Howzit
|
|
|
429
517
|
templates.each do |template|
|
|
430
518
|
template, subtopics = detect_subtopics(template)
|
|
431
519
|
|
|
432
|
-
file = template.sub(/(\.md)?$/i,
|
|
520
|
+
file = template.sub(/(\.md)?$/i, '.md')
|
|
433
521
|
file = File.join(Howzit.config.template_folder, file)
|
|
434
522
|
|
|
435
523
|
next unless File.exist?(file)
|
|
@@ -493,7 +581,7 @@ module Howzit
|
|
|
493
581
|
buildnotes = []
|
|
494
582
|
filename = nil
|
|
495
583
|
|
|
496
|
-
while dir !=
|
|
584
|
+
while dir != '/' && (dir =~ %r{[A-Z]:/}).nil?
|
|
497
585
|
Dir.chdir(dir)
|
|
498
586
|
filename = glob_note
|
|
499
587
|
unless filename.nil?
|
|
@@ -516,7 +604,7 @@ module Howzit
|
|
|
516
604
|
## @return [String] file path
|
|
517
605
|
##
|
|
518
606
|
def glob_note
|
|
519
|
-
Dir.glob(
|
|
607
|
+
Dir.glob('*.{txt,md,markdown}').select(&:build_note?).sort[0]
|
|
520
608
|
end
|
|
521
609
|
|
|
522
610
|
##
|
|
@@ -529,9 +617,9 @@ module Howzit
|
|
|
529
617
|
def find_note_file
|
|
530
618
|
filename = glob_note
|
|
531
619
|
|
|
532
|
-
if filename.nil? &&
|
|
620
|
+
if filename.nil? && 'git'.available?
|
|
533
621
|
proj_dir = `git rev-parse --show-toplevel 2>/dev/null`.strip
|
|
534
|
-
unless proj_dir ==
|
|
622
|
+
unless proj_dir == ''
|
|
535
623
|
Dir.chdir(proj_dir)
|
|
536
624
|
filename = glob_note
|
|
537
625
|
end
|
|
@@ -575,8 +663,8 @@ module Howzit
|
|
|
575
663
|
|
|
576
664
|
data = leader.metadata
|
|
577
665
|
|
|
578
|
-
if data.key?(
|
|
579
|
-
templates = data[
|
|
666
|
+
if data.key?('template')
|
|
667
|
+
templates = data['template'].strip.split(/\s*,\s*/)
|
|
580
668
|
|
|
581
669
|
template_topics.concat(gather_templates(templates))
|
|
582
670
|
end
|
|
@@ -618,13 +706,13 @@ module Howzit
|
|
|
618
706
|
|
|
619
707
|
lines = sect.split(/\n/)
|
|
620
708
|
title = lines.slice!(0).strip
|
|
621
|
-
prefix =
|
|
709
|
+
prefix = ''
|
|
622
710
|
if path && path != note_file
|
|
623
711
|
if path =~ /#{Howzit.config.template_folder}/
|
|
624
|
-
short_path = File.basename(path,
|
|
712
|
+
short_path = File.basename(path, '.md')
|
|
625
713
|
else
|
|
626
|
-
home = ENV[
|
|
627
|
-
short_path = File.dirname(path.sub(/^#{home}/,
|
|
714
|
+
home = ENV['HOME']
|
|
715
|
+
short_path = File.dirname(path.sub(/^#{home}/, '~'))
|
|
628
716
|
prefix = "_from #{short_path}_\n\n"
|
|
629
717
|
end
|
|
630
718
|
title = "#{short_path}:#{title}"
|
|
@@ -636,7 +724,7 @@ module Howzit
|
|
|
636
724
|
end
|
|
637
725
|
|
|
638
726
|
template_topics.each do |topic|
|
|
639
|
-
topics.push(topic) unless find_topic(topic.title.sub(/^.+:/,
|
|
727
|
+
topics.push(topic) unless find_topic(topic.title.sub(/^.+:/, '')).count.positive?
|
|
640
728
|
end
|
|
641
729
|
|
|
642
730
|
topics
|
|
@@ -655,7 +743,7 @@ module Howzit
|
|
|
655
743
|
upstream_topics = read_upstream
|
|
656
744
|
|
|
657
745
|
upstream_topics.each do |topic|
|
|
658
|
-
@topics.push(topic) unless find_topic(topic.title.sub(/^.+:/,
|
|
746
|
+
@topics.push(topic) unless find_topic(topic.title.sub(/^.+:/, '')).count.positive?
|
|
659
747
|
end
|
|
660
748
|
Howzit.has_read_upstream = true
|
|
661
749
|
end
|
|
@@ -670,11 +758,11 @@ module Howzit
|
|
|
670
758
|
## Open build note in editor
|
|
671
759
|
##
|
|
672
760
|
def edit_note
|
|
673
|
-
editor = Howzit.options.fetch(:editor, ENV[
|
|
761
|
+
editor = Howzit.options.fetch(:editor, ENV['EDITOR'])
|
|
674
762
|
|
|
675
763
|
editor = Howzit.config.update_editor if editor.nil?
|
|
676
764
|
|
|
677
|
-
raise
|
|
765
|
+
raise 'No editor defined' if editor.nil?
|
|
678
766
|
|
|
679
767
|
raise "Invalid editor (#{editor})" unless Util.valid_command?(editor)
|
|
680
768
|
|
|
@@ -688,7 +776,7 @@ module Howzit
|
|
|
688
776
|
## @param template [String] The template name
|
|
689
777
|
##
|
|
690
778
|
def create_template(template)
|
|
691
|
-
file = template.sub(/(\.md)?$/i,
|
|
779
|
+
file = template.sub(/(\.md)?$/i, '.md')
|
|
692
780
|
file = File.join(Howzit.config.template_folder, file)
|
|
693
781
|
create_template_file(file, prompt: false)
|
|
694
782
|
end
|
|
@@ -697,11 +785,11 @@ module Howzit
|
|
|
697
785
|
## Open template in editor
|
|
698
786
|
##
|
|
699
787
|
def edit_template_file(file)
|
|
700
|
-
editor = Howzit.options.fetch(:editor, ENV[
|
|
788
|
+
editor = Howzit.options.fetch(:editor, ENV['EDITOR'])
|
|
701
789
|
|
|
702
790
|
editor = Howzit.config.update_editor if editor.nil?
|
|
703
791
|
|
|
704
|
-
raise
|
|
792
|
+
raise 'No editor defined' if editor.nil?
|
|
705
793
|
|
|
706
794
|
raise "Invalid editor (#{editor})" unless Util.valid_command?(editor)
|
|
707
795
|
|
|
@@ -722,12 +810,12 @@ module Howzit
|
|
|
722
810
|
new_topic = topic.is_a?(String) ? find_topic(topic)[0] : topic.dup
|
|
723
811
|
|
|
724
812
|
output = if run
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
813
|
+
new_topic.run
|
|
814
|
+
else
|
|
815
|
+
new_topic.print_out({ single: single })
|
|
816
|
+
end
|
|
729
817
|
|
|
730
|
-
output.nil? ?
|
|
818
|
+
output.nil? ? '' : output.join("\n\n")
|
|
731
819
|
end
|
|
732
820
|
|
|
733
821
|
##
|
|
@@ -735,6 +823,10 @@ module Howzit
|
|
|
735
823
|
##
|
|
736
824
|
def process
|
|
737
825
|
output = []
|
|
826
|
+
if Howzit.options[:run]
|
|
827
|
+
Howzit.run_log = []
|
|
828
|
+
Howzit.multi_topic_run = false
|
|
829
|
+
end
|
|
738
830
|
|
|
739
831
|
unless note_file
|
|
740
832
|
Process.exit 0 if Howzit.options[:list_runnable_titles] || Howzit.options[:list_topic_titles]
|
|
@@ -748,7 +840,7 @@ module Howzit
|
|
|
748
840
|
Process.exit(0)
|
|
749
841
|
elsif Howzit.options[:output_title] && !Howzit.options[:run]
|
|
750
842
|
if @title && !@title.empty?
|
|
751
|
-
header = @title.format_header({ hr: "\u{2550}", color:
|
|
843
|
+
header = @title.format_header({ hr: "\u{2550}", color: '{bwK}' })
|
|
752
844
|
output.push("#{header}\n")
|
|
753
845
|
end
|
|
754
846
|
end
|
|
@@ -774,62 +866,139 @@ module Howzit
|
|
|
774
866
|
Process.exit(0)
|
|
775
867
|
end
|
|
776
868
|
|
|
777
|
-
|
|
778
|
-
|
|
869
|
+
# Handle grep and choose modes (batch all results)
|
|
779
870
|
if Howzit.options[:grep]
|
|
871
|
+
topic_matches = []
|
|
780
872
|
matches = grep(Howzit.options[:grep])
|
|
781
873
|
case Howzit.options[:multiple_matches]
|
|
782
874
|
when :all
|
|
783
875
|
topic_matches.concat(matches.sort_by(&:title))
|
|
784
876
|
else
|
|
785
|
-
topic_matches.concat(Prompt.choose(matches.map(&:title), height: :max))
|
|
877
|
+
topic_matches.concat(Prompt.choose(matches.map(&:title), height: :max, query: Howzit.options[:grep]))
|
|
786
878
|
end
|
|
879
|
+
process_topic_matches(topic_matches, output)
|
|
787
880
|
elsif Howzit.options[:choose]
|
|
881
|
+
topic_matches = []
|
|
788
882
|
titles = Prompt.choose(list_topics, height: :max)
|
|
789
883
|
titles.each { |title| topic_matches.push(find_topic(title)[0]) }
|
|
790
|
-
|
|
884
|
+
process_topic_matches(topic_matches, output)
|
|
791
885
|
elsif !Howzit.cli_args.empty?
|
|
792
|
-
|
|
886
|
+
# Collect all topic matches first (showing menus as needed)
|
|
887
|
+
search = topic_search_terms_from_cli
|
|
888
|
+
topic_matches = collect_topic_matches(search, output)
|
|
889
|
+
process_topic_matches(topic_matches, output)
|
|
890
|
+
else
|
|
891
|
+
# No arguments - show all topics
|
|
892
|
+
if Howzit.options[:run]
|
|
893
|
+
Howzit.run_log = []
|
|
894
|
+
Howzit.multi_topic_run = topics.length > 1
|
|
895
|
+
end
|
|
896
|
+
topics.each { |k| output.push(process_topic(k, false, single: false)) }
|
|
897
|
+
finalize_output(output)
|
|
898
|
+
end
|
|
899
|
+
end
|
|
793
900
|
|
|
794
|
-
|
|
795
|
-
|
|
901
|
+
##
|
|
902
|
+
## Collect all topic matches from search terms, showing menus as needed
|
|
903
|
+
## but not displaying/running until all selections are made
|
|
904
|
+
##
|
|
905
|
+
## @param search_terms [Array] Array of search term strings
|
|
906
|
+
## @param output [Array] Output array for error messages
|
|
907
|
+
##
|
|
908
|
+
## @return [Array] Array of all matched topics
|
|
909
|
+
##
|
|
910
|
+
def collect_topic_matches(search_terms, output)
|
|
911
|
+
all_matches = []
|
|
796
912
|
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
topic_matches.concat(matches)
|
|
807
|
-
else
|
|
808
|
-
titles = matches.map(&:title)
|
|
809
|
-
res = Prompt.choose(titles)
|
|
810
|
-
old_matching = Howzit.options[:matching]
|
|
811
|
-
Howzit.options[:matching] = "exact"
|
|
812
|
-
res.each { |title| topic_matches.concat(find_topic(title)) }
|
|
813
|
-
Howzit.options[:matching] = old_matching
|
|
814
|
-
end
|
|
815
|
-
end
|
|
816
|
-
end
|
|
913
|
+
search_terms.each do |s|
|
|
914
|
+
# First check for exact whole-word matches
|
|
915
|
+
exact_matches = find_topic_exact(s)
|
|
916
|
+
|
|
917
|
+
topic_matches = if !exact_matches.empty?
|
|
918
|
+
exact_matches
|
|
919
|
+
else
|
|
920
|
+
resolve_fuzzy_matches(s, output)
|
|
921
|
+
end
|
|
817
922
|
|
|
818
|
-
if topic_matches.empty?
|
|
819
|
-
|
|
820
|
-
|
|
923
|
+
if topic_matches.empty?
|
|
924
|
+
output.push(%({bR}ERROR:{xr} No topic match found for {bw}#{s}{x}\n).c)
|
|
925
|
+
else
|
|
926
|
+
all_matches.concat(topic_matches)
|
|
821
927
|
end
|
|
822
928
|
end
|
|
823
929
|
|
|
930
|
+
all_matches
|
|
931
|
+
end
|
|
932
|
+
|
|
933
|
+
##
|
|
934
|
+
## Resolve fuzzy matches for a search term
|
|
935
|
+
##
|
|
936
|
+
## @param search_term [String] The search term
|
|
937
|
+
## @param output [Array] Output array for errors
|
|
938
|
+
##
|
|
939
|
+
## @return [Array] Array of matched topics
|
|
940
|
+
##
|
|
941
|
+
def resolve_fuzzy_matches(search_term, output)
|
|
942
|
+
matches = find_topic(search_term)
|
|
943
|
+
|
|
944
|
+
return [] if matches.empty?
|
|
945
|
+
|
|
946
|
+
case Howzit.options[:multiple_matches]
|
|
947
|
+
when :first
|
|
948
|
+
[matches[0]]
|
|
949
|
+
when :best
|
|
950
|
+
[matches.sort_by { |a| [a.title.comp_distance(search_term), a.title.length] }.first]
|
|
951
|
+
when :all
|
|
952
|
+
matches
|
|
953
|
+
else
|
|
954
|
+
titles = matches.map(&:title)
|
|
955
|
+
res = Prompt.choose(titles, query: search_term)
|
|
956
|
+
old_matching = Howzit.options[:matching]
|
|
957
|
+
Howzit.options[:matching] = 'exact'
|
|
958
|
+
selected = res.flat_map { |title| find_topic(title) }
|
|
959
|
+
Howzit.options[:matching] = old_matching
|
|
960
|
+
selected
|
|
961
|
+
end
|
|
962
|
+
end
|
|
963
|
+
|
|
964
|
+
##
|
|
965
|
+
## Process collected topic matches and display output
|
|
966
|
+
##
|
|
967
|
+
## @param topic_matches [Array] Array of matched topics
|
|
968
|
+
## @param output [Array] Output array
|
|
969
|
+
##
|
|
970
|
+
def process_topic_matches(topic_matches, output)
|
|
971
|
+
if topic_matches.empty? && !Howzit.options[:show_all_on_error]
|
|
972
|
+
Util.show(output.join("\n"), { color: true, highlight: false, paginate: false, wrap: 0 })
|
|
973
|
+
Process.exit 1
|
|
974
|
+
end
|
|
975
|
+
|
|
976
|
+
if Howzit.options[:run]
|
|
977
|
+
Howzit.run_log = []
|
|
978
|
+
Howzit.multi_topic_run = topic_matches.length > 1
|
|
979
|
+
end
|
|
980
|
+
|
|
824
981
|
if !topic_matches.empty?
|
|
825
|
-
# If we found a match
|
|
826
982
|
topic_matches.map! { |topic| topic.is_a?(String) ? find_topic(topic)[0] : topic }
|
|
827
983
|
topic_matches.each { |topic_match| output.push(process_topic(topic_match, Howzit.options[:run], single: true)) }
|
|
828
984
|
else
|
|
829
|
-
# If there's no argument or no match found, output all
|
|
830
985
|
topics.each { |k| output.push(process_topic(k, false, single: false)) }
|
|
831
986
|
end
|
|
832
|
-
|
|
987
|
+
|
|
988
|
+
finalize_output(output)
|
|
989
|
+
end
|
|
990
|
+
|
|
991
|
+
##
|
|
992
|
+
## Finalize and display output with run summary if applicable
|
|
993
|
+
##
|
|
994
|
+
## @param output [Array] Output array
|
|
995
|
+
##
|
|
996
|
+
def finalize_output(output)
|
|
997
|
+
if Howzit.options[:run]
|
|
998
|
+
Howzit.options[:paginate] = false
|
|
999
|
+
summary = Howzit::RunReport.format
|
|
1000
|
+
output.push(summary) unless summary.empty?
|
|
1001
|
+
end
|
|
833
1002
|
Util.show(output.join("\n").strip, Howzit.options)
|
|
834
1003
|
end
|
|
835
1004
|
end
|