howzit 2.1.28 → 2.1.29
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 +47 -0
- data/Rakefile +4 -3
- data/bin/howzit +65 -65
- data/howzit.gemspec +1 -1
- data/lib/howzit/buildnote.rb +4 -8
- data/lib/howzit/colors.rb +50 -22
- data/lib/howzit/condition_evaluator.rb +307 -0
- data/lib/howzit/conditional_content.rb +96 -0
- data/lib/howzit/config.rb +15 -3
- data/lib/howzit/console_logger.rb +14 -2
- data/lib/howzit/prompt.rb +20 -12
- data/lib/howzit/run_report.rb +1 -1
- data/lib/howzit/script_comm.rb +105 -0
- data/lib/howzit/stringutils.rb +4 -4
- data/lib/howzit/task.rb +11 -2
- data/lib/howzit/topic.rb +29 -9
- data/lib/howzit/util.rb +11 -3
- data/lib/howzit/version.rb +1 -1
- data/lib/howzit.rb +3 -0
- data/spec/condition_evaluator_spec.rb +261 -0
- data/spec/conditional_blocks_integration_spec.rb +159 -0
- data/spec/conditional_content_spec.rb +296 -0
- data/spec/script_comm_spec.rb +303 -0
- data/spec/spec_helper.rb +3 -1
- metadata +14 -3
|
@@ -0,0 +1,296 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'spec_helper'
|
|
4
|
+
|
|
5
|
+
describe Howzit::ConditionalContent do
|
|
6
|
+
describe '.process' do
|
|
7
|
+
context 'with simple @if blocks' do
|
|
8
|
+
it 'includes content when condition is true' do
|
|
9
|
+
content = <<~CONTENT
|
|
10
|
+
@if "test" == "test"
|
|
11
|
+
This should be included
|
|
12
|
+
@end
|
|
13
|
+
CONTENT
|
|
14
|
+
|
|
15
|
+
result = described_class.process(content, {})
|
|
16
|
+
expect(result).to include('This should be included')
|
|
17
|
+
expect(result).not_to include('@if')
|
|
18
|
+
expect(result).not_to include('@end')
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
it 'excludes content when condition is false' do
|
|
22
|
+
content = <<~CONTENT
|
|
23
|
+
@if "test" == "other"
|
|
24
|
+
This should NOT be included
|
|
25
|
+
@end
|
|
26
|
+
CONTENT
|
|
27
|
+
|
|
28
|
+
result = described_class.process(content, {})
|
|
29
|
+
expect(result).not_to include('This should NOT be included')
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
context 'with @unless blocks' do
|
|
34
|
+
it 'includes content when condition is false' do
|
|
35
|
+
content = <<~CONTENT
|
|
36
|
+
@unless "test" == "other"
|
|
37
|
+
This should be included
|
|
38
|
+
@end
|
|
39
|
+
CONTENT
|
|
40
|
+
|
|
41
|
+
result = described_class.process(content, {})
|
|
42
|
+
expect(result).to include('This should be included')
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
it 'excludes content when condition is true' do
|
|
46
|
+
content = <<~CONTENT
|
|
47
|
+
@unless "test" == "test"
|
|
48
|
+
This should NOT be included
|
|
49
|
+
@end
|
|
50
|
+
CONTENT
|
|
51
|
+
|
|
52
|
+
result = described_class.process(content, {})
|
|
53
|
+
expect(result).not_to include('This should NOT be included')
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
context 'with nested blocks' do
|
|
58
|
+
it 'handles nested @if blocks correctly' do
|
|
59
|
+
content = <<~CONTENT
|
|
60
|
+
@if "outer" == "outer"
|
|
61
|
+
Outer content
|
|
62
|
+
@if "inner" == "inner"
|
|
63
|
+
Inner content
|
|
64
|
+
@end
|
|
65
|
+
More outer content
|
|
66
|
+
@end
|
|
67
|
+
CONTENT
|
|
68
|
+
|
|
69
|
+
result = described_class.process(content, {})
|
|
70
|
+
expect(result).to include('Outer content')
|
|
71
|
+
expect(result).to include('Inner content')
|
|
72
|
+
expect(result).to include('More outer content')
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
it 'excludes nested content when outer condition is false' do
|
|
76
|
+
content = <<~CONTENT
|
|
77
|
+
@if "outer" == "other"
|
|
78
|
+
Outer content
|
|
79
|
+
@if "inner" == "inner"
|
|
80
|
+
Inner content
|
|
81
|
+
@end
|
|
82
|
+
More outer content
|
|
83
|
+
@end
|
|
84
|
+
CONTENT
|
|
85
|
+
|
|
86
|
+
result = described_class.process(content, {})
|
|
87
|
+
expect(result).not_to include('Outer content')
|
|
88
|
+
expect(result).not_to include('Inner content')
|
|
89
|
+
expect(result).not_to include('More outer content')
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
it 'excludes nested content when inner condition is false' do
|
|
93
|
+
content = <<~CONTENT
|
|
94
|
+
@if "outer" == "outer"
|
|
95
|
+
Outer content
|
|
96
|
+
@if "inner" == "other"
|
|
97
|
+
Inner content
|
|
98
|
+
@end
|
|
99
|
+
More outer content
|
|
100
|
+
@end
|
|
101
|
+
CONTENT
|
|
102
|
+
|
|
103
|
+
result = described_class.process(content, {})
|
|
104
|
+
expect(result).to include('Outer content')
|
|
105
|
+
expect(result).not_to include('Inner content')
|
|
106
|
+
expect(result).to include('More outer content')
|
|
107
|
+
end
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
context 'with @run directives inside blocks' do
|
|
111
|
+
it 'includes @run when condition is true' do
|
|
112
|
+
content = <<~CONTENT
|
|
113
|
+
@if 1 == 1
|
|
114
|
+
@run(echo "test")
|
|
115
|
+
@end
|
|
116
|
+
CONTENT
|
|
117
|
+
|
|
118
|
+
result = described_class.process(content, {})
|
|
119
|
+
expect(result).to include('@run(echo "test")')
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
it 'excludes @run when condition is false' do
|
|
123
|
+
content = <<~CONTENT
|
|
124
|
+
@if 1 == 2
|
|
125
|
+
@run(echo "test")
|
|
126
|
+
@end
|
|
127
|
+
CONTENT
|
|
128
|
+
|
|
129
|
+
result = described_class.process(content, {})
|
|
130
|
+
expect(result).not_to include('@run(echo "test")')
|
|
131
|
+
end
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
context 'with code blocks inside conditional blocks' do
|
|
135
|
+
it 'includes code blocks when condition is true' do
|
|
136
|
+
content = <<~CONTENT
|
|
137
|
+
@if "test" == "test"
|
|
138
|
+
```run
|
|
139
|
+
echo "hello"
|
|
140
|
+
```
|
|
141
|
+
@end
|
|
142
|
+
CONTENT
|
|
143
|
+
|
|
144
|
+
result = described_class.process(content, {})
|
|
145
|
+
expect(result).to include('```run')
|
|
146
|
+
expect(result).to include('echo "hello"')
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
it 'excludes code blocks when condition is false' do
|
|
150
|
+
content = <<~CONTENT
|
|
151
|
+
@if "test" == "other"
|
|
152
|
+
```run
|
|
153
|
+
echo "hello"
|
|
154
|
+
```
|
|
155
|
+
@end
|
|
156
|
+
CONTENT
|
|
157
|
+
|
|
158
|
+
result = described_class.process(content, {})
|
|
159
|
+
expect(result).not_to include('```run')
|
|
160
|
+
expect(result).not_to include('echo "hello"')
|
|
161
|
+
end
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
context 'with metadata in conditions' do
|
|
165
|
+
it 'uses metadata from context' do
|
|
166
|
+
content = <<~CONTENT
|
|
167
|
+
@if env == "production"
|
|
168
|
+
Production content
|
|
169
|
+
@end
|
|
170
|
+
CONTENT
|
|
171
|
+
|
|
172
|
+
context = { metadata: { 'env' => 'production' } }
|
|
173
|
+
result = described_class.process(content, context)
|
|
174
|
+
expect(result).to include('Production content')
|
|
175
|
+
end
|
|
176
|
+
end
|
|
177
|
+
|
|
178
|
+
context 'with multiple sequential blocks' do
|
|
179
|
+
it 'handles multiple independent blocks' do
|
|
180
|
+
content = <<~CONTENT
|
|
181
|
+
@if 1 == 1
|
|
182
|
+
First block
|
|
183
|
+
@end
|
|
184
|
+
@if 2 == 2
|
|
185
|
+
Second block
|
|
186
|
+
@end
|
|
187
|
+
CONTENT
|
|
188
|
+
|
|
189
|
+
result = described_class.process(content, {})
|
|
190
|
+
expect(result).to include('First block')
|
|
191
|
+
expect(result).to include('Second block')
|
|
192
|
+
end
|
|
193
|
+
end
|
|
194
|
+
|
|
195
|
+
context 'with @elsif blocks' do
|
|
196
|
+
it 'includes first branch when @if condition is true' do
|
|
197
|
+
content = <<~CONTENT
|
|
198
|
+
@if 1 == 1
|
|
199
|
+
First branch
|
|
200
|
+
@elsif 2 == 2
|
|
201
|
+
Second branch
|
|
202
|
+
@else
|
|
203
|
+
Third branch
|
|
204
|
+
@end
|
|
205
|
+
CONTENT
|
|
206
|
+
|
|
207
|
+
result = described_class.process(content, {})
|
|
208
|
+
expect(result).to include('First branch')
|
|
209
|
+
expect(result).not_to include('Second branch')
|
|
210
|
+
expect(result).not_to include('Third branch')
|
|
211
|
+
end
|
|
212
|
+
|
|
213
|
+
it 'includes second branch when @if is false and @elsif is true' do
|
|
214
|
+
content = <<~CONTENT
|
|
215
|
+
@if 1 == 2
|
|
216
|
+
First branch
|
|
217
|
+
@elsif 2 == 2
|
|
218
|
+
Second branch
|
|
219
|
+
@else
|
|
220
|
+
Third branch
|
|
221
|
+
@end
|
|
222
|
+
CONTENT
|
|
223
|
+
|
|
224
|
+
result = described_class.process(content, {})
|
|
225
|
+
expect(result).not_to include('First branch')
|
|
226
|
+
expect(result).to include('Second branch')
|
|
227
|
+
expect(result).not_to include('Third branch')
|
|
228
|
+
end
|
|
229
|
+
|
|
230
|
+
it 'includes else branch when @if and @elsif are both false' do
|
|
231
|
+
content = <<~CONTENT
|
|
232
|
+
@if 1 == 2
|
|
233
|
+
First branch
|
|
234
|
+
@elsif 2 == 3
|
|
235
|
+
Second branch
|
|
236
|
+
@else
|
|
237
|
+
Third branch
|
|
238
|
+
@end
|
|
239
|
+
CONTENT
|
|
240
|
+
|
|
241
|
+
result = described_class.process(content, {})
|
|
242
|
+
expect(result).not_to include('First branch')
|
|
243
|
+
expect(result).not_to include('Second branch')
|
|
244
|
+
expect(result).to include('Third branch')
|
|
245
|
+
end
|
|
246
|
+
|
|
247
|
+
it 'handles multiple @elsif branches' do
|
|
248
|
+
content = <<~CONTENT
|
|
249
|
+
@if 1 == 2
|
|
250
|
+
First
|
|
251
|
+
@elsif 2 == 3
|
|
252
|
+
Second
|
|
253
|
+
@elsif 3 == 3
|
|
254
|
+
Third
|
|
255
|
+
@else
|
|
256
|
+
Fourth
|
|
257
|
+
@end
|
|
258
|
+
CONTENT
|
|
259
|
+
|
|
260
|
+
result = described_class.process(content, {})
|
|
261
|
+
expect(result).not_to include('First')
|
|
262
|
+
expect(result).not_to include('Second')
|
|
263
|
+
expect(result).to include('Third')
|
|
264
|
+
expect(result).not_to include('Fourth')
|
|
265
|
+
end
|
|
266
|
+
|
|
267
|
+
it 'handles nested @elsif blocks' do
|
|
268
|
+
content = <<~CONTENT
|
|
269
|
+
@if 1 == 1
|
|
270
|
+
Outer true
|
|
271
|
+
@if 2 == 2
|
|
272
|
+
Inner true
|
|
273
|
+
@elsif 3 == 3
|
|
274
|
+
Inner elsif
|
|
275
|
+
@else
|
|
276
|
+
Inner else
|
|
277
|
+
@end
|
|
278
|
+
@elsif 4 == 4
|
|
279
|
+
Outer elsif
|
|
280
|
+
@else
|
|
281
|
+
Outer else
|
|
282
|
+
@end
|
|
283
|
+
CONTENT
|
|
284
|
+
|
|
285
|
+
result = described_class.process(content, {})
|
|
286
|
+
expect(result).to include('Outer true')
|
|
287
|
+
expect(result).to include('Inner true')
|
|
288
|
+
expect(result).not_to include('Inner elsif')
|
|
289
|
+
expect(result).not_to include('Inner else')
|
|
290
|
+
expect(result).not_to include('Outer elsif')
|
|
291
|
+
expect(result).not_to include('Outer else')
|
|
292
|
+
end
|
|
293
|
+
end
|
|
294
|
+
end
|
|
295
|
+
end
|
|
296
|
+
|
|
@@ -0,0 +1,303 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'spec_helper'
|
|
4
|
+
|
|
5
|
+
describe Howzit::ScriptComm do
|
|
6
|
+
before do
|
|
7
|
+
Howzit.named_arguments = {}
|
|
8
|
+
Howzit.options[:log_level] = 0
|
|
9
|
+
# Clear any existing HOWZIT_COMM_FILE
|
|
10
|
+
ENV.delete('HOWZIT_COMM_FILE')
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
after do
|
|
14
|
+
# Clean up any leftover communication files
|
|
15
|
+
if ENV['HOWZIT_COMM_FILE'] && File.exist?(ENV['HOWZIT_COMM_FILE']) && File.file?(ENV['HOWZIT_COMM_FILE'])
|
|
16
|
+
begin
|
|
17
|
+
File.unlink(ENV['HOWZIT_COMM_FILE'])
|
|
18
|
+
rescue Errno::EPERM, Errno::ENOENT
|
|
19
|
+
# Ignore permission errors or missing files
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
ENV.delete('HOWZIT_COMM_FILE')
|
|
23
|
+
Howzit.named_arguments = {}
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
describe '.setup' do
|
|
27
|
+
it 'creates a communication file' do
|
|
28
|
+
comm_file = Howzit::ScriptComm.setup
|
|
29
|
+
expect(comm_file).to be_a(String)
|
|
30
|
+
expect(File.exist?(comm_file)).to be true
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
it 'sets HOWZIT_COMM_FILE environment variable' do
|
|
34
|
+
comm_file = Howzit::ScriptComm.setup
|
|
35
|
+
expect(ENV['HOWZIT_COMM_FILE']).to eq(comm_file)
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
it 'creates a writable file' do
|
|
39
|
+
comm_file = Howzit::ScriptComm.setup
|
|
40
|
+
expect { File.write(comm_file, 'test') }.not_to raise_error
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
describe '.process' do
|
|
45
|
+
it 'returns empty hash for non-existent file' do
|
|
46
|
+
result = Howzit::ScriptComm.process('/nonexistent/path')
|
|
47
|
+
expect(result).to eq({ logs: [], vars: {} })
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
it 'processes log messages' do
|
|
51
|
+
comm_file = Howzit::ScriptComm.setup
|
|
52
|
+
File.write(comm_file, "LOG:info:Test message\nLOG:warn:Warning message\n")
|
|
53
|
+
result = Howzit::ScriptComm.process(comm_file)
|
|
54
|
+
expect(result[:logs].length).to eq(2)
|
|
55
|
+
expect(result[:logs][0][:level]).to eq(:info)
|
|
56
|
+
expect(result[:logs][0][:message]).to eq('Test message')
|
|
57
|
+
expect(result[:logs][1][:level]).to eq(:warn)
|
|
58
|
+
expect(result[:logs][1][:message]).to eq('Warning message')
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
it 'processes variables' do
|
|
62
|
+
comm_file = Howzit::ScriptComm.setup
|
|
63
|
+
File.write(comm_file, "VAR:TEST_VAR=test_value\nVAR:ANOTHER_VAR=another_value\n")
|
|
64
|
+
result = Howzit::ScriptComm.process(comm_file)
|
|
65
|
+
expect(result[:vars]).to eq({ 'TEST_VAR' => 'test_value', 'ANOTHER_VAR' => 'another_value' })
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
it 'processes mixed logs and variables' do
|
|
69
|
+
comm_file = Howzit::ScriptComm.setup
|
|
70
|
+
File.write(comm_file, "LOG:info:Starting\nVAR:STATUS=running\nLOG:info:Done\nVAR:STATUS=complete\n")
|
|
71
|
+
result = Howzit::ScriptComm.process(comm_file)
|
|
72
|
+
expect(result[:logs].length).to eq(2)
|
|
73
|
+
expect(result[:vars]).to eq({ 'STATUS' => 'complete' })
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
it 'handles all log levels' do
|
|
77
|
+
comm_file = Howzit::ScriptComm.setup
|
|
78
|
+
File.write(comm_file, "LOG:info:Info message\nLOG:warn:Warn message\nLOG:error:Error message\nLOG:debug:Debug message\n")
|
|
79
|
+
result = Howzit::ScriptComm.process(comm_file)
|
|
80
|
+
expect(result[:logs].map { |l| l[:level] }).to contain_exactly(:info, :warn, :error, :debug)
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
it 'ignores empty lines' do
|
|
84
|
+
comm_file = Howzit::ScriptComm.setup
|
|
85
|
+
File.write(comm_file, "\nLOG:info:Message\n\nVAR:TEST=value\n\n")
|
|
86
|
+
result = Howzit::ScriptComm.process(comm_file)
|
|
87
|
+
expect(result[:logs].length).to eq(1)
|
|
88
|
+
expect(result[:vars]).to eq({ 'TEST' => 'value' })
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
it 'handles case-insensitive log levels' do
|
|
92
|
+
comm_file = Howzit::ScriptComm.setup
|
|
93
|
+
File.write(comm_file, "LOG:INFO:Uppercase\nLOG:Warn:Mixed\nLOG:error:lowercase\n")
|
|
94
|
+
result = Howzit::ScriptComm.process(comm_file)
|
|
95
|
+
expect(result[:logs].map { |l| l[:level] }).to contain_exactly(:info, :warn, :error)
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
it 'handles case-insensitive variable names' do
|
|
99
|
+
comm_file = Howzit::ScriptComm.setup
|
|
100
|
+
File.write(comm_file, "VAR:test_var=lowercase\nVAR:TEST_VAR=uppercase\n")
|
|
101
|
+
result = Howzit::ScriptComm.process(comm_file)
|
|
102
|
+
# Variable names match case-insensitively, but both are stored (last one wins)
|
|
103
|
+
# The regex captures the original case, so both keys are present
|
|
104
|
+
expect(result[:vars].keys).to include('TEST_VAR')
|
|
105
|
+
expect(result[:vars].keys).to include('test_var')
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
it 'removes the communication file after processing' do
|
|
109
|
+
comm_file = Howzit::ScriptComm.setup
|
|
110
|
+
File.write(comm_file, "VAR:TEST=value\n")
|
|
111
|
+
Howzit::ScriptComm.process(comm_file)
|
|
112
|
+
expect(File.exist?(comm_file)).to be false
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
it 'handles malformed lines gracefully' do
|
|
116
|
+
comm_file = Howzit::ScriptComm.setup
|
|
117
|
+
File.write(comm_file, "INVALID:line\nLOG:info:Valid message\nVAR:TEST=value\nBOGUS\n")
|
|
118
|
+
result = Howzit::ScriptComm.process(comm_file)
|
|
119
|
+
expect(result[:logs].length).to eq(1)
|
|
120
|
+
expect(result[:vars]).to eq({ 'TEST' => 'value' })
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
it 'handles file read errors gracefully' do
|
|
124
|
+
# Create a file that will cause a read error
|
|
125
|
+
comm_file = Howzit::ScriptComm.setup
|
|
126
|
+
# Make it unreadable (but this is platform-dependent, so just test that it doesn't crash)
|
|
127
|
+
# Instead, test with a file that doesn't exist after setup
|
|
128
|
+
File.unlink(comm_file) if File.exist?(comm_file)
|
|
129
|
+
result = Howzit::ScriptComm.process(comm_file)
|
|
130
|
+
expect(result).to eq({ logs: [], vars: {} })
|
|
131
|
+
end
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
describe '.apply' do
|
|
135
|
+
it 'applies log messages to console' do
|
|
136
|
+
comm_file = Howzit::ScriptComm.setup
|
|
137
|
+
File.write(comm_file, "LOG:info:Test info message\n")
|
|
138
|
+
expect(Howzit.console).to receive(:info).with('Test info message')
|
|
139
|
+
Howzit::ScriptComm.apply(comm_file)
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
it 'applies variables to named_arguments' do
|
|
143
|
+
comm_file = Howzit::ScriptComm.setup
|
|
144
|
+
File.write(comm_file, "VAR:TEST_VAR=test_value\n")
|
|
145
|
+
Howzit::ScriptComm.apply(comm_file)
|
|
146
|
+
expect(Howzit.named_arguments['TEST_VAR']).to eq('test_value')
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
it 'merges variables with existing named_arguments' do
|
|
150
|
+
Howzit.named_arguments = { 'EXISTING' => 'old_value' }
|
|
151
|
+
comm_file = Howzit::ScriptComm.setup
|
|
152
|
+
File.write(comm_file, "VAR:NEW_VAR=new_value\n")
|
|
153
|
+
Howzit::ScriptComm.apply(comm_file)
|
|
154
|
+
expect(Howzit.named_arguments).to eq({ 'EXISTING' => 'old_value', 'NEW_VAR' => 'new_value' })
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
it 'overwrites existing variables' do
|
|
158
|
+
Howzit.named_arguments = { 'TEST_VAR' => 'old_value' }
|
|
159
|
+
comm_file = Howzit::ScriptComm.setup
|
|
160
|
+
File.write(comm_file, "VAR:TEST_VAR=new_value\n")
|
|
161
|
+
Howzit::ScriptComm.apply(comm_file)
|
|
162
|
+
expect(Howzit.named_arguments['TEST_VAR']).to eq('new_value')
|
|
163
|
+
end
|
|
164
|
+
|
|
165
|
+
it 'handles multiple log levels' do
|
|
166
|
+
comm_file = Howzit::ScriptComm.setup
|
|
167
|
+
File.write(comm_file, "LOG:info:Info\nLOG:warn:Warn\nLOG:error:Error\nLOG:debug:Debug\n")
|
|
168
|
+
expect(Howzit.console).to receive(:info).with('Info')
|
|
169
|
+
expect(Howzit.console).to receive(:warn).with('Warn')
|
|
170
|
+
expect(Howzit.console).to receive(:error).with('Error')
|
|
171
|
+
expect(Howzit.console).to receive(:debug).with('Debug')
|
|
172
|
+
Howzit::ScriptComm.apply(comm_file)
|
|
173
|
+
end
|
|
174
|
+
|
|
175
|
+
it 'does nothing if file is empty' do
|
|
176
|
+
comm_file = Howzit::ScriptComm.setup
|
|
177
|
+
File.write(comm_file, "\n")
|
|
178
|
+
Howzit::ScriptComm.apply(comm_file)
|
|
179
|
+
expect(Howzit.named_arguments).to be_empty
|
|
180
|
+
end
|
|
181
|
+
|
|
182
|
+
it 'initializes named_arguments if nil' do
|
|
183
|
+
Howzit.named_arguments = nil
|
|
184
|
+
comm_file = Howzit::ScriptComm.setup
|
|
185
|
+
File.write(comm_file, "VAR:TEST=value\n")
|
|
186
|
+
Howzit::ScriptComm.apply(comm_file)
|
|
187
|
+
expect(Howzit.named_arguments).to eq({ 'TEST' => 'value' })
|
|
188
|
+
end
|
|
189
|
+
end
|
|
190
|
+
|
|
191
|
+
describe 'integration with Task#run_run' do
|
|
192
|
+
it 'processes communication file after script execution' do
|
|
193
|
+
# Create a temporary script that writes to communication file
|
|
194
|
+
script_file = Tempfile.new('test_script')
|
|
195
|
+
script_file.write(<<~SCRIPT)
|
|
196
|
+
#!/bin/bash
|
|
197
|
+
echo "VAR:TEST_VAR=script_value" >> "$HOWZIT_COMM_FILE"
|
|
198
|
+
echo "LOG:info:Script message" >> "$HOWZIT_COMM_FILE"
|
|
199
|
+
SCRIPT
|
|
200
|
+
script_file.close
|
|
201
|
+
File.chmod(0o755, script_file.path)
|
|
202
|
+
|
|
203
|
+
task = Howzit::Task.new({ type: :run,
|
|
204
|
+
title: 'Test Script',
|
|
205
|
+
action: script_file.path })
|
|
206
|
+
|
|
207
|
+
allow(Howzit.console).to receive(:info)
|
|
208
|
+
expect(Howzit.console).to receive(:info).with('Script message')
|
|
209
|
+
|
|
210
|
+
task.run
|
|
211
|
+
|
|
212
|
+
expect(Howzit.named_arguments['TEST_VAR']).to eq('script_value')
|
|
213
|
+
|
|
214
|
+
script_file.unlink
|
|
215
|
+
end
|
|
216
|
+
|
|
217
|
+
it 'makes variables available for subsequent tasks' do
|
|
218
|
+
# Create first script that sets a variable
|
|
219
|
+
script1 = Tempfile.new('script1')
|
|
220
|
+
script1.write(<<~SCRIPT)
|
|
221
|
+
#!/bin/bash
|
|
222
|
+
echo "VAR:BUILD_VERSION=1.2.3" >> "$HOWZIT_COMM_FILE"
|
|
223
|
+
SCRIPT
|
|
224
|
+
script1.close
|
|
225
|
+
File.chmod(0o755, script1.path)
|
|
226
|
+
|
|
227
|
+
# Create second script that uses the variable (simulated via echo)
|
|
228
|
+
script2 = Tempfile.new('script2')
|
|
229
|
+
script2.write(<<~SCRIPT)
|
|
230
|
+
#!/bin/bash
|
|
231
|
+
echo "Version would be: ${BUILD_VERSION}"
|
|
232
|
+
SCRIPT
|
|
233
|
+
script2.close
|
|
234
|
+
File.chmod(0o755, script2.path)
|
|
235
|
+
|
|
236
|
+
# Run first task
|
|
237
|
+
task1 = Howzit::Task.new({ type: :run,
|
|
238
|
+
title: 'Set Version',
|
|
239
|
+
action: script1.path })
|
|
240
|
+
allow(Howzit.console).to receive(:info)
|
|
241
|
+
task1.run
|
|
242
|
+
|
|
243
|
+
# Verify variable is set
|
|
244
|
+
expect(Howzit.named_arguments['BUILD_VERSION']).to eq('1.2.3')
|
|
245
|
+
|
|
246
|
+
# Create a task with variable substitution in the action
|
|
247
|
+
# Note: Variable substitution happens when Task is created,
|
|
248
|
+
# so we need to create task2 AFTER task1 runs
|
|
249
|
+
action_with_var = "echo Version: ${BUILD_VERSION}"
|
|
250
|
+
action_rendered = action_with_var.dup
|
|
251
|
+
action_rendered.render_named_placeholders
|
|
252
|
+
expect(action_rendered).to include('1.2.3')
|
|
253
|
+
|
|
254
|
+
script1.unlink
|
|
255
|
+
script2.unlink
|
|
256
|
+
end
|
|
257
|
+
end
|
|
258
|
+
|
|
259
|
+
describe 'integration with Task#run_block' do
|
|
260
|
+
it 'processes communication file after block execution' do
|
|
261
|
+
block_content = <<~BLOCK
|
|
262
|
+
#!/bin/bash
|
|
263
|
+
echo "VAR:BLOCK_VAR=block_value" >> "$HOWZIT_COMM_FILE"
|
|
264
|
+
echo "LOG:info:Block message" >> "$HOWZIT_COMM_FILE"
|
|
265
|
+
BLOCK
|
|
266
|
+
|
|
267
|
+
task = Howzit::Task.new({ type: :block,
|
|
268
|
+
title: 'Test Block',
|
|
269
|
+
action: block_content })
|
|
270
|
+
|
|
271
|
+
allow(Howzit.console).to receive(:info)
|
|
272
|
+
expect(Howzit.console).to receive(:info).with('Block message')
|
|
273
|
+
|
|
274
|
+
task.run
|
|
275
|
+
|
|
276
|
+
expect(Howzit.named_arguments['BLOCK_VAR']).to eq('block_value')
|
|
277
|
+
end
|
|
278
|
+
|
|
279
|
+
it 'cleans up communication file after block execution' do
|
|
280
|
+
comm_file_path = nil
|
|
281
|
+
allow(Howzit::ScriptComm).to receive(:setup).and_wrap_original do |m|
|
|
282
|
+
comm_file_path = m.call
|
|
283
|
+
comm_file_path
|
|
284
|
+
end
|
|
285
|
+
|
|
286
|
+
block_content = <<~BLOCK
|
|
287
|
+
#!/bin/bash
|
|
288
|
+
echo "VAR:TEST=value" >> "$HOWZIT_COMM_FILE"
|
|
289
|
+
BLOCK
|
|
290
|
+
|
|
291
|
+
task = Howzit::Task.new({ type: :block,
|
|
292
|
+
title: 'Test Block',
|
|
293
|
+
action: block_content })
|
|
294
|
+
|
|
295
|
+
allow(Howzit.console).to receive(:info)
|
|
296
|
+
task.run
|
|
297
|
+
|
|
298
|
+
# Communication file should be cleaned up
|
|
299
|
+
expect(File.exist?(comm_file_path)).to be false if comm_file_path
|
|
300
|
+
end
|
|
301
|
+
end
|
|
302
|
+
end
|
|
303
|
+
|
data/spec/spec_helper.rb
CHANGED
|
@@ -16,6 +16,8 @@ RSpec.configure do |c|
|
|
|
16
16
|
c.before(:each) do
|
|
17
17
|
allow(FileUtils).to receive(:remove_entry_secure).with(anything)
|
|
18
18
|
save_buildnote
|
|
19
|
+
# Reset buildnote cache to ensure fresh instance with updated file
|
|
20
|
+
Howzit.instance_variable_set(:@buildnote, nil)
|
|
19
21
|
Howzit.options[:include_upstream] = false
|
|
20
22
|
Howzit.options[:default] = true
|
|
21
23
|
Howzit.options[:matching] = 'partial'
|
|
@@ -87,5 +89,5 @@ def save_buildnote
|
|
|
87
89
|
end
|
|
88
90
|
|
|
89
91
|
def delete_buildnote
|
|
90
|
-
FileUtils.
|
|
92
|
+
FileUtils.rm_f('builda.md')
|
|
91
93
|
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.29
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Brett Terpstra
|
|
@@ -127,14 +127,14 @@ dependencies:
|
|
|
127
127
|
requirements:
|
|
128
128
|
- - "~>"
|
|
129
129
|
- !ruby/object:Gem::Version
|
|
130
|
-
version: '
|
|
130
|
+
version: '1.60'
|
|
131
131
|
type: :development
|
|
132
132
|
prerelease: false
|
|
133
133
|
version_requirements: !ruby/object:Gem::Requirement
|
|
134
134
|
requirements:
|
|
135
135
|
- - "~>"
|
|
136
136
|
- !ruby/object:Gem::Version
|
|
137
|
-
version: '
|
|
137
|
+
version: '1.60'
|
|
138
138
|
- !ruby/object:Gem::Dependency
|
|
139
139
|
name: simplecov
|
|
140
140
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -298,11 +298,14 @@ files:
|
|
|
298
298
|
- lib/howzit.rb
|
|
299
299
|
- lib/howzit/buildnote.rb
|
|
300
300
|
- lib/howzit/colors.rb
|
|
301
|
+
- lib/howzit/condition_evaluator.rb
|
|
302
|
+
- lib/howzit/conditional_content.rb
|
|
301
303
|
- lib/howzit/config.rb
|
|
302
304
|
- lib/howzit/console_logger.rb
|
|
303
305
|
- lib/howzit/hash.rb
|
|
304
306
|
- lib/howzit/prompt.rb
|
|
305
307
|
- lib/howzit/run_report.rb
|
|
308
|
+
- lib/howzit/script_comm.rb
|
|
306
309
|
- lib/howzit/stringutils.rb
|
|
307
310
|
- lib/howzit/task.rb
|
|
308
311
|
- lib/howzit/topic.rb
|
|
@@ -311,9 +314,13 @@ files:
|
|
|
311
314
|
- scripts/runtests.sh
|
|
312
315
|
- spec/buildnote_spec.rb
|
|
313
316
|
- spec/cli_spec.rb
|
|
317
|
+
- spec/condition_evaluator_spec.rb
|
|
318
|
+
- spec/conditional_blocks_integration_spec.rb
|
|
319
|
+
- spec/conditional_content_spec.rb
|
|
314
320
|
- spec/prompt_spec.rb
|
|
315
321
|
- spec/ruby_gem_spec.rb
|
|
316
322
|
- spec/run_report_spec.rb
|
|
323
|
+
- spec/script_comm_spec.rb
|
|
317
324
|
- spec/spec_helper.rb
|
|
318
325
|
- spec/stringutils_spec.rb
|
|
319
326
|
- spec/task_spec.rb
|
|
@@ -346,9 +353,13 @@ summary: Provides a way to access Markdown project notes by topic with query cap
|
|
|
346
353
|
test_files:
|
|
347
354
|
- spec/buildnote_spec.rb
|
|
348
355
|
- spec/cli_spec.rb
|
|
356
|
+
- spec/condition_evaluator_spec.rb
|
|
357
|
+
- spec/conditional_blocks_integration_spec.rb
|
|
358
|
+
- spec/conditional_content_spec.rb
|
|
349
359
|
- spec/prompt_spec.rb
|
|
350
360
|
- spec/ruby_gem_spec.rb
|
|
351
361
|
- spec/run_report_spec.rb
|
|
362
|
+
- spec/script_comm_spec.rb
|
|
352
363
|
- spec/spec_helper.rb
|
|
353
364
|
- spec/stringutils_spec.rb
|
|
354
365
|
- spec/task_spec.rb
|