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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: ac1a715b89896b392f75d2222bda1a4f5899dda9da7a12871548faee8d3c86bb
4
- data.tar.gz: 5fd7954991346dd940594b264d5b162a91451ed7c89e7510eb6b0315d2c39195
3
+ metadata.gz: 921255b90c33af932d6ac33284a24d4f0fc61b5fe3ec0afb45f912c8fcc6e678
4
+ data.tar.gz: 076f40ed6dc79bafbc7bdba3198f39fabf56754d74e1ef6bd38f451cc34ccafd
5
5
  SHA512:
6
- metadata.gz: 1ee917248f47a0ccc7770fdd419fe3d65dfe2ca99c79d44efe0b46a9fd599761e0c218e622c39989c806de07f92480c0de1e4c7c869d7d9f7db2439a9fb70fd0
7
- data.tar.gz: 2c3eeb35fee738e405c30bdbb5fee6e8cad6bb0acc599c1115ad77315179e28dd37c6aace1b125c5306d9702b2cbc4c2dec1394695c9908a73853917f73a1fa3
6
+ metadata.gz: 01c7168af16a7788b80acb43553f2ae81ee0a8d1dfe7bf7cf048a6c7a77a898257fb1308b277282d9494464189322eab67b164a8c694f0aa8437db8bc8654d36
7
+ data.tar.gz: 4f719733a2fbea191224f8c0fbcbb44f559d2d9bec1352f9d6c6caf851550593eb3622b2453cfdd584513d3b186f085157cf17cde5dfe61706f60a6d8b2e8050
data/CHANGELOG.md CHANGED
@@ -1,5 +1,13 @@
1
1
  ## [Unreleased]
2
2
 
3
+ ## [0.1.8] - 2025-06-01
4
+
5
+ - Added run option
6
+
7
+ ## [0.1.7] - 2025-06-01
8
+
9
+ - Added rerun functionality
10
+
3
11
  ## [0.1.6] - 2025-06-01
4
12
 
5
13
  - Refactor code to state pattern
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+)$/i))
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
- return { execute: false, lines_to_pass_through: [file_enum.next] }
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
- return { execute: false, lines_to_pass_through: [consumed_blank_line, file_enum.next] }
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
 
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Markdown
4
4
  module Run
5
- VERSION = "0.1.6"
5
+ VERSION = "0.1.8"
6
6
  end
7
7
  end
@@ -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.6
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://rubygems.org/gems/markdown-run
60
+ homepage: https://github.com/aurelienbottazini/markdown-run
61
61
  licenses:
62
62
  - MIT
63
63
  metadata:
64
- homepage_uri: https://rubygems.org/gems/markdown-run
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: