howzit 2.0.15 → 2.0.18
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +24 -0
- data/bin/howzit +9 -0
- data/lib/howzit/buildnote.rb +254 -149
- data/lib/howzit/config.rb +1 -0
- data/lib/howzit/prompt.rb +16 -6
- data/lib/howzit/stringutils.rb +18 -2
- data/lib/howzit/task.rb +26 -16
- data/lib/howzit/topic.rb +3 -2
- 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: 16fc4dc237a8f226da219e66cbaee59984e8931bfe8e03fe5b0126451fb0d0d0
|
4
|
+
data.tar.gz: a169f5a88a1a6b345b6732c4782d166d7c933a6ce986a66b14e9df35602e1b7b
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: d197932f7a501e0a4ffb5eb76ca60660223b98cee1346c573028922b05790652a8e8c08a4624c72645423145d342d0b3866580d9b05552fe72a87edda47b4a08
|
7
|
+
data.tar.gz: 4e318240e7db8d6117fec620fd5505afab5ba35427b391e6a1dc90b4eccb978f6865054d300d00dad574ea4edc5ccb006c74112fca78f00dce14dc07c55095da
|
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,27 @@
|
|
1
|
+
### 2.0.18
|
2
|
+
|
3
|
+
2022-08-08 03:48
|
4
|
+
|
5
|
+
#### NEW
|
6
|
+
|
7
|
+
- Use --ask to require confirmation of all tasks when running a topic
|
8
|
+
|
9
|
+
### 2.0.17
|
10
|
+
|
11
|
+
2022-08-07 06:09
|
12
|
+
|
13
|
+
### 2.0.16
|
14
|
+
|
15
|
+
2022-08-07 06:05
|
16
|
+
|
17
|
+
#### NEW
|
18
|
+
|
19
|
+
- --edit-template NAME will open a template in your editor, offering to create it if missing
|
20
|
+
|
21
|
+
#### FIXED
|
22
|
+
|
23
|
+
- If --show-code is given (or :show_all_code: is set to true), show content of @directives instead of title
|
24
|
+
|
1
25
|
### 2.0.15
|
2
26
|
|
3
27
|
2022-08-05 17:16
|
data/bin/howzit
CHANGED
@@ -20,6 +20,10 @@ OptionParser.new do |opts|
|
|
20
20
|
|
21
21
|
opts.separator " Behavior:\n\n" #=================================================================== BEHAVIOR
|
22
22
|
|
23
|
+
opts.on('--ask', 'Request confirmation for all tasks when running a topic') do
|
24
|
+
Howzit.options[:ask] = true
|
25
|
+
end
|
26
|
+
|
23
27
|
opts.on('--default', 'Answer all prompts with default response') do
|
24
28
|
Howzit.options[:default] = true
|
25
29
|
end
|
@@ -94,6 +98,11 @@ OptionParser.new do |opts|
|
|
94
98
|
Process.exit 0
|
95
99
|
end
|
96
100
|
|
101
|
+
opts.on('--edit-template NAME', 'Create or edit a template') do |template|
|
102
|
+
Howzit.buildnote.edit_template(template)
|
103
|
+
Process.exit 0
|
104
|
+
end
|
105
|
+
|
97
106
|
opts.on('--config-get [KEY]', 'Display the configuration settings or setting for a specific key') do |k|
|
98
107
|
if k.nil?
|
99
108
|
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/config.rb
CHANGED
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,16 +66,16 @@ 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 = []
|
63
73
|
matches = Howzit.buildnote.find_topic(@action)
|
64
74
|
raise "Topic not found: #{@action}" if matches.empty?
|
65
75
|
|
66
|
-
|
76
|
+
Howzit.console.info("{by}Running tasks from {bw}#{matches[0].title}{x}".c)
|
67
77
|
output.concat(matches[0].run(nested: true))
|
68
|
-
|
78
|
+
Howzit.console.info("{by}End include: #{matches[0].tasks.count} tasks{x}".c)
|
69
79
|
[output, matches[0].tasks.count]
|
70
80
|
end
|
71
81
|
|
@@ -74,7 +84,7 @@ module Howzit
|
|
74
84
|
##
|
75
85
|
def run_run
|
76
86
|
title = Howzit.options[:show_all_code] ? @action : @title
|
77
|
-
|
87
|
+
Howzit.console.info("{bg}Running {bw}#{title}{x}".c)
|
78
88
|
system(@action)
|
79
89
|
end
|
80
90
|
|
@@ -83,7 +93,7 @@ module Howzit
|
|
83
93
|
##
|
84
94
|
def run_copy
|
85
95
|
title = Howzit.options[:show_all_code] ? @action : @title
|
86
|
-
|
96
|
+
Howzit.console.info("{bg}Copied {bw}#{title}{bg} to clipboard{x}".c)
|
87
97
|
os_copy(@action)
|
88
98
|
end
|
89
99
|
|
@@ -97,21 +107,21 @@ module Howzit
|
|
97
107
|
out = "{bg}Copying {bw}#{string}".c
|
98
108
|
case os
|
99
109
|
when /darwin.*/i
|
100
|
-
|
110
|
+
Howzit.console.debug("#{out} (macOS){x}".c)
|
101
111
|
`echo #{Shellwords.escape(string)}'\\c'|pbcopy`
|
102
112
|
when /mingw|mswin/i
|
103
|
-
|
113
|
+
Howzit.console.debug("#{out} (Windows){x}".c)
|
104
114
|
`echo #{Shellwords.escape(string)} | clip`
|
105
115
|
else
|
106
116
|
if 'xsel'.available?
|
107
|
-
|
117
|
+
Howzit.console.debug("#{out} (Linux, xsel){x}".c)
|
108
118
|
`echo #{Shellwords.escape(string)}'\\c'|xsel -i`
|
109
119
|
elsif 'xclip'.available?
|
110
|
-
|
120
|
+
Howzit.console.debug("#{out} (Linux, xclip){x}".c)
|
111
121
|
`echo #{Shellwords.escape(string)}'\\c'|xclip -i`
|
112
122
|
else
|
113
|
-
|
114
|
-
|
123
|
+
Howzit.console.debug(out)
|
124
|
+
Howzit.console.warn('Unable to determine executable for clipboard.')
|
115
125
|
end
|
116
126
|
end
|
117
127
|
end
|
data/lib/howzit/topic.rb
CHANGED
@@ -51,7 +51,7 @@ module Howzit
|
|
51
51
|
end
|
52
52
|
|
53
53
|
@tasks.each do |task|
|
54
|
-
if task.optional
|
54
|
+
if task.optional || Howzit.options[:ask]
|
55
55
|
note = if task.type == :include
|
56
56
|
task_count = Howzit.buildnote.find_topic(task.action)[0].tasks.count
|
57
57
|
" (#{task_count} tasks)"
|
@@ -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.18
|
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-08 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
|