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.
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].nil? ? nil : attributes[:title].to_s
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
- res = system(@action)
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
- puts TTY::Box.frame("{by}#{@prereqs.join("\n\n").wrap(cols - 4)}{x}".c, width: cols)
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
- puts TTY::Box.frame("{bw}#{@postreqs.join("\n\n").wrap(cols - 4)}{x}".c, width: cols) unless @postreqs.empty?
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
- topic = @content.dup
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, # Make a copy to avoid reference issues
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
- @prereqs = @content.scan(/(?<=@before\n).*?(?=\n@end)/im).map(&:strip)
367
- @postreqs = @content.scan(/(?<=@after\n).*?(?=\n@end)/im).map(&:strip)
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
- @content.scan(rx) { matches << Regexp.last_match }
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
- puts text
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
- puts output
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 = "{bg}Pasting from clipboard".c
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)
@@ -3,5 +3,5 @@
3
3
  # Primary module for this gem.
4
4
  module Howzit
5
5
  # Current Howzit version.
6
- VERSION = '2.1.28'
6
+ VERSION = '2.1.29'
7
7
  end
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
+