howzit 2.1.35 → 2.1.38
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 +69 -0
- data/README.md +4 -0
- data/bin/howzit +4 -0
- data/lib/howzit/buildnote.rb +266 -16
- data/lib/howzit/config.rb +28 -3
- data/lib/howzit/directive.rb +3 -0
- data/lib/howzit/script_support.rb +151 -49
- data/lib/howzit/stringutils.rb +20 -1
- data/lib/howzit/task.rb +80 -2
- data/lib/howzit/topic.rb +7 -5
- data/lib/howzit/version.rb +1 -1
- data/spec/stack_mode_spec.rb +321 -0
- data/src/_README.md +4 -0
- metadata +3 -1
|
@@ -0,0 +1,321 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'spec_helper'
|
|
4
|
+
require 'fileutils'
|
|
5
|
+
require 'tmpdir'
|
|
6
|
+
|
|
7
|
+
describe 'Stack mode and directory execution' do
|
|
8
|
+
let(:temp_dir) { File.expand_path(Dir.mktmpdir('howzit_stack_test')) }
|
|
9
|
+
let(:parent_dir) { File.expand_path(File.join(temp_dir, 'parent')) }
|
|
10
|
+
let(:current_dir) { File.expand_path(File.join(temp_dir, 'parent', 'current')) }
|
|
11
|
+
|
|
12
|
+
before do
|
|
13
|
+
# Create directory structure
|
|
14
|
+
FileUtils.mkdir_p(parent_dir)
|
|
15
|
+
FileUtils.mkdir_p(current_dir)
|
|
16
|
+
|
|
17
|
+
# Create parent build note
|
|
18
|
+
parent_note = <<~EONOTE
|
|
19
|
+
# Parent Build Note
|
|
20
|
+
|
|
21
|
+
## Parent Topic
|
|
22
|
+
|
|
23
|
+
@run(echo "parent_task") Parent task
|
|
24
|
+
EONOTE
|
|
25
|
+
File.write(File.join(parent_dir, 'buildnotes.md'), parent_note)
|
|
26
|
+
|
|
27
|
+
# Create current build note
|
|
28
|
+
current_note = <<~EONOTE
|
|
29
|
+
# Current Build Note
|
|
30
|
+
|
|
31
|
+
## Current Topic
|
|
32
|
+
|
|
33
|
+
@run(echo "current_task") Current task
|
|
34
|
+
EONOTE
|
|
35
|
+
File.write(File.join(current_dir, 'buildnotes.md'), current_note)
|
|
36
|
+
|
|
37
|
+
# Change to current directory
|
|
38
|
+
Dir.chdir(current_dir)
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
after do
|
|
42
|
+
Dir.chdir(Dir.tmpdir) # Change out of test directory
|
|
43
|
+
FileUtils.rm_rf(temp_dir) if Dir.exist?(temp_dir)
|
|
44
|
+
Howzit.instance_variable_set(:@buildnote, nil)
|
|
45
|
+
Howzit.options[:stack] = false
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
describe 'source_file tracking' do
|
|
49
|
+
context 'without --stack mode' do
|
|
50
|
+
before do
|
|
51
|
+
Howzit.options[:stack] = false
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
it 'sets source_file to current directory build note' do
|
|
55
|
+
buildnote = Howzit::BuildNote.new
|
|
56
|
+
topic = buildnote.find_topic('Current Topic')[0]
|
|
57
|
+
|
|
58
|
+
expected_file = File.expand_path(File.join(current_dir, 'buildnotes.md'))
|
|
59
|
+
actual_file = File.expand_path(topic.source_file)
|
|
60
|
+
# Use realpath to handle macOS /var -> /private/var symlink
|
|
61
|
+
expect(File.realpath(actual_file)).to eq(File.realpath(expected_file))
|
|
62
|
+
expect(File.realpath(File.dirname(actual_file))).to eq(File.realpath(current_dir))
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
it 'does not include parent directory topics' do
|
|
66
|
+
buildnote = Howzit::BuildNote.new
|
|
67
|
+
matches = buildnote.find_topic('Parent Topic')
|
|
68
|
+
|
|
69
|
+
expect(matches).to be_empty
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
context 'with --stack mode' do
|
|
74
|
+
before do
|
|
75
|
+
Howzit.options[:stack] = true
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
it 'sets source_file correctly for current directory topic' do
|
|
79
|
+
buildnote = Howzit::BuildNote.new
|
|
80
|
+
topic = buildnote.find_topic('Current Topic')[0]
|
|
81
|
+
|
|
82
|
+
expected_file = File.expand_path(File.join(current_dir, 'buildnotes.md'))
|
|
83
|
+
actual_file = File.expand_path(topic.source_file)
|
|
84
|
+
# Use realpath to handle macOS /var -> /private/var symlink
|
|
85
|
+
expect(File.realpath(actual_file)).to eq(File.realpath(expected_file))
|
|
86
|
+
expect(File.realpath(File.dirname(actual_file))).to eq(File.realpath(current_dir))
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
it 'sets source_file correctly for parent directory topic' do
|
|
90
|
+
buildnote = Howzit::BuildNote.new
|
|
91
|
+
topic = buildnote.find_topic('Parent Topic')[0]
|
|
92
|
+
|
|
93
|
+
expected_file = File.expand_path(File.join(parent_dir, 'buildnotes.md'))
|
|
94
|
+
actual_file = File.expand_path(topic.source_file)
|
|
95
|
+
# Use realpath to handle macOS /var -> /private/var symlink
|
|
96
|
+
expect(File.realpath(actual_file)).to eq(File.realpath(expected_file))
|
|
97
|
+
expect(File.realpath(File.dirname(actual_file))).to eq(File.realpath(parent_dir))
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
it 'includes topics from both current and parent directories' do
|
|
101
|
+
buildnote = Howzit::BuildNote.new
|
|
102
|
+
current_topic = buildnote.find_topic('Current Topic')[0]
|
|
103
|
+
parent_topic = buildnote.find_topic('Parent Topic')[0]
|
|
104
|
+
|
|
105
|
+
expect(current_topic).not_to be_nil
|
|
106
|
+
expect(parent_topic).not_to be_nil
|
|
107
|
+
# Use realpath to handle macOS /var -> /private/var symlink
|
|
108
|
+
expect(File.realpath(current_topic.source_file)).to eq(File.realpath(File.join(current_dir, 'buildnotes.md')))
|
|
109
|
+
expect(File.realpath(parent_topic.source_file)).to eq(File.realpath(File.join(parent_dir, 'buildnotes.md')))
|
|
110
|
+
end
|
|
111
|
+
end
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
describe 'task execution directory' do
|
|
115
|
+
context 'without --stack mode' do
|
|
116
|
+
before do
|
|
117
|
+
Howzit.options[:stack] = false
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
it 'sets source_file but does not prepare to change directory' do
|
|
121
|
+
buildnote = Howzit::BuildNote.new
|
|
122
|
+
topic = buildnote.find_topic('Current Topic')[0]
|
|
123
|
+
task = topic.tasks.first
|
|
124
|
+
|
|
125
|
+
expected_file = File.expand_path(File.join(current_dir, 'buildnotes.md'))
|
|
126
|
+
# Use realpath to handle macOS /var -> /private/var symlink
|
|
127
|
+
expect(File.realpath(task.source_file)).to eq(File.realpath(expected_file))
|
|
128
|
+
|
|
129
|
+
# In normal mode, exec_dir should be nil (no directory change)
|
|
130
|
+
# We can't easily test the actual chdir without mocking, but we can verify
|
|
131
|
+
# that the task has the correct source_file set
|
|
132
|
+
expect(task.source_file).not_to be_nil
|
|
133
|
+
end
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
context 'with --stack mode' do
|
|
137
|
+
before do
|
|
138
|
+
Howzit.options[:stack] = true
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
it 'sets source_file for current directory tasks' do
|
|
142
|
+
buildnote = Howzit::BuildNote.new
|
|
143
|
+
topic = buildnote.find_topic('Current Topic')[0]
|
|
144
|
+
task = topic.tasks.first
|
|
145
|
+
|
|
146
|
+
expected_file = File.expand_path(File.join(current_dir, 'buildnotes.md'))
|
|
147
|
+
# Use realpath to handle macOS /var -> /private/var symlink
|
|
148
|
+
expect(File.realpath(task.source_file)).to eq(File.realpath(expected_file))
|
|
149
|
+
|
|
150
|
+
# Current directory tasks should have source_file set but exec_dir logic
|
|
151
|
+
# should determine not to change (same directory)
|
|
152
|
+
expect(task.source_file).not_to be_nil
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
it 'sets source_file for parent directory tasks and prepares to change directory' do
|
|
156
|
+
buildnote = Howzit::BuildNote.new
|
|
157
|
+
topic = buildnote.find_topic('Parent Topic')[0]
|
|
158
|
+
task = topic.tasks.first
|
|
159
|
+
|
|
160
|
+
expected_file = File.expand_path(File.join(parent_dir, 'buildnotes.md'))
|
|
161
|
+
# Use realpath to handle macOS /var -> /private/var symlink
|
|
162
|
+
expect(File.realpath(task.source_file)).to eq(File.realpath(expected_file))
|
|
163
|
+
|
|
164
|
+
# Verify source_file is from parent directory
|
|
165
|
+
source_dir = File.realpath(File.dirname(task.source_file))
|
|
166
|
+
expect(source_dir).to eq(File.realpath(parent_dir))
|
|
167
|
+
|
|
168
|
+
# In stack mode with parent directory, the task should have source_file set
|
|
169
|
+
# and the execution logic should change to that directory
|
|
170
|
+
expect(task.source_file).not_to be_nil
|
|
171
|
+
expect(File.realpath(File.dirname(task.source_file))).not_to eq(File.realpath(current_dir))
|
|
172
|
+
end
|
|
173
|
+
end
|
|
174
|
+
end
|
|
175
|
+
|
|
176
|
+
describe 'template topics' do
|
|
177
|
+
let(:template_dir) { File.expand_path(File.join(temp_dir, 'templates')) }
|
|
178
|
+
let(:template_file) { File.expand_path(File.join(template_dir, 'test_template.md')) }
|
|
179
|
+
|
|
180
|
+
before do
|
|
181
|
+
FileUtils.mkdir_p(template_dir)
|
|
182
|
+
|
|
183
|
+
template_note = <<~EONOTE
|
|
184
|
+
# Template Note
|
|
185
|
+
|
|
186
|
+
## Template Topic
|
|
187
|
+
|
|
188
|
+
@run(echo "template_task") Template task
|
|
189
|
+
EONOTE
|
|
190
|
+
File.write(template_file, template_note)
|
|
191
|
+
|
|
192
|
+
# Add template to current build note
|
|
193
|
+
current_note_with_template = <<~EONOTE
|
|
194
|
+
template: test_template
|
|
195
|
+
|
|
196
|
+
# Current Build Note
|
|
197
|
+
|
|
198
|
+
## Current Topic
|
|
199
|
+
|
|
200
|
+
@run(echo "current_task") Current task
|
|
201
|
+
EONOTE
|
|
202
|
+
File.write(File.join(current_dir, 'buildnotes.md'), current_note_with_template)
|
|
203
|
+
|
|
204
|
+
# Set template folder in config
|
|
205
|
+
allow(Howzit.config).to receive(:template_folder).and_return(template_dir)
|
|
206
|
+
end
|
|
207
|
+
|
|
208
|
+
after do
|
|
209
|
+
# Reset template folder
|
|
210
|
+
allow(Howzit.config).to receive(:template_folder).and_call_original
|
|
211
|
+
end
|
|
212
|
+
|
|
213
|
+
it 'sets source_file for template topics' do
|
|
214
|
+
buildnote = Howzit::BuildNote.new
|
|
215
|
+
matches = buildnote.find_topic('Template Topic')
|
|
216
|
+
|
|
217
|
+
# Template topics might not be found if template loading fails
|
|
218
|
+
# Let's check if any topics exist first
|
|
219
|
+
if matches.empty?
|
|
220
|
+
# Try to find by checking all topics
|
|
221
|
+
all_topics = buildnote.topics
|
|
222
|
+
topic = all_topics.find { |t| t.title.include?('Template') }
|
|
223
|
+
else
|
|
224
|
+
topic = matches[0]
|
|
225
|
+
end
|
|
226
|
+
|
|
227
|
+
expect(topic).not_to be_nil, "Template topic not found. Available topics: #{buildnote.topics.map(&:title).join(', ')}"
|
|
228
|
+
expect(File.expand_path(topic.source_file)).to eq(File.expand_path(template_file))
|
|
229
|
+
end
|
|
230
|
+
|
|
231
|
+
it 'sets source_file for template tasks but does not prepare to change directory in stack mode' do
|
|
232
|
+
Howzit.options[:stack] = true
|
|
233
|
+
|
|
234
|
+
buildnote = Howzit::BuildNote.new
|
|
235
|
+
matches = buildnote.find_topic('Template Topic')
|
|
236
|
+
topic = matches.empty? ? buildnote.topics.find { |t| t.title.include?('Template') } : matches[0]
|
|
237
|
+
|
|
238
|
+
skip 'Template topic not found - template loading may have failed' if topic.nil?
|
|
239
|
+
|
|
240
|
+
task = topic.tasks.first
|
|
241
|
+
|
|
242
|
+
# Template tasks should have source_file set to template file
|
|
243
|
+
# Use realpath to handle macOS /var -> /private/var symlink
|
|
244
|
+
expect(File.realpath(task.source_file)).to eq(File.realpath(template_file))
|
|
245
|
+
|
|
246
|
+
# But in stack mode, templates should not change directory (they're detected as templates)
|
|
247
|
+
# The execution logic checks if source_file is in template_folder and skips directory change
|
|
248
|
+
expect(task.source_file).not_to be_nil
|
|
249
|
+
end
|
|
250
|
+
|
|
251
|
+
it 'sets source_file for template tasks but does not prepare to change directory in normal mode' do
|
|
252
|
+
Howzit.options[:stack] = false
|
|
253
|
+
|
|
254
|
+
buildnote = Howzit::BuildNote.new
|
|
255
|
+
matches = buildnote.find_topic('Template Topic')
|
|
256
|
+
topic = matches.empty? ? buildnote.topics.find { |t| t.title.include?('Template') } : matches[0]
|
|
257
|
+
|
|
258
|
+
skip 'Template topic not found - template loading may have failed' if topic.nil?
|
|
259
|
+
|
|
260
|
+
task = topic.tasks.first
|
|
261
|
+
|
|
262
|
+
# In normal mode, no directory changes should occur
|
|
263
|
+
expect(task.source_file).not_to be_nil
|
|
264
|
+
end
|
|
265
|
+
end
|
|
266
|
+
|
|
267
|
+
describe 'block tasks' do
|
|
268
|
+
before do
|
|
269
|
+
# Create a build note with a block task
|
|
270
|
+
block_note = <<~EONOTE
|
|
271
|
+
# Build Note
|
|
272
|
+
|
|
273
|
+
## Block Topic
|
|
274
|
+
|
|
275
|
+
```run
|
|
276
|
+
#!/usr/bin/env ruby
|
|
277
|
+
puts Dir.pwd
|
|
278
|
+
```
|
|
279
|
+
EONOTE
|
|
280
|
+
File.write(File.join(current_dir, 'buildnotes.md'), block_note)
|
|
281
|
+
end
|
|
282
|
+
|
|
283
|
+
context 'with --stack mode and parent directory' do
|
|
284
|
+
before do
|
|
285
|
+
Howzit.options[:stack] = true
|
|
286
|
+
|
|
287
|
+
# Create parent with block task
|
|
288
|
+
parent_block_note = <<~EONOTE
|
|
289
|
+
# Parent Build Note
|
|
290
|
+
|
|
291
|
+
## Parent Block Topic
|
|
292
|
+
|
|
293
|
+
```run
|
|
294
|
+
#!/usr/bin/env ruby
|
|
295
|
+
puts Dir.pwd
|
|
296
|
+
```
|
|
297
|
+
EONOTE
|
|
298
|
+
File.write(File.join(parent_dir, 'buildnotes.md'), parent_block_note)
|
|
299
|
+
end
|
|
300
|
+
|
|
301
|
+
it 'sets source_file for parent directory block tasks and prepares to change directory' do
|
|
302
|
+
buildnote = Howzit::BuildNote.new
|
|
303
|
+
topic = buildnote.find_topic('Parent Block Topic')[0]
|
|
304
|
+
task = topic.tasks.first
|
|
305
|
+
|
|
306
|
+
expected_file = File.expand_path(File.join(parent_dir, 'buildnotes.md'))
|
|
307
|
+
# Use realpath to handle macOS /var -> /private/var symlink
|
|
308
|
+
expect(File.realpath(task.source_file)).to eq(File.realpath(expected_file))
|
|
309
|
+
|
|
310
|
+
# Verify source_file is from parent directory
|
|
311
|
+
source_dir = File.realpath(File.dirname(task.source_file))
|
|
312
|
+
expect(source_dir).to eq(File.realpath(parent_dir))
|
|
313
|
+
|
|
314
|
+
# In stack mode with parent directory, the task should have source_file set
|
|
315
|
+
# and the execution logic should change to that directory
|
|
316
|
+
expect(task.source_file).not_to be_nil
|
|
317
|
+
expect(File.realpath(File.dirname(task.source_file))).not_to eq(File.realpath(current_dir))
|
|
318
|
+
end
|
|
319
|
+
end
|
|
320
|
+
end
|
|
321
|
+
end
|
data/src/_README.md
CHANGED
|
@@ -17,6 +17,8 @@ Howzit is a tool that allows you to keep Markdown-formatted notes about a projec
|
|
|
17
17
|
- Use `@include()` to import another topic's tasks
|
|
18
18
|
- Use fenced code blocks to include/run embedded scripts
|
|
19
19
|
- Scripts can communicate back to Howzit, sending log messages and setting variables
|
|
20
|
+
- Log helper functions output messages immediately with color-coded output (cyan for info, yellow for warn, red for error, dim for debug)
|
|
21
|
+
- Log helper supports `-r`/`--report` flag (or `report` parameter) for delayed output via communication file
|
|
20
22
|
- Conditional blocks (`@if`/`@unless`/`@elsif`/`@else`) for conditionally including content and tasks
|
|
21
23
|
- String comparison operators including fuzzy match (`**=`) for flexible pattern matching
|
|
22
24
|
- File contents conditions to check file contents in conditional blocks
|
|
@@ -25,6 +27,8 @@ Howzit is a tool that allows you to keep Markdown-formatted notes about a projec
|
|
|
25
27
|
- Templates for easily including repeat tasks
|
|
26
28
|
- Grep topics for pattern and choose from matches
|
|
27
29
|
- Use positional and named variables when executing tasks
|
|
30
|
+
- Topic display shows variable definitions with syntax highlighting (blue parentheses, bright white variable names, yellow defaults)
|
|
31
|
+
- Variable placeholders in content are highlighted to show where substitution occurs
|
|
28
32
|
|
|
29
33
|
## Getting Started
|
|
30
34
|
|
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.38
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Brett Terpstra
|
|
@@ -327,6 +327,7 @@ files:
|
|
|
327
327
|
- spec/sequential_conditional_spec.rb
|
|
328
328
|
- spec/set_var_spec.rb
|
|
329
329
|
- spec/spec_helper.rb
|
|
330
|
+
- spec/stack_mode_spec.rb
|
|
330
331
|
- spec/stringutils_spec.rb
|
|
331
332
|
- spec/task_spec.rb
|
|
332
333
|
- spec/topic_spec.rb
|
|
@@ -369,6 +370,7 @@ test_files:
|
|
|
369
370
|
- spec/sequential_conditional_spec.rb
|
|
370
371
|
- spec/set_var_spec.rb
|
|
371
372
|
- spec/spec_helper.rb
|
|
373
|
+
- spec/stack_mode_spec.rb
|
|
372
374
|
- spec/stringutils_spec.rb
|
|
373
375
|
- spec/task_spec.rb
|
|
374
376
|
- spec/topic_spec.rb
|