markdown-run 0.1.6 → 0.1.8
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 +8 -0
- data/README.md +31 -0
- data/exe/markdown-run +71 -5
- data/lib/markdown/run/version.rb +1 -1
- data/test_markdown_exec.rb +163 -0
- metadata +3 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 921255b90c33af932d6ac33284a24d4f0fc61b5fe3ec0afb45f912c8fcc6e678
|
4
|
+
data.tar.gz: 076f40ed6dc79bafbc7bdba3198f39fabf56754d74e1ef6bd38f451cc34ccafd
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 01c7168af16a7788b80acb43553f2ae81ee0a8d1dfe7bf7cf048a6c7a77a898257fb1308b277282d9494464189322eab67b164a8c694f0aa8437db8bc8654d36
|
7
|
+
data.tar.gz: 4f719733a2fbea191224f8c0fbcbb44f559d2d9bec1352f9d6c6caf851550593eb3622b2453cfdd584513d3b186f085157cf17cde5dfe61706f60a6d8b2e8050
|
data/CHANGELOG.md
CHANGED
data/README.md
CHANGED
@@ -8,6 +8,15 @@ Do not rerun code blocks if result block is present.
|
|
8
8
|
|
9
9
|
Meant to be used from the terminal or from an editor with a keybinding.
|
10
10
|
|
11
|
+
Supported languages:
|
12
|
+
|
13
|
+
- Javascript
|
14
|
+
- Ruby
|
15
|
+
- sqlite3
|
16
|
+
- postgresql
|
17
|
+
- bash
|
18
|
+
- zsh
|
19
|
+
|
11
20
|
## Installation
|
12
21
|
|
13
22
|
`gem install markdown-run`
|
@@ -43,9 +52,31 @@ example vscode keybinding
|
|
43
52
|
},
|
44
53
|
```
|
45
54
|
|
55
|
+
### Code block options
|
56
|
+
|
57
|
+
- `run=true` or `run=false` to control whether a code block should be executed at all. `run=true` is the default if not specified
|
58
|
+
- `rerun=true` or `rerun=false` to control whether a code block should be re-executed if a result block already exists. `rerun=false` is the default if not specified
|
59
|
+
|
60
|
+
Options can be combined. If `run=false` is specified, the code block will not execute regardless of the `rerun` setting.
|
61
|
+
|
62
|
+
Examples:
|
63
|
+
|
64
|
+
```js run=false
|
65
|
+
console.log("This will not execute at all");
|
66
|
+
```
|
67
|
+
|
68
|
+
```js rerun=true
|
69
|
+
console.log("This will re-execute even if result exists");
|
70
|
+
```
|
71
|
+
|
72
|
+
```js run=true rerun=false
|
73
|
+
console.log("This will execute only if no result exists");
|
74
|
+
```
|
75
|
+
|
46
76
|
## Frontmatter
|
47
77
|
|
48
78
|
You can add a yaml frontmatter to redefine code block behavior.
|
79
|
+
|
49
80
|
For example sql blocks run by default against sqlite
|
50
81
|
To have them run with postgres you can add at the top of your markdown file:
|
51
82
|
|
data/exe/markdown-run
CHANGED
@@ -88,6 +88,8 @@ class MarkdownProcessor
|
|
88
88
|
@state = :outside_code_block
|
89
89
|
@current_block_lang = ""
|
90
90
|
@current_code_content = ""
|
91
|
+
@current_block_rerun = false
|
92
|
+
@current_block_run = true
|
91
93
|
@aliases = {}
|
92
94
|
end
|
93
95
|
|
@@ -193,6 +195,26 @@ class MarkdownProcessor
|
|
193
195
|
line && line.strip == ""
|
194
196
|
end
|
195
197
|
|
198
|
+
def parse_rerun_option(options_string)
|
199
|
+
return false unless options_string
|
200
|
+
|
201
|
+
# Match rerun=true or rerun=false
|
202
|
+
match = options_string.match(/rerun\s*=\s*(true|false)/i)
|
203
|
+
return false unless match
|
204
|
+
|
205
|
+
match[1].downcase == "true"
|
206
|
+
end
|
207
|
+
|
208
|
+
def parse_run_option(options_string)
|
209
|
+
return true unless options_string
|
210
|
+
|
211
|
+
# Match run=true or run=false
|
212
|
+
match = options_string.match(/run\s*=\s*(true|false)/i)
|
213
|
+
return true unless match
|
214
|
+
|
215
|
+
match[1].downcase == "true"
|
216
|
+
end
|
217
|
+
|
196
218
|
def safe_enum_operation(file_enum, operation)
|
197
219
|
file_enum.send(operation)
|
198
220
|
rescue StopIteration
|
@@ -221,11 +243,12 @@ class MarkdownProcessor
|
|
221
243
|
def handle_outside_code_block(current_line, file_enum)
|
222
244
|
if current_line.match?(/^```ruby\s+RESULT$/i)
|
223
245
|
handle_existing_ruby_result_block(current_line, file_enum)
|
224
|
-
elsif (match_data = current_line.match(/^```(\w+)
|
246
|
+
elsif (match_data = current_line.match(/^```(\w+)(?:\s+(.*))?$/i))
|
225
247
|
lang = match_data[1].downcase
|
248
|
+
options_string = match_data[2]
|
226
249
|
resolved_lang = resolve_language(lang)
|
227
250
|
if SUPPORTED_LANGUAGES.key?(resolved_lang)
|
228
|
-
start_code_block(current_line, lang)
|
251
|
+
start_code_block(current_line, lang, options_string)
|
229
252
|
else
|
230
253
|
@output_lines << current_line
|
231
254
|
end
|
@@ -255,9 +278,11 @@ class MarkdownProcessor
|
|
255
278
|
@state = :inside_result_block
|
256
279
|
end
|
257
280
|
|
258
|
-
def start_code_block(current_line, lang)
|
281
|
+
def start_code_block(current_line, lang, options_string = nil)
|
259
282
|
@output_lines << current_line
|
260
283
|
@current_block_lang = resolve_language(lang)
|
284
|
+
@current_block_rerun = parse_rerun_option(options_string)
|
285
|
+
@current_block_run = parse_run_option(options_string)
|
261
286
|
@state = :inside_code_block
|
262
287
|
@current_code_content = ""
|
263
288
|
end
|
@@ -273,6 +298,7 @@ class MarkdownProcessor
|
|
273
298
|
decision = decide_execution(file_enum)
|
274
299
|
|
275
300
|
if decision[:execute]
|
301
|
+
# If we consumed lines for rerun, don't add them to output (they'll be replaced)
|
276
302
|
execute_and_add_result(decision[:blank_line])
|
277
303
|
else
|
278
304
|
skip_and_pass_through_result(decision[:lines_to_pass_through], file_enum)
|
@@ -282,17 +308,37 @@ class MarkdownProcessor
|
|
282
308
|
end
|
283
309
|
|
284
310
|
def decide_execution(file_enum)
|
311
|
+
# If run=false, skip execution entirely (no result block creation)
|
312
|
+
unless @current_block_run
|
313
|
+
return { execute: false, lines_to_pass_through: [] }
|
314
|
+
end
|
315
|
+
|
285
316
|
peek1 = peek_next_line(file_enum)
|
286
317
|
expected_header_regex = result_block_regex(@current_block_lang)
|
287
318
|
|
288
319
|
if line_matches_pattern?(peek1, expected_header_regex)
|
289
|
-
|
320
|
+
# If rerun=true, execute even if result block exists
|
321
|
+
if @current_block_rerun
|
322
|
+
# Consume the existing result block and execute
|
323
|
+
consumed_lines = [file_enum.next]
|
324
|
+
consume_existing_result_block(file_enum, consumed_lines)
|
325
|
+
return { execute: true, consumed_lines: consumed_lines }
|
326
|
+
else
|
327
|
+
return { execute: false, lines_to_pass_through: [file_enum.next] }
|
328
|
+
end
|
290
329
|
elsif is_blank_line?(peek1)
|
291
330
|
consumed_blank_line = file_enum.next
|
292
331
|
peek2 = peek_next_line(file_enum)
|
293
332
|
|
294
333
|
if line_matches_pattern?(peek2, expected_header_regex)
|
295
|
-
|
334
|
+
if @current_block_rerun
|
335
|
+
# Consume the blank line and existing result block, then execute
|
336
|
+
consumed_lines = [consumed_blank_line, file_enum.next]
|
337
|
+
consume_existing_result_block(file_enum, consumed_lines)
|
338
|
+
return { execute: true, consumed_lines: consumed_lines, blank_line: consumed_blank_line }
|
339
|
+
else
|
340
|
+
return { execute: false, lines_to_pass_through: [consumed_blank_line, file_enum.next] }
|
341
|
+
end
|
296
342
|
else
|
297
343
|
return { execute: true, blank_line: consumed_blank_line }
|
298
344
|
end
|
@@ -313,6 +359,12 @@ class MarkdownProcessor
|
|
313
359
|
end
|
314
360
|
|
315
361
|
def skip_and_pass_through_result(lines_to_pass_through, file_enum)
|
362
|
+
# Handle run=false case where there are no lines to pass through
|
363
|
+
if lines_to_pass_through.empty?
|
364
|
+
warn "Skipping execution due to run=false option."
|
365
|
+
return
|
366
|
+
end
|
367
|
+
|
316
368
|
lang_specific_result_type = ruby_style_result?(@current_block_lang) ? "```ruby RESULT" : "```RESULT"
|
317
369
|
|
318
370
|
warn "Found existing '#{lang_specific_result_type}' block for current #{@current_block_lang} block, skipping execution."
|
@@ -334,9 +386,23 @@ class MarkdownProcessor
|
|
334
386
|
end
|
335
387
|
end
|
336
388
|
|
389
|
+
def consume_existing_result_block(file_enum, consumed_lines)
|
390
|
+
begin
|
391
|
+
loop do
|
392
|
+
result_block_line = file_enum.next
|
393
|
+
consumed_lines << result_block_line
|
394
|
+
break if is_block_end?(result_block_line)
|
395
|
+
end
|
396
|
+
rescue StopIteration
|
397
|
+
warn "Warning: End of file reached while consuming existing result block for rerun."
|
398
|
+
end
|
399
|
+
end
|
400
|
+
|
337
401
|
def reset_code_block_state
|
338
402
|
@state = :outside_code_block
|
339
403
|
@current_code_content = ""
|
404
|
+
@current_block_rerun = false
|
405
|
+
@current_block_run = true
|
340
406
|
end
|
341
407
|
end
|
342
408
|
|
data/lib/markdown/run/version.rb
CHANGED
data/test_markdown_exec.rb
CHANGED
@@ -10,6 +10,9 @@ require "minitest/autorun"
|
|
10
10
|
require "fileutils"
|
11
11
|
require "tmpdir"
|
12
12
|
|
13
|
+
# Load the main script to access process_markdown_file_main
|
14
|
+
load File.expand_path("./exe/markdown-run", __dir__)
|
15
|
+
|
13
16
|
# --- Minitest Test Class Definition ---
|
14
17
|
class TestMarkdownExec < Minitest::Test
|
15
18
|
def setup
|
@@ -131,4 +134,164 @@ class TestMarkdownExec < Minitest::Test
|
|
131
134
|
assert file_content.include?("```RESULT\n"), "RESULT block should be created for aliased language"
|
132
135
|
assert file_content.include?("aliased to psql"), "Output should contain the expected result"
|
133
136
|
end
|
137
|
+
|
138
|
+
def test_rerun_functionality
|
139
|
+
# Test 1: Default behavior (no rerun option) should skip existing result
|
140
|
+
md_content_with_result = <<~MARKDOWN
|
141
|
+
```ruby
|
142
|
+
puts "Should not change: \#{Time.now.to_i}"
|
143
|
+
```
|
144
|
+
|
145
|
+
```ruby RESULT
|
146
|
+
Should not change: 999999999
|
147
|
+
```
|
148
|
+
MARKDOWN
|
149
|
+
create_md_file(md_content_with_result)
|
150
|
+
process_markdown_file_main(@test_md_file_path)
|
151
|
+
|
152
|
+
file_content = read_md_file
|
153
|
+
assert file_content.include?("Should not change: 999999999"), "Default behavior should preserve existing result"
|
154
|
+
refute file_content.match?(/Should not change: (?!999999999)\d+/), "Default behavior should not generate new timestamp"
|
155
|
+
|
156
|
+
# Test 2: rerun=false should skip existing result
|
157
|
+
md_content_rerun_false = <<~MARKDOWN
|
158
|
+
```ruby rerun=false
|
159
|
+
puts "Should not change either: \#{Time.now.to_i}"
|
160
|
+
```
|
161
|
+
|
162
|
+
```ruby RESULT
|
163
|
+
Should not change either: 888888888
|
164
|
+
```
|
165
|
+
MARKDOWN
|
166
|
+
create_md_file(md_content_rerun_false)
|
167
|
+
process_markdown_file_main(@test_md_file_path)
|
168
|
+
|
169
|
+
file_content = read_md_file
|
170
|
+
assert file_content.include?("Should not change either: 888888888"), "rerun=false should preserve existing result"
|
171
|
+
refute file_content.match?(/Should not change either: (?!888888888)\d+/), "rerun=false should not generate new timestamp"
|
172
|
+
|
173
|
+
# Test 3: rerun=true should replace existing result
|
174
|
+
md_content_rerun_true = <<~MARKDOWN
|
175
|
+
```ruby rerun=true
|
176
|
+
puts "Should change: \#{Time.now.to_i}"
|
177
|
+
```
|
178
|
+
|
179
|
+
```ruby RESULT
|
180
|
+
Should change: 777777777
|
181
|
+
```
|
182
|
+
MARKDOWN
|
183
|
+
create_md_file(md_content_rerun_true)
|
184
|
+
process_markdown_file_main(@test_md_file_path)
|
185
|
+
|
186
|
+
file_content = read_md_file
|
187
|
+
refute file_content.include?("Should change: 777777777"), "rerun=true should replace existing result"
|
188
|
+
assert file_content.match?(/Should change: \d+/), "rerun=true should generate new result with actual timestamp"
|
189
|
+
|
190
|
+
# Test 4: rerun=true with blank line before result block
|
191
|
+
md_content_rerun_true_blank = <<~MARKDOWN
|
192
|
+
```ruby rerun=true
|
193
|
+
puts "Should also change: \#{Time.now.to_i}"
|
194
|
+
```
|
195
|
+
|
196
|
+
```ruby RESULT
|
197
|
+
Should also change: 666666666
|
198
|
+
```
|
199
|
+
MARKDOWN
|
200
|
+
create_md_file(md_content_rerun_true_blank)
|
201
|
+
process_markdown_file_main(@test_md_file_path)
|
202
|
+
|
203
|
+
file_content = read_md_file
|
204
|
+
refute file_content.include?("Should also change: 666666666"), "rerun=true with blank line should replace existing result"
|
205
|
+
assert file_content.match?(/Should also change: \d+/), "rerun=true with blank line should generate new result"
|
206
|
+
end
|
207
|
+
|
208
|
+
def test_run_functionality
|
209
|
+
# Test 1: Default behavior (run=true implicit) should execute new code block
|
210
|
+
md_content_default = <<~MARKDOWN
|
211
|
+
```ruby
|
212
|
+
puts "Should execute by default"
|
213
|
+
```
|
214
|
+
MARKDOWN
|
215
|
+
create_md_file(md_content_default)
|
216
|
+
process_markdown_file_main(@test_md_file_path)
|
217
|
+
|
218
|
+
file_content = read_md_file
|
219
|
+
assert file_content.include?("```ruby RESULT"), "Default behavior should create result block"
|
220
|
+
assert file_content.include?("Should execute by default"), "Default behavior should execute and show output"
|
221
|
+
|
222
|
+
# Test 2: run=true explicit should execute new code block
|
223
|
+
md_content_run_true = <<~MARKDOWN
|
224
|
+
```ruby run=true
|
225
|
+
puts "Should execute with run=true"
|
226
|
+
```
|
227
|
+
MARKDOWN
|
228
|
+
create_md_file(md_content_run_true)
|
229
|
+
process_markdown_file_main(@test_md_file_path)
|
230
|
+
|
231
|
+
file_content = read_md_file
|
232
|
+
assert file_content.include?("```ruby RESULT"), "run=true should create result block"
|
233
|
+
assert file_content.include?("Should execute with run=true"), "run=true should execute and show output"
|
234
|
+
|
235
|
+
# Test 3: run=false should not execute at all (no result block created)
|
236
|
+
md_content_run_false = <<~MARKDOWN
|
237
|
+
```ruby run=false
|
238
|
+
puts "Should not execute"
|
239
|
+
puts "No result block should be created"
|
240
|
+
```
|
241
|
+
MARKDOWN
|
242
|
+
create_md_file(md_content_run_false)
|
243
|
+
process_markdown_file_main(@test_md_file_path)
|
244
|
+
|
245
|
+
file_content = read_md_file
|
246
|
+
refute file_content.include?("```ruby RESULT"), "run=false should not create result block"
|
247
|
+
refute file_content.match?(/puts "Should not execute"\n# >>/), "run=false should not execute code (no # >> output)"
|
248
|
+
assert file_content.include?("puts \"Should not execute\""), "run=false should preserve original code block"
|
249
|
+
|
250
|
+
# Test 4: run=false with existing result block should skip execution but preserve result
|
251
|
+
md_content_run_false_with_result = <<~MARKDOWN
|
252
|
+
```ruby run=false
|
253
|
+
puts "Should not execute"
|
254
|
+
```
|
255
|
+
|
256
|
+
```ruby RESULT
|
257
|
+
Old result that should be preserved
|
258
|
+
```
|
259
|
+
MARKDOWN
|
260
|
+
create_md_file(md_content_run_false_with_result)
|
261
|
+
process_markdown_file_main(@test_md_file_path)
|
262
|
+
|
263
|
+
file_content = read_md_file
|
264
|
+
assert file_content.include?("Old result that should be preserved"), "run=false should preserve existing result"
|
265
|
+
refute file_content.match?(/puts "Should not execute"\n# >>/), "run=false should not create new execution output"
|
266
|
+
|
267
|
+
# Test 5: Combined options - run=false with rerun=true should still not execute
|
268
|
+
md_content_combined = <<~MARKDOWN
|
269
|
+
```ruby run=false rerun=true
|
270
|
+
puts "Should not execute despite rerun=true"
|
271
|
+
```
|
272
|
+
|
273
|
+
```ruby RESULT
|
274
|
+
Existing result
|
275
|
+
```
|
276
|
+
MARKDOWN
|
277
|
+
create_md_file(md_content_combined)
|
278
|
+
process_markdown_file_main(@test_md_file_path)
|
279
|
+
|
280
|
+
file_content = read_md_file
|
281
|
+
assert file_content.include?("Existing result"), "run=false should override rerun=true"
|
282
|
+
refute file_content.match?(/puts "Should not execute despite rerun=true"\n# >>/), "run=false should prevent execution even with rerun=true"
|
283
|
+
|
284
|
+
# Test 6: Combined options - run=true with rerun=false should execute if no result exists
|
285
|
+
md_content_run_true_rerun_false = <<~MARKDOWN
|
286
|
+
```ruby run=true rerun=false
|
287
|
+
puts "Should execute because no result exists"
|
288
|
+
```
|
289
|
+
MARKDOWN
|
290
|
+
create_md_file(md_content_run_true_rerun_false)
|
291
|
+
process_markdown_file_main(@test_md_file_path)
|
292
|
+
|
293
|
+
file_content = read_md_file
|
294
|
+
assert file_content.include?("```ruby RESULT"), "run=true rerun=false should execute when no result exists"
|
295
|
+
assert file_content.include?("Should execute because no result exists"), "run=true rerun=false should show output when no result exists"
|
296
|
+
end
|
134
297
|
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: markdown-run
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1.
|
4
|
+
version: 0.1.8
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Aurélien Bottazini
|
@@ -57,11 +57,11 @@ files:
|
|
57
57
|
- lib/markdown/run/version.rb
|
58
58
|
- markdown-run-sample.md
|
59
59
|
- test_markdown_exec.rb
|
60
|
-
homepage: https://
|
60
|
+
homepage: https://github.com/aurelienbottazini/markdown-run
|
61
61
|
licenses:
|
62
62
|
- MIT
|
63
63
|
metadata:
|
64
|
-
homepage_uri: https://
|
64
|
+
homepage_uri: https://github.com/aurelienbottazini/markdown-run
|
65
65
|
source_code_uri: https://github.com/aurelienbottazini/markdown-run
|
66
66
|
changelog_uri: https://github.com/aurelienbottazini/markdown-run/blob/main/CHANGELOG.md
|
67
67
|
post_install_message:
|