markdown-run 0.1.12 → 0.2.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 2764390f72c90c38bd4862f78db857bd5585d13e59f1a2c87a3a9c67c2f66529
4
- data.tar.gz: 95caace78ee0db7d12ac3a6a7a5a9abcddf32c1e946ff3d9cf71beed12c89a91
3
+ metadata.gz: 9118fa7e0d2838fe0201d314199cd8df7c1be394a962c0f15971a4cf8489875b
4
+ data.tar.gz: 8cb4c27814f03fb2afaba2b52c148df7c917e7764ed6de82ab8d9c63744c55f5
5
5
  SHA512:
6
- metadata.gz: 90aea8dd4f03bc75969643bedd914ed2a8a8a1b45adf777d51fdfabb60deefd5e36cd1b73ac37225848b4663e12872a69c2ba4c3624029ac4c81c333a8eb6a8c
7
- data.tar.gz: 7dfc12cda3abca94774e013904e2d9853713dce9c8b4059d6ca6bd90319ddff4f9f0d826c230d508f2438fdce57626f7868c1b94c9756ec3a122e0645be2121c
6
+ metadata.gz: a8221dae4563d208936a0b0f3783f40ec83a08ff5daf49159106af90f38130790031e692ecdea812685ee51bd17c67639d8c584a2c28e2ab5975359fdfea94db
7
+ data.tar.gz: eb2874f8cb94acc7cee3a735119bfe85163e2f378aea120353597393566bf4038100f71e3fec0bd93f08c09bac85db79299c398b8e713286fe4ff3853546638b
@@ -0,0 +1,19 @@
1
+ {
2
+ "permissions": {
3
+ "allow": [
4
+ "Bash(ruby -e:*)",
5
+ "Bash(ruby -I lib -r markdown_run -e:*)",
6
+ "Bash(flog:*)",
7
+ "Bash(rake test:*)",
8
+ "Bash(bin/markdown-run:*)",
9
+ "Bash(exe/markdown-run:*)",
10
+ "Bash(cat:*)",
11
+ "Bash(for i in {1..3})",
12
+ "Bash(do echo \"Run $i:\" exe/markdown-run markdown-run-sample.md)",
13
+ "Bash(gem list:*)",
14
+ "Bash(rake:*)"
15
+ ],
16
+ "deny": [],
17
+ "ask": []
18
+ }
19
+ }
data/.tool-versions CHANGED
@@ -1 +1 @@
1
- ruby 3.3.5
1
+ ruby 3.4.6
data/CHANGELOG.md CHANGED
@@ -1,5 +1,13 @@
1
1
  ## [Unreleased]
2
2
 
3
+ ## [0.2.1]
4
+
5
+ - fix extra blank line with ruby blocks
6
+
7
+ ## [0.2.0]
8
+
9
+ - Refactor block options to be proper markdown
10
+
3
11
  ## [0.1.12] - 2025-06-04
4
12
 
5
13
  - Added `flamegraph` option for psql code blocks to generate PostgreSQL query execution plan flamegraphs as SVG images
data/README.md CHANGED
@@ -55,6 +55,16 @@ example vscode keybinding
55
55
 
56
56
  ### Code block options
57
57
 
58
+ Options are specified using Pandoc-style curly braces after the language identifier:
59
+
60
+ ```markdown
61
+ ```ruby {rerun=true}
62
+ puts "Hello World"
63
+ ```
64
+ ```
65
+
66
+ **Available options:**
67
+
58
68
  - `run=true` or `run=false` to control whether a code block should be executed at all. `run=true` is the default if not specified
59
69
  - `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
60
70
  - `result=true` or `result=false` to control whether the result block should be displayed after execution. `result=true` is the default if not specified. When `result=false`, the code still executes but the result block is hidden
@@ -77,31 +87,31 @@ Explicit assignments (e.g., `run=false`) take precedence over standalone options
77
87
 
78
88
  Examples:
79
89
 
80
- ```js run=false
90
+ ```js {run=false}
81
91
  console.log("This will not execute at all");
82
92
  ```
83
93
 
84
- ```js rerun
94
+ ```js {rerun}
85
95
  console.log("This will re-execute even if result exists");
86
96
  ```
87
97
 
88
- ```js run=true rerun=false
98
+ ```js {run=true rerun=false}
89
99
  console.log("This will execute only if no result exists");
90
100
  ```
91
101
 
92
- ```ruby result=false run
102
+ ```ruby {result=false run}
93
103
  puts "This executes but the result block is hidden"
94
104
  ```
95
105
 
96
- ```psql explain
106
+ ```psql {explain}
97
107
  SELECT * FROM users WHERE id = 1;
98
108
  ```
99
109
 
100
- ```psql explain=true
110
+ ```psql {explain=true}
101
111
  EXPLAIN (ANALYZE) SELECT * FROM large_table;
102
112
  ```
103
113
 
104
- ```psql flamegraph
114
+ ```psql {flamegraph}
105
115
  SELECT u.name, COUNT(o.id) as order_count
106
116
  FROM users u
107
117
  LEFT JOIN orders o ON u.id = o.user_id
@@ -111,12 +121,12 @@ ORDER BY order_count DESC
111
121
  LIMIT 10;
112
122
  ```
113
123
 
114
- ```psql flamegraph=true result=false
124
+ ```psql {flamegraph=true result=false}
115
125
  -- This will generate a flamegraph but hide the JSON result block
116
126
  SELECT * FROM complex_query_with_joins;
117
127
  ```
118
128
 
119
- ```psql result=false explain
129
+ ```psql {result=false explain}
120
130
  SELECT * FROM large_table;
121
131
  -- This will execute the explain query and show the Dalibo link but hide the result block
122
132
  ```
