howzit 2.1.46 → 2.1.47
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 +16 -0
- data/lib/howzit/buildnote.rb +3 -2
- data/lib/howzit/directive.rb +1 -1
- data/lib/howzit/task.rb +21 -3
- data/lib/howzit/topic.rb +36 -14
- data/lib/howzit/version.rb +1 -1
- data/spec/default_metadata_spec.rb +142 -0
- 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: 477bc94918f7906d52412423ee73992f2d255279d757f36d0feed85f74c1f454
|
|
4
|
+
data.tar.gz: 926727bfe69e059367fa75d4b55b095786663487cdc8f311b13ee92dd44d15e0
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 8f4c4228bc522c7608f77d0ab4e7f4f802753e71c7f37d2f41d9f81e01764a8b0b285aaed8bc5bad6a9ccaa0544d83007bde4c7aea7c0e41bc2fc834bb8304d7
|
|
7
|
+
data.tar.gz: a24505c7b3f22f7d4bb52eb91372f44d0ef200285faf4006bb8a93588bdb63318063d26b5571026374f8f67b648678d0bd4edf4223c5561c39ad625ec2a08a69
|
data/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,19 @@
|
|
|
1
|
+
### 2.1.47
|
|
2
|
+
|
|
3
|
+
2026-09-27 13:20
|
|
4
|
+
|
|
5
|
+
#### IMPROVED
|
|
6
|
+
|
|
7
|
+
- **Included topics** receive their bracketed arguments as positional arguments ($1, $2) in shell run blocks for the duration of the include
|
|
8
|
+
|
|
9
|
+
#### FIXED
|
|
10
|
+
|
|
11
|
+
- **default: metadata** now splits topics only on commas outside of brackets, so `default: Build, Deploy[prod, fast], Test` passes both arguments to Deploy
|
|
12
|
+
- **Bracketed arguments in default:** (e.g. `Deploy[prod]`) are now bound to the topic's title parameters, so `${target}` and friends render in @run commands instead of coming out empty
|
|
13
|
+
- **@include bracketed arguments** (e.g. `@include(Deploy [staging])`) now bind to the included topic's parameters, and including the same topic multiple times with different arguments works
|
|
14
|
+
- **Unspecified bracketed arguments** fall back to the topic's parameter defaults
|
|
15
|
+
- **Topics that use @include with arguments** no longer lose their own argument definitions
|
|
16
|
+
|
|
1
17
|
### 2.1.46
|
|
2
18
|
|
|
3
19
|
2026-09-27 12:52
|
data/lib/howzit/buildnote.rb
CHANGED
|
@@ -1608,7 +1608,7 @@ module Howzit
|
|
|
1608
1608
|
else
|
|
1609
1609
|
[]
|
|
1610
1610
|
end
|
|
1611
|
-
output.push(process_topic(topic_match, Howzit.options[:run], single: true))
|
|
1611
|
+
output.push(process_topic(topic_match.with_arguments(Howzit.arguments), Howzit.options[:run], single: true))
|
|
1612
1612
|
break if stop_after_failure?(topic_specs[(idx + 1)..].map(&:first))
|
|
1613
1613
|
end
|
|
1614
1614
|
finalize_output(output)
|
|
@@ -1640,7 +1640,8 @@ module Howzit
|
|
|
1640
1640
|
## @return [Array] Array of topic specification strings
|
|
1641
1641
|
##
|
|
1642
1642
|
def parse_default_metadata(default_value)
|
|
1643
|
-
|
|
1643
|
+
# Commas inside [bracketed arguments] separate arguments, not topics
|
|
1644
|
+
default_value.strip.split(/\s*,\s*(?![^\[]*\])/).map(&:strip).reject(&:empty?)
|
|
1644
1645
|
end
|
|
1645
1646
|
|
|
1646
1647
|
##
|
data/lib/howzit/directive.rb
CHANGED
|
@@ -138,7 +138,7 @@ module Howzit
|
|
|
138
138
|
if title =~ /\[(.*?)\] *$/
|
|
139
139
|
args = Regexp.last_match(1).split(/ *, */).map(&:render_arguments)
|
|
140
140
|
Howzit.arguments = args
|
|
141
|
-
|
|
141
|
+
task_data[:include_args] = args
|
|
142
142
|
title.sub!(/ *\[.*?\] *$/, '')
|
|
143
143
|
end
|
|
144
144
|
title = title.render_arguments if title && !title.empty?
|
data/lib/howzit/task.rb
CHANGED
|
@@ -27,6 +27,7 @@ module Howzit
|
|
|
27
27
|
@prefix = "{bw}\u{25B7}\u{25B7} {x}"
|
|
28
28
|
# arrow = "{bw}\u{279F}{x}"
|
|
29
29
|
@arguments = attributes[:arguments] || []
|
|
30
|
+
@include_args = attributes[:include_args]
|
|
30
31
|
|
|
31
32
|
@type = attributes[:type] || :run
|
|
32
33
|
@title = attributes[:title]&.to_s
|
|
@@ -171,18 +172,35 @@ module Howzit
|
|
|
171
172
|
output = []
|
|
172
173
|
action = @action
|
|
173
174
|
|
|
174
|
-
matches = Howzit.buildnote.find_topic(action)
|
|
175
|
+
matches = Howzit.buildnote.find_topic(action.sub(/ *\[.*?\] *$/, ''))
|
|
175
176
|
raise "Topic not found: #{action}" if matches.empty?
|
|
176
177
|
|
|
177
|
-
topic = matches[0]
|
|
178
|
+
topic = matches[0].with_arguments(@include_args)
|
|
178
179
|
Howzit.console.info("#{@prefix}{by}Running tasks from {bw}#{topic.title}{x}".c)
|
|
179
|
-
output.concat(topic.run(nested: true))
|
|
180
|
+
output.concat(with_positional_arguments(@include_args) { topic.run(nested: true) })
|
|
180
181
|
Howzit.console.info("{by}End include: #{topic.all_tasks.count} tasks{x}".c)
|
|
181
182
|
@last_status = nil
|
|
182
183
|
@include_results = topic.results.slice(:total, :success, :errors)
|
|
183
184
|
[output, @include_results[:total], @include_results[:errors].zero?]
|
|
184
185
|
end
|
|
185
186
|
|
|
187
|
+
##
|
|
188
|
+
## Temporarily set Howzit.arguments (e.g. $1 in included shell blocks)
|
|
189
|
+
##
|
|
190
|
+
## @param args [Array] Positional values, or nil to leave unchanged
|
|
191
|
+
##
|
|
192
|
+
def with_positional_arguments(args)
|
|
193
|
+
return yield if args.nil? || args.empty?
|
|
194
|
+
|
|
195
|
+
previous = Howzit.arguments
|
|
196
|
+
Howzit.arguments = args
|
|
197
|
+
begin
|
|
198
|
+
yield
|
|
199
|
+
ensure
|
|
200
|
+
Howzit.arguments = previous
|
|
201
|
+
end
|
|
202
|
+
end
|
|
203
|
+
|
|
186
204
|
##
|
|
187
205
|
## Apply log level for this task
|
|
188
206
|
##
|
data/lib/howzit/topic.rb
CHANGED
|
@@ -18,8 +18,11 @@ module Howzit
|
|
|
18
18
|
## @param metadata [Hash] Optional metadata hash
|
|
19
19
|
## @param source_file [String] Optional path to the build note file this topic came from
|
|
20
20
|
## @param level [Integer] Markdown header level (2 for ##, 3 for ###, etc.)
|
|
21
|
+
## @param positional [Array] Values to bind to the title's (arguments),
|
|
22
|
+
## overriding CLI arguments
|
|
21
23
|
##
|
|
22
|
-
def initialize(title, content, metadata = nil, source_file: nil, level: 2)
|
|
24
|
+
def initialize(title, content, metadata = nil, source_file: nil, level: 2, positional: nil)
|
|
25
|
+
@raw_title = title
|
|
23
26
|
@title = title
|
|
24
27
|
@content = content
|
|
25
28
|
@parent = nil
|
|
@@ -30,7 +33,7 @@ module Howzit
|
|
|
30
33
|
@named_args = {}
|
|
31
34
|
@metadata = metadata
|
|
32
35
|
@source_file = source_file
|
|
33
|
-
arguments(from_cli_snapshot:
|
|
36
|
+
arguments(from_cli_snapshot: positional.nil?, positional: positional)
|
|
34
37
|
|
|
35
38
|
@directives = parse_directives_with_conditionals
|
|
36
39
|
@tasks = gather_tasks
|
|
@@ -71,24 +74,43 @@ module Howzit
|
|
|
71
74
|
list
|
|
72
75
|
end
|
|
73
76
|
|
|
77
|
+
##
|
|
78
|
+
## A copy of this topic with bracketed arguments (e.g.
|
|
79
|
+
## `@include(Deploy [prod])` or `default: Deploy[prod]`) bound to the
|
|
80
|
+
## title's (arguments). Tasks are rebuilt so commands render with them.
|
|
81
|
+
##
|
|
82
|
+
## @param args [Array] Positional values
|
|
83
|
+
##
|
|
84
|
+
## @return [Topic] a new Topic, or self if no args given
|
|
85
|
+
##
|
|
86
|
+
def with_arguments(args)
|
|
87
|
+
return self if args.nil? || args.empty?
|
|
88
|
+
|
|
89
|
+
topic = Topic.new(@raw_title, @content, @metadata, source_file: @source_file, level: @level, positional: args)
|
|
90
|
+
topic.parent = @parent
|
|
91
|
+
topic.parent_topic = @parent_topic
|
|
92
|
+
topic.subtopics.concat(@subtopics)
|
|
93
|
+
topic
|
|
94
|
+
end
|
|
95
|
+
|
|
74
96
|
# Get named arguments from title
|
|
75
97
|
# from_cli_snapshot: use Howzit.cli_topic_positional_args (argv after `--`) so earlier
|
|
76
|
-
# topics' gather_tasks cannot clobber positional binding.
|
|
77
|
-
#
|
|
78
|
-
def arguments(from_cli_snapshot: false)
|
|
98
|
+
# topics' gather_tasks cannot clobber positional binding. positional: binds
|
|
99
|
+
# bracketed arguments from @include [a,b] and default: (see #with_arguments).
|
|
100
|
+
def arguments(from_cli_snapshot: false, positional: nil)
|
|
79
101
|
@arg_definitions = []
|
|
80
102
|
return unless @title =~ /\(.*?\) *$/
|
|
81
103
|
|
|
82
|
-
positional
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
104
|
+
positional ||= if from_cli_snapshot
|
|
105
|
+
# Specs / non-CLI: leave unset to keep using Howzit.arguments
|
|
106
|
+
if Howzit.cli_topic_positional_args.nil?
|
|
107
|
+
Howzit.arguments || []
|
|
108
|
+
else
|
|
109
|
+
Howzit.cli_topic_positional_args
|
|
110
|
+
end
|
|
86
111
|
else
|
|
87
|
-
Howzit.
|
|
112
|
+
Howzit.arguments || []
|
|
88
113
|
end
|
|
89
|
-
else
|
|
90
|
-
Howzit.arguments || []
|
|
91
|
-
end
|
|
92
114
|
|
|
93
115
|
a = @title.match(/\((?<args>.*?)\) *$/)
|
|
94
116
|
args = a['args'].split(/ *, */).each(&:strip)
|
|
@@ -538,7 +560,7 @@ module Howzit
|
|
|
538
560
|
if title =~ /\[(.*?)\] *$/
|
|
539
561
|
args = Regexp.last_match(1).split(/ *, */).map(&:render_arguments)
|
|
540
562
|
Howzit.arguments = args
|
|
541
|
-
|
|
563
|
+
task_args[:include_args] = args
|
|
542
564
|
title.sub!(/ *\[.*?\] *$/, '')
|
|
543
565
|
end
|
|
544
566
|
# Apply variable substitution to title after bracket processing
|
data/lib/howzit/version.rb
CHANGED
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'spec_helper'
|
|
4
|
+
require 'fileutils'
|
|
5
|
+
require 'tmpdir'
|
|
6
|
+
|
|
7
|
+
describe 'default: metadata' do
|
|
8
|
+
let(:temp_dir) { File.expand_path(Dir.mktmpdir('howzit_default_test')) }
|
|
9
|
+
let(:out_file) { File.join(temp_dir, 'out.txt') }
|
|
10
|
+
let(:default_line) { 'default: Build, Deploy[prod, fast], Test' }
|
|
11
|
+
let(:buildnote) do
|
|
12
|
+
note = Howzit::BuildNote.new
|
|
13
|
+
allow(note).to receive(:finalize_output)
|
|
14
|
+
note
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
before do
|
|
18
|
+
File.write(
|
|
19
|
+
File.join(temp_dir, 'buildnotes.md'),
|
|
20
|
+
<<~NOTE
|
|
21
|
+
#{default_line}
|
|
22
|
+
|
|
23
|
+
# Default Project
|
|
24
|
+
|
|
25
|
+
## Build
|
|
26
|
+
|
|
27
|
+
@run(echo "build" >> "#{out_file}") Build it
|
|
28
|
+
|
|
29
|
+
## Deploy (target, speed:slow)
|
|
30
|
+
|
|
31
|
+
@run(echo "deploy ${target} ${speed}" >> "#{out_file}") Deploy it
|
|
32
|
+
|
|
33
|
+
## Test
|
|
34
|
+
|
|
35
|
+
@run(echo "test" >> "#{out_file}") Test it
|
|
36
|
+
|
|
37
|
+
## Broken
|
|
38
|
+
|
|
39
|
+
@run(false) Fail
|
|
40
|
+
|
|
41
|
+
## Release
|
|
42
|
+
|
|
43
|
+
@include(Deploy [staging])
|
|
44
|
+
@include(Deploy [prod, fast])
|
|
45
|
+
NOTE
|
|
46
|
+
)
|
|
47
|
+
|
|
48
|
+
Dir.chdir(temp_dir)
|
|
49
|
+
Howzit.instance_variable_set(:@buildnote, buildnote)
|
|
50
|
+
Howzit.options[:stack] = false
|
|
51
|
+
Howzit.options[:include_upstream] = false
|
|
52
|
+
Howzit.options[:run] = true
|
|
53
|
+
Howzit.options[:force] = false
|
|
54
|
+
Howzit.options[:log_level] = 3
|
|
55
|
+
Howzit.cli_args = []
|
|
56
|
+
Howzit.run_log = []
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
after do
|
|
60
|
+
Howzit.options[:run] = false
|
|
61
|
+
Howzit.options[:log_level] = 1
|
|
62
|
+
Howzit.cli_args = []
|
|
63
|
+
Howzit.run_log = []
|
|
64
|
+
Dir.chdir(Dir.tmpdir)
|
|
65
|
+
FileUtils.rm_rf(temp_dir) if Dir.exist?(temp_dir)
|
|
66
|
+
Howzit.instance_variable_set(:@buildnote, nil)
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def output_lines
|
|
70
|
+
File.read(out_file).lines.map(&:strip)
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
describe 'parsing' do
|
|
74
|
+
it 'splits topics on commas outside of brackets' do
|
|
75
|
+
expect(buildnote.send(:parse_default_metadata, 'Build, Deploy[prod, fast],Test')).to eq(
|
|
76
|
+
['Build', 'Deploy[prod, fast]', 'Test']
|
|
77
|
+
)
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
it 'separates bracketed arguments from the topic name' do
|
|
81
|
+
expect(buildnote.send(:parse_topic_with_args, 'Deploy[prod, fast]')).to eq(['Deploy', 'prod, fast'])
|
|
82
|
+
expect(buildnote.send(:parse_topic_with_args, 'Build')).to eq(['Build', nil])
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
describe 'running' do
|
|
87
|
+
it 'runs the default topics in order with howzit -r' do
|
|
88
|
+
buildnote.run
|
|
89
|
+
|
|
90
|
+
expect(output_lines).to eq(['build', 'deploy prod fast', 'test'])
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
it 'runs the default topics with howzit -r default' do
|
|
94
|
+
Howzit.cli_args = ['default']
|
|
95
|
+
buildnote.run
|
|
96
|
+
|
|
97
|
+
expect(output_lines).to eq(['build', 'deploy prod fast', 'test'])
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
context 'with fewer arguments than the topic accepts' do
|
|
101
|
+
let(:default_line) { 'default: Deploy[prod]' }
|
|
102
|
+
|
|
103
|
+
it 'uses the topic defaults for the rest' do
|
|
104
|
+
buildnote.run
|
|
105
|
+
|
|
106
|
+
expect(output_lines).to eq(['deploy prod slow'])
|
|
107
|
+
end
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
context 'with a missing topic' do
|
|
111
|
+
let(:default_line) { 'default: Build, Nonexistent, Test' }
|
|
112
|
+
|
|
113
|
+
it 'runs the topics that match' do
|
|
114
|
+
buildnote.run
|
|
115
|
+
|
|
116
|
+
expect(output_lines).to eq(%w[build test])
|
|
117
|
+
end
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
context 'with a failing topic' do
|
|
121
|
+
let(:default_line) { 'default: Build, Broken, Test' }
|
|
122
|
+
|
|
123
|
+
it 'stops after the failure and skips the remaining topics' do
|
|
124
|
+
buildnote.run
|
|
125
|
+
|
|
126
|
+
expect(output_lines).to eq(['build'])
|
|
127
|
+
expect(Howzit.run_log.map { |e| [e[:task], e[:skipped] ? :skipped : e[:success]] }).to eq(
|
|
128
|
+
[['Build it', true], ['Fail', false], ['Test it', :skipped]]
|
|
129
|
+
)
|
|
130
|
+
end
|
|
131
|
+
end
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
describe '@include with bracketed arguments' do
|
|
135
|
+
it 'binds the arguments to the included topic for each include' do
|
|
136
|
+
Howzit.cli_args = ['release']
|
|
137
|
+
buildnote.run
|
|
138
|
+
|
|
139
|
+
expect(output_lines).to eq(['deploy staging slow', 'deploy prod fast'])
|
|
140
|
+
end
|
|
141
|
+
end
|
|
142
|
+
end
|
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.47
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Brett Terpstra
|
|
@@ -320,6 +320,7 @@ files:
|
|
|
320
320
|
- spec/condition_evaluator_spec.rb
|
|
321
321
|
- spec/conditional_blocks_integration_spec.rb
|
|
322
322
|
- spec/conditional_content_spec.rb
|
|
323
|
+
- spec/default_metadata_spec.rb
|
|
323
324
|
- spec/includes_metadata_spec.rb
|
|
324
325
|
- spec/log_level_spec.rb
|
|
325
326
|
- spec/prompt_spec.rb
|
|
@@ -369,6 +370,7 @@ test_files:
|
|
|
369
370
|
- spec/condition_evaluator_spec.rb
|
|
370
371
|
- spec/conditional_blocks_integration_spec.rb
|
|
371
372
|
- spec/conditional_content_spec.rb
|
|
373
|
+
- spec/default_metadata_spec.rb
|
|
372
374
|
- spec/includes_metadata_spec.rb
|
|
373
375
|
- spec/log_level_spec.rb
|
|
374
376
|
- spec/prompt_spec.rb
|