howzit 2.1.47 → 2.1.49
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 +31 -0
- data/README.md +1 -1
- data/bin/howzit +1 -1
- data/lib/howzit/task.rb +1 -1
- data/lib/howzit/topic.rb +145 -31
- data/lib/howzit/util.rb +41 -22
- data/lib/howzit/version.rb +1 -1
- data/lib/howzit.rb +2 -1
- data/spec/collected_notes_spec.rb +142 -0
- data/spec/topic_spec.rb +30 -0
- data/spec/util_spec.rb +40 -0
- data/src/_README.md +1 -1
- metadata +3 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 91e3e382d6e58e8a3a50cb663de149af066ee02b80bafcdbf6db5d68c79426af
|
|
4
|
+
data.tar.gz: ebaf51a69ba374ba5fa64a548560cfa0b8e03f70b85a77de3a1f43aa94baf944
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 96e353abbe3fec9eb5cce2c1fcbbdc45d425394594f394251dc2719937ba69a2acbf87e7d3ccae437baf25b33c400ef0ddf7c01cd42765cc4f39009968f703cf
|
|
7
|
+
data.tar.gz: 48c8b7253abbd0adfe88a0b5ccb1108684db73252c87823ef29aa59507feefd814bc689831585255e73624136f55cd39ee0797ab84666c49dc3a56107b76fa31
|
data/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,34 @@
|
|
|
1
|
+
### 2.1.49
|
|
2
|
+
|
|
3
|
+
2026-10-21 05:15
|
|
4
|
+
|
|
5
|
+
#### CHANGED
|
|
6
|
+
|
|
7
|
+
- **Markdown highlighting** tries `apex` first when `:highlighter:` is `auto`, then mdcat, then mdless. mdless is still installed with the gem
|
|
8
|
+
- **Directives inside a code fence** are shown as code instead of being turned into tasks
|
|
9
|
+
|
|
10
|
+
#### FIXED
|
|
11
|
+
|
|
12
|
+
- **Apex output** uses `-t terminal256 --no-paginate`, including when `:highlighter:` is set to `apex` alone, so topics render as terminal color instead of HTML
|
|
13
|
+
- **Placeholders inside fenced code** are left as written, so a syntax highlighter such as Pygments does not print howzit's color codes as text like `[1m`
|
|
14
|
+
|
|
15
|
+
### 2.1.48
|
|
16
|
+
|
|
17
|
+
2026-10-09 07:20
|
|
18
|
+
|
|
19
|
+
#### CHANGED
|
|
20
|
+
|
|
21
|
+
- **@before blocks** from a topic, its subtopics, and any included topics are shown together in one box before anything runs, with a single confirmation prompt instead of one prompt per topic
|
|
22
|
+
- **@after blocks** from a topic, its subtopics, and any included topics are shown together in one box at the end of the run instead of after each topic finishes
|
|
23
|
+
|
|
24
|
+
#### IMPROVED
|
|
25
|
+
|
|
26
|
+
- **Collected notes** appear in document order: the topic's own block, then included topics in @include order, then subtopics
|
|
27
|
+
- **@after notes from subtopics skipped after a failure** are still shown in the final box
|
|
28
|
+
- **Notes from included topics** render variables with the bracketed arguments passed via @include(Topic [args])
|
|
29
|
+
- **@before blocks** now render ${variables}, matching @after
|
|
30
|
+
- **Topics included more than once** only contribute their notes once
|
|
31
|
+
|
|
1
32
|
### 2.1.47
|
|
2
33
|
|
|
3
34
|
2026-09-27 13:20
|
data/README.md
CHANGED
|
@@ -37,7 +37,7 @@ Howzit is a tool that allows you to keep Markdown-formatted notes about a projec
|
|
|
37
37
|
- Ruby 2.4+ (It probably works on older Rubys, but is untested prior to 2.4.1.)
|
|
38
38
|
- Optional: if [`fzf`](https://github.com/junegunn/fzf) is available, it will be used for handling multiple choice selections
|
|
39
39
|
- Optional: if [`bat`](https://github.com/sharkdp/bat) is available it will page with that
|
|
40
|
-
- Optional: [`
|
|
40
|
+
- Optional: [`apex`](https://github.com/ApexMarkdown/apex) is preferred for formatting output when it is installed. Otherwise howzit uses [`mdcat`](https://github.com/lunaryorn/mdcat) or [`mdless`](https://github.com/ttscoff/mdless). mdless is installed with the howzit gem.
|
|
41
41
|
|
|
42
42
|
### Installing
|
|
43
43
|
|
data/bin/howzit
CHANGED
|
@@ -210,7 +210,7 @@ OptionParser.new do |opts|
|
|
|
210
210
|
Howzit.options[:header_format] = t
|
|
211
211
|
end
|
|
212
212
|
|
|
213
|
-
opts.on('--[no-]md-highlight', 'Highlight Markdown syntax (default on),
|
|
213
|
+
opts.on('--[no-]md-highlight', 'Highlight Markdown syntax (default on), prefers apex, then mdcat or mdless') do |m|
|
|
214
214
|
Howzit.options[:highlight] = Howzit.options[:color] ? m : false
|
|
215
215
|
end
|
|
216
216
|
|
data/lib/howzit/task.rb
CHANGED
data/lib/howzit/topic.rb
CHANGED
|
@@ -165,34 +165,58 @@ module Howzit
|
|
|
165
165
|
## @return [Array] output lines
|
|
166
166
|
##
|
|
167
167
|
def run(nested: false, as_subtopic: false)
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
cols = check_cols
|
|
171
|
-
|
|
172
|
-
confirm_prereqs(cols) if !@prereqs.empty? && (all_tasks.any? || sequential?)
|
|
173
|
-
|
|
174
|
-
if sequential?
|
|
175
|
-
run_sequential(output: output)
|
|
176
|
-
elsif @tasks.any?
|
|
177
|
-
run_tasks(output)
|
|
178
|
-
elsif all_tasks.empty? && !as_subtopic && !directives?
|
|
179
|
-
Howzit.console.warn "{r}--run: No {br}@directive{xr} found in {bw}#{@title}{x}".c
|
|
180
|
-
end
|
|
168
|
+
# The outermost run shows @before/@after notes for the whole tree
|
|
169
|
+
return run_topic(nested: nested, as_subtopic: as_subtopic) if Howzit.topic_run_active
|
|
181
170
|
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
171
|
+
Howzit.topic_run_active = true
|
|
172
|
+
begin
|
|
173
|
+
cols = check_cols
|
|
174
|
+
confirm_prereqs(cols) if all_tasks.any? || sequential?
|
|
175
|
+
output = run_topic(nested: nested, as_subtopic: as_subtopic)
|
|
176
|
+
show_postreqs(cols)
|
|
177
|
+
output
|
|
178
|
+
ensure
|
|
179
|
+
Howzit.topic_run_active = false
|
|
186
180
|
end
|
|
181
|
+
end
|
|
187
182
|
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
183
|
+
##
|
|
184
|
+
## @before notes for this topic, the topics it includes, and its
|
|
185
|
+
## subtopics, in document order
|
|
186
|
+
##
|
|
187
|
+
## @return [Array] rendered notes
|
|
188
|
+
##
|
|
189
|
+
def all_prereqs
|
|
190
|
+
collect_notes(:prereqs)
|
|
191
|
+
end
|
|
192
192
|
|
|
193
|
-
|
|
193
|
+
##
|
|
194
|
+
## @after notes for this topic, the topics it includes, and its
|
|
195
|
+
## subtopics, in document order
|
|
196
|
+
##
|
|
197
|
+
## @return [Array] rendered notes
|
|
198
|
+
##
|
|
199
|
+
def all_postreqs
|
|
200
|
+
collect_notes(:postreqs)
|
|
201
|
+
end
|
|
194
202
|
|
|
195
|
-
|
|
203
|
+
##
|
|
204
|
+
## Gather notes recursively through @include tasks and subtopics
|
|
205
|
+
##
|
|
206
|
+
## @param kind [Symbol] :prereqs or :postreqs
|
|
207
|
+
## @param args [Array] Bracketed arguments from an @include
|
|
208
|
+
## @param seen [Array] Titles of topics already collected
|
|
209
|
+
##
|
|
210
|
+
def collect_notes(kind, args = nil, seen = [])
|
|
211
|
+
return [] if seen.include?(@title)
|
|
212
|
+
|
|
213
|
+
seen << @title
|
|
214
|
+
notes = render_notes(kind == :prereqs ? @prereqs : @postreqs, args)
|
|
215
|
+
@tasks.select { |task| task.type == :include }.each do |task|
|
|
216
|
+
topic = Howzit.buildnote.find_topic(task.action.sub(/ *\[.*?\] *$/, ''))[0]
|
|
217
|
+
notes.concat(topic.collect_notes(kind, task.include_args, seen)) if topic
|
|
218
|
+
end
|
|
219
|
+
notes + @subtopics.flat_map { |sub| sub.collect_notes(kind, nil, seen) }
|
|
196
220
|
end
|
|
197
221
|
|
|
198
222
|
##
|
|
@@ -215,8 +239,11 @@ module Howzit
|
|
|
215
239
|
end
|
|
216
240
|
|
|
217
241
|
def confirm_prereqs(cols)
|
|
242
|
+
prereqs = all_prereqs
|
|
243
|
+
return if prereqs.empty?
|
|
244
|
+
|
|
218
245
|
begin
|
|
219
|
-
puts TTY::Box.frame("{by}#{
|
|
246
|
+
puts TTY::Box.frame("{by}#{prereqs.join("\n\n").wrap(cols - 4)}{x}".c, width: cols)
|
|
220
247
|
rescue Errno::EPIPE
|
|
221
248
|
# Pipe closed, ignore
|
|
222
249
|
end
|
|
@@ -225,11 +252,11 @@ module Howzit
|
|
|
225
252
|
end
|
|
226
253
|
|
|
227
254
|
def show_postreqs(cols)
|
|
228
|
-
|
|
255
|
+
postreqs = all_postreqs
|
|
256
|
+
return if postreqs.empty?
|
|
229
257
|
|
|
230
|
-
#
|
|
231
|
-
|
|
232
|
-
wrapped_content = postreqs_content.split(/\n/).map { |line| line.wrap(cols - 4) }.join("\n")
|
|
258
|
+
# Wrap each line individually to preserve structure
|
|
259
|
+
wrapped_content = postreqs.join("\n\n").split(/\n/).map { |line| line.wrap(cols - 4) }.join("\n")
|
|
233
260
|
puts TTY::Box.frame("{bw}#{wrapped_content}{x}".c, width: cols)
|
|
234
261
|
rescue Errno::EPIPE
|
|
235
262
|
# Pipe closed, ignore
|
|
@@ -466,7 +493,18 @@ module Howzit
|
|
|
466
493
|
topic.gsub!(/(?mix)^(`{3,})run([?!]*)\s*
|
|
467
494
|
([^\n]*)[\s\S]*?\n\1\s*$/, '@@@run\2 \3')
|
|
468
495
|
end
|
|
496
|
+
in_fence = false
|
|
497
|
+
fence_char = nil
|
|
498
|
+
fence_len = 0
|
|
469
499
|
topic.split(/\n/).each do |l|
|
|
500
|
+
# Fenced code is passed through unchanged. Howzit color codes inside a
|
|
501
|
+
# fence get mangled by the syntax highlighter Apex shells out to.
|
|
502
|
+
if in_fence
|
|
503
|
+
output.push(l)
|
|
504
|
+
in_fence = false if closing_fence?(l, fence_char, fence_len)
|
|
505
|
+
next
|
|
506
|
+
end
|
|
507
|
+
|
|
470
508
|
case l
|
|
471
509
|
when /@(before|after|prereq|end|if|unless)/
|
|
472
510
|
next
|
|
@@ -477,12 +515,22 @@ module Howzit
|
|
|
477
515
|
when /(?<fence>`{3,})run(?<optional>[!?]{1,2})? *(?<title>.*?)$/i
|
|
478
516
|
desc = title_code_block(Regexp.last_match.named_captures.symbolize_keys)
|
|
479
517
|
output.push("{bmK}\u{25B6} {bwK}#{desc}{x}\n```".c)
|
|
518
|
+
fence_char = '`'
|
|
519
|
+
fence_len = Regexp.last_match[:fence].length
|
|
520
|
+
in_fence = true
|
|
480
521
|
when /@@@run(?<optional>[!?]{1,2})? *(?<title>.*?)$/i
|
|
481
522
|
output.push("{bmK}\u{25B6} {bwK}#{title_code_block(Regexp.last_match.named_captures.symbolize_keys)}{x}".c)
|
|
482
523
|
else
|
|
483
|
-
l
|
|
484
|
-
|
|
485
|
-
|
|
524
|
+
opened = opening_fence(l)
|
|
525
|
+
if opened
|
|
526
|
+
fence_char, fence_len = opened
|
|
527
|
+
in_fence = true
|
|
528
|
+
output.push(l)
|
|
529
|
+
else
|
|
530
|
+
l.wrap!(Howzit.options[:wrap]) if Howzit.options[:wrap].positive?
|
|
531
|
+
# Highlight variable placeholders in content
|
|
532
|
+
output.push(highlight_variables(l))
|
|
533
|
+
end
|
|
486
534
|
end
|
|
487
535
|
end
|
|
488
536
|
Howzit.named_arguments = @named_args
|
|
@@ -593,6 +641,72 @@ module Howzit
|
|
|
593
641
|
|
|
594
642
|
private
|
|
595
643
|
|
|
644
|
+
##
|
|
645
|
+
## A markdown opening fence (` ``` ` or `~~~`, up to three spaces of indent)
|
|
646
|
+
##
|
|
647
|
+
## @return [Array(String, Integer), nil] fence character and length
|
|
648
|
+
##
|
|
649
|
+
def opening_fence(line)
|
|
650
|
+
match = line.match(/\A {0,3}(?<marker>`{3,}|~{3,})/)
|
|
651
|
+
return unless match
|
|
652
|
+
|
|
653
|
+
marker = match[:marker]
|
|
654
|
+
[marker[0], marker.length]
|
|
655
|
+
end
|
|
656
|
+
|
|
657
|
+
def closing_fence?(line, char, length)
|
|
658
|
+
line.match?(/\A {0,3}#{Regexp.escape(char)}{#{length},}[ \t]*\z/)
|
|
659
|
+
end
|
|
660
|
+
|
|
661
|
+
def run_topic(nested:, as_subtopic:)
|
|
662
|
+
@results = { total: 0, success: 0, errors: 0, message: ''.c }
|
|
663
|
+
output = []
|
|
664
|
+
|
|
665
|
+
if sequential?
|
|
666
|
+
run_sequential(output: output)
|
|
667
|
+
elsif @tasks.any?
|
|
668
|
+
run_tasks(output)
|
|
669
|
+
elsif all_tasks.empty? && !as_subtopic && !directives?
|
|
670
|
+
Howzit.console.warn "{r}--run: No {br}@directive{xr} found in {bw}#{@title}{x}".c
|
|
671
|
+
end
|
|
672
|
+
|
|
673
|
+
if halted?
|
|
674
|
+
log_skipped_tasks(@subtopics.flat_map(&:all_tasks))
|
|
675
|
+
else
|
|
676
|
+
run_subtopics(output)
|
|
677
|
+
end
|
|
678
|
+
|
|
679
|
+
if @results[:total].positive? || (all_tasks.any? && !sequential?)
|
|
680
|
+
@results[:message] += results_message
|
|
681
|
+
output.push(@results[:message]) if Howzit.options[:log_level] < 2 && !nested && !Howzit.options[:run]
|
|
682
|
+
end
|
|
683
|
+
|
|
684
|
+
output
|
|
685
|
+
end
|
|
686
|
+
|
|
687
|
+
##
|
|
688
|
+
## Render notes with this topic's variables, plus any bracketed
|
|
689
|
+
## @include arguments bound to its title parameters
|
|
690
|
+
##
|
|
691
|
+
def render_notes(notes, args)
|
|
692
|
+
return [] if notes.empty?
|
|
693
|
+
|
|
694
|
+
previous = Howzit.named_arguments
|
|
695
|
+
begin
|
|
696
|
+
Howzit.named_arguments = (previous || {}).merge(@named_args.compact).merge(bound_arguments(args))
|
|
697
|
+
notes.map(&:render_arguments)
|
|
698
|
+
ensure
|
|
699
|
+
Howzit.named_arguments = previous
|
|
700
|
+
end
|
|
701
|
+
end
|
|
702
|
+
|
|
703
|
+
def bound_arguments(args)
|
|
704
|
+
return {} if args.nil? || args.empty?
|
|
705
|
+
|
|
706
|
+
names = @arg_definitions.map { |definition| definition.split(':', 2).first }
|
|
707
|
+
names.zip(args).to_h.compact
|
|
708
|
+
end
|
|
709
|
+
|
|
596
710
|
##
|
|
597
711
|
## Collect all directives in the topic content
|
|
598
712
|
##
|
data/lib/howzit/util.rb
CHANGED
|
@@ -50,31 +50,50 @@ module Howzit
|
|
|
50
50
|
end
|
|
51
51
|
end
|
|
52
52
|
|
|
53
|
-
#
|
|
54
|
-
#
|
|
53
|
+
# Prefer apex, then mdcat, then mdless. mdless ships with the gem,
|
|
54
|
+
# so highlighting still works when apex is not installed.
|
|
55
55
|
def which_highlighter
|
|
56
|
-
if Howzit.options[:highlighter] =~ /auto/i
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
56
|
+
command = if Howzit.options[:highlighter] =~ /auto/i
|
|
57
|
+
auto_highlighter
|
|
58
|
+
else
|
|
59
|
+
configured_highlighter
|
|
60
|
+
end
|
|
61
|
+
apex_command(command)
|
|
62
|
+
end
|
|
60
63
|
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
64
|
+
# Apex defaults to HTML. Howzit always wants terminal escapes, and it
|
|
65
|
+
# pages the result itself, so add those flags unless the command
|
|
66
|
+
# already sets a format or a pagination option. This covers both
|
|
67
|
+
# auto and an explicit `:highlighter: apex`.
|
|
68
|
+
def apex_command(command)
|
|
69
|
+
return command if command.nil? || command.empty?
|
|
70
|
+
return command unless command.split.first == 'apex'
|
|
66
71
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
72
|
+
args = command.split[1..]
|
|
73
|
+
has_format = args.any? { |arg| ['-t', '--to'].include?(arg) || arg.start_with?('--to=') }
|
|
74
|
+
args.push('-t', 'terminal256') unless has_format
|
|
75
|
+
has_pager = args.any? { |arg| ['--no-paginate', '--paginate', '-p'].include?(arg) }
|
|
76
|
+
args << '--no-paginate' unless has_pager
|
|
77
|
+
['apex', *args].join(' ')
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
def auto_highlighter
|
|
81
|
+
highlighters = %w[apex mdcat mdless]
|
|
82
|
+
highlighters.delete_if(&:nil?).select!(&:available?)
|
|
83
|
+
return nil if highlighters.empty?
|
|
84
|
+
|
|
85
|
+
hl = highlighters.first
|
|
86
|
+
args = '--no-pager' if hl == 'mdless'
|
|
87
|
+
[hl, args].compact.join(' ')
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
def configured_highlighter
|
|
91
|
+
hl = Howzit.options[:highlighter].split(/ /)[0]
|
|
92
|
+
return Howzit.options[:highlighter] if hl.available?
|
|
93
|
+
|
|
94
|
+
Howzit.console.error Color.template("{Rw}Error:{xbw} Specified highlighter (#{Howzit.options[:highlighter]}) not found, switching to auto")
|
|
95
|
+
Howzit.options[:highlighter] = 'auto'
|
|
96
|
+
auto_highlighter
|
|
78
97
|
end
|
|
79
98
|
|
|
80
99
|
# When pagination is enabled, find the best (in my opinion) option,
|
data/lib/howzit/version.rb
CHANGED
data/lib/howzit.rb
CHANGED
|
@@ -60,7 +60,8 @@ require 'tty/box'
|
|
|
60
60
|
# Main module for howzit
|
|
61
61
|
module Howzit
|
|
62
62
|
class << self
|
|
63
|
-
attr_accessor :arguments, :named_arguments, :cli_topic_positional_args, :cli_args, :run_log, :multi_topic_run
|
|
63
|
+
attr_accessor :arguments, :named_arguments, :cli_topic_positional_args, :cli_args, :run_log, :multi_topic_run,
|
|
64
|
+
:topic_run_active
|
|
64
65
|
|
|
65
66
|
##
|
|
66
67
|
## Holds a Configuration object with methods and a @settings hash
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'spec_helper'
|
|
4
|
+
require 'fileutils'
|
|
5
|
+
require 'tmpdir'
|
|
6
|
+
require 'stringio'
|
|
7
|
+
|
|
8
|
+
describe 'collected @before/@after notes' do
|
|
9
|
+
let(:temp_dir) { File.expand_path(Dir.mktmpdir('howzit_notes_test')) }
|
|
10
|
+
let(:out_file) { File.join(temp_dir, 'out.txt') }
|
|
11
|
+
let(:child_two_command) { 'false' }
|
|
12
|
+
let(:buildnote) do
|
|
13
|
+
note = Howzit::BuildNote.new
|
|
14
|
+
allow(note).to receive(:finalize_output)
|
|
15
|
+
note
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
before do
|
|
19
|
+
File.write(
|
|
20
|
+
File.join(temp_dir, 'buildnotes.md'),
|
|
21
|
+
<<~NOTE
|
|
22
|
+
# Notes Project
|
|
23
|
+
|
|
24
|
+
## Parent
|
|
25
|
+
|
|
26
|
+
@before
|
|
27
|
+
Parent before
|
|
28
|
+
@end
|
|
29
|
+
|
|
30
|
+
@run(echo "parent" >> "#{out_file}") Parent task
|
|
31
|
+
|
|
32
|
+
@include(Shared [prod])
|
|
33
|
+
|
|
34
|
+
@after
|
|
35
|
+
Parent after
|
|
36
|
+
@end
|
|
37
|
+
|
|
38
|
+
### Child One
|
|
39
|
+
|
|
40
|
+
@before
|
|
41
|
+
Child before
|
|
42
|
+
@end
|
|
43
|
+
|
|
44
|
+
@run(echo "child" >> "#{out_file}") Child task
|
|
45
|
+
|
|
46
|
+
@after
|
|
47
|
+
Child after
|
|
48
|
+
@end
|
|
49
|
+
|
|
50
|
+
### Child Two
|
|
51
|
+
|
|
52
|
+
@run(#{child_two_command}) Child two task
|
|
53
|
+
|
|
54
|
+
### Child Three
|
|
55
|
+
|
|
56
|
+
@run(echo "three" >> "#{out_file}") Three task
|
|
57
|
+
|
|
58
|
+
@after
|
|
59
|
+
Three after
|
|
60
|
+
@end
|
|
61
|
+
|
|
62
|
+
## Shared (target)
|
|
63
|
+
|
|
64
|
+
@run(echo "shared ${target}" >> "#{out_file}") Shared task
|
|
65
|
+
|
|
66
|
+
@after
|
|
67
|
+
Shared after ${target}
|
|
68
|
+
@end
|
|
69
|
+
NOTE
|
|
70
|
+
)
|
|
71
|
+
|
|
72
|
+
Dir.chdir(temp_dir)
|
|
73
|
+
Howzit.instance_variable_set(:@buildnote, buildnote)
|
|
74
|
+
Howzit.options[:stack] = false
|
|
75
|
+
Howzit.options[:include_upstream] = false
|
|
76
|
+
Howzit.options[:run] = true
|
|
77
|
+
Howzit.options[:force] = false
|
|
78
|
+
Howzit.options[:log_level] = 3
|
|
79
|
+
Howzit.cli_args = ['parent']
|
|
80
|
+
Howzit.run_log = []
|
|
81
|
+
allow(Howzit::Prompt).to receive(:yn).and_return(true)
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
after do
|
|
85
|
+
Howzit.options[:run] = false
|
|
86
|
+
Howzit.options[:log_level] = 1
|
|
87
|
+
Howzit.cli_args = []
|
|
88
|
+
Howzit.run_log = []
|
|
89
|
+
Dir.chdir(Dir.tmpdir)
|
|
90
|
+
FileUtils.rm_rf(temp_dir) if Dir.exist?(temp_dir)
|
|
91
|
+
Howzit.instance_variable_set(:@buildnote, nil)
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
def run_and_capture
|
|
95
|
+
original = $stdout
|
|
96
|
+
$stdout = StringIO.new
|
|
97
|
+
buildnote.run
|
|
98
|
+
$stdout.string.gsub(/\e\[[\d;]*m/, '')
|
|
99
|
+
ensure
|
|
100
|
+
$stdout = original
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
def positions(text, notes)
|
|
104
|
+
notes.map { |note| text.index(note) }
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
it 'asks once for all prerequisites before running anything' do
|
|
108
|
+
text = run_and_capture
|
|
109
|
+
|
|
110
|
+
expect(Howzit::Prompt).to have_received(:yn).once
|
|
111
|
+
expect(positions(text, ['Parent before', 'Child before'])).to all(be_a(Integer))
|
|
112
|
+
expect(text.scan('Parent before').count).to eq 1
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
it 'shows all @after notes together in document order, including included topics' do
|
|
116
|
+
text = run_and_capture
|
|
117
|
+
|
|
118
|
+
notes = ['Parent after', 'Shared after prod', 'Child after', 'Three after']
|
|
119
|
+
found = positions(text, notes)
|
|
120
|
+
expect(found).to all(be_a(Integer))
|
|
121
|
+
expect(found).to eq(found.sort)
|
|
122
|
+
expect(text.scan('Child after').count).to eq 1
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
it 'shows notes from subtopics skipped after a failure' do
|
|
126
|
+
text = run_and_capture
|
|
127
|
+
|
|
128
|
+
expect(File.read(out_file).lines.map(&:strip)).to eq(['parent', 'shared prod', 'child'])
|
|
129
|
+
expect(text).to include('Three after')
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
context 'when nothing fails' do
|
|
133
|
+
let(:child_two_command) { 'true' }
|
|
134
|
+
|
|
135
|
+
it 'shows the combined @after box after all tasks have run' do
|
|
136
|
+
text = run_and_capture
|
|
137
|
+
|
|
138
|
+
expect(File.read(out_file).lines.map(&:strip)).to eq(['parent', 'shared prod', 'child', 'three'])
|
|
139
|
+
expect(text.scan('Parent after').count).to eq 1
|
|
140
|
+
end
|
|
141
|
+
end
|
|
142
|
+
end
|
data/spec/topic_spec.rb
CHANGED
|
@@ -103,6 +103,36 @@ describe Howzit::Topic do
|
|
|
103
103
|
Howzit.options[:show_all_code] = true
|
|
104
104
|
expect(topic.print_out({ single: true, header: true }).join("\n").uncolor).to match(/▶ ls -1/)
|
|
105
105
|
end
|
|
106
|
+
|
|
107
|
+
it 'leaves placeholders inside fenced code for the syntax highlighter' do
|
|
108
|
+
Howzit::Color.coloring = true
|
|
109
|
+
Howzit.options[:show_all_code] = true
|
|
110
|
+
note = Howzit::Topic.new('Widgets', <<~MD)
|
|
111
|
+
We set ${TEST_VAR} in a script block
|
|
112
|
+
|
|
113
|
+
```bash
|
|
114
|
+
echo "${TEST_VAR}"
|
|
115
|
+
@run(ls) shown as code
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
```run Full capture script
|
|
119
|
+
if [[ -n ${only} ]]
|
|
120
|
+
```
|
|
121
|
+
MD
|
|
122
|
+
|
|
123
|
+
lines = note.print_out({ single: true, header: false })
|
|
124
|
+
prose = lines.find { |line| line.include?('We set') }
|
|
125
|
+
echo_line = lines.find { |line| line.include?('echo') }
|
|
126
|
+
directive = lines.find { |line| line.include?('@run') }
|
|
127
|
+
condition = lines.find { |line| line.include?('only') }
|
|
128
|
+
|
|
129
|
+
expect(prose).to include("\e[")
|
|
130
|
+
expect(echo_line).to eq('echo "${TEST_VAR}"')
|
|
131
|
+
expect(directive).to eq('@run(ls) shown as code')
|
|
132
|
+
expect(condition).to eq('if [[ -n ${only} ]]')
|
|
133
|
+
ensure
|
|
134
|
+
Howzit::Color.coloring = false
|
|
135
|
+
end
|
|
106
136
|
end
|
|
107
137
|
|
|
108
138
|
describe '.arguments' do
|
data/spec/util_spec.rb
CHANGED
|
@@ -23,10 +23,50 @@ describe Howzit::Util do
|
|
|
23
23
|
end
|
|
24
24
|
|
|
25
25
|
describe '.which_highlighter' do
|
|
26
|
+
after { Howzit.options[:highlighter] = 'auto' }
|
|
27
|
+
|
|
26
28
|
it 'finds mdless' do
|
|
27
29
|
Howzit.options[:highlighter] = 'mdless'
|
|
28
30
|
expect(util.which_highlighter).to eq 'mdless'
|
|
29
31
|
end
|
|
32
|
+
|
|
33
|
+
it 'prefers apex with terminal256 output and pagination disabled' do
|
|
34
|
+
Howzit.options[:highlighter] = 'auto'
|
|
35
|
+
allow(util).to receive(:command_exist?).and_return(false)
|
|
36
|
+
allow(util).to receive(:command_exist?).with('apex').and_return(true)
|
|
37
|
+
|
|
38
|
+
expect(util.which_highlighter).to eq 'apex -t terminal256 --no-paginate'
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
it 'uses mdcat when apex is not installed' do
|
|
42
|
+
Howzit.options[:highlighter] = 'auto'
|
|
43
|
+
allow(util).to receive(:command_exist?).and_return(false)
|
|
44
|
+
allow(util).to receive(:command_exist?).with('mdcat').and_return(true)
|
|
45
|
+
|
|
46
|
+
expect(util.which_highlighter).to eq 'mdcat'
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
it 'uses mdless without its pager when apex and mdcat are not installed' do
|
|
50
|
+
Howzit.options[:highlighter] = 'auto'
|
|
51
|
+
allow(util).to receive(:command_exist?).and_return(false)
|
|
52
|
+
allow(util).to receive(:command_exist?).with('mdless').and_return(true)
|
|
53
|
+
|
|
54
|
+
expect(util.which_highlighter).to eq 'mdless --no-pager'
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
it 'adds terminal256 output when the highlighter is set to apex' do
|
|
58
|
+
Howzit.options[:highlighter] = 'apex'
|
|
59
|
+
allow(util).to receive(:command_exist?).with('apex').and_return(true)
|
|
60
|
+
|
|
61
|
+
expect(util.which_highlighter).to eq 'apex -t terminal256 --no-paginate'
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
it 'keeps an explicit apex output format' do
|
|
65
|
+
Howzit.options[:highlighter] = 'apex -t html'
|
|
66
|
+
allow(util).to receive(:command_exist?).with('apex').and_return(true)
|
|
67
|
+
|
|
68
|
+
expect(util.which_highlighter).to eq 'apex -t html --no-paginate'
|
|
69
|
+
end
|
|
30
70
|
end
|
|
31
71
|
|
|
32
72
|
describe '.which_pager' do
|
data/src/_README.md
CHANGED
|
@@ -37,7 +37,7 @@ Howzit is a tool that allows you to keep Markdown-formatted notes about a projec
|
|
|
37
37
|
- Ruby 2.4+ (It probably works on older Rubys, but is untested prior to 2.4.1.)
|
|
38
38
|
- Optional: if [`fzf`](https://github.com/junegunn/fzf) is available, it will be used for handling multiple choice selections
|
|
39
39
|
- Optional: if [`bat`](https://github.com/sharkdp/bat) is available it will page with that
|
|
40
|
-
- Optional: [`
|
|
40
|
+
- Optional: [`apex`](https://github.com/ApexMarkdown/apex) is preferred for formatting output when it is installed. Otherwise howzit uses [`mdcat`](https://github.com/lunaryorn/mdcat) or [`mdless`](https://github.com/ttscoff/mdless). mdless is installed with the howzit gem.
|
|
41
41
|
|
|
42
42
|
### Installing
|
|
43
43
|
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: howzit
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 2.1.
|
|
4
|
+
version: 2.1.49
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Brett Terpstra
|
|
@@ -317,6 +317,7 @@ files:
|
|
|
317
317
|
- scripts/runtests.sh
|
|
318
318
|
- spec/buildnote_spec.rb
|
|
319
319
|
- spec/cli_spec.rb
|
|
320
|
+
- spec/collected_notes_spec.rb
|
|
320
321
|
- spec/condition_evaluator_spec.rb
|
|
321
322
|
- spec/conditional_blocks_integration_spec.rb
|
|
322
323
|
- spec/conditional_content_spec.rb
|
|
@@ -367,6 +368,7 @@ summary: Provides a way to access Markdown project notes by topic with query cap
|
|
|
367
368
|
test_files:
|
|
368
369
|
- spec/buildnote_spec.rb
|
|
369
370
|
- spec/cli_spec.rb
|
|
371
|
+
- spec/collected_notes_spec.rb
|
|
370
372
|
- spec/condition_evaluator_spec.rb
|
|
371
373
|
- spec/conditional_blocks_integration_spec.rb
|
|
372
374
|
- spec/conditional_content_spec.rb
|