@@ -125,7 +135,7 @@ SELECT * FROM large_table;
125
135
 
126
136
  PostgreSQL flamegraph blocks generate interactive SVG flamegraphs from query execution plans:
127
137
 
128
- ```psql flamegraph
138
+ ```psql {flamegraph}
129
139
  SELECT users.*, orders.total
130
140
  FROM users
131
141
  JOIN orders ON users.id = orders.user_id
data/Rakefile CHANGED
@@ -1,3 +1,4 @@
1
+ require "bundler/gem_tasks"
1
2
  require "rake/testtask"
2
3
 
3
4
  Rake::TestTask.new(:test) do |t|
@@ -19,6 +20,44 @@ task :flog_detailed do
19
20
  system("flog -d lib/ exe/ test/")
20
21
  end
21
22
 
23
+ desc "Run tests with detailed timing information"
24
+ task :test_profile do
25
+ require 'benchmark'
26
+
27
+ puts "Running tests with detailed profiling..."
28
+
29
+ # Run tests with verbose output and capture timing
30
+ output = `bundle exec rake test TESTOPTS="-v" 2>&1`
31
+
32
+ # Extract test timings
33
+ timings = []
34
+ output.scan(/^(.+) = ([0-9]+\.[0-9]+) s = \.$/) do |test_name, time|
35
+ timings << [test_name, time.to_f]
36
+ end
37
+
38
+ # Sort by time (descending)
39
+ timings.sort_by! { |_, time| -time }
40
+
41
+ puts "\n" + "="*80
42
+ puts "TOP 15 SLOWEST TESTS"
43
+ puts "="*80
44
+
45
+ timings.first(15).each_with_index do |(test_name, time), index|
46
+ test_display = test_name.length > 60 ? test_name[0...60] + "..." : test_name
47
+ printf "%2d. %-63s %6.2f s\n", index + 1, test_display, time
48
+ end
49
+
50
+ puts "\n" + "="*80
51
+ puts "SUMMARY"
52
+ puts "="*80
53
+ total_time = timings.sum { |_, time| time }
54
+ slow_tests = timings.select { |_, time| time > 0.1 }
55
+
56
+ puts "Total test time: #{total_time.round(2)} seconds"
57
+ puts "Tests slower than 0.1s: #{slow_tests.count}"
58
+ puts "Time spent in slow tests: #{slow_tests.sum { |_, time| time }.round(2)} seconds (#{((slow_tests.sum { |_, time| time } / total_time) * 100).round(1)}%)"
59
+ end
60
+
22
61
  desc "Release"
23
62
  task :release do
24
63
  `gem bump`
@@ -29,4 +68,19 @@ task :release do
29
68
  `gem release`
30
69
  end
31
70
 
71
+ # Coverage task
72
+ desc "Run tests with SimpleCov coverage report"
73
+ task :coverage do
74
+ ENV['COVERAGE'] = 'true'
75
+ Rake::Task[:test].invoke
76
+
77
+ puts "\n🎯 Coverage Report Generated!"
78
+ puts "📊 Open coverage/index.html to view detailed coverage report"
79
+
80
+ # Try to open coverage report automatically (works on macOS)
81
+ if RUBY_PLATFORM.include?('darwin')
82
+ system('open coverage/index.html')
83
+ end
84
+ end
85
+
32
86
  task default: :test
@@ -17,7 +17,7 @@ module CodeBlockHelper
17
17
 
18
18
  def start_code_block(current_line, lang, options_string = nil)
19
19
  @output_lines << current_line
20
- @current_block_lang = resolve_language(lang)
20
+ @current_block_lang = @language_resolver.resolve_language(lang)
21
21
  @current_block_rerun = @code_block_parser.parse_rerun_option(options_string, @current_block_lang)
22
22
  @current_block_run = @code_block_parser.parse_run_option(options_string, @current_block_lang)
23
23
  @current_block_explain = @code_block_parser.parse_explain_option(options_string, @current_block_lang)
@@ -29,19 +29,25 @@ module CodeBlockHelper
29
29
 
30
30
  def accumulate_code_content(current_line)
31
31
  @current_code_content += current_line
32
- @output_lines << current_line
32
+ # For ruby blocks, don't output code content yet - we'll replace it with xmpfilter output
33
+ unless ruby_style_result?(@current_block_lang)
34
+ @output_lines << current_line
35
+ end
33
36
  end
34
37
 
35
38
  def end_code_block(current_line, file_enum)
36
- @output_lines << current_line
39
+ # For ruby blocks, don't output closing ``` yet - we'll add it after the xmpfilter output
40
+ unless ruby_style_result?(@current_block_lang)
41
+ @output_lines << current_line
42
+ end
37
43
 
38
44
  decision = decide_execution(file_enum)
39
45
 
40
46
  if decision[:execute]
41
47
  # If we consumed lines for rerun, don't add them to output (they'll be replaced)
42
- execute_and_add_result(decision[:blank_line])
48
+ execute_and_add_result(decision[:blank_line], current_line)
43
49
  else
44
- skip_and_pass_through_result(decision[:lines_to_pass_through], file_enum, decision)
50
+ skip_and_pass_through_result(decision[:lines_to_pass_through], file_enum, decision, current_line)
45
51
  end
46
52
 
47
53
  reset_code_block_state
@@ -49,7 +55,7 @@ module CodeBlockHelper
49
55
 
50
56
  def decide_execution(file_enum)
