markdown-run 0.1.11 → 0.2.0
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/.tool-versions +1 -1
- data/CHANGELOG.md +7 -0
- data/README.md +74 -7
- data/Rakefile +64 -0
- data/lib/code_block_helper.rb +15 -7
- data/lib/code_block_parser.rb +11 -4
- data/lib/code_executor.rb +118 -31
- data/lib/dalibo_helper.rb +2 -2
- data/lib/execution_decider.rb +108 -10
- data/lib/flamegraph_helper.rb +39 -0
- data/lib/frontmatter_parser.rb +14 -6
- data/lib/language_configs.rb +30 -22
- data/lib/language_resolver.rb +17 -0
- data/lib/markdown/run/version.rb +1 -1
- data/lib/markdown_file_writer.rb +5 -2
- data/lib/markdown_processor.rb +5 -5
- data/lib/pg_flamegraph_svg.rb +221 -0
- data/lib/postgres_helper.rb +94 -0
- data/lib/result_helper.rb +78 -18
- data/lib/test_silencer.rb +41 -0
- data/markdown-run-sample/markdown-run-sample-flamegraph-20250604-233020-29c2a34ec32b.svg +29 -0
- data/markdown-run-sample.md +9 -0
- metadata +23 -6
data/lib/execution_decider.rb
CHANGED
@@ -3,17 +3,23 @@ require_relative "enum_helper"
|
|
3
3
|
class ExecutionDecider
|
4
4
|
include EnumHelper
|
5
5
|
|
6
|
-
def initialize(current_block_run, current_block_rerun, current_block_lang, current_block_explain = false, current_block_result = true)
|
6
|
+
def initialize(current_block_run, current_block_rerun, current_block_lang, current_block_explain = false, current_block_flamegraph = false, current_block_result = true)
|
7
7
|
@current_block_run = current_block_run
|
8
8
|
@current_block_rerun = current_block_rerun
|
9
9
|
@current_block_lang = current_block_lang
|
10
10
|
@current_block_explain = current_block_explain
|
11
|
+
@current_block_flamegraph = current_block_flamegraph
|
11
12
|
@current_block_result = current_block_result
|
12
13
|
end
|
13
14
|
|
14
|
-
def decide(file_enum, result_block_regex_method)
|
15
|
+
def decide(file_enum, result_block_regex_method, code_content = nil)
|
15
16
|
return skip_execution_run_false if run_disabled?
|
16
17
|
|
18
|
+
# For ruby blocks, check if code content contains xmpfilter results (# >>)
|
19
|
+
if is_ruby_block? && code_content && has_xmpfilter_results?(code_content)
|
20
|
+
return handle_inline_ruby_results
|
21
|
+
end
|
22
|
+
|
17
23
|
expected_header_regex = result_block_regex_method.call(@current_block_lang)
|
18
24
|
peek1 = peek_next_line(file_enum)
|
19
25
|
|
@@ -21,8 +27,10 @@ class ExecutionDecider
|
|
21
27
|
handle_immediate_result_block(file_enum)
|
22
28
|
elsif is_blank_line?(peek1)
|
23
29
|
handle_blank_line_scenario(file_enum, expected_header_regex)
|
24
|
-
elsif @current_block_explain && is_dalibo_link?(peek1)
|
30
|
+
elsif (@current_block_explain || @current_block_flamegraph) && is_dalibo_link?(peek1)
|
25
31
|
handle_immediate_dalibo_link(file_enum)
|
32
|
+
elsif @current_block_flamegraph && is_flamegraph_link?(peek1)
|
33
|
+
handle_immediate_flamegraph_link(file_enum)
|
26
34
|
else
|
27
35
|
execute_without_existing_result
|
28
36
|
end
|
@@ -48,11 +56,11 @@ class ExecutionDecider
|
|
48
56
|
|
49
57
|
def handle_blank_line_scenario(file_enum, expected_header_regex)
|
50
58
|
consumed_blank_line = file_enum.next
|
51
|
-
|
59
|
+
|
52
60
|
# Look ahead past multiple blank lines to find actual content
|
53
61
|
peek2 = peek_next_line(file_enum)
|
54
62
|
additional_blanks = []
|
55
|
-
|
63
|
+
|
56
64
|
# Consume consecutive blank lines
|
57
65
|
while is_blank_line?(peek2)
|
58
66
|
additional_blanks << file_enum.next
|
@@ -61,8 +69,10 @@ class ExecutionDecider
|
|
61
69
|
|
62
70
|
if line_matches_pattern?(peek2, expected_header_regex)
|
63
71
|
handle_result_after_blank_lines(file_enum, consumed_blank_line, additional_blanks)
|
64
|
-
elsif @current_block_explain && is_dalibo_link?(peek2)
|
72
|
+
elsif (@current_block_explain || @current_block_flamegraph) && is_dalibo_link?(peek2)
|
65
73
|
handle_dalibo_after_blank_lines(file_enum, consumed_blank_line, additional_blanks)
|
74
|
+
elsif @current_block_flamegraph && is_flamegraph_link?(peek2)
|
75
|
+
handle_flamegraph_after_blank_lines(file_enum, consumed_blank_line, additional_blanks)
|
66
76
|
else
|
67
77
|
execute_with_blank_lines(consumed_blank_line, additional_blanks)
|
68
78
|
end
|
@@ -194,7 +204,7 @@ class ExecutionDecider
|
|
194
204
|
# Consume all consecutive Dalibo links and blank lines
|
195
205
|
loop do
|
196
206
|
next_line = peek_next_line(file_enum)
|
197
|
-
|
207
|
+
|
198
208
|
if is_blank_line?(next_line) || is_dalibo_link?(next_line)
|
199
209
|
consumed_line = file_enum.next
|
200
210
|
consumed_lines << consumed_line
|
@@ -205,7 +215,7 @@ class ExecutionDecider
|
|
205
215
|
end
|
206
216
|
|
207
217
|
def is_dalibo_link?(line)
|
208
|
-
line&.start_with?("
|
218
|
+
line&.start_with?("[Dalibo]")
|
209
219
|
end
|
210
220
|
|
211
221
|
def line_matches_pattern?(line, pattern)
|
@@ -217,9 +227,97 @@ class ExecutionDecider
|
|
217
227
|
end
|
218
228
|
|
219
229
|
def should_auto_replace_dalibo_link?
|
220
|
-
# Auto-replace Dalibo links when using explain with result=false
|
230
|
+
# Auto-replace Dalibo links when using explain or flamegraph with result=false
|
221
231
|
# This makes sense because with result=false, there's only a Dalibo link,
|
222
232
|
# so it should be updated on each run
|
223
|
-
@current_block_explain && !@current_block_result
|
233
|
+
(@current_block_explain || @current_block_flamegraph) && !@current_block_result
|
234
|
+
end
|
235
|
+
|
236
|
+
def is_flamegraph_link?(line)
|
237
|
+
line&.start_with?("![PostgreSQL Query Flamegraph]")
|
238
|
+
end
|
239
|
+
|
240
|
+
def handle_flamegraph_after_blank_lines(file_enum, consumed_blank_line, additional_blanks)
|
241
|
+
# For flamegraph result=false, always replace existing flamegraph links
|
242
|
+
# For flamegraph result=true, follow normal rerun logic
|
243
|
+
if should_auto_replace_flamegraph_link? || @current_block_rerun
|
244
|
+
execute_with_consumed_flamegraph_and_blanks(file_enum, consumed_blank_line, additional_blanks)
|
245
|
+
else
|
246
|
+
skip_with_blanks_and_flamegraph(file_enum, consumed_blank_line, additional_blanks)
|
247
|
+
end
|
248
|
+
end
|
249
|
+
|
250
|
+
def handle_immediate_flamegraph_link(file_enum)
|
251
|
+
# For flamegraph result=false, always replace existing flamegraph links
|
252
|
+
# For flamegraph result=true, follow normal rerun logic
|
253
|
+
if should_auto_replace_flamegraph_link? || @current_block_rerun
|
254
|
+
execute_with_consumed_flamegraph(file_enum)
|
255
|
+
else
|
256
|
+
skip_with_existing_flamegraph(file_enum)
|
257
|
+
end
|
258
|
+
end
|
259
|
+
|
260
|
+
def execute_with_consumed_flamegraph(file_enum)
|
261
|
+
consumed_lines = []
|
262
|
+
consume_flamegraph_links(file_enum, consumed_lines)
|
263
|
+
{ execute: true, consumed_lines: consumed_lines, consume_existing_flamegraph: true }
|
264
|
+
end
|
265
|
+
|
266
|
+
def skip_with_existing_flamegraph(file_enum)
|
267
|
+
consumed_lines = []
|
268
|
+
consume_flamegraph_links(file_enum, consumed_lines)
|
269
|
+
{ execute: false, lines_to_pass_through: consumed_lines, flamegraph_content: true }
|
270
|
+
end
|
271
|
+
|
272
|
+
def execute_with_consumed_flamegraph_and_blanks(file_enum, consumed_blank_line, additional_blanks)
|
273
|
+
consumed_lines = [consumed_blank_line] + additional_blanks
|
274
|
+
consume_flamegraph_links(file_enum, consumed_lines)
|
275
|
+
{ execute: true, consumed_lines: consumed_lines, blank_line: consumed_blank_line, consume_existing_flamegraph: true }
|
276
|
+
end
|
277
|
+
|
278
|
+
def skip_with_blanks_and_flamegraph(file_enum, consumed_blank_line, additional_blanks)
|
279
|
+
consumed_lines = [consumed_blank_line] + additional_blanks
|
280
|
+
consume_flamegraph_links(file_enum, consumed_lines)
|
281
|
+
{ execute: false, lines_to_pass_through: consumed_lines, flamegraph_content: true }
|
282
|
+
end
|
283
|
+
|
284
|
+
def consume_flamegraph_links(file_enum, consumed_lines)
|
285
|
+
# Consume all consecutive flamegraph links and blank lines
|
286
|
+
loop do
|
287
|
+
next_line = peek_next_line(file_enum)
|
288
|
+
|
289
|
+
if is_blank_line?(next_line) || is_flamegraph_link?(next_line)
|
290
|
+
consumed_line = file_enum.next
|
291
|
+
consumed_lines << consumed_line
|
292
|
+
else
|
293
|
+
break
|
294
|
+
end
|
295
|
+
end
|
296
|
+
end
|
297
|
+
|
298
|
+
def should_auto_replace_flamegraph_link?
|
299
|
+
# Auto-replace flamegraph links when using flamegraph with result=false
|
300
|
+
# This makes sense because with result=false, there's only a flamegraph link,
|
301
|
+
# so it should be updated on each run
|
302
|
+
@current_block_flamegraph && !@current_block_result
|
303
|
+
end
|
304
|
+
|
305
|
+
def is_ruby_block?
|
306
|
+
@current_block_lang == "ruby"
|
307
|
+
end
|
308
|
+
|
309
|
+
def has_xmpfilter_results?(code_content)
|
310
|
+
# Check if code contains xmpfilter comment markers (# >>)
|
311
|
+
code_content.include?("# >>")
|
312
|
+
end
|
313
|
+
|
314
|
+
def handle_inline_ruby_results
|
315
|
+
if @current_block_rerun
|
316
|
+
# Rerun requested, so execute and replace inline results
|
317
|
+
{ execute: true }
|
318
|
+
else
|
319
|
+
# Has inline results and rerun not requested, skip execution
|
320
|
+
{ execute: false, lines_to_pass_through: [] }
|
321
|
+
end
|
224
322
|
end
|
225
323
|
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
module FlamegraphHelper
|
2
|
+
private
|
3
|
+
|
4
|
+
FLAMEGRAPH_LINK_PREFIX = "FLAMEGRAPH_LINK:"
|
5
|
+
|
6
|
+
def extract_flamegraph_link(result_output)
|
7
|
+
# Check if the result contains a flamegraph link marker
|
8
|
+
if result_output.start_with?(FLAMEGRAPH_LINK_PREFIX)
|
9
|
+
lines = result_output.split("\n", 2)
|
10
|
+
flamegraph_path = lines[0].sub(FLAMEGRAPH_LINK_PREFIX, "")
|
11
|
+
clean_result = lines[1] || ""
|
12
|
+
flamegraph_link = ""
|
13
|
+
[flamegraph_link, clean_result]
|
14
|
+
else
|
15
|
+
[nil, result_output]
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def consume_flamegraph_link_if_present(file_enum, consumed_lines)
|
20
|
+
# Look ahead to see if there are flamegraph links after the result block
|
21
|
+
begin
|
22
|
+
# Keep consuming blank lines and flamegraph links until we hit something else
|
23
|
+
loop do
|
24
|
+
next_line = peek_next_line(file_enum)
|
25
|
+
|
26
|
+
if is_blank_line?(next_line)
|
27
|
+
consumed_lines << file_enum.next
|
28
|
+
elsif next_line&.start_with?("![PostgreSQL Query Flamegraph]")
|
29
|
+
consumed_lines << file_enum.next
|
30
|
+
else
|
31
|
+
# Hit something that's not a blank line or flamegraph link, stop consuming
|
32
|
+
break
|
33
|
+
end
|
34
|
+
end
|
35
|
+
rescue StopIteration
|
36
|
+
# End of file reached, nothing more to consume
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
data/lib/frontmatter_parser.rb
CHANGED
@@ -1,16 +1,22 @@
|
|
1
1
|
require "yaml"
|
2
2
|
require_relative "enum_helper"
|
3
|
+
require_relative "language_resolver"
|
4
|
+
require_relative "test_silencer"
|
3
5
|
|
4
6
|
class FrontmatterParser
|
5
7
|
include EnumHelper
|
6
8
|
|
7
|
-
def initialize
|
8
|
-
@
|
9
|
+
def initialize(language_resolver = nil)
|
10
|
+
@language_resolver = language_resolver || LanguageResolver.new
|
9
11
|
@defaults = {}
|
10
12
|
@language_defaults = {}
|
11
13
|
end
|
12
14
|
|
13
|
-
attr_reader :
|
15
|
+
attr_reader :defaults, :language_defaults
|
16
|
+
|
17
|
+
def language_resolver
|
18
|
+
@language_resolver
|
19
|
+
end
|
14
20
|
|
15
21
|
def parse_frontmatter(file_enum, output_lines)
|
16
22
|
first_line = peek_next_line(file_enum)
|
@@ -21,7 +27,7 @@ class FrontmatterParser
|
|
21
27
|
end
|
22
28
|
|
23
29
|
def resolve_language(lang)
|
24
|
-
@
|
30
|
+
@language_resolver.resolve_language(lang)
|
25
31
|
end
|
26
32
|
|
27
33
|
def get_default_value(option_name, language, fallback_default)
|
@@ -72,7 +78,7 @@ class FrontmatterParser
|
|
72
78
|
frontmatter = YAML.safe_load(frontmatter_lines.join)
|
73
79
|
extract_aliases(frontmatter) if frontmatter.is_a?(Hash)
|
74
80
|
rescue YAML::SyntaxError => e
|
75
|
-
|
81
|
+
TestSilencer.warn_unless_testing "Warning: Invalid YAML frontmatter: #{e.message}"
|
76
82
|
end
|
77
83
|
end
|
78
84
|
|
@@ -83,13 +89,15 @@ class FrontmatterParser
|
|
83
89
|
# Extract aliases
|
84
90
|
aliases = markdown_run_config["alias"]
|
85
91
|
if aliases.is_a?(Array)
|
92
|
+
new_aliases = {}
|
86
93
|
aliases.each do |alias_config|
|
87
94
|
next unless alias_config.is_a?(Hash)
|
88
95
|
|
89
96
|
alias_config.each do |alias_name, target_lang|
|
90
|
-
|
97
|
+
new_aliases[alias_name.to_s] = target_lang.to_s
|
91
98
|
end
|
92
99
|
end
|
100
|
+
@language_resolver.update_aliases(new_aliases)
|
93
101
|
end
|
94
102
|
|
95
103
|
# Extract defaults
|
data/lib/language_configs.rb
CHANGED
@@ -1,7 +1,9 @@
|
|
1
|
-
require
|
1
|
+
require "securerandom"
|
2
|
+
require_relative "test_silencer"
|
3
|
+
require_relative "postgres_helper"
|
2
4
|
|
3
5
|
JS_CONFIG = {
|
4
|
-
command:
|
6
|
+
command: proc { |temp_file_path: nil, **|
|
5
7
|
# Check if bun is available
|
6
8
|
bun_exists = system("command -v bun > /dev/null 2>&1")
|
7
9
|
if bun_exists
|
@@ -16,35 +18,41 @@ JS_CONFIG = {
|
|
16
18
|
}.freeze
|
17
19
|
|
18
20
|
SQLITE_CONFIG = {
|
19
|
-
command:
|
21
|
+
command: proc { |code_content: nil, temp_file_path: nil, **| [ "sqlite3 #{temp_file_path}", { stdin_data: code_content } ] },
|
20
22
|
temp_file_suffix: ".db" # Temp file is the database
|
21
23
|
}.freeze
|
22
24
|
|
23
25
|
SUPPORTED_LANGUAGES = {
|
24
26
|
"psql" => {
|
25
|
-
command:
|
26
|
-
|
27
|
-
|
28
|
-
|
27
|
+
command: proc { |code_content: nil, explain: false, flamegraph: false, **|
|
28
|
+
unless PostgresHelper.available?
|
29
|
+
if PostgresHelper.psql_command && PostgresHelper.using_docker?
|
30
|
+
TestSilencer.abort_unless_testing "Error: PostgreSQL is running in Docker but required environment variables (PGUSER, PGDATABASE) are not set. Please set these variables before running psql commands."
|
31
|
+
else
|
32
|
+
TestSilencer.abort_unless_testing "Error: psql command not found. Please install PostgreSQL locally, ensure psql is in your PATH, or run PostgreSQL in a Docker container."
|
33
|
+
end
|
29
34
|
end
|
30
35
|
|
31
|
-
|
32
|
-
|
36
|
+
PostgresHelper.validate_env_vars!
|
37
|
+
psql_cmd = PostgresHelper.psql_command
|
38
|
+
|
39
|
+
# Modify the SQL query if explain or flamegraph option is enabled
|
40
|
+
if explain || flamegraph
|
33
41
|
# Wrap the query with EXPLAIN (ANALYZE, BUFFERS, FORMAT JSON)
|
34
42
|
# Remove any trailing semicolons and whitespace, then add our EXPLAIN wrapper
|
35
|
-
clean_query = code_content.strip.gsub(/;\s*$/,
|
43
|
+
clean_query = code_content.strip.gsub(/;\s*$/, "")
|
36
44
|
explained_query = "EXPLAIN (ANALYZE, BUFFERS, FORMAT JSON) #{clean_query};"
|
37
|
-
[ "
|
45
|
+
[ "#{psql_cmd} -A -t -X", { stdin_data: explained_query } ]
|
38
46
|
else
|
39
|
-
[ "
|
47
|
+
[ "#{psql_cmd} -A -t -X", { stdin_data: code_content } ]
|
40
48
|
end
|
41
49
|
}
|
42
50
|
},
|
43
51
|
"ruby" => {
|
44
|
-
command:
|
52
|
+
command: proc { |temp_file_path: nil, **|
|
45
53
|
xmpfilter_exists = system("command -v xmpfilter > /dev/null 2>&1")
|
46
54
|
unless xmpfilter_exists
|
47
|
-
|
55
|
+
TestSilencer.abort_unless_testing "Error: xmpfilter command not found. Please install xmpfilter or ensure it is in your PATH."
|
48
56
|
end
|
49
57
|
[ "xmpfilter #{temp_file_path}", {} ]
|
50
58
|
},
|
@@ -57,40 +65,40 @@ SUPPORTED_LANGUAGES = {
|
|
57
65
|
"sqlite" => SQLITE_CONFIG,
|
58
66
|
"sqlite3" => SQLITE_CONFIG, # Alias for sqlite
|
59
67
|
"bash" => {
|
60
|
-
command:
|
68
|
+
command: proc { |temp_file_path: nil, **|
|
61
69
|
bash_exists = system("command -v bash > /dev/null 2>&1")
|
62
70
|
unless bash_exists
|
63
|
-
|
71
|
+
TestSilencer.abort_unless_testing "Error: bash command not found. Please ensure bash is in your PATH."
|
64
72
|
end
|
65
73
|
[ "bash #{temp_file_path}", {} ]
|
66
74
|
},
|
67
75
|
temp_file_suffix: ".sh"
|
68
76
|
},
|
69
77
|
"zsh" => {
|
70
|
-
command:
|
78
|
+
command: proc { |temp_file_path: nil, **|
|
71
79
|
zsh_exists = system("command -v zsh > /dev/null 2>&1")
|
72
80
|
unless zsh_exists
|
73
|
-
|
81
|
+
TestSilencer.abort_unless_testing "Error: zsh command not found. Please ensure zsh is in your PATH."
|
74
82
|
end
|
75
83
|
[ "zsh #{temp_file_path}", {} ]
|
76
84
|
},
|
77
85
|
temp_file_suffix: ".zsh"
|
78
86
|
},
|
79
87
|
"sh" => {
|
80
|
-
command:
|
88
|
+
command: proc { |temp_file_path: nil, **|
|
81
89
|
sh_exists = system("command -v sh > /dev/null 2>&1")
|
82
90
|
unless sh_exists
|
83
|
-
|
91
|
+
TestSilencer.abort_unless_testing "Error: sh command not found. Please ensure sh is in your PATH."
|
84
92
|
end
|
85
93
|
[ "sh #{temp_file_path}", {} ]
|
86
94
|
},
|
87
95
|
temp_file_suffix: ".sh"
|
88
96
|
},
|
89
97
|
"mermaid" => {
|
90
|
-
command:
|
98
|
+
command: proc { |temp_file_path: nil, input_file_path: nil, **|
|
91
99
|
mmdc_exists = system("command -v mmdc > /dev/null 2>&1")
|
92
100
|
unless mmdc_exists
|
93
|
-
|
101
|
+
TestSilencer.abort_unless_testing "Error: mmdc command not found. Please install @mermaid-js/mermaid-cli: npm install -g @mermaid-js/mermaid-cli"
|
94
102
|
end
|
95
103
|
|
96
104
|
# Generate SVG output file path with directory structure based on markdown file
|
@@ -0,0 +1,17 @@
|
|
1
|
+
class LanguageResolver
|
2
|
+
def initialize(aliases = {})
|
3
|
+
@aliases = aliases
|
4
|
+
end
|
5
|
+
|
6
|
+
def resolve_language(lang)
|
7
|
+
@aliases[lang] || lang
|
8
|
+
end
|
9
|
+
|
10
|
+
def update_aliases(new_aliases)
|
11
|
+
@aliases.merge!(new_aliases)
|
12
|
+
end
|
13
|
+
|
14
|
+
def get_aliases
|
15
|
+
@aliases.dup
|
16
|
+
end
|
17
|
+
end
|
data/lib/markdown/run/version.rb
CHANGED
data/lib/markdown_file_writer.rb
CHANGED
@@ -1,7 +1,9 @@
|
|
1
1
|
require "tempfile"
|
2
2
|
require "fileutils"
|
3
|
+
require_relative "test_silencer"
|
3
4
|
|
4
5
|
class MarkdownFileWriter
|
6
|
+
|
5
7
|
def self.write_output_to_file(output_lines, input_file_path)
|
6
8
|
temp_dir = File.dirname(File.expand_path(input_file_path))
|
7
9
|
|
@@ -13,13 +15,14 @@ class MarkdownFileWriter
|
|
13
15
|
begin
|
14
16
|
FileUtils.mv(temp_output_file.path, input_file_path)
|
15
17
|
rescue Errno::EACCES, Errno::EXDEV
|
16
|
-
|
18
|
+
TestSilencer.warn_unless_testing "Atomic move failed. Falling back to copy and delete."
|
17
19
|
FileUtils.cp(temp_output_file.path, input_file_path)
|
18
20
|
FileUtils.rm_f(temp_output_file.path)
|
19
21
|
end
|
20
22
|
end
|
21
23
|
|
22
|
-
|
24
|
+
# Only show the warning message if we're not running tests
|
25
|
+
TestSilencer.warn_unless_testing("Markdown processing complete. Output written to #{input_file_path}")
|
23
26
|
true # Indicate success
|
24
27
|
end
|
25
28
|
end
|
data/lib/markdown_processor.rb
CHANGED
@@ -1,4 +1,5 @@
|
|
1
1
|
require_relative "language_configs"
|
2
|
+
require_relative "language_resolver"
|
2
3
|
require_relative "frontmatter_parser"
|
3
4
|
require_relative "code_block_parser"
|
4
5
|
require_relative "code_executor"
|
@@ -20,8 +21,9 @@ class MarkdownProcessor
|
|
20
21
|
@output_lines = []
|
21
22
|
reset_code_block_state
|
22
23
|
|
23
|
-
@
|
24
|
-
@
|
24
|
+
@language_resolver = LanguageResolver.new
|
25
|
+
@frontmatter_parser = FrontmatterParser.new(@language_resolver)
|
26
|
+
@code_block_parser = CodeBlockParser.new(@frontmatter_parser, @language_resolver)
|
25
27
|
end
|
26
28
|
|
27
29
|
def process_file(file_enum)
|
@@ -38,9 +40,7 @@ class MarkdownProcessor
|
|
38
40
|
|
39
41
|
private
|
40
42
|
|
41
|
-
|
42
|
-
@frontmatter_parser.resolve_language(lang)
|
43
|
-
end
|
43
|
+
|
44
44
|
|
45
45
|
def is_block_end?(line)
|
46
46
|
@code_block_parser.is_block_end?(line)
|