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
data/lib/howzit/task.rb
CHANGED
|
@@ -26,7 +26,7 @@ module Howzit
|
|
|
26
26
|
@arguments = attributes[:arguments] || []
|
|
27
27
|
|
|
28
28
|
@type = attributes[:type] || :run
|
|
29
|
-
@title = attributes[:title]
|
|
29
|
+
@title = attributes[:title]&.to_s
|
|
30
30
|
@parent = attributes[:parent] || nil
|
|
31
31
|
|
|
32
32
|
@action = attributes[:action].render_arguments || nil
|
|
@@ -61,6 +61,7 @@ module Howzit
|
|
|
61
61
|
Howzit.console.info "#{@prefix}{bg}Running block {bw}#{@title}{x}".c if Howzit.options[:log_level] < 2
|
|
62
62
|
block = @action
|
|
63
63
|
script = Tempfile.new('howzit_script')
|
|
64
|
+
comm_file = ScriptComm.setup
|
|
64
65
|
begin
|
|
65
66
|
script.write(block)
|
|
66
67
|
script.close
|
|
@@ -69,6 +70,8 @@ module Howzit
|
|
|
69
70
|
ensure
|
|
70
71
|
script.close
|
|
71
72
|
script.unlink
|
|
73
|
+
# Process script communication
|
|
74
|
+
ScriptComm.apply(comm_file) if comm_file
|
|
72
75
|
end
|
|
73
76
|
|
|
74
77
|
update_last_status(res ? 0 : 1)
|
|
@@ -112,7 +115,13 @@ module Howzit
|
|
|
112
115
|
end
|
|
113
116
|
Howzit.console.info("#{@prefix}{bg}Running {bw}#{display_title}{x}".c)
|
|
114
117
|
ENV['HOWZIT_SCRIPTS'] = File.expand_path('~/.config/howzit/scripts')
|
|
115
|
-
|
|
118
|
+
comm_file = ScriptComm.setup
|
|
119
|
+
begin
|
|
120
|
+
res = system(@action)
|
|
121
|
+
ensure
|
|
122
|
+
# Process script communication
|
|
123
|
+
ScriptComm.apply(comm_file) if comm_file
|
|
124
|
+
end
|
|
116
125
|
update_last_status(res ? 0 : 1)
|
|
117
126
|
res
|
|
118
127
|
end
|
data/lib/howzit/topic.rb
CHANGED
|
@@ -14,13 +14,15 @@ module Howzit
|
|
|
14
14
|
##
|
|
15
15
|
## @param title [String] The topic title
|
|
16
16
|
## @param content [String] The raw topic content
|
|
17
|
+
## @param metadata [Hash] Optional metadata hash
|
|
17
18
|
##
|
|
18
|
-
def initialize(title, content)
|
|
19
|
+
def initialize(title, content, metadata = nil)
|
|
19
20
|
@title = title
|
|
20
21
|
@content = content
|
|
21
22
|
@parent = nil
|
|
22
23
|
@nest_level = 0
|
|
23
24
|
@named_args = {}
|
|
25
|
+
@metadata = metadata
|
|
24
26
|
arguments
|
|
25
27
|
|
|
26
28
|
@tasks = gather_tasks
|
|
@@ -81,7 +83,11 @@ module Howzit
|
|
|
81
83
|
|
|
82
84
|
if @tasks.count.positive?
|
|
83
85
|
unless @prereqs.empty?
|
|
84
|
-
|
|
86
|
+
begin
|
|
87
|
+
puts TTY::Box.frame("{by}#{@prereqs.join("\n\n").wrap(cols - 4)}{x}".c, width: cols)
|
|
88
|
+
rescue Errno::EPIPE
|
|
89
|
+
# Pipe closed, ignore
|
|
90
|
+
end
|
|
85
91
|
res = Prompt.yn('Have the above prerequisites been met?', default: true)
|
|
86
92
|
Process.exit 1 unless res
|
|
87
93
|
|
|
@@ -123,7 +129,13 @@ module Howzit
|
|
|
123
129
|
|
|
124
130
|
output.push(@results[:message]) if Howzit.options[:log_level] < 2 && !nested && !Howzit.options[:run]
|
|
125
131
|
|
|
126
|
-
|
|
132
|
+
unless @postreqs.empty?
|
|
133
|
+
begin
|
|
134
|
+
puts TTY::Box.frame("{bw}#{@postreqs.join("\n\n").wrap(cols - 4)}{x}".c, width: cols)
|
|
135
|
+
rescue Errno::EPIPE
|
|
136
|
+
# Pipe closed, ignore
|
|
137
|
+
end
|
|
138
|
+
end
|
|
127
139
|
|
|
128
140
|
output
|
|
129
141
|
end
|
|
@@ -254,14 +266,16 @@ module Howzit
|
|
|
254
266
|
output.push(@title.format_header)
|
|
255
267
|
output.push('')
|
|
256
268
|
end
|
|
257
|
-
|
|
269
|
+
# Process conditional blocks first
|
|
270
|
+
metadata = @metadata || Howzit.buildnote&.metadata
|
|
271
|
+
topic = ConditionalContent.process(@content.dup, { metadata: metadata })
|
|
258
272
|
unless Howzit.options[:show_all_code]
|
|
259
273
|
topic.gsub!(/(?mix)^(`{3,})run([?!]*)\s*
|
|
260
274
|
([^\n]*)[\s\S]*?\n\1\s*$/, '@@@run\2 \3')
|
|
261
275
|
end
|
|
262
276
|
topic.split(/\n/).each do |l|
|
|
263
277
|
case l
|
|
264
|
-
when /@(before|after|prereq|end)/
|
|
278
|
+
when /@(before|after|prereq|end|if|unless)/
|
|
265
279
|
next
|
|
266
280
|
when /@include(?<optional>[!?]{1,2})?\((?<action>[^)]+)\)/
|
|
267
281
|
output.concat(process_include(Regexp.last_match.named_captures.symbolize_keys, opt))
|
|
@@ -300,7 +314,7 @@ module Howzit
|
|
|
300
314
|
# Store the actual title (not overridden by show_all_code - that's only for display)
|
|
301
315
|
task_args = { type: :include,
|
|
302
316
|
arguments: nil,
|
|
303
|
-
title: title.dup,
|
|
317
|
+
title: title.dup, # Make a copy to avoid reference issues
|
|
304
318
|
action: obj,
|
|
305
319
|
parent: self }
|
|
306
320
|
# Set named_arguments before processing titles for variable substitution
|
|
@@ -363,15 +377,21 @@ module Howzit
|
|
|
363
377
|
|
|
364
378
|
def gather_tasks
|
|
365
379
|
runnable = []
|
|
366
|
-
|
|
367
|
-
|
|
380
|
+
# Process conditional blocks first
|
|
381
|
+
# Set named_arguments before processing so conditions can access them
|
|
382
|
+
Howzit.named_arguments = @named_args
|
|
383
|
+
metadata = @metadata || Howzit.buildnote&.metadata
|
|
384
|
+
processed_content = ConditionalContent.process(@content, { metadata: metadata })
|
|
385
|
+
|
|
386
|
+
@prereqs = processed_content.scan(/(?<=@before\n).*?(?=\n@end)/im).map(&:strip)
|
|
387
|
+
@postreqs = processed_content.scan(/(?<=@after\n).*?(?=\n@end)/im).map(&:strip)
|
|
368
388
|
|
|
369
389
|
rx = /(?mix)(?:
|
|
370
390
|
@(?<cmd>include|run|copy|open|url)(?<optional>[!?]{1,2})?\((?<action>[^)]*?)\)(?<title>[^\n]+)?
|
|
371
391
|
|(?<fence>`{3,})run(?<optional2>[!?]{1,2})?(?<title2>[^\n]+)?(?<block>.*?)\k<fence>
|
|
372
392
|
)/
|
|
373
393
|
matches = []
|
|
374
|
-
|
|
394
|
+
processed_content.scan(rx) { matches << Regexp.last_match }
|
|
375
395
|
matches.each do |m|
|
|
376
396
|
c = m.named_captures.symbolize_keys
|
|
377
397
|
Howzit.named_arguments = @named_args
|
data/lib/howzit/util.rb
CHANGED
|
@@ -118,7 +118,11 @@ module Howzit
|
|
|
118
118
|
# Paginate the output
|
|
119
119
|
def page(text)
|
|
120
120
|
unless $stdout.isatty
|
|
121
|
-
|
|
121
|
+
begin
|
|
122
|
+
puts text
|
|
123
|
+
rescue Errno::EPIPE
|
|
124
|
+
# Pipe closed, ignore
|
|
125
|
+
end
|
|
122
126
|
return
|
|
123
127
|
end
|
|
124
128
|
|
|
@@ -181,7 +185,11 @@ module Howzit
|
|
|
181
185
|
if options[:paginate] && Howzit.options[:paginate]
|
|
182
186
|
page(output)
|
|
183
187
|
else
|
|
184
|
-
|
|
188
|
+
begin
|
|
189
|
+
puts output
|
|
190
|
+
rescue Errno::EPIPE
|
|
191
|
+
# Pipe closed, ignore
|
|
192
|
+
end
|
|
185
193
|
end
|
|
186
194
|
end
|
|
187
195
|
|
|
@@ -219,7 +227,7 @@ module Howzit
|
|
|
219
227
|
##
|
|
220
228
|
def os_paste
|
|
221
229
|
os = RbConfig::CONFIG['target_os']
|
|
222
|
-
out =
|
|
230
|
+
out = '{bg}Pasting from clipboard'.c
|
|
223
231
|
case os
|
|
224
232
|
when /darwin.*/i
|
|
225
233
|
Howzit.console.debug("#{out} (macOS){x}".c)
|
data/lib/howzit/version.rb
CHANGED
data/lib/howzit.rb
CHANGED
|
@@ -38,6 +38,9 @@ require_relative 'howzit/stringutils'
|
|
|
38
38
|
|
|
39
39
|
require_relative 'howzit/console_logger'
|
|
40
40
|
require_relative 'howzit/config'
|
|
41
|
+
require_relative 'howzit/script_comm'
|
|
42
|
+
require_relative 'howzit/condition_evaluator'
|
|
43
|
+
require_relative 'howzit/conditional_content'
|
|
41
44
|
require_relative 'howzit/task'
|
|
42
45
|
require_relative 'howzit/topic'
|
|
43
46
|
require_relative 'howzit/buildnote'
|
|
@@ -0,0 +1,261 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'spec_helper'
|
|
4
|
+
|
|
5
|
+
describe Howzit::ConditionEvaluator do
|
|
6
|
+
describe '.evaluate' do
|
|
7
|
+
context 'with string comparisons' do
|
|
8
|
+
it 'evaluates == correctly' do
|
|
9
|
+
expect(described_class.evaluate('"test" == "test"', {})).to be true
|
|
10
|
+
expect(described_class.evaluate('"test" == "other"', {})).to be false
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
it 'evaluates =~ regex correctly' do
|
|
14
|
+
expect(described_class.evaluate('"test" =~ /es/', {})).to be true
|
|
15
|
+
expect(described_class.evaluate('"test" =~ /xyz/', {})).to be false
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
it 'evaluates *= (contains) correctly' do
|
|
19
|
+
expect(described_class.evaluate('"testing" *= "est"', {})).to be true
|
|
20
|
+
expect(described_class.evaluate('"testing" *= "xyz"', {})).to be false
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
it 'evaluates ^= (starts with) correctly' do
|
|
24
|
+
expect(described_class.evaluate('"testing" ^= "test"', {})).to be true
|
|
25
|
+
expect(described_class.evaluate('"testing" ^= "ing"', {})).to be false
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
it 'evaluates $= (ends with) correctly' do
|
|
29
|
+
expect(described_class.evaluate('"testing" $= "ing"', {})).to be true
|
|
30
|
+
expect(described_class.evaluate('"testing" $= "test"', {})).to be false
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
it 'evaluates **= (fuzzy match) correctly' do
|
|
34
|
+
expect(described_class.evaluate('"fluffy" **= "ffy"', {})).to be true
|
|
35
|
+
expect(described_class.evaluate('"fluffy" **= "fly"', {})).to be true
|
|
36
|
+
expect(described_class.evaluate('"fluffy" **= "fuf"', {})).to be true
|
|
37
|
+
expect(described_class.evaluate('"fluffy" **= "xyz"', {})).to be false
|
|
38
|
+
expect(described_class.evaluate('"fluffy" **= "fxy"', {})).to be false
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
context 'with numeric comparisons' do
|
|
43
|
+
it 'evaluates == correctly' do
|
|
44
|
+
expect(described_class.evaluate('5 == 5', {})).to be true
|
|
45
|
+
expect(described_class.evaluate('5 == 3', {})).to be false
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
it 'evaluates != correctly' do
|
|
49
|
+
expect(described_class.evaluate('5 != 3', {})).to be true
|
|
50
|
+
expect(described_class.evaluate('5 != 5', {})).to be false
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
it 'evaluates > correctly' do
|
|
54
|
+
expect(described_class.evaluate('5 > 3', {})).to be true
|
|
55
|
+
expect(described_class.evaluate('3 > 5', {})).to be false
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
it 'evaluates >= correctly' do
|
|
59
|
+
expect(described_class.evaluate('5 >= 5', {})).to be true
|
|
60
|
+
expect(described_class.evaluate('5 >= 3', {})).to be true
|
|
61
|
+
expect(described_class.evaluate('3 >= 5', {})).to be false
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
it 'evaluates < correctly' do
|
|
65
|
+
expect(described_class.evaluate('3 < 5', {})).to be true
|
|
66
|
+
expect(described_class.evaluate('5 < 3', {})).to be false
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
it 'evaluates <= correctly' do
|
|
70
|
+
expect(described_class.evaluate('3 <= 3', {})).to be true
|
|
71
|
+
expect(described_class.evaluate('3 <= 5', {})).to be true
|
|
72
|
+
expect(described_class.evaluate('5 <= 3', {})).to be false
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
context 'with negation' do
|
|
77
|
+
it 'handles not prefix' do
|
|
78
|
+
expect(described_class.evaluate('not "test" == "other"', {})).to be true
|
|
79
|
+
expect(described_class.evaluate('not "test" == "test"', {})).to be false
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
it 'handles ! prefix' do
|
|
83
|
+
expect(described_class.evaluate('! "test" == "other"', {})).to be true
|
|
84
|
+
expect(described_class.evaluate('! "test" == "test"', {})).to be false
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
context 'with positional arguments' do
|
|
89
|
+
before do
|
|
90
|
+
Howzit.arguments = %w[arg1 arg2 arg3]
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
after do
|
|
94
|
+
Howzit.arguments = nil
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
it 'evaluates $1, $2, etc.' do
|
|
98
|
+
expect(described_class.evaluate('$1 == "arg1"', {})).to be true
|
|
99
|
+
expect(described_class.evaluate('$2 == "arg2"', {})).to be true
|
|
100
|
+
expect(described_class.evaluate('$1 == "other"', {})).to be false
|
|
101
|
+
end
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
context 'with named arguments' do
|
|
105
|
+
before do
|
|
106
|
+
Howzit.named_arguments = { test: 'value', other: 'thing' }
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
after do
|
|
110
|
+
Howzit.named_arguments = nil
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
it 'evaluates named arguments' do
|
|
114
|
+
expect(described_class.evaluate('test == "value"', {})).to be true
|
|
115
|
+
expect(described_class.evaluate('other == "thing"', {})).to be true
|
|
116
|
+
expect(described_class.evaluate('test == "other"', {})).to be false
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
it 'evaluates named arguments with ${} syntax' do
|
|
120
|
+
Howzit.named_arguments = { var: 'val', env: 'production' }
|
|
121
|
+
expect(described_class.evaluate('${var} == "val"', {})).to be true
|
|
122
|
+
expect(described_class.evaluate('${env} == "production"', {})).to be true
|
|
123
|
+
expect(described_class.evaluate('${var} == "other"', {})).to be false
|
|
124
|
+
end
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
context 'with metadata' do
|
|
128
|
+
it 'evaluates metadata keys' do
|
|
129
|
+
context = { metadata: { 'key1' => 'value1', 'key2' => 'value2' } }
|
|
130
|
+
expect(described_class.evaluate('key1 == "value1"', context)).to be true
|
|
131
|
+
expect(described_class.evaluate('key2 == "value2"', context)).to be true
|
|
132
|
+
expect(described_class.evaluate('key1 == "other"', context)).to be false
|
|
133
|
+
end
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
context 'with environment variables' do
|
|
137
|
+
it 'evaluates environment variables' do
|
|
138
|
+
ENV['TEST_VAR'] = 'test_value'
|
|
139
|
+
expect(described_class.evaluate('TEST_VAR == "test_value"', {})).to be true
|
|
140
|
+
ENV.delete('TEST_VAR')
|
|
141
|
+
end
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
context 'with special conditions' do
|
|
145
|
+
it 'evaluates git dirty' do
|
|
146
|
+
# Just test that it doesn't crash
|
|
147
|
+
result = described_class.evaluate('git dirty', {})
|
|
148
|
+
expect([true, false]).to include(result)
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
it 'evaluates git clean' do
|
|
152
|
+
result = described_class.evaluate('git clean', {})
|
|
153
|
+
expect([true, false]).to include(result)
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
it 'evaluates file exists' do
|
|
157
|
+
expect(described_class.evaluate('file exists /dev/null', {})).to be true
|
|
158
|
+
expect(described_class.evaluate('file exists /nonexistent/file', {})).to be false
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
it 'evaluates dir exists' do
|
|
162
|
+
expect(described_class.evaluate('dir exists /tmp', {})).to be true
|
|
163
|
+
expect(described_class.evaluate('dir exists /nonexistent/dir', {})).to be false
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
it 'evaluates cwd' do
|
|
167
|
+
result = described_class.evaluate('cwd', {})
|
|
168
|
+
expect(result).to be true
|
|
169
|
+
end
|
|
170
|
+
|
|
171
|
+
it 'evaluates working directory' do
|
|
172
|
+
result = described_class.evaluate('working directory', {})
|
|
173
|
+
expect(result).to be true
|
|
174
|
+
end
|
|
175
|
+
|
|
176
|
+
it 'evaluates cwd with string comparisons' do
|
|
177
|
+
cwd = Dir.pwd
|
|
178
|
+
expect(described_class.evaluate("cwd == \"#{cwd}\"", {})).to be true
|
|
179
|
+
expect(described_class.evaluate("cwd =~ /#{Regexp.escape(File.basename(cwd))}/", {})).to be true
|
|
180
|
+
expect(described_class.evaluate("cwd *= \"#{File.basename(cwd)}\"", {})).to be true
|
|
181
|
+
expect(described_class.evaluate("cwd ^= \"#{cwd[0..10]}\"", {})).to be true
|
|
182
|
+
expect(described_class.evaluate("cwd $= \"#{cwd[-10..-1]}\"", {})).to be true
|
|
183
|
+
end
|
|
184
|
+
end
|
|
185
|
+
|
|
186
|
+
context 'with file contents conditions' do
|
|
187
|
+
let(:test_file) { 'test_version.txt' }
|
|
188
|
+
|
|
189
|
+
before do
|
|
190
|
+
File.write(test_file, '0.1.2')
|
|
191
|
+
end
|
|
192
|
+
|
|
193
|
+
after do
|
|
194
|
+
File.delete(test_file) if File.exist?(test_file)
|
|
195
|
+
end
|
|
196
|
+
|
|
197
|
+
it 'evaluates file contents ^= (starts with) correctly' do
|
|
198
|
+
expect(described_class.evaluate("file contents #{test_file} ^= 0.", {})).to be true
|
|
199
|
+
expect(described_class.evaluate("file contents #{test_file} ^= 1.", {})).to be false
|
|
200
|
+
end
|
|
201
|
+
|
|
202
|
+
it 'evaluates file contents *= (contains) correctly' do
|
|
203
|
+
expect(described_class.evaluate("file contents #{test_file} *= 1.2", {})).to be true
|
|
204
|
+
expect(described_class.evaluate("file contents #{test_file} *= 2.3", {})).to be false
|
|
205
|
+
end
|
|
206
|
+
|
|
207
|
+
it 'evaluates file contents $= (ends with) correctly' do
|
|
208
|
+
expect(described_class.evaluate("file contents #{test_file} $= .2", {})).to be true
|
|
209
|
+
expect(described_class.evaluate("file contents #{test_file} $= .3", {})).to be false
|
|
210
|
+
end
|
|
211
|
+
|
|
212
|
+
it 'evaluates file contents == correctly' do
|
|
213
|
+
expect(described_class.evaluate("file contents #{test_file} == 0.1.2", {})).to be true
|
|
214
|
+
expect(described_class.evaluate("file contents #{test_file} == 1.0.0", {})).to be false
|
|
215
|
+
end
|
|
216
|
+
|
|
217
|
+
it 'evaluates file contents != correctly' do
|
|
218
|
+
expect(described_class.evaluate("file contents #{test_file} != 1.0.0", {})).to be true
|
|
219
|
+
expect(described_class.evaluate("file contents #{test_file} != 0.1.2", {})).to be false
|
|
220
|
+
end
|
|
221
|
+
|
|
222
|
+
it 'evaluates file contents **= (fuzzy match) correctly' do
|
|
223
|
+
expect(described_class.evaluate("file contents #{test_file} **= 012", {})).to be true
|
|
224
|
+
expect(described_class.evaluate("file contents #{test_file} **= 021", {})).to be false
|
|
225
|
+
end
|
|
226
|
+
|
|
227
|
+
it 'evaluates file contents =~ (regex) correctly' do
|
|
228
|
+
expect(described_class.evaluate("file contents #{test_file} =~ /0\\.\\d+\\.\\d+/", {})).to be true
|
|
229
|
+
expect(described_class.evaluate("file contents #{test_file} =~ /1\\.\\d+\\.\\d+/", {})).to be false
|
|
230
|
+
end
|
|
231
|
+
|
|
232
|
+
it 'returns false for non-existent file' do
|
|
233
|
+
expect(described_class.evaluate('file contents nonexistent.txt ^= 0.', {})).to be false
|
|
234
|
+
end
|
|
235
|
+
|
|
236
|
+
it 'handles file path as variable' do
|
|
237
|
+
context = { metadata: { 'version_file' => test_file } }
|
|
238
|
+
expect(described_class.evaluate('file contents version_file ^= 0.', context)).to be true
|
|
239
|
+
end
|
|
240
|
+
end
|
|
241
|
+
|
|
242
|
+
context 'with simple existence checks' do
|
|
243
|
+
before do
|
|
244
|
+
Howzit.named_arguments = { defined_var: 'value' }
|
|
245
|
+
end
|
|
246
|
+
|
|
247
|
+
after do
|
|
248
|
+
Howzit.named_arguments = nil
|
|
249
|
+
end
|
|
250
|
+
|
|
251
|
+
it 'returns true for defined variables' do
|
|
252
|
+
expect(described_class.evaluate('defined_var', {})).to be true
|
|
253
|
+
end
|
|
254
|
+
|
|
255
|
+
it 'returns false for undefined variables' do
|
|
256
|
+
expect(described_class.evaluate('undefined_var', {})).to be false
|
|
257
|
+
end
|
|
258
|
+
end
|
|
259
|
+
end
|
|
260
|
+
end
|
|
261
|
+
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'spec_helper'
|
|
4
|
+
|
|
5
|
+
describe 'Conditional Blocks Integration' do
|
|
6
|
+
before do
|
|
7
|
+
Howzit.options[:include_upstream] = false
|
|
8
|
+
Howzit.options[:default] = true
|
|
9
|
+
Howzit.options[:matching] = 'partial'
|
|
10
|
+
Howzit.options[:multiple_matches] = 'choose'
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
after do
|
|
14
|
+
FileUtils.rm_f('builda.md')
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
describe 'with conditional blocks in topics' do
|
|
18
|
+
it 'includes content when @if condition is true' do
|
|
19
|
+
note = <<~EONOTE
|
|
20
|
+
# Test
|
|
21
|
+
|
|
22
|
+
## Test Topic
|
|
23
|
+
|
|
24
|
+
@if "test" == "test"
|
|
25
|
+
This should be included
|
|
26
|
+
@end
|
|
27
|
+
EONOTE
|
|
28
|
+
File.open('builda.md', 'w') { |f| f.puts note }
|
|
29
|
+
Howzit.instance_variable_set(:@buildnote, nil) # Force reload
|
|
30
|
+
topic = Howzit.buildnote.find_topic('Test Topic')[0]
|
|
31
|
+
expect(topic).not_to be_nil
|
|
32
|
+
output = topic.print_out
|
|
33
|
+
expect(output.join("\n")).to include('This should be included')
|
|
34
|
+
expect(output.join("\n")).not_to include('@if')
|
|
35
|
+
expect(output.join("\n")).not_to include('@end')
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
it 'excludes content when @if condition is false' do
|
|
39
|
+
note = <<~EONOTE
|
|
40
|
+
# Test
|
|
41
|
+
|
|
42
|
+
## Test Topic
|
|
43
|
+
|
|
44
|
+
@if "test" == "other"
|
|
45
|
+
This should NOT be included
|
|
46
|
+
@end
|
|
47
|
+
EONOTE
|
|
48
|
+
File.open('builda.md', 'w') { |f| f.puts note }
|
|
49
|
+
Howzit.instance_variable_set(:@buildnote, nil) # Force reload
|
|
50
|
+
topic = Howzit.buildnote.find_topic('Test Topic')[0]
|
|
51
|
+
expect(topic).not_to be_nil
|
|
52
|
+
output = topic.print_out
|
|
53
|
+
expect(output.join("\n")).not_to include('This should NOT be included')
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
it 'includes tasks when condition is true' do
|
|
57
|
+
note = <<~EONOTE
|
|
58
|
+
# Test
|
|
59
|
+
|
|
60
|
+
## Test Topic
|
|
61
|
+
|
|
62
|
+
@if 1 == 1
|
|
63
|
+
@run(echo "test") Test Command
|
|
64
|
+
@end
|
|
65
|
+
EONOTE
|
|
66
|
+
File.open('builda.md', 'w') { |f| f.puts note }
|
|
67
|
+
Howzit.instance_variable_set(:@buildnote, nil) # Force reload
|
|
68
|
+
topic = Howzit.buildnote.find_topic('Test Topic')[0]
|
|
69
|
+
expect(topic).not_to be_nil
|
|
70
|
+
expect(topic.tasks.count).to eq(1)
|
|
71
|
+
expect(topic.tasks[0].action).to include('echo "test"')
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
it 'excludes tasks when condition is false' do
|
|
75
|
+
note = <<~EONOTE
|
|
76
|
+
# Test
|
|
77
|
+
|
|
78
|
+
## Test Topic
|
|
79
|
+
|
|
80
|
+
@if 1 == 2
|
|
81
|
+
@run(echo "hidden") Hidden Command
|
|
82
|
+
@end
|
|
83
|
+
EONOTE
|
|
84
|
+
File.open('builda.md', 'w') { |f| f.puts note }
|
|
85
|
+
Howzit.instance_variable_set(:@buildnote, nil) # Force reload
|
|
86
|
+
topic = Howzit.buildnote.find_topic('Test Topic')[0]
|
|
87
|
+
expect(topic).not_to be_nil
|
|
88
|
+
expect(topic.tasks.count).to eq(0)
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
it 'handles nested conditional blocks' do
|
|
92
|
+
note = <<~EONOTE
|
|
93
|
+
# Test
|
|
94
|
+
|
|
95
|
+
## Test Topic
|
|
96
|
+
|
|
97
|
+
@if "outer" == "outer"
|
|
98
|
+
Outer content
|
|
99
|
+
@if "inner" == "inner"
|
|
100
|
+
Inner content
|
|
101
|
+
@end
|
|
102
|
+
More outer content
|
|
103
|
+
@end
|
|
104
|
+
EONOTE
|
|
105
|
+
File.open('builda.md', 'w') { |f| f.puts note }
|
|
106
|
+
Howzit.instance_variable_set(:@buildnote, nil) # Force reload
|
|
107
|
+
topic = Howzit.buildnote.find_topic('Test Topic')[0]
|
|
108
|
+
expect(topic).not_to be_nil
|
|
109
|
+
output = topic.print_out
|
|
110
|
+
expect(output.join("\n")).to include('Outer content')
|
|
111
|
+
expect(output.join("\n")).to include('Inner content')
|
|
112
|
+
expect(output.join("\n")).to include('More outer content')
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
it 'handles metadata in conditions' do
|
|
116
|
+
note = <<~EONOTE
|
|
117
|
+
env: production
|
|
118
|
+
|
|
119
|
+
# Test
|
|
120
|
+
|
|
121
|
+
## Test Topic
|
|
122
|
+
|
|
123
|
+
@if env == "production"
|
|
124
|
+
Production content
|
|
125
|
+
@end
|
|
126
|
+
EONOTE
|
|
127
|
+
File.open('builda.md', 'w') { |f| f.puts note }
|
|
128
|
+
Howzit.instance_variable_set(:@buildnote, nil) # Force reload
|
|
129
|
+
topic = Howzit.buildnote.find_topic('Test Topic')[0]
|
|
130
|
+
expect(topic).not_to be_nil
|
|
131
|
+
output = topic.print_out
|
|
132
|
+
expect(output.join("\n")).to include('Production content')
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
it 'handles @unless blocks correctly' do
|
|
136
|
+
note = <<~EONOTE
|
|
137
|
+
# Test
|
|
138
|
+
|
|
139
|
+
## Test Topic
|
|
140
|
+
|
|
141
|
+
@unless "test" == "other"
|
|
142
|
+
This should be included
|
|
143
|
+
@end
|
|
144
|
+
|
|
145
|
+
@unless "test" == "test"
|
|
146
|
+
This should NOT be included
|
|
147
|
+
@end
|
|
148
|
+
EONOTE
|
|
149
|
+
File.open('builda.md', 'w') { |f| f.puts note }
|
|
150
|
+
Howzit.instance_variable_set(:@buildnote, nil) # Force reload
|
|
151
|
+
topic = Howzit.buildnote.find_topic('Test Topic')[0]
|
|
152
|
+
expect(topic).not_to be_nil
|
|
153
|
+
output = topic.print_out
|
|
154
|
+
expect(output.join("\n")).to include('This should be included')
|
|
155
|
+
expect(output.join("\n")).not_to include('This should NOT be included')
|
|
156
|
+
end
|
|
157
|
+
end
|
|
158
|
+
end
|
|
159
|
+
|