51
57
  decider = ExecutionDecider.new(@current_block_run, @current_block_rerun, @current_block_lang, @current_block_explain, @current_block_flamegraph, @current_block_result)
52
- decision = decider.decide(file_enum, method(:result_block_regex))
58
+ decision = decider.decide(file_enum, method(:result_block_regex), @current_code_content)
53
59
 
54
60
  # Handle the consume_existing flag for rerun scenarios
55
61
  if decision[:consume_existing]
@@ -1,13 +1,15 @@
1
1
  require_relative "language_configs"
2
2
 
3
3
  class CodeBlockParser
4
- # Code block header pattern: ```language options
5
- CODE_BLOCK_START_PATTERN = /^```(\w+)(?:\s+(.*))?$/i
4
+ # Code block header pattern: ```language {options}
5
+ # Supports both ```language and ```language {options} formats
6
+ CODE_BLOCK_START_PATTERN = /^```(\w+)(?:\s*\{(.*)\})?$/i
6
7
  RUBY_RESULT_BLOCK_PATTERN = /^```ruby\s+RESULT$/i
7
8
  BLOCK_END_PATTERN = "```"
8
9
 
9
- def initialize(frontmatter_parser)
10
+ def initialize(frontmatter_parser, language_resolver)
10
11
  @frontmatter_parser = frontmatter_parser
12
+ @language_resolver = language_resolver
11
13
  end
12
14
 
13
15
  def parse_code_block_header(line)
@@ -62,7 +64,7 @@ class CodeBlockParser
62
64
  private
63
65
 
64
66
  def resolve_language(lang)
65
- @frontmatter_parser.resolve_language(lang)
67
+ @language_resolver.resolve_language(lang)
66
68
  end
67
69
 
68
70
  def parse_boolean_option(options_string, option_name, default_value)
data/lib/code_executor.rb CHANGED
@@ -1,6 +1,7 @@
1
1
  require "tempfile"
2
2
  require "open3"
3
3
  require_relative "language_configs"
4
+ require_relative "test_silencer"
4
5
 
5
6
  class CodeExecutor
6
7
  def self.execute(code_content, lang, temp_dir, input_file_path = nil, explain = false, flamegraph = false)
@@ -13,7 +14,7 @@ class CodeExecutor
13
14
 
14
15
  return handle_unsupported_language(lang) unless lang_config
15
16
 
16
- warn "Executing #{lang_key} code block..."
17
+ TestSilencer.warn_unless_testing("Executing #{lang_key} code block...")
17
18
 
18
19
  result = execute_with_config(code_content, lang_config, temp_dir, lang_key, input_file_path, explain, flamegraph)
19
20
  process_execution_result(result, lang_config, lang_key, explain, flamegraph)
@@ -26,7 +27,7 @@ class CodeExecutor
26
27
  end
27
28
 
28
29
  def handle_unsupported_language(lang)
29
- warn "Unsupported language: #{lang}"
30
+ TestSilencer.warn_unless_testing "Unsupported language: #{lang}"
30
31
  "ERROR: Unsupported language: #{lang}"
31
32
  end
32
33
 
@@ -46,7 +47,13 @@ class CodeExecutor
46
47
  Tempfile.create([lang_key, temp_file_suffix], temp_dir) do |temp_file|
47
48
  temp_file.write(code_content)
48
49
  temp_file.close
49
- command_to_run, exec_options = cmd_lambda.call(code_content, temp_file.path, input_file_path, explain, flamegraph)
50
+ command_to_run, exec_options = cmd_lambda.call(**{
51
+ code_content: code_content,
52
+ temp_file_path: temp_file.path,
53
+ input_file_path: input_file_path,
54
+ explain: explain,
55
+ flamegraph: flamegraph
56
+ })
50
57
 
51
58
  # Extract output_path if present (for mermaid)
52
59
  output_path = exec_options.delete(:output_path) if exec_options.is_a?(Hash)
@@ -64,7 +71,13 @@ class CodeExecutor
64
71
  end
65
72
 
66
73
  def execute_direct_command(code_content, cmd_lambda, input_file_path = nil, explain = false, flamegraph = false)
67
- command_to_run, exec_options = cmd_lambda.call(code_content, nil, input_file_path, explain, flamegraph)
74
+ command_to_run, exec_options = cmd_lambda.call(**{
75
+ code_content: code_content,
76
+ temp_file_path: nil,
77
+ input_file_path: input_file_path,
78
+ explain: explain,
79
+ flamegraph: flamegraph
80
+ })
68
81
  captured_stdout, captured_stderr, captured_status_obj = Open3.capture3(command_to_run, **exec_options)
69
82
  { stdout: captured_stdout, stderr: captured_stderr, status: captured_status_obj, input_file_path: input_file_path }
70
83
  end
@@ -104,8 +117,8 @@ class CodeExecutor
104
117
  end
105
118
 
106
119
  def add_error_to_output(exit_status, lang_config, lang_key, result_output, stderr_output)
107
- warn "Code execution failed for language '#{lang_key}' with status #{exit_status}."
108
- warn "Stderr:\n#{stderr_output}" if stderr_has_content?(stderr_output)
120
+ TestSilencer.warn_unless_testing "Code execution failed for language '#{lang_key}' with status #{exit_status}."
121
+ TestSilencer.warn_unless_testing "Stderr:\n#{stderr_output}" if stderr_has_content?(stderr_output)
109
122
 
110
123
  is_js_error_already_formatted = lang_config && lang_config[:error_handling] == :js_specific && result_output.include?("Stderr:")
111
124
  unless result_output.downcase.include?("error:") || is_js_error_already_formatted
@@ -116,15 +129,11 @@ class CodeExecutor
116
129
  result_output
