howzit 2.0.15 → 2.0.16
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +12 -0
- data/bin/howzit +5 -0
- data/lib/howzit/buildnote.rb +254 -149
- data/lib/howzit/prompt.rb +16 -6
- data/lib/howzit/stringutils.rb +18 -2
- data/lib/howzit/task.rb +16 -6
- data/lib/howzit/topic.rb +2 -1
- data/lib/howzit/util.rb +5 -0
- data/lib/howzit/version.rb +1 -1
- data/spec/prompt_spec.rb +37 -0
- data/spec/spec_helper.rb +7 -9
- data/spec/task_spec.rb +1 -2
- data/spec/topic_spec.rb +45 -15
- data/spec/util_spec.rb +52 -0
- metadata +6 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: df621f3d6e973282799aa664b3042f22f641754d979cf711848146b04c89cd92
|
4
|
+
data.tar.gz: 134041be4b4eaecc4cb31793dbe57dfdefed601b56ef8d1b17b399615831c0ad
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: c896a32bef2ea983ef78082e685ac0f89e93a2e48b751c9dc5a3f744eb2687cf3005ffdcd5ea09284a3710ea56cc001746193855a195986290f9a98242598317
|
7
|
+
data.tar.gz: 05ccc75166128a3e2aefa7f143cd6076bef60dba024bb7c508aabc5865334f2e95bafaedeb151850704c6ac6fcc888101560f74e75b4b4cfc1afabe8ddaacaae
|
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,15 @@
|
|
1
|
+
### 2.0.16
|
2
|
+
|
3
|
+
2022-08-07 06:05
|
4
|
+
|
5
|
+
#### NEW
|
6
|
+
|
7
|
+
- --edit-template NAME will open a template in your editor, offering to create it if missing
|
8
|
+
|
9
|
+
#### FIXED
|
10
|
+
|
11
|
+
- If --show-code is given (or :show_all_code: is set to true), show content of @directives instead of title
|
12
|
+
|
1
13
|
### 2.0.15
|
2
14
|
|
3
15
|
2022-08-05 17:16
|
data/bin/howzit
CHANGED
@@ -94,6 +94,11 @@ OptionParser.new do |opts|
|
|
94
94
|
Process.exit 0
|
95
95
|
end
|
96
96
|
|
97
|
+
opts.on('--edit-template NAME', 'Create or edit a template') do |template|
|
98
|
+
Howzit.buildnote.edit_template(template)
|
99
|
+
Process.exit 0
|
100
|
+
end
|
101
|
+
|
97
102
|
opts.on('--config-get [KEY]', 'Display the configuration settings or setting for a specific key') do |k|
|
98
103
|
if k.nil?
|
99
104
|
Howzit::Config::DEFAULTS.sort_by { |key, _| key }.each do |key, _|
|
data/lib/howzit/buildnote.rb
CHANGED
@@ -11,23 +11,15 @@ module Howzit
|
|
11
11
|
## Initialize a build note
|
12
12
|
##
|
13
13
|
## @param file [String] The path to the build note file
|
14
|
-
## @param args [Array] additional args
|
15
14
|
##
|
16
|
-
def initialize(file: nil
|
15
|
+
def initialize(file: nil)
|
17
16
|
@topics = []
|
18
|
-
if note_file.nil?
|
19
|
-
res = Prompt.yn('No build notes file found, create one?', default: true)
|
17
|
+
create_note(prompt: true) if note_file.nil?
|
20
18
|
|
21
|
-
create_note if res
|
22
|
-
Process.exit 0
|
23
|
-
end
|
24
19
|
content = Util.read_file(note_file)
|
25
|
-
if content.nil? || content.empty?
|
26
|
-
|
27
|
-
|
28
|
-
else
|
29
|
-
@metadata = content.split(/^#/)[0].strip.get_metadata
|
30
|
-
end
|
20
|
+
raise "{br}No content found in build note (#{note_file}){x}".c if content.nil? || content.empty?
|
21
|
+
|
22
|
+
@metadata = content.split(/^#/)[0].strip.get_metadata
|
31
23
|
|
32
24
|
read_help(file)
|
33
25
|
end
|
@@ -55,6 +47,17 @@ module Howzit
|
|
55
47
|
edit_note
|
56
48
|
end
|
57
49
|
|
50
|
+
##
|
51
|
+
## Public method to open a template in the editor
|
52
|
+
##
|
53
|
+
## @param template [String] The template title
|
54
|
+
##
|
55
|
+
def edit_template(template)
|
56
|
+
file = template.sub(/(\.md)?$/i, '.md')
|
57
|
+
file = File.join(Howzit.config.template_folder, file)
|
58
|
+
edit_template_file(file)
|
59
|
+
end
|
60
|
+
|
58
61
|
##
|
59
62
|
## Find a topic based on a fuzzy match
|
60
63
|
##
|
@@ -62,6 +65,7 @@ module Howzit
|
|
62
65
|
##
|
63
66
|
def find_topic(term = nil)
|
64
67
|
return @topics if term.nil?
|
68
|
+
|
65
69
|
@topics.filter do |topic|
|
66
70
|
rx = term.to_rx
|
67
71
|
topic.title.downcase =~ rx
|
@@ -137,15 +141,14 @@ module Howzit
|
|
137
141
|
find_topic(Howzit.options[:for_topic]).each do |topic|
|
138
142
|
s_out = []
|
139
143
|
|
140
|
-
topic.tasks.each
|
141
|
-
s_out.push(task.to_list)
|
142
|
-
end
|
144
|
+
topic.tasks.each { |task| s_out.push(task.to_list) }
|
143
145
|
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
146
|
+
next if s_out.empty?
|
147
|
+
|
148
|
+
output.push("- {bw}#{topic.title}{x}".c)
|
149
|
+
output.push(s_out.join("\n"))
|
148
150
|
end
|
151
|
+
|
149
152
|
output.join("\n")
|
150
153
|
end
|
151
154
|
|
@@ -158,22 +161,75 @@ module Howzit
|
|
158
161
|
read_help_file(file)
|
159
162
|
end
|
160
163
|
|
164
|
+
##
|
165
|
+
## Create a template file
|
166
|
+
##
|
167
|
+
## @param file [String] file path
|
168
|
+
## @param prompt [Boolean] confirm file creation?
|
169
|
+
##
|
170
|
+
def create_template_file(file, prompt: false)
|
171
|
+
trap('SIGINT') do
|
172
|
+
Howzit.console.info "\nCancelled"
|
173
|
+
exit!
|
174
|
+
end
|
175
|
+
|
176
|
+
default = !$stdout.isatty || Howzit.options[:default]
|
177
|
+
|
178
|
+
if prompt && !default && !File.exist?(file)
|
179
|
+
res = Prompt.yn("{bg}Template {bw}#{File.basename(file)}{bg} not found, create it?{x}".c, default: true)
|
180
|
+
Process.exit 0 unless res
|
181
|
+
end
|
182
|
+
|
183
|
+
title = File.basename(file, '.md')
|
184
|
+
|
185
|
+
note = <<~EOBUILDNOTES
|
186
|
+
# #{title}
|
187
|
+
|
188
|
+
## Template Topic
|
189
|
+
|
190
|
+
EOBUILDNOTES
|
191
|
+
|
192
|
+
if File.exist?(file) && !default
|
193
|
+
file = "{by}#{file}".c
|
194
|
+
unless Prompt.yn("Are you sure you want to overwrite #{file}", default: false)
|
195
|
+
puts 'Cancelled'
|
196
|
+
Process.exit 0
|
197
|
+
end
|
198
|
+
end
|
199
|
+
|
200
|
+
File.open(file, 'w') do |f|
|
201
|
+
f.puts note
|
202
|
+
puts "{by}Template {bw}#{title}{by} written to {bw}#{file}{x}".c
|
203
|
+
end
|
204
|
+
|
205
|
+
if File.exist?(file) && !default && Prompt.yn("{bg}Do you want to open {bw}#{file} {bg}for editing?{x}".c,
|
206
|
+
default: false)
|
207
|
+
edit_template_file(file)
|
208
|
+
end
|
209
|
+
|
210
|
+
Process.exit 0
|
211
|
+
end
|
212
|
+
|
161
213
|
# Create a buildnotes skeleton
|
162
|
-
def create_note
|
214
|
+
def create_note(prompt: false)
|
163
215
|
trap('SIGINT') do
|
164
216
|
Howzit.console.info "\nCancelled"
|
165
217
|
exit!
|
166
218
|
end
|
219
|
+
|
167
220
|
default = !$stdout.isatty || Howzit.options[:default]
|
221
|
+
|
222
|
+
if prompt && !default
|
223
|
+
res = Prompt.yn('No build notes file found, create one?', default: true)
|
224
|
+
Process.exit 0 unless res
|
225
|
+
end
|
226
|
+
|
168
227
|
# First make sure there isn't already a buildnotes file
|
169
228
|
if note_file
|
170
229
|
fname = "{by}#{note_file}{bw}".c
|
171
230
|
unless default
|
172
231
|
res = Prompt.yn("#{fname} exists and appears to be a build note, continue anyway?", default: false)
|
173
|
-
unless res
|
174
|
-
puts 'Canceled'
|
175
|
-
Process.exit 0
|
176
|
-
end
|
232
|
+
Process.exit 0 unless res
|
177
233
|
end
|
178
234
|
end
|
179
235
|
|
@@ -228,9 +284,7 @@ module Howzit
|
|
228
284
|
|
229
285
|
if File.exist?(fname) && !default
|
230
286
|
file = "{by}#{fname}".c
|
231
|
-
|
232
|
-
|
233
|
-
unless res
|
287
|
+
unless Prompt.yn("Are you absolutely sure you want to overwrite #{file}", default: false)
|
234
288
|
puts 'Canceled'
|
235
289
|
Process.exit 0
|
236
290
|
end
|
@@ -238,13 +292,12 @@ module Howzit
|
|
238
292
|
|
239
293
|
File.open(fname, 'w') do |f|
|
240
294
|
f.puts note
|
241
|
-
puts "{by}Build notes for #{title} written to #{fname}".c
|
295
|
+
puts "{by}Build notes for {bw}#{title}{by} written to {bw}#{fname}{x}".c
|
242
296
|
end
|
243
297
|
|
244
|
-
if File.exist?(fname) && !default
|
245
|
-
|
246
|
-
|
247
|
-
edit_note if res
|
298
|
+
if File.exist?(fname) && !default && Prompt.yn("{bg}Do you want to open {bw}#{fname} {bg}for editing?{x}".c,
|
299
|
+
default: false)
|
300
|
+
edit_note
|
248
301
|
end
|
249
302
|
|
250
303
|
Process.exit 0
|
@@ -261,6 +314,139 @@ module Howzit
|
|
261
314
|
|
262
315
|
private
|
263
316
|
|
317
|
+
##
|
318
|
+
## Import the contents of a filename as new topics
|
319
|
+
##
|
320
|
+
## @param mtch [MatchData] the filename match from
|
321
|
+
## the include directive
|
322
|
+
##
|
323
|
+
def include_file(mtch)
|
324
|
+
file = File.expand_path(mtch[1])
|
325
|
+
|
326
|
+
return mtch[0] unless File.exist?(file)
|
327
|
+
|
328
|
+
content = Util.read_file(file)
|
329
|
+
home = ENV['HOME']
|
330
|
+
short_path = File.dirname(file.sub(/^#{home}/, '~'))
|
331
|
+
prefix = "#{short_path}/#{File.basename(file)}:"
|
332
|
+
parts = content.split(/^##+/)
|
333
|
+
parts.shift
|
334
|
+
if parts.empty?
|
335
|
+
content
|
336
|
+
else
|
337
|
+
"## #{parts.join('## ')}".gsub(/^(##+ *)(?=\S)/, "\\1#{prefix}")
|
338
|
+
end
|
339
|
+
end
|
340
|
+
|
341
|
+
##
|
342
|
+
## Test to ensure that any `required` metadata in a
|
343
|
+
## template is fulfilled by the build note
|
344
|
+
##
|
345
|
+
## @param template [String] The template to read
|
346
|
+
## from
|
347
|
+
##
|
348
|
+
def ensure_requirements(template)
|
349
|
+
t_leader = Util.read_file(template).split(/^#/)[0].strip
|
350
|
+
if t_leader.length > 0
|
351
|
+
t_meta = t_leader.get_metadata
|
352
|
+
if t_meta.key?('required')
|
353
|
+
required = t_meta['required'].strip.split(/\s*,\s*/)
|
354
|
+
required.each do |req|
|
355
|
+
unless @metadata.keys.include?(req.downcase)
|
356
|
+
Howzit.console.error %({bRw}ERROR:{xbr} Missing required metadata key from template '{bw}#{File.basename(template, '.md')}{xr}'{x}).c
|
357
|
+
Howzit.console.error %({br}Please define {by}#{req.downcase}{xr} in build notes{x}).c
|
358
|
+
Process.exit 1
|
359
|
+
end
|
360
|
+
end
|
361
|
+
end
|
362
|
+
end
|
363
|
+
end
|
364
|
+
|
365
|
+
##
|
366
|
+
## Test a template string for bracketed subtopics
|
367
|
+
##
|
368
|
+
## @param template [String] The template name
|
369
|
+
##
|
370
|
+
## @return [Array] [[String] updated template name, [Array]
|
371
|
+
## subtopic titles]
|
372
|
+
##
|
373
|
+
def detect_subtopics(template)
|
374
|
+
subtopics = nil
|
375
|
+
|
376
|
+
if template =~ /\[(.*?)\]$/
|
377
|
+
subtopics = Regexp.last_match[1].split(/\s*\|\s*/).map { |t| t.gsub(/\*/, '.*?')}
|
378
|
+
template.sub!(/\[.*?\]$/, '').strip
|
379
|
+
end
|
380
|
+
|
381
|
+
[template, subtopics]
|
382
|
+
end
|
383
|
+
|
384
|
+
##
|
385
|
+
## Enumerate templates and read their associated files
|
386
|
+
## into topics
|
387
|
+
##
|
388
|
+
## @param templates [Array] The templates to read
|
389
|
+
##
|
390
|
+
## @return [Array] template topics
|
391
|
+
##
|
392
|
+
def gather_templates(templates)
|
393
|
+
template_topics = []
|
394
|
+
|
395
|
+
templates.each do |template|
|
396
|
+
template, subtopics = detect_subtopics(template)
|
397
|
+
|
398
|
+
file = template.sub(/(\.md)?$/i, '.md')
|
399
|
+
file = File.join(Howzit.config.template_folder, file)
|
400
|
+
|
401
|
+
next unless File.exist?(file)
|
402
|
+
|
403
|
+
ensure_requirements(file)
|
404
|
+
|
405
|
+
template_topics.concat(read_template(template, file, subtopics))
|
406
|
+
end
|
407
|
+
|
408
|
+
template_topics
|
409
|
+
end
|
410
|
+
|
411
|
+
##
|
412
|
+
## Filter topics based on subtopic titles
|
413
|
+
##
|
414
|
+
## @param note [BuildNote] The note
|
415
|
+
## @param subtopics [Array] The subtopics to
|
416
|
+
## extract
|
417
|
+
##
|
418
|
+
## @return [Array] extracted subtopics
|
419
|
+
##
|
420
|
+
def extract_subtopics(note, subtopics)
|
421
|
+
template_topics = []
|
422
|
+
|
423
|
+
subtopics.each do |subtopic|
|
424
|
+
note.topics.each { |topic| template_topics.push(topic) if topic.title =~ /^(.*?:)?#{subtopic}$/i }
|
425
|
+
end
|
426
|
+
|
427
|
+
template_topics
|
428
|
+
end
|
429
|
+
|
430
|
+
##
|
431
|
+
## Read a template file
|
432
|
+
##
|
433
|
+
## @param template [String] The template title
|
434
|
+
## @param file [String] The file path
|
435
|
+
## @param subtopics [Array] The subtopics to
|
436
|
+
## extract, nil to return all
|
437
|
+
##
|
438
|
+
## @return [Array] extracted topics
|
439
|
+
##
|
440
|
+
def read_template(template, file, subtopics = nil)
|
441
|
+
note = BuildNote.new(file: file)
|
442
|
+
|
443
|
+
template_topics = subtopics.nil? ? note.topics : extract_subtopics(note, subtopics)
|
444
|
+
template_topics.map do |topic|
|
445
|
+
topic.parent = template
|
446
|
+
topic
|
447
|
+
end
|
448
|
+
end
|
449
|
+
|
264
450
|
##
|
265
451
|
## Traverse up directory tree looking for build notes
|
266
452
|
##
|
@@ -341,30 +527,6 @@ module Howzit
|
|
341
527
|
topics_dict
|
342
528
|
end
|
343
529
|
|
344
|
-
##
|
345
|
-
## Test to ensure that any `required` metadata in a
|
346
|
-
## template is fulfilled by the build note
|
347
|
-
##
|
348
|
-
## @param template [String] The template to read
|
349
|
-
## from
|
350
|
-
##
|
351
|
-
def ensure_requirements(template)
|
352
|
-
t_leader = Util.read_file(template).split(/^#/)[0].strip
|
353
|
-
if t_leader.length > 0
|
354
|
-
t_meta = t_leader.get_metadata
|
355
|
-
if t_meta.key?('required')
|
356
|
-
required = t_meta['required'].strip.split(/\s*,\s*/)
|
357
|
-
required.each do |req|
|
358
|
-
unless @metadata.keys.include?(req.downcase)
|
359
|
-
Howzit.console.error %({bRw}ERROR:{xbr} Missing required metadata key from template '{bw}#{File.basename(template, '.md')}{xr}'{x}).c
|
360
|
-
Howzit.console.error %({br}Please define {by}#{req.downcase}{xr} in build notes{x}).c
|
361
|
-
Process.exit 1
|
362
|
-
end
|
363
|
-
end
|
364
|
-
end
|
365
|
-
end
|
366
|
-
end
|
367
|
-
|
368
530
|
##
|
369
531
|
## Read a list of topics from an included template
|
370
532
|
##
|
@@ -375,87 +537,17 @@ module Howzit
|
|
375
537
|
|
376
538
|
template_topics = []
|
377
539
|
|
378
|
-
if leader.
|
379
|
-
data = leader.get_metadata
|
380
|
-
|
381
|
-
if data.key?('template')
|
382
|
-
templates = data['template'].strip.split(/\s*,\s*/)
|
383
|
-
templates.each do |t|
|
384
|
-
tasks = nil
|
385
|
-
if t =~ /\[(.*?)\]$/
|
386
|
-
tasks = Regexp.last_match[1].split(/\s*,\s*/).map {|t| t.gsub(/\*/, '.*?')}
|
387
|
-
t = t.sub(/\[.*?\]$/, '').strip
|
388
|
-
end
|
389
|
-
|
390
|
-
t_file = t.sub(/(\.md)?$/, '.md')
|
391
|
-
template = File.join(Howzit.config.template_folder, t_file)
|
392
|
-
if File.exist?(template)
|
393
|
-
ensure_requirements(template)
|
394
|
-
|
395
|
-
t_topics = BuildNote.new(file: template)
|
396
|
-
if tasks
|
397
|
-
tasks.each do |task|
|
398
|
-
t_topics.topics.each do |topic|
|
399
|
-
if topic.title =~ /^(.*?:)?#{task}$/i
|
400
|
-
topic.parent = t
|
401
|
-
template_topics.push(topic)
|
402
|
-
end
|
403
|
-
end
|
404
|
-
end
|
405
|
-
else
|
406
|
-
t_topics.topics.map! do |topic|
|
407
|
-
topic.parent = t
|
408
|
-
topic
|
409
|
-
end
|
410
|
-
|
411
|
-
template_topics.concat(t_topics.topics)
|
412
|
-
end
|
413
|
-
end
|
414
|
-
end
|
415
|
-
end
|
416
|
-
end
|
417
|
-
template_topics
|
418
|
-
end
|
540
|
+
return template_topics if leader.empty?
|
419
541
|
|
420
|
-
|
421
|
-
## Import the contents of a filename as new topics
|
422
|
-
##
|
423
|
-
## @param mtch [MatchData] the filename match from
|
424
|
-
## the include directive
|
425
|
-
##
|
426
|
-
def include_file(mtch)
|
427
|
-
file = File.expand_path(mtch[1])
|
542
|
+
data = leader.get_metadata
|
428
543
|
|
429
|
-
|
544
|
+
if data.key?('template')
|
545
|
+
templates = data['template'].strip.split(/\s*,\s*/)
|
430
546
|
|
431
|
-
|
432
|
-
home = ENV['HOME']
|
433
|
-
short_path = File.dirname(file.sub(/^#{home}/, '~'))
|
434
|
-
prefix = "#{short_path}/#{File.basename(file)}:"
|
435
|
-
parts = content.split(/^##+/)
|
436
|
-
parts.shift
|
437
|
-
if parts.empty?
|
438
|
-
content
|
439
|
-
else
|
440
|
-
"## #{parts.join('## ')}".gsub(/^(##+ *)(?=\S)/, "\\1#{prefix}")
|
547
|
+
template_topics.concat(gather_templates(templates))
|
441
548
|
end
|
442
|
-
end
|
443
|
-
|
444
|
-
##
|
445
|
-
## Get the title of the build note (top level header)
|
446
|
-
##
|
447
|
-
## @param truncate [Integer] Truncate to width
|
448
|
-
##
|
449
|
-
def note_title(truncate = 0)
|
450
|
-
help = Util.read_file(note_file)
|
451
|
-
title = help.match(/(?:^(\S.*?)(?=\n==)|^# ?(.*?)$)/)
|
452
|
-
title = if title
|
453
|
-
title[1].nil? ? title[2] : title[1]
|
454
|
-
else
|
455
|
-
note_file.sub(/(\.\w+)?$/, '')
|
456
|
-
end
|
457
549
|
|
458
|
-
|
550
|
+
template_topics
|
459
551
|
end
|
460
552
|
|
461
553
|
# Read in the build notes file and output a hash of
|
@@ -477,7 +569,7 @@ module Howzit
|
|
477
569
|
Process.exit 1
|
478
570
|
end
|
479
571
|
|
480
|
-
@title = note_title
|
572
|
+
@title = help.note_title(filename)
|
481
573
|
|
482
574
|
help.gsub!(/@include\((.*?)\)/) do
|
483
575
|
include_file(Regexp.last_match)
|
@@ -547,14 +639,33 @@ module Howzit
|
|
547
639
|
|
548
640
|
raise "Invalid editor (#{editor})" unless Util.valid_command?(editor)
|
549
641
|
|
550
|
-
if note_file.nil?
|
551
|
-
|
642
|
+
create_note(prompt: true) if note_file.nil?
|
643
|
+
`#{editor} "#{note_file}"`
|
644
|
+
end
|
552
645
|
|
553
|
-
|
554
|
-
|
555
|
-
|
556
|
-
|
557
|
-
|
646
|
+
##
|
647
|
+
## Public method to create a new template
|
648
|
+
##
|
649
|
+
## @param template [String] The template name
|
650
|
+
##
|
651
|
+
def create_template(template)
|
652
|
+
file = template.sub(/(\.md)?$/i, '.md')
|
653
|
+
file = File.join(Howzit.config.template_folder, file)
|
654
|
+
create_template_file(file, prompt: false)
|
655
|
+
end
|
656
|
+
|
657
|
+
##
|
658
|
+
## Open template in editor
|
659
|
+
##
|
660
|
+
def edit_template_file(file)
|
661
|
+
editor = Howzit.options.fetch(:editor, ENV['EDITOR'])
|
662
|
+
|
663
|
+
raise 'No editor defined' if editor.nil?
|
664
|
+
|
665
|
+
raise "Invalid editor (#{editor})" unless Util.valid_command?(editor)
|
666
|
+
|
667
|
+
create_template_file(file, prompt: true) unless File.exist?(file)
|
668
|
+
`#{editor} "#{file}"`
|
558
669
|
end
|
559
670
|
|
560
671
|
##
|
@@ -589,17 +700,11 @@ module Howzit
|
|
589
700
|
unless note_file
|
590
701
|
Process.exit 0 if Howzit.options[:list_runnable_titles] || Howzit.options[:list_topic_titles]
|
591
702
|
|
592
|
-
|
593
|
-
ARGV.length.times do
|
594
|
-
ARGV.shift
|
595
|
-
end
|
596
|
-
res = yn("No build notes file found, create one?", false)
|
597
|
-
create_note if res
|
598
|
-
Process.exit 1
|
703
|
+
create_note(prompt: true)
|
599
704
|
end
|
600
705
|
|
601
706
|
if Howzit.options[:title_only]
|
602
|
-
out = note_title(20)
|
707
|
+
out = Util.read_file(note_file).note_title(note_file, 20)
|
603
708
|
$stdout.print(out.strip)
|
604
709
|
Process.exit(0)
|
605
710
|
elsif Howzit.options[:output_title] && !Howzit.options[:run]
|
data/lib/howzit/prompt.rb
CHANGED
@@ -75,16 +75,24 @@ module Howzit
|
|
75
75
|
## options and accepts a numeric response
|
76
76
|
##
|
77
77
|
## @param matches [Array] The options list
|
78
|
+
## @param height [Symbol] height of fzf menu
|
79
|
+
## (:auto adjusts height to
|
80
|
+
## number of options, anything
|
81
|
+
## else gets max height for
|
82
|
+
## terminal)
|
78
83
|
##
|
79
84
|
## @return [Array] the selected results
|
80
85
|
##
|
81
86
|
def choose(matches, height: :auto)
|
82
|
-
|
83
|
-
|
84
|
-
else
|
85
|
-
TTY::Screen.rows
|
86
|
-
end
|
87
|
+
return [] if !$stdout.isatty || matches.count.zero?
|
88
|
+
|
87
89
|
if Util.command_exist?('fzf')
|
90
|
+
height = if height == :auto
|
91
|
+
matches.count + 3
|
92
|
+
else
|
93
|
+
TTY::Screen.rows
|
94
|
+
end
|
95
|
+
|
88
96
|
settings = [
|
89
97
|
'-0',
|
90
98
|
'-1',
|
@@ -102,6 +110,8 @@ module Howzit
|
|
102
110
|
return res.split(/\n/)
|
103
111
|
end
|
104
112
|
|
113
|
+
return matches if matches.count == 1
|
114
|
+
|
105
115
|
res = matches[0..9]
|
106
116
|
stty_save = `stty -g`.chomp
|
107
117
|
|
@@ -126,7 +136,7 @@ module Howzit
|
|
126
136
|
puts 'Out of range'
|
127
137
|
options_list(matches)
|
128
138
|
end
|
129
|
-
|
139
|
+
ensure
|
130
140
|
system('stty', stty_save)
|
131
141
|
exit
|
132
142
|
end
|
data/lib/howzit/stringutils.rb
CHANGED
@@ -16,6 +16,22 @@ module Howzit
|
|
16
16
|
true
|
17
17
|
end
|
18
18
|
|
19
|
+
##
|
20
|
+
## Get the title of the build note (top level header)
|
21
|
+
##
|
22
|
+
## @param truncate [Integer] Truncate to width
|
23
|
+
##
|
24
|
+
def note_title(file, truncate = 0)
|
25
|
+
title = match(/(?:^(\S.*?)(?=\n==)|^# ?(.*?)$)/)
|
26
|
+
title = if title
|
27
|
+
title[1].nil? ? title[2] : title[1]
|
28
|
+
else
|
29
|
+
file.sub(/(\.\w+)?$/, '')
|
30
|
+
end
|
31
|
+
|
32
|
+
title && truncate.positive? ? title.trunc(truncate) : title
|
33
|
+
end
|
34
|
+
|
19
35
|
##
|
20
36
|
## Replace slash escaped characters in a string with a
|
21
37
|
## zero-width space that will prevent a shell from
|
@@ -275,9 +291,9 @@ module Howzit
|
|
275
291
|
data = {}
|
276
292
|
meta.each do |k, v|
|
277
293
|
case k
|
278
|
-
when /^
|
294
|
+
when /^te?m?pl(ate)?s?$/
|
279
295
|
data['template'] = v
|
280
|
-
when /^req\w
|
296
|
+
when /^req\w*$/
|
281
297
|
data['required'] = v
|
282
298
|
else
|
283
299
|
data[k] = v
|
data/lib/howzit/task.rb
CHANGED
@@ -8,11 +8,21 @@ module Howzit
|
|
8
8
|
##
|
9
9
|
## Initialize a Task object
|
10
10
|
##
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
11
|
+
## @param attributes [Hash] the task attributes
|
12
|
+
## @param optional [Boolean] Task requires
|
13
|
+
## confirmation
|
14
|
+
## @param default [Boolean] Default response
|
15
|
+
## for confirmation dialog
|
16
|
+
##
|
17
|
+
## @option attributes :type [Symbol] task type (:block, :run, :include, :copy)
|
18
|
+
## @option attributes :title [String] task title
|
19
|
+
## @option attributes :action [String] task action
|
20
|
+
## @option attributes :parent [String] title of nested (included) topic origin
|
21
|
+
def initialize(attributes, optional: false, default: true)
|
22
|
+
@type = attributes[:type] || :run
|
23
|
+
@title = attributes[:title] || nil
|
24
|
+
@action = attributes[:action].render_arguments || nil
|
25
|
+
@parent = attributes[:parent] || nil
|
16
26
|
@optional = optional
|
17
27
|
@default = default
|
18
28
|
end
|
@@ -56,7 +66,7 @@ module Howzit
|
|
56
66
|
##
|
57
67
|
## Execute an include task
|
58
68
|
##
|
59
|
-
## @return [Integer] number of tasks executed
|
69
|
+
## @return [Array] [[Array] output, [Integer] number of tasks executed]
|
60
70
|
##
|
61
71
|
def run_include
|
62
72
|
output = []
|
data/lib/howzit/topic.rb
CHANGED
@@ -137,6 +137,7 @@ module Howzit
|
|
137
137
|
cmd = m[:cmd]
|
138
138
|
obj = m[:action]
|
139
139
|
title = m[:title].empty? ? obj : m[:title].strip
|
140
|
+
title = Howzit.options[:show_all_code] ? obj : title
|
140
141
|
optional = m[:optional] =~ /[?!]+/ ? true : false
|
141
142
|
default = m[:optional] =~ /!/ ? false : true
|
142
143
|
option = if optional
|
@@ -222,7 +223,7 @@ module Howzit
|
|
222
223
|
default = c[:optional] =~ /!/ ? false : true
|
223
224
|
obj = c[:action]
|
224
225
|
title = c[:title].nil? ? obj : c[:title].strip
|
225
|
-
|
226
|
+
title = Howzit.options[:show_all_code] ? obj : title
|
226
227
|
case cmd
|
227
228
|
when /include/i
|
228
229
|
# matches = Howzit.buildnote.find_topic(obj)
|
data/lib/howzit/util.rb
CHANGED
data/lib/howzit/version.rb
CHANGED
data/spec/prompt_spec.rb
ADDED
@@ -0,0 +1,37 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'spec_helper'
|
4
|
+
|
5
|
+
describe Howzit::Prompt do
|
6
|
+
subject(:prompt) { Howzit::Prompt }
|
7
|
+
|
8
|
+
describe '.yn' do
|
9
|
+
it 'returns default response' do
|
10
|
+
Howzit.options[:default] = true
|
11
|
+
expect(prompt.yn('Test prompt', default: true)).to be_truthy
|
12
|
+
expect(prompt.yn('Test prompt', default: false)).not_to be_truthy
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
describe '.color_single_options' do
|
17
|
+
it 'returns uncolored string' do
|
18
|
+
Howzit::Color.coloring = false
|
19
|
+
expect(prompt.color_single_options(%w[y n])).to eq "[y/n]"
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
describe '.options_list' do
|
24
|
+
it 'creates a formatted list of options' do
|
25
|
+
options = %w[one two three four five].each_with_object([]) do |x, arr|
|
26
|
+
arr << "Option item #{x}"
|
27
|
+
end
|
28
|
+
expect { prompt.options_list(options) }.to output(/ 2 \) Option item two/).to_stdout
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
describe '.choose' do
|
33
|
+
it 'returns a single match' do
|
34
|
+
expect(prompt.choose(['option 1']).count).to eq 1
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
data/spec/spec_helper.rb
CHANGED
@@ -1,13 +1,11 @@
|
|
1
|
-
#
|
1
|
+
# frozen_string_literal: true
|
2
2
|
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
# SimpleCov.formatter = SimpleCov::Formatter::HTMLFormatter
|
10
|
-
# end
|
3
|
+
unless ENV['CI'] == 'true'
|
4
|
+
# SimpleCov::Formatter::Codecov # For CI
|
5
|
+
require 'simplecov'
|
6
|
+
SimpleCov.formatter = SimpleCov::Formatter::HTMLFormatter
|
7
|
+
SimpleCov.start
|
8
|
+
end
|
11
9
|
|
12
10
|
require 'howzit'
|
13
11
|
require 'cli-test'
|
data/spec/task_spec.rb
CHANGED
data/spec/topic_spec.rb
CHANGED
@@ -7,14 +7,14 @@ describe Howzit::Topic do
|
|
7
7
|
content = 'Test Content'
|
8
8
|
subject(:topic) { Howzit::Topic.new(title, content) }
|
9
9
|
|
10
|
-
describe
|
11
|
-
it
|
10
|
+
describe '.new' do
|
11
|
+
it 'makes a new topic instance' do
|
12
12
|
expect(topic).to be_a Howzit::Topic
|
13
13
|
end
|
14
|
-
it
|
14
|
+
it 'has the correct title' do
|
15
15
|
expect(topic.title).to eq title
|
16
16
|
end
|
17
|
-
it
|
17
|
+
it 'has the correct content' do
|
18
18
|
expect(topic.content).to eq content
|
19
19
|
end
|
20
20
|
end
|
@@ -23,34 +23,49 @@ end
|
|
23
23
|
describe Howzit::Topic do
|
24
24
|
subject(:topic) { @hz.find_topic('Topic Balogna')[0] }
|
25
25
|
|
26
|
-
describe
|
27
|
-
it
|
28
|
-
expect(topic.title).to match
|
26
|
+
describe '.title' do
|
27
|
+
it 'has the correct title' do
|
28
|
+
expect(topic.title).to match(/Topic Balogna/)
|
29
29
|
end
|
30
30
|
end
|
31
31
|
|
32
|
-
describe
|
33
|
-
it
|
32
|
+
describe '.tasks' do
|
33
|
+
it 'has 2 tasks' do
|
34
34
|
expect(topic.tasks.count).to eq 2
|
35
35
|
end
|
36
36
|
end
|
37
37
|
|
38
|
-
describe
|
39
|
-
it
|
38
|
+
describe '.prereqs' do
|
39
|
+
it 'has prereq' do
|
40
40
|
expect(topic.prereqs.count).to eq 1
|
41
41
|
end
|
42
|
-
|
42
|
+
end
|
43
|
+
|
44
|
+
describe '.postreqs' do
|
45
|
+
it 'has postreq' do
|
43
46
|
expect(topic.postreqs.count).to eq 1
|
44
47
|
end
|
45
48
|
end
|
46
49
|
|
47
|
-
describe
|
50
|
+
describe '.grep' do
|
51
|
+
it 'returns true for matching pattern in content' do
|
52
|
+
expect(topic.grep('prereq.*?ite')).to be_truthy
|
53
|
+
end
|
54
|
+
it 'returns true for matching pattern in title' do
|
55
|
+
expect(topic.grep('bal.*?na')).to be_truthy
|
56
|
+
end
|
57
|
+
it 'fails on bad pattern' do
|
58
|
+
expect(topic.grep('xxx+')).to_not be_truthy
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
describe '.run' do
|
48
63
|
Howzit.options[:default] = true
|
49
|
-
it
|
64
|
+
it 'shows prereq and postreq' do
|
50
65
|
expect { topic.run }.to output(/prerequisite/).to_stdout
|
51
66
|
expect { topic.run }.to output(/postrequisite/).to_stdout
|
52
67
|
end
|
53
|
-
it
|
68
|
+
it 'Copies to clipboard' do
|
54
69
|
expect {
|
55
70
|
ENV['RUBYOPT'] = '-W1'
|
56
71
|
Howzit.options[:log_level] = 0
|
@@ -58,4 +73,19 @@ describe Howzit::Topic do
|
|
58
73
|
}.to output(/Copied/).to_stderr
|
59
74
|
end
|
60
75
|
end
|
76
|
+
|
77
|
+
describe '.print_out' do
|
78
|
+
Howzit.options[:header_format] = :block
|
79
|
+
Howzit.options[:color] = false
|
80
|
+
it 'prints the topic title' do
|
81
|
+
expect(topic.print_out({single: true, header: true}).join("\n").uncolor).to match(/▌Topic Balogna/)
|
82
|
+
end
|
83
|
+
it 'prints a task title' do
|
84
|
+
expect(topic.print_out({single: true, header: true}).join("\n").uncolor).to match(/▶ Null Output/)
|
85
|
+
end
|
86
|
+
it 'prints task action with --show-code' do
|
87
|
+
Howzit.options[:show_all_code] = true
|
88
|
+
expect(topic.print_out({single: true, header: true}).join("\n").uncolor).to match(/▶ ls -1/)
|
89
|
+
end
|
90
|
+
end
|
61
91
|
end
|
data/spec/util_spec.rb
ADDED
@@ -0,0 +1,52 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'spec_helper'
|
4
|
+
|
5
|
+
describe Howzit::Util do
|
6
|
+
subject(:util) { Howzit::Util }
|
7
|
+
|
8
|
+
describe '.read_file' do
|
9
|
+
it 'reads file to a string' do
|
10
|
+
buildnote = util.read_file('builda.md')
|
11
|
+
expect(buildnote).not_to be_empty
|
12
|
+
expect(buildnote).to be_a String
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
describe '.valid_command?' do
|
17
|
+
it 'finds a command' do
|
18
|
+
expect(util.command_exist?('ls')).to be_truthy
|
19
|
+
end
|
20
|
+
it 'validates a command' do
|
21
|
+
expect(util.valid_command?('ls -1')).to be_truthy
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
describe '.which_highlighter' do
|
26
|
+
it 'finds mdless' do
|
27
|
+
Howzit.options[:highlighter] = 'mdless'
|
28
|
+
expect(util.which_highlighter).to eq 'mdless'
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
describe '.which_pager' do
|
33
|
+
it 'finds the more utility' do
|
34
|
+
Howzit.options[:pager] = 'more'
|
35
|
+
expect(util.which_pager).to eq 'more'
|
36
|
+
Howzit.options[:pager] = 'auto'
|
37
|
+
expect(util.which_pager).to_not eq 'more'
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
describe '.show' do
|
42
|
+
it 'prints output' do
|
43
|
+
buildnote = util.read_file('builda.md')
|
44
|
+
expect { util.show(buildnote) }.to output(/Balogna/).to_stdout
|
45
|
+
end
|
46
|
+
|
47
|
+
it 'pages output' do
|
48
|
+
buildnote = util.read_file('builda.md')
|
49
|
+
expect { util.page(buildnote) }.to output(/Balogna/).to_stdout
|
50
|
+
end
|
51
|
+
end
|
52
|
+
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: 2.0.
|
4
|
+
version: 2.0.16
|
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-07 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -331,10 +331,12 @@ files:
|
|
331
331
|
- spec/.rubocop.yml
|
332
332
|
- spec/buildnote_spec.rb
|
333
333
|
- spec/cli_spec.rb
|
334
|
+
- spec/prompt_spec.rb
|
334
335
|
- spec/ruby_gem_spec.rb
|
335
336
|
- spec/spec_helper.rb
|
336
337
|
- spec/task_spec.rb
|
337
338
|
- spec/topic_spec.rb
|
339
|
+
- spec/util_spec.rb
|
338
340
|
- update_readmes.rb
|
339
341
|
homepage: https://github.com/ttscoff/howzit
|
340
342
|
licenses:
|
@@ -364,7 +366,9 @@ test_files:
|
|
364
366
|
- spec/.rubocop.yml
|
365
367
|
- spec/buildnote_spec.rb
|
366
368
|
- spec/cli_spec.rb
|
369
|
+
- spec/prompt_spec.rb
|
367
370
|
- spec/ruby_gem_spec.rb
|
368
371
|
- spec/spec_helper.rb
|
369
372
|
- spec/task_spec.rb
|
370
373
|
- spec/topic_spec.rb
|
374
|
+
- spec/util_spec.rb
|