markdown-run 0.1.0 → 0.1.1
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/.rubocop.yml +2 -8
- data/CHANGELOG.md +4 -0
- data/README.md +8 -21
- data/exe/markdown-run +125 -103
- data/lib/markdown/run/version.rb +1 -1
- metadata +19 -8
- data/Rakefile +0 -12
- data/lib/markdown/run.rb +0 -10
- data/sig/markdown/run.rbs +0 -6
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: d2e8f65ab44ef28a81d72e8abc661a6f0e2c90a1426024fd6846911f19c2b578
|
4
|
+
data.tar.gz: 31cf8bef8665311e688abc5f3f74f0d1f1fcd5a5230260c354baeb0406f3a5a3
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 53c01a92fd506129d65501a83d4ad5f644828499e78632c22ecf0c5a2c2289665453b8807e875f141f205920f6388e219692ff4f2270a66ca908005b0c91f7c3
|
7
|
+
data.tar.gz: 80aab9de978acc7c80f3c091ee33bad06e483f2aed00f02d42153491db1db2b8ccf440a2ff1b2c174b0c4274272f1c372e5360e9361c4c7736c144fe55f66c80
|
data/.rubocop.yml
CHANGED
data/CHANGELOG.md
CHANGED
data/README.md
CHANGED
@@ -1,39 +1,26 @@
|
|
1
|
-
|
1
|
+
[](https://github.com/aurelienbottazini/markdown-run/actions/workflows/main.yml)
|
2
2
|
|
3
|
-
|
4
|
-
|
5
|
-
Welcome to your new gem! In this directory, you'll find the files you need to be able to package up your Ruby library into a gem. Put your Ruby code in the file `lib/markdown/run`. To experiment with that code, run `bin/console` for an interactive prompt.
|
3
|
+
# Markdown-run
|
6
4
|
|
7
5
|
## Installation
|
8
6
|
|
9
|
-
|
10
|
-
|
11
|
-
Install the gem and add to the application's Gemfile by executing:
|
12
|
-
|
13
|
-
$ bundle add UPDATE_WITH_YOUR_GEM_NAME_IMMEDIATELY_AFTER_RELEASE_TO_RUBYGEMS_ORG
|
14
|
-
|
15
|
-
If bundler is not being used to manage dependencies, install the gem by executing:
|
16
|
-
|
17
|
-
$ gem install UPDATE_WITH_YOUR_GEM_NAME_IMMEDIATELY_AFTER_RELEASE_TO_RUBYGEMS_ORG
|
7
|
+
`gem install markdown-run`
|
18
8
|
|
19
9
|
## Usage
|
20
10
|
|
21
|
-
|
11
|
+
- Run specs `markdown-run`
|
12
|
+
- Run on a markdown file `markdown-run your-filename`
|
22
13
|
|
23
14
|
## Development
|
24
15
|
|
25
|
-
|
26
|
-
|
27
|
-
To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and the created tag, and push the `.gem` file to [rubygems.org](https://rubygems.org).
|
16
|
+
Just run the script with `./exe/markdown-run`
|
28
17
|
|
29
18
|
## Contributing
|
30
19
|
|
31
|
-
Bug reports
|
20
|
+
Bug reports are welcome on GitHub at https://github.com/aurelienbottazini/markdown-run.
|
32
21
|
|
33
22
|
## License
|
34
23
|
|
35
24
|
The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
|
36
25
|
|
37
|
-
##
|
38
|
-
|
39
|
-
Everyone interacting in the Markdown::Run project's codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](https://github.com/aurelienbottazini/markdown-run/blob/main/CODE_OF_CONDUCT.md).
|
26
|
+
## [code of conduct](https://github.com/aurelienbottazini/markdown-run/blob/main/CODE_OF_CONDUCT.md).
|
data/exe/markdown-run
CHANGED
@@ -1,50 +1,74 @@
|
|
1
1
|
#!/usr/bin/env ruby
|
2
2
|
|
3
|
-
require
|
4
|
-
require
|
5
|
-
require
|
3
|
+
require "tempfile"
|
4
|
+
require "open3"
|
5
|
+
require "fileutils"
|
6
6
|
|
7
7
|
# --- Language Execution Configuration ---
|
8
8
|
JS_CONFIG = {
|
9
|
-
command: ->(_code_content, temp_file_path) {
|
10
|
-
|
9
|
+
command: ->(_code_content, temp_file_path) {
|
10
|
+
# Check if bun is available
|
11
|
+
bun_exists = system("command -v bun > /dev/null 2>&1")
|
12
|
+
if bun_exists
|
13
|
+
[ "bun #{temp_file_path}", {} ]
|
14
|
+
else
|
15
|
+
# Fallback to node if bun is not available
|
16
|
+
[ "node #{temp_file_path}", {} ]
|
17
|
+
end
|
18
|
+
},
|
19
|
+
temp_file_suffix: ".js",
|
11
20
|
error_handling: :js_specific # For specific stderr appending on error
|
12
21
|
}.freeze
|
13
22
|
|
14
23
|
SQLITE_CONFIG = {
|
15
24
|
command: ->(code_content, temp_file_path) { [ "sqlite3 #{temp_file_path}", { stdin_data: code_content } ] },
|
16
|
-
temp_file_suffix:
|
25
|
+
temp_file_suffix: ".db" # Temp file is the database
|
17
26
|
}.freeze
|
18
27
|
|
19
28
|
SUPPORTED_LANGUAGES = {
|
20
|
-
|
21
|
-
command: ->(code_content, _temp_file_path) {
|
29
|
+
"psql" => {
|
30
|
+
command: ->(code_content, _temp_file_path) {
|
31
|
+
psql_exists = system("command -v psql > /dev/null 2>&1")
|
32
|
+
unless psql_exists
|
33
|
+
warn "Error: psql command not found. Please install PostgreSQL or ensure psql is in your PATH."
|
34
|
+
exit 1 # Abort the script
|
35
|
+
end
|
36
|
+
[ "psql -A -t -X", { stdin_data: code_content } ]
|
37
|
+
}
|
22
38
|
},
|
23
|
-
|
24
|
-
command: ->(_code_content, temp_file_path) {
|
25
|
-
|
26
|
-
|
39
|
+
"ruby" => {
|
40
|
+
command: ->(_code_content, temp_file_path) {
|
41
|
+
xmpfilter_exists = system("command -v xmpfilter > /dev/null 2>&1")
|
42
|
+
unless xmpfilter_exists
|
43
|
+
warn "Error: xmpfilter command not found. Please install xmpfilter or ensure it is in your PATH."
|
44
|
+
exit 1 # Abort the script
|
45
|
+
end
|
46
|
+
[ "xmpfilter #{temp_file_path}", {} ]
|
47
|
+
},
|
48
|
+
temp_file_suffix: ".rb",
|
49
|
+
result_block_type: "ruby" # For special '```ruby RESULT' blocks
|
27
50
|
},
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
51
|
+
"js" => JS_CONFIG,
|
52
|
+
"javascript" => JS_CONFIG, # Alias for js
|
53
|
+
"sqlite" => SQLITE_CONFIG,
|
54
|
+
"sqlite3" => SQLITE_CONFIG # Alias for sqlite
|
32
55
|
}.freeze
|
33
56
|
|
34
|
-
LANGUAGE_REGEX_PART = SUPPORTED_LANGUAGES.keys.map { |lang| Regexp.escape(lang) }.join(
|
35
|
-
CODE_BLOCK_START_REGEX = /^```(#{LANGUAGE_REGEX_PART})$/i
|
57
|
+
LANGUAGE_REGEX_PART = SUPPORTED_LANGUAGES.keys.map { |lang| Regexp.escape(lang) }.join("|").freeze
|
58
|
+
CODE_BLOCK_START_REGEX = /^```(#{LANGUAGE_REGEX_PART})$/i
|
36
59
|
# --- End Language Execution Configuration ---
|
37
60
|
|
38
61
|
if ARGV.empty?
|
39
|
-
require
|
62
|
+
require "bundler/inline"
|
40
63
|
gemfile(true) do
|
41
|
-
source
|
42
|
-
gem
|
64
|
+
source "https://rubygems.org"
|
65
|
+
gem "minitest" # You can specify a version like '~> 5.10' if needed
|
66
|
+
gem "rcodetools"
|
43
67
|
end
|
44
68
|
|
45
69
|
puts "Running tests..."
|
46
|
-
require
|
47
|
-
require
|
70
|
+
require "minitest/spec"
|
71
|
+
require "minitest/autorun"
|
48
72
|
end
|
49
73
|
|
50
74
|
# Script to process markdown files, execute code blocks based on language,
|
@@ -54,7 +78,9 @@ def execute_code_block(code_content, lang, temp_dir)
|
|
54
78
|
result_output = ""
|
55
79
|
stderr_output = ""
|
56
80
|
exit_status = 0
|
57
|
-
captured_stdout
|
81
|
+
captured_stdout = nil
|
82
|
+
captured_stderr = nil
|
83
|
+
captured_status_obj = nil
|
58
84
|
|
59
85
|
lang_key = lang.downcase # Normalize lang input for lookup
|
60
86
|
lang_config = SUPPORTED_LANGUAGES[lang_key]
|
@@ -69,7 +95,7 @@ def execute_code_block(code_content, lang, temp_dir)
|
|
69
95
|
# It returns [command_string, options_hash_for_open3]
|
70
96
|
|
71
97
|
if temp_file_suffix # Needs a temporary file. Use lang_key as prefix.
|
72
|
-
Tempfile.create([lang_key, temp_file_suffix], temp_dir) do |temp_file|
|
98
|
+
Tempfile.create([ lang_key, temp_file_suffix ], temp_dir) do |temp_file|
|
73
99
|
temp_file.write(code_content)
|
74
100
|
temp_file.close
|
75
101
|
# Pass temp_file.path. Lambda decides if it needs code_content directly.
|
@@ -108,9 +134,7 @@ def execute_code_block(code_content, lang, temp_dir)
|
|
108
134
|
is_js_error_already_formatted = lang_config && lang_config[:error_handling] == :js_specific && result_output.include?("Stderr:")
|
109
135
|
unless result_output.downcase.include?("error:") || is_js_error_already_formatted
|
110
136
|
error_prefix = "Execution failed (status: #{exit_status})."
|
111
|
-
if stderr_output && !stderr_output.strip.empty?
|
112
|
-
error_prefix += " Stderr: #{stderr_output.strip}"
|
113
|
-
end
|
137
|
+
error_prefix += " Stderr: #{stderr_output.strip}" if stderr_output && !stderr_output.strip.empty?
|
114
138
|
result_output = "#{error_prefix}\n#{result_output}"
|
115
139
|
end
|
116
140
|
end
|
@@ -148,7 +172,7 @@ def process_markdown_file_main(input_file_path)
|
|
148
172
|
loop do
|
149
173
|
block_line = file_enum.next
|
150
174
|
output_lines << block_line
|
151
|
-
break if block_line.strip ==
|
175
|
+
break if block_line.strip == "```"
|
152
176
|
end
|
153
177
|
rescue StopIteration
|
154
178
|
warn "Warning: End of file reached inside a '```ruby RESULT' block."
|
@@ -167,94 +191,92 @@ def process_markdown_file_main(input_file_path)
|
|
167
191
|
output_lines << current_line
|
168
192
|
next # Continue to the next line from the file
|
169
193
|
end
|
170
|
-
|
194
|
+
elsif current_line.strip == "```" # We are in_code_block (current_block_lang is set, current_code_content is accumulating)
|
171
195
|
# Case 4: End of the current code block (```)
|
172
|
-
|
173
|
-
|
196
|
+
output_lines << current_line # The closing ``` of the code block
|
197
|
+
|
198
|
+
execute_this_block = true # Assume execution by default
|
199
|
+
lines_to_pass_through_if_skipped = [] # For blank line + RESULT header if skipping
|
200
|
+
blank_line_before_new_result = nil # If a blank line is consumed but block is still executed
|
174
201
|
|
175
|
-
|
176
|
-
|
177
|
-
|
202
|
+
# Peek ahead logic
|
203
|
+
peek1 = nil
|
204
|
+
begin; peek1 = file_enum.peek; rescue StopIteration; end # If EOF, execute_this_block remains true
|
178
205
|
|
179
|
-
|
180
|
-
|
181
|
-
begin; peek1 = file_enum.peek; rescue StopIteration; end # If EOF, execute_this_block remains true
|
206
|
+
current_lang_config = SUPPORTED_LANGUAGES[current_block_lang] # Get config for the current block
|
207
|
+
is_ruby_style_result = current_lang_config && current_lang_config[:result_block_type] == "ruby"
|
182
208
|
|
183
|
-
|
184
|
-
is_ruby_style_result = current_lang_config && current_lang_config[:result_block_type] == 'ruby'
|
209
|
+
expected_header_regex = is_ruby_style_result ? /^```ruby\s+RESULT$/i : /^```RESULT$/i
|
185
210
|
|
186
|
-
|
211
|
+
if peek1 && peek1.match?(expected_header_regex)
|
212
|
+
execute_this_block = false
|
213
|
+
lines_to_pass_through_if_skipped << file_enum.next # Consume RESULT header
|
214
|
+
elsif peek1 && peek1.strip == "" # Blank line detected
|
215
|
+
consumed_blank_line = file_enum.next # Consume the blank line from enum
|
187
216
|
|
188
|
-
|
217
|
+
peek2 = nil
|
218
|
+
begin; peek2 = file_enum.peek; rescue StopIteration; end # EOF after blank line
|
219
|
+
|
220
|
+
if peek2 && peek2.match?(expected_header_regex)
|
189
221
|
execute_this_block = false
|
222
|
+
lines_to_pass_through_if_skipped << consumed_blank_line # Add consumed blank line
|
190
223
|
lines_to_pass_through_if_skipped << file_enum.next # Consume RESULT header
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
if peek2 && peek2.match?(expected_header_regex)
|
198
|
-
execute_this_block = false
|
199
|
-
lines_to_pass_through_if_skipped << consumed_blank_line # Add consumed blank line
|
200
|
-
lines_to_pass_through_if_skipped << file_enum.next # Consume RESULT header
|
201
|
-
else
|
202
|
-
# Blank line, but not followed by a RESULT header (or EOF after blank).
|
203
|
-
# We will execute the block. The consumed_blank_line should be outputted.
|
204
|
-
blank_line_before_new_result = consumed_blank_line
|
205
|
-
# execute_this_block remains true
|
206
|
-
end
|
224
|
+
else
|
225
|
+
# Blank line, but not followed by a RESULT header (or EOF after blank).
|
226
|
+
# We will execute the block. The consumed_blank_line should be outputted.
|
227
|
+
blank_line_before_new_result = consumed_blank_line
|
228
|
+
# execute_this_block remains true
|
207
229
|
end
|
208
|
-
|
209
|
-
|
210
|
-
|
211
|
-
|
212
|
-
|
213
|
-
|
214
|
-
|
215
|
-
|
216
|
-
|
217
|
-
|
218
|
-
|
219
|
-
|
220
|
-
|
221
|
-
|
222
|
-
|
223
|
-
|
224
|
-
|
225
|
-
|
226
|
-
|
227
|
-
|
228
|
-
|
229
|
-
|
230
|
-
|
231
|
-
|
232
|
-
|
233
|
-
|
234
|
-
|
235
|
-
|
236
|
-
|
237
|
-
rescue StopIteration
|
238
|
-
warn "Warning: End of file reached inside a skipped 'RESULT' block."
|
239
|
-
break # Exit main loop
|
230
|
+
end
|
231
|
+
# If peek1 was nil (EOF) or something else not matching, execute_this_block remains true.
|
232
|
+
|
233
|
+
if execute_this_block
|
234
|
+
output_lines << blank_line_before_new_result if blank_line_before_new_result
|
235
|
+
if !current_code_content.strip.empty?
|
236
|
+
warn "Executing #{current_block_lang} code block..."
|
237
|
+
result_output = execute_code_block(current_code_content, current_block_lang, temp_dir)
|
238
|
+
|
239
|
+
output_lines << "\n" if blank_line_before_new_result.nil?
|
240
|
+
output_lines << (is_ruby_style_result ? "```ruby RESULT\n" : "```RESULT\n")
|
241
|
+
output_lines << result_output
|
242
|
+
output_lines << "\n" unless result_output.empty? || result_output.end_with?("\n")
|
243
|
+
output_lines << "```\n\n" # Ensures a blank line after the RESULT block
|
244
|
+
else
|
245
|
+
warn "Skipping empty code block for language '#{current_block_lang}'."
|
246
|
+
end
|
247
|
+
else # Do not execute; a RESULT block follows and was identified
|
248
|
+
lang_specific_result_type = is_ruby_style_result ? "```ruby RESULT" : "```RESULT"
|
249
|
+
warn "Found existing '#{lang_specific_result_type}' block for current #{current_block_lang} block, skipping execution."
|
250
|
+
|
251
|
+
output_lines.concat(lines_to_pass_through_if_skipped) # Add blank line (if any) and RESULT header
|
252
|
+
|
253
|
+
# Consume the rest of the RESULT block content until its closing ```
|
254
|
+
begin
|
255
|
+
loop do
|
256
|
+
result_block_line = file_enum.next
|
257
|
+
output_lines << result_block_line
|
258
|
+
break if result_block_line.strip == "```"
|
240
259
|
end
|
260
|
+
rescue StopIteration
|
261
|
+
warn "Warning: End of file reached inside a skipped 'RESULT' block."
|
262
|
+
break # Exit main loop
|
241
263
|
end
|
264
|
+
end
|
242
265
|
|
243
|
-
|
244
|
-
|
245
|
-
|
266
|
+
in_code_block = false
|
267
|
+
current_code_content = "" # Reset for next block
|
268
|
+
next
|
246
269
|
# Case 5: Line inside an active code block
|
247
|
-
|
248
|
-
|
249
|
-
|
250
|
-
|
251
|
-
end
|
270
|
+
else
|
271
|
+
current_code_content += current_line
|
272
|
+
output_lines << current_line # This line is part of the original code block being recorded
|
273
|
+
next # Continue to the next line from the file
|
252
274
|
end
|
253
275
|
end # loop
|
254
276
|
|
255
277
|
# Write the modified content back to the input file
|
256
|
-
Tempfile.create([
|
257
|
-
temp_output_file.write(output_lines.join(
|
278
|
+
Tempfile.create([ "md_exec_out_", File.extname(input_file_path) ], temp_dir) do |temp_output_file|
|
279
|
+
temp_output_file.write(output_lines.join(""))
|
258
280
|
temp_output_file.close
|
259
281
|
begin
|
260
282
|
FileUtils.mv(temp_output_file.path, input_file_path)
|
@@ -295,6 +317,8 @@ class TestMarkdownExec < Minitest::Test
|
|
295
317
|
end
|
296
318
|
|
297
319
|
def test_psql_block_execution
|
320
|
+
skip "Skipping test_psql_block_execution on GitHub CI" if ENV['CI']
|
321
|
+
|
298
322
|
md_content = <<~MARKDOWN
|
299
323
|
```psql
|
300
324
|
SELECT 'hello psql test';
|
@@ -364,6 +388,4 @@ class TestMarkdownExec < Minitest::Test
|
|
364
388
|
end
|
365
389
|
end
|
366
390
|
|
367
|
-
unless ARGV.empty?
|
368
|
-
process_markdown_file_main(ARGV[0])
|
369
|
-
end
|
391
|
+
process_markdown_file_main(ARGV[0]) unless ARGV.empty?
|
data/lib/markdown/run/version.rb
CHANGED
metadata
CHANGED
@@ -1,15 +1,29 @@
|
|
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.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Aurélien Bottazini
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2025-05-
|
12
|
-
dependencies:
|
11
|
+
date: 2025-05-14 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: rcodetools
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ">="
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '0'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ">="
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '0'
|
13
27
|
description: Run code blocks in Markdown files. Store output in a code block following
|
14
28
|
the original code block.
|
15
29
|
email:
|
@@ -24,16 +38,13 @@ files:
|
|
24
38
|
- CODE_OF_CONDUCT.md
|
25
39
|
- LICENSE.txt
|
26
40
|
- README.md
|
27
|
-
- Rakefile
|
28
41
|
- exe/markdown-run
|
29
|
-
- lib/markdown/run.rb
|
30
42
|
- lib/markdown/run/version.rb
|
31
|
-
|
32
|
-
homepage: https://github.com/aurelienbottazini/markdown-run
|
43
|
+
homepage: https://rubygems.org/gems/markdown-run
|
33
44
|
licenses:
|
34
45
|
- MIT
|
35
46
|
metadata:
|
36
|
-
homepage_uri: https://
|
47
|
+
homepage_uri: https://rubygems.org/gems/markdown-run
|
37
48
|
source_code_uri: https://github.com/aurelienbottazini/markdown-run
|
38
49
|
changelog_uri: https://github.com/aurelienbottazini/markdown-run/blob/main/CHANGELOG.md
|
39
50
|
post_install_message:
|
data/Rakefile
DELETED
data/lib/markdown/run.rb
DELETED