117
130
  end
118
131
 
119
- def stderr_has_content?(stderr_output)
120
- stderr_output && !stderr_output.strip.empty?
121
- end
122
-
123
132
  def handle_mermaid_svg_result(result, lang_key)
124
133
  output_path = result[:output_path]
125
134
 
126
135
  unless output_path && File.exist?(output_path)
127
- warn "Warning: Mermaid SVG file not generated at expected path: #{output_path}"
136
+ TestSilencer.warn_unless_testing "Warning: Mermaid SVG file not generated at expected path: #{output_path}"
128
137
  return "Error: SVG file not generated"
129
138
  end
130
139
 
@@ -143,7 +152,7 @@ class CodeExecutor
143
152
  relative_path = svg_filename
144
153
  end
145
154
 
146
- warn "Generated Mermaid SVG: #{relative_path}"
155
+ TestSilencer.warn_unless_testing("Generated Mermaid SVG: #{relative_path}")
147
156
 
148
157
  # Return markdown image tag instead of typical result content
149
158
  "![Mermaid Diagram](#{relative_path})"
@@ -225,7 +234,7 @@ class CodeExecutor
225
234
  relative_path = File.basename(output_path)
226
235
  end
227
236
 
228
- warn "Generated PostgreSQL flamegraph: #{relative_path}"
237
+ TestSilencer.warn_unless_testing("Generated PostgreSQL flamegraph: #{relative_path}")
229
238
 
230
239
  # Return a special format that the markdown processor can parse
231
240
  # Preserve any existing Dalibo link prefix
@@ -239,10 +248,10 @@ class CodeExecutor
239
248
  end
240
249
 
241
250
  rescue JSON::ParserError => e
242
- warn "Error parsing EXPLAIN JSON: #{e.message}"
251
+ TestSilencer.warn_unless_testing "Error parsing EXPLAIN JSON: #{e.message}"
243
252
  result_output
244
253
  rescue => e
245
- warn "Error generating flamegraph: #{e.message}"
254
+ TestSilencer.warn_unless_testing "Error generating flamegraph: #{e.message}"
246
255
  result_output
247
256
  end
248
257
  end
@@ -279,11 +288,11 @@ class CodeExecutor
279
288
  nil
280
289
  end
281
290
  else
282
- warn "Failed to submit plan to Dalibo: #{response.code} #{response.message}"
291
+ TestSilencer.warn_unless_testing "Failed to submit plan to Dalibo: #{response.code} #{response.message}"
283
292
  nil
284
293
  end
285
294
  rescue => e
286
- warn "Error submitting plan to Dalibo: #{e.message}"
295
+ TestSilencer.warn_unless_testing "Error submitting plan to Dalibo: #{e.message}"
287
296
  nil
288
297
  end
289
298
  end
@@ -12,9 +12,14 @@ class ExecutionDecider
12
12
  @current_block_result = current_block_result
13
13
  end
14
14
 
15
- def decide(file_enum, result_block_regex_method)
15
+ def decide(file_enum, result_block_regex_method, code_content = nil)
16
16
  return skip_execution_run_false if run_disabled?
17
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
+
18
23
  expected_header_regex = result_block_regex_method.call(@current_block_lang)
19
24
  peek1 = peek_next_line(file_enum)
20
25
 
@@ -296,4 +301,23 @@ class ExecutionDecider
296
301
  # so it should be updated on each run
297
302
  @current_block_flamegraph && !@current_block_result
298
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
322
+ end
299
323
  end
@@ -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
- @aliases = {}
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 :aliases, :defaults, :language_defaults
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
- @aliases[lang] || lang
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
- warn "Warning: Invalid YAML frontmatter: #{e.message}"
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
- @aliases[alias_name.to_s] = target_lang.to_s
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
@@ -1,7 +1,9 @@
1
- require 'securerandom'
1
+ require "securerandom"
2
+ require_relative "test_silencer"
3
+ require_relative "postgres_helper"
2
4
 
3
5
  JS_CONFIG = {
4
- command: ->(_code_content, temp_file_path, input_file_path = nil, explain = false, flamegraph = false) {
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: ->(code_content, temp_file_path, input_file_path = nil, explain = false, flamegraph = false) { [ "sqlite3 #{temp_file_path}", { stdin_data: code_content } ] },
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: ->(code_content, _temp_file_path, input_file_path = nil, explain = false, flamegraph = false) {
26
- psql_exists = system("command -v psql > /dev/null 2>&1")
27
- unless psql_exists
28
- abort "Error: psql command not found. Please install PostgreSQL or ensure psql is in your PATH."
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
 
36
+ PostgresHelper.validate_env_vars!
37
+ psql_cmd = PostgresHelper.psql_command
38
+
31
39
  # Modify the SQL query if explain or flamegraph option is enabled
32
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
- [ "psql -A -t -X", { stdin_data: explained_query } ]
45
+ [ "#{psql_cmd} -A -t -X", { stdin_data: explained_query } ]
38
46
  else
39
- [ "psql -A -t -X", { stdin_data: code_content } ]
47
+ [ "#{psql_cmd} -A -t -X", { stdin_data: code_content } ]
40
48
  end
41
49
  }
42
50
  },
43
51
  "ruby" => {
44
- command: ->(_code_content, temp_file_path, input_file_path = nil, explain = false, flamegraph = false) {
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
- abort "Error: xmpfilter command not found. Please install xmpfilter or ensure it is in your PATH."
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: ->(_code_content, temp_file_path, input_file_path = nil, explain = false, flamegraph = false) {
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
- abort "Error: bash command not found. Please ensure bash is in your PATH."
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: ->(_code_content, temp_file_path, input_file_path = nil, explain = false, flamegraph = false) {
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
- abort "Error: zsh command not found. Please ensure zsh is in your PATH."
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: ->(_code_content, temp_file_path, input_file_path = nil, explain = false, flamegraph = false) {
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
- abort "Error: sh command not found. Please ensure sh is in your PATH."
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: ->(code_content, temp_file_path, input_file_path = nil, explain = false, flamegraph = false) {
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
- abort "Error: mmdc command not found. Please install @mermaid-js/mermaid-cli: npm install -g @mermaid-js/mermaid-cli"
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
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Markdown
4
4
  module Run
5
- VERSION = "0.1.12"
5
+ VERSION = "0.2.1"
6
6
  end
7
7
  end
@@ -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
- warn "Atomic move failed. Falling back to copy and delete."
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
- warn "Markdown processing complete. Output written to #{input_file_path}"
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
@@ -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
- @frontmatter_parser = FrontmatterParser.new
24
- @code_block_parser = CodeBlockParser.new(@frontmatter_parser)
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
- def resolve_language(lang)
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)
@@ -0,0 +1,94 @@
1
+ require_relative "test_silencer"
2
+
3
+ module PostgresHelper
4
+ class << self
5
+ def psql_command
6
+ @psql_command ||= detect_psql_command
7
+ end
8
+
9
+ def available?
10
+ return false if psql_command.nil?
11
+
12
+ # If using Docker, check required env vars are present
13
+ if using_docker?
14
+ has_required_env_vars?
15
+ else
16
+ true
17
+ end
18
+ end
19
+
20
+ def using_docker?
21
+ cmd = psql_command
22
+ cmd && cmd.start_with?("docker exec")
23
+ end
24
+
25
+ # Reset cached command detection (for tests)
26
+ def reset_cache!
27
+ @psql_command = nil
28
+ end
29
+
30
+ def validate_env_vars!
31
+ return unless using_docker?
32
+
33
+ required_vars = ["PGUSER", "PGDATABASE"]
34
+ missing_vars = required_vars.reject { |var| ENV[var] }
35
+
36
+ if missing_vars.any?
37
+ TestSilencer.abort_unless_testing "Error: PostgreSQL is running in Docker but required environment variables are missing: #{missing_vars.join(", ")}. Please set these variables before running psql commands."
38
+ end
39
+ end
40
+
41
+ private
42
+
43
+ def has_required_env_vars?
44
+ required_vars = ["PGUSER", "PGDATABASE"]
45
+ required_vars.all? { |var| ENV[var] }
46
+ end
47
+
48
+ def detect_psql_command
49
+ # Check if psql is available locally
50
+ return "psql" if system("command -v psql > /dev/null 2>&1")
51
+
52
+ # Check if docker is available
53
+ return nil unless system("command -v docker > /dev/null 2>&1")
54
+
55
+ # Find running postgres container
56
+ container_id = find_postgres_container
57
+ return nil if container_id.nil? || container_id.empty?
58
+
59
+ # Build docker exec command with environment variables passed through
60
+ env_vars = build_env_vars
61
+ "docker exec -i -u postgres #{env_vars}#{container_id} psql"
62
+ end
63
+
64
+ def build_env_vars
65
+ # Pass through PostgreSQL environment variables if they exist
66
+ pg_env_vars = [ "PGUSER", "PGPASSWORD", "PGDATABASE", "PGHOST", "PGPORT" ]
67
+ env_flags = pg_env_vars.map do |var|
68
+ value = ENV[var]
69
+ "-e #{var}=\"#{value}\" " if value
70
+ end.compact.join
71
+ env_flags.empty? ? "" : "#{env_flags} "
72
+ end
73
+
74
+ def find_postgres_container
75
+ # Try to find a running postgres container
76
+ # First, check for container with postgres in the name
77
+ output = `docker ps --filter "ancestor=postgres" --format "{{.ID}}" 2>/dev/null`.strip
78
+ return output.split("\n").first unless output.empty?
79
+
80
+ # Alternative: check for containers with "postgres" in name
81
+ output = `docker ps --filter "name=postgres" --format "{{.ID}}" 2>/dev/null`.strip
82
+ return output.split("\n").first unless output.empty?
83
+
84
+ # Last resort: check if any running container has psql command
85
+ containers = `docker ps --format "{{.ID}}" 2>/dev/null`.strip.split("\n")
86
+ containers.each do |container_id|
87
+ has_psql = system("docker exec #{container_id} which psql > /dev/null 2>&1")
88
+ return container_id if has_psql
89
+ end
90
+
91
+ nil
92
+ end
93
+ end
94
+ end
data/lib/result_helper.rb CHANGED
@@ -1,4 +1,5 @@
1
1
  require_relative "flamegraph_helper"
2
+ require_relative "test_silencer"
2
3
 
3
4
  module ResultHelper
4
5
  include FlamegraphHelper
@@ -16,7 +17,7 @@ module ResultHelper
16
17
  end
17
18
 
18
19
  def result_block_header(lang)
19
- ruby_style_result?(lang) ? "```ruby RESULT\n" : "```RESULT\n"
20
+ ruby_style_result?(lang) ? "```ruby\n" : "``` {result}\n"
20
21
  end
21
22
 
22
23
  def result_block_regex(lang)
@@ -24,14 +25,21 @@ module ResultHelper
24
25
  # For mermaid, look for existing image tags with .svg extension
25
26
  /^!\[.*\]\(.*\.svg\)$/i
26
27
  elsif ruby_style_result?(lang)
28
+ # For ruby, check for old-style ```ruby RESULT blocks (for backward compatibility during migration)
27
29
  /^```ruby\s+RESULT$/i
28
30
  else
29
- /^```RESULT$/i
31
+ /^```\s*\{result\}$/i
30
32
  end
31
33
  end
32
34
 
33
- def add_result_block(result_output, blank_line_before_new_result)
34
- if mermaid_style_result?(@current_block_lang)
35
+ def add_result_block(result_output, blank_line_before_new_result, closing_line = nil)
36
+ if ruby_style_result?(@current_block_lang)
37
+ # For ruby, replace the code block content with xmpfilter output
38
+ @output_lines << result_output
39
+ @output_lines << "\n" unless result_output.empty? || result_output.end_with?("\n")
40
+ @output_lines << (closing_line || "```\n")
41
+ @output_lines << "\n"
42
+ elsif mermaid_style_result?(@current_block_lang)
35
43
  # For mermaid, add the image tag directly without a result block
36
44
  @output_lines << "\n" if blank_line_before_new_result.nil?
37
45
  @output_lines << result_output
@@ -55,16 +63,17 @@ module ResultHelper
55
63
  end
56
64
 
57
65
  def handle_existing_ruby_result_block(current_line, file_enum)
58
- warn "Found existing '```ruby RESULT' block, passing through."
66
+ TestSilencer.warn_unless_testing("Found existing '```ruby RESULT' or '```ruby' result block, passing through.")
59
67
  @output_lines << current_line
60
68
  @state = :inside_result_block
61
69
  end
62
70
 
63
71
 
64
- def execute_and_add_result(blank_line_before_new_result)
65
- warn "Skipping empty code block for language '#{@current_block_lang}'." && return unless has_content?(@current_code_content)
72
+ def execute_and_add_result(blank_line_before_new_result, closing_line = nil)
73
+ TestSilencer.warn_unless_testing("Skipping empty code block for language '#{@current_block_lang}'.") && return unless has_content?(@current_code_content)
66
74
 
67
- @output_lines << blank_line_before_new_result if blank_line_before_new_result
75
+ # For ruby blocks, don't add blank line since we're replacing the code block inline, not adding a RESULT block
76
+ @output_lines << blank_line_before_new_result if blank_line_before_new_result && !ruby_style_result?(@current_block_lang)
68
77
 
69
78
  result_output = CodeExecutor.execute(@current_code_content, @current_block_lang, @temp_dir, @input_file_path, @current_block_explain, @current_block_flamegraph)
70
79
 
@@ -76,7 +85,12 @@ module ResultHelper
76
85
 
77
86
  # Add the result block only if result=true (default)
78
87
  if @current_block_result
79
- add_result_block(clean_result || result_after_dalibo, blank_line_before_new_result)
88
+ add_result_block(clean_result || result_after_dalibo, blank_line_before_new_result, closing_line)
89
+ elsif ruby_style_result?(@current_block_lang)
90
+ # For ruby blocks with result=false, output the original code content without xmpfilter results
91
+ @output_lines << @current_code_content
92
+ @output_lines << (closing_line || "```\n")
93
+ @output_lines << "\n"
80
94
  end
81
95
 
82
96
  # Always add Dalibo link if it exists, even when result=false
@@ -100,16 +114,21 @@ module ResultHelper
100
114
  end
101
115
  end
102
116
 
103
- def skip_and_pass_through_result(lines_to_pass_through, file_enum, decision = nil)
117
+ def skip_and_pass_through_result(lines_to_pass_through, file_enum, decision = nil, closing_line = nil)
104
118
  # Handle run=false case where there are no lines to pass through
105
119
  if lines_to_pass_through.empty?
106
- warn "Skipping execution due to run=false option."
120
+ TestSilencer.warn_unless_testing("Skipping execution due to run=false option.")
121
+ # For ruby blocks, we still need to output the code content and closing line
122
+ if ruby_style_result?(@current_block_lang)
123
+ @output_lines << @current_code_content
124
+ @output_lines << (closing_line || "```\n")
125
+ end
107
126
  return
108
127
  end
109
128
 
110
129
  # Check if this is Dalibo content
111
130
  if decision && decision[:dalibo_content]
112
- warn "Found existing Dalibo link for current #{@current_block_lang} block, skipping execution."
131
+ TestSilencer.warn_unless_testing("Found existing Dalibo link for current #{@current_block_lang} block, skipping execution.")
113
132
  @output_lines.concat(lines_to_pass_through)
114
133
  # No additional consumption needed for Dalibo links
115
134
  return
@@ -117,19 +136,27 @@ module ResultHelper
117
136
 
118
137
  # Check if this is flamegraph content
119
138
  if decision && decision[:flamegraph_content]
120
- warn "Found existing flamegraph for current #{@current_block_lang} block, skipping execution."
139
+ TestSilencer.warn_unless_testing("Found existing flamegraph for current #{@current_block_lang} block, skipping execution.")
121
140
  @output_lines.concat(lines_to_pass_through)
122
141
  # No additional consumption needed for flamegraph links
123
142
  return
124
143
  end
125
144
 
126
145
  if mermaid_style_result?(@current_block_lang)
127
- warn "Found existing mermaid SVG image for current #{@current_block_lang} block, skipping execution."
146
+ TestSilencer.warn_unless_testing("Found existing mermaid SVG image for current #{@current_block_lang} block, skipping execution.")
128
147
  @output_lines.concat(lines_to_pass_through)
129
148
  # For mermaid, no additional consumption needed since it's just an image line
149
+ elsif ruby_style_result?(@current_block_lang)
150
+ # For ruby blocks with existing old-style RESULT blocks, discard them and output inline results
151
+ TestSilencer.warn_unless_testing("Found existing ruby result for current #{@current_block_lang} block, skipping execution.")
152
+ @output_lines << @current_code_content
153
+ @output_lines << (closing_line || "```\n")
154
+ @output_lines << "\n"
155
+ # Always consume and discard the old-style RESULT block
156
+ consume_and_discard_result_block(file_enum)
130
157
  else
131
- lang_specific_result_type = ruby_style_result?(@current_block_lang) ? "```ruby RESULT" : "```RESULT"
132
- warn "Found existing '#{lang_specific_result_type}' block for current #{@current_block_lang} block, skipping execution."
158
+ lang_specific_result_type = "``` {result}"
159
+ TestSilencer.warn_unless_testing("Found existing '#{lang_specific_result_type}' block for current #{@current_block_lang} block, skipping execution.")
133
160
  @output_lines.concat(lines_to_pass_through)
134
161
  consume_result_block_content(file_enum)
135
162
  end
@@ -141,6 +168,12 @@ module ResultHelper
141
168
  end
142
169
  end
143
170
 
171
+ def consume_and_discard_result_block(file_enum)
172
+ consume_block_lines(file_enum) do |line|
173
+ # Discard the line - don't add to output
174
+ end
175
+ end
176
+
144
177
  def consume_existing_result_block(file_enum, consumed_lines)
145
178
  return if mermaid_style_result?(@current_block_lang)
146
179
 
@@ -161,7 +194,7 @@ module ResultHelper
161
194
  break if is_block_end?(result_block_line)
162
195
  end
163
196
  rescue StopIteration
164
- warn "Warning: End of file reached while consuming result block."
197
+ TestSilencer.warn_unless_testing "Warning: End of file reached while consuming result block."
165
198
  end
166
199
  end
167
200
  end
@@ -0,0 +1,41 @@
1
+ # Module to provide test-aware warning functionality
2
+ module TestSilencer
3
+ # Helper method to detect if we're running in a test environment
4
+ def self.running_tests?
5
+ # Check for common test environment indicators
6
+ @running_tests ||= begin
7
+ caller.any? { |line| line.include?('/test/') || line.include?('minitest') || line.include?('rspec') } ||
8
+ defined?(Minitest) ||
9
+ ENV['RAILS_ENV'] == 'test' ||
10
+ ENV['RACK_ENV'] == 'test' ||
11
+ ($PROGRAM_NAME.include?('rake') && ARGV.include?('test')) ||
12
+ $PROGRAM_NAME.include?('rake_test_loader')
13
+ end
14
+ end
15
+
16
+ # Generic warn method that silences output during tests
17
+ def self.warn_unless_testing(message)
18
+ warn message unless running_tests?
19
+ end
20
+
21
+ # Silently abort during tests by raising SystemExit without printing the message
22
+ def self.abort_unless_testing(message)
23
+ if running_tests?
24
+ # During tests, raise SystemExit without printing the error message
25
+ raise SystemExit.new(1)
26
+ else
27
+ # In production, use normal abort which prints the message and exits
28
+ abort message
29
+ end
30
+ end
31
+
32
+ # Suppress Ruby warnings during tests
33
+ def self.setup_warning_suppression
34
+ if running_tests?
35
+ # Temporarily reduce verbosity during tests
36
+ original_verbose = $VERBOSE
37
+ $VERBOSE = nil
38
+ at_exit { $VERBOSE = original_verbose }
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,29 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <svg width="1200" height="104" xmlns="http://www.w3.org/2000/svg">
3
+ <style>
4
+ .frame { stroke: white; stroke-width: 1; cursor: pointer; }
5
+ .frame:hover { stroke: black; stroke-width: 2; }
6
+ .frame-text { font-family: monospace; font-size: 20px; fill: white; pointer-events: none; }
7
+ .title { font-family: Arial; font-size: 16px; font-weight: bold; fill: #333; }
8
+ .subtitle { font-family: Arial; font-size: 12px; fill: #666; }
9
+ </style>
10
+
11
+ <!-- Title -->
12
+ <text x="600" y="20" class="title" text-anchor="middle">PostgreSQL Query Execution Plan Flamegraph</text>
13
+ <text x="600" y="35" class="subtitle" text-anchor="middle">Total Execution Time: 0.0ms</text>
14
+
15
+ <!-- Flamegraph -->
16
+ <g transform="translate(0, 45)">
17
+ <rect class="frame"
18
+ x="0.0"
19
+ y="0"
20
+ width="1200.0"
21
+ height="24"
22
+ fill="#95a5a6">
23
+ <title>Result [0.0ms]
24
+ Time: 0.0ms
25
+ Percentage: 100.0%</title>
26
+ </rect>
27
+ <text class="frame-text" x="4.0" y="21">Result [0.0ms]</text>
28
+ </g>
29
+ </svg>
@@ -5,35 +5,32 @@ And the result should be shown in a following codeblock
5
5
 
6
6
  ```ruby
7
7
  p "foo"
8
- ```
9
-
10
- ```ruby RESULT
11
- p "foo"
12
8
  # >> "foo"
13
9
  ```
14
10
 
11
+
15
12
  ```bash
16
13
  date
17
14
  ```
18
15
 
19
- ```RESULT
20
- Sun May 18 15:05:35 CEST 2025
16
+ ``` {result}
17
+ Fri Oct 17 02:39:33 PM CEST 2025
21
18
  ```
22
19
 
23
20
  ```zsh
24
21
  date
25
22
  ```
26
23
 
27
- ```RESULT
28
- Sun May 18 15:05:13 CEST 2025
24
+ ``` {result}
25
+ Fri Oct 17 02:39:33 PM CEST 2025
29
26
  ```
30
27
 
31
28
  ```sh
32
29
  date
33
30
  ```
34
31
 
35
- ```RESULT
36
- Sun May 18 15:07:05 CEST 2025
32
+ ``` {result}
33
+ Fri Oct 17 02:39:33 PM CEST 2025
37
34
  ```
38
35
 
39
36
  ```js
@@ -41,47 +38,7 @@ Sun May 18 15:07:05 CEST 2025
41
38
  console.log(3);
42
39
  ```
43
40
 
44
- ```RESULT
41
+ ``` {result}
45
42
  3
46
43
  ```
47
44
 
48
- ```psql
49
- \d
50
- ```
51
-
52
- ```RESULT
53
- public|ar_internal_metadata|table|aurelienbottazini
54
- public|schema_migrations|table|aurelienbottazini
55
- ```
56
-
57
- ```sqlite3
58
- .stats
59
- ```
60
-
61
- ```RESULT
62
- Memory Used: 147824 (max 147888) bytes
63
- Number of Outstanding Allocations: 169 (max 170)
64
- Number of Pcache Overflow Bytes: 4608 (max 4608) bytes
65
- Largest Allocation: 122400 bytes
66
- Largest Pcache Allocation: 4104 bytes
67
- Lookaside Slots Used: 34 (max 34)
68
- Successful lookaside attempts: 34
69
- Lookaside failures due to size: 0
70
- Lookaside failures due to OOM: 0
71
- Pager Heap Usage: 5632 bytes
72
- Page cache hits: 0
73
- Page cache misses: 0
74
- Page cache writes: 0
75
- Page cache spills: 0
76
- Schema Heap Usage: 0 bytes
77
- Statement Heap/Lookaside Usage: 0 bytes
78
- ```
79
-
80
- ```psql rerun flamegraph
81
- select 42 as answer;
82
- ```
83
-
84
- ```RESULT
85
- ![PostgreSQL Query Flamegraph](pg-flamegraph-20250604-222931.svg)
86
- ```
87
-
metadata CHANGED
@@ -1,14 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: markdown-run
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.12
4
+ version: 0.2.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Aurélien Bottazini
8
- autorequire:
9
8
  bindir: exe
10
9
  cert_chain: []
11
- date: 2025-06-04 00:00:00.000000000 Z
10
+ date: 1980-01-02 00:00:00.000000000 Z
12
11
  dependencies:
13
12
  - !ruby/object:Gem::Dependency
14
13
  name: rcodetools
@@ -80,6 +79,20 @@ dependencies:
80
79
  - - ">="
81
80
  - !ruby/object:Gem::Version
82
81
  version: '0'
82
+ - !ruby/object:Gem::Dependency
83
+ name: simplecov
84
+ requirement: !ruby/object:Gem::Requirement
85
+ requirements:
86
+ - - "~>"
87
+ - !ruby/object:Gem::Version
88
+ version: '0.22'
89
+ type: :development
90
+ prerelease: false
91
+ version_requirements: !ruby/object:Gem::Requirement
92
+ requirements:
93
+ - - "~>"
94
+ - !ruby/object:Gem::Version
95
+ version: '0.22'
83
96
  description: Run code blocks in Markdown files for Ruby, JavaScript, sqlite, psql,
84
97
  bash, zsh, and mermaid. Insert execution results next to the original code blocks.
85
98
  Generate SVG diagrams from mermaid blocks.
@@ -90,6 +103,7 @@ executables:
90
103
  extensions: []
91
104
  extra_rdoc_files: []
92
105
  files:
106
+ - ".claude/settings.local.json"
93
107
  - ".rubocop.yml"
94
108
  - ".tool-versions"
95
109
  - CHANGELOG.md
@@ -107,13 +121,17 @@ files:
107
121
  - lib/flamegraph_helper.rb
108
122
  - lib/frontmatter_parser.rb
109
123
  - lib/language_configs.rb
124
+ - lib/language_resolver.rb
110
125
  - lib/markdown/run/version.rb
111
126
  - lib/markdown_file_writer.rb
112
127
  - lib/markdown_processor.rb
113
128
  - lib/markdown_run.rb
114
129
  - lib/pg_flamegraph_svg.rb
130
+ - lib/postgres_helper.rb
115
131
  - lib/result_helper.rb
132
+ - lib/test_silencer.rb
116
133
  - markdown-run-sample.md
134
+ - markdown-run-sample/markdown-run-sample-flamegraph-20250604-233020-29c2a34ec32b.svg
117
135
  homepage: https://github.com/aurelienbottazini/markdown-run
118
136
  licenses:
119
137
  - MIT
@@ -121,7 +139,6 @@ metadata:
121
139
  homepage_uri: https://github.com/aurelienbottazini/markdown-run
122
140
  source_code_uri: https://github.com/aurelienbottazini/markdown-run
123
141
  changelog_uri: https://github.com/aurelienbottazini/markdown-run/blob/main/CHANGELOG.md
124
- post_install_message:
125
142
  rdoc_options: []
126
143
  require_paths:
127
144
  - lib
@@ -136,8 +153,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
136
153
  - !ruby/object:Gem::Version
137
154
  version: '0'
138
155
  requirements: []
139
- rubygems_version: 3.5.16
140
- signing_key:
156
+ rubygems_version: 3.6.9
141
157
  specification_version: 4
142
158
  summary: Run code blocks in Markdown files
143
159
  test_files: []