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.
@@ -0,0 +1,221 @@
1
+ require 'json'
2
+ require 'securerandom'
3
+
4
+ class PostgreSQLFlameGraphSVG
5
+ def initialize(explain_json, width = 1200, height = 600)
6
+ @explain_data = JSON.parse(explain_json)
7
+ @width = width
8
+ @height = height
9
+ @font_size = 20
10
+ @min_width = 1
11
+ @colors = [
12
+ '#e74c3c', '#3498db', '#2ecc71', '#f39c12', '#9b59b6',
13
+ '#1abc9c', '#e67e22', '#95a5a6', '#34495e', '#e91e63'
14
+ ]
15
+ end
16
+
17
+ def generate_svg
18
+ plan = @explain_data[0]['Plan']
19
+
20
+ # Calculate the layout
21
+ flamegraph_data = build_flamegraph_data(plan)
22
+
23
+ # Generate SVG
24
+ generate_svg_content(flamegraph_data)
25
+ end
26
+
27
+ private
28
+
29
+ def build_flamegraph_data(plan, depth = 0, start_time = 0)
30
+ node_name = format_node_name(plan)
31
+ actual_time = plan['Actual Total Time'] || 0
32
+
33
+ # Create the current node
34
+ current_node = {
35
+ name: node_name,
36
+ time: actual_time,
37
+ depth: depth,
38
+ start: start_time,
39
+ children: []
40
+ }
41
+
42
+ # Process children
43
+ if plan['Plans']
44
+ child_start = start_time
45
+ plan['Plans'].each do |child_plan|
46
+ child_node = build_flamegraph_data(child_plan, depth + 1, child_start)
47
+ current_node[:children] << child_node
48
+ child_start += child_node[:time]
49
+ end
50
+ end
51
+
52
+ current_node
53
+ end
54
+
55
+ def format_node_name(node)
56
+ node_type = node['Node Type']
57
+
58
+ # Add relevant details
59
+ details = []
60
+
61
+ if node['Relation Name']
62
+ details << node['Relation Name']
63
+ end
64
+
65
+ if node['Index Name']
66
+ details << "idx:#{node['Index Name']}"
67
+ end
68
+
69
+ if node['Join Type']
70
+ details << node['Join Type']
71
+ end
72
+
73
+ # Build the final name
74
+ name = node_type
75
+ unless details.empty?
76
+ name += " (#{details.join(', ')})"
77
+ end
78
+
79
+ # Add timing info
80
+ if node['Actual Total Time']
81
+ name += " [#{node['Actual Total Time'].round(2)}ms]"
82
+ end
83
+
84
+ name
85
+ end
86
+
87
+ def generate_svg_content(flamegraph_data)
88
+ max_depth = calculate_max_depth(flamegraph_data)
89
+ total_time = flamegraph_data[:time]
90
+
91
+ svg_height = (max_depth + 1) * (@font_size + 4) + 80
92
+
93
+ svg = <<~SVG
94
+ <?xml version="1.0" encoding="UTF-8"?>
95
+ <svg width="#{@width}" height="#{svg_height}" xmlns="http://www.w3.org/2000/svg">
96
+ <style>
97
+ .frame { stroke: white; stroke-width: 1; cursor: pointer; }
98
+ .frame:hover { stroke: black; stroke-width: 2; }
99
+ .frame-text { font-family: monospace; font-size: #{@font_size}px; fill: white; pointer-events: none; }
100
+ .title { font-family: Arial; font-size: 16px; font-weight: bold; fill: #333; }
101
+ .subtitle { font-family: Arial; font-size: 12px; fill: #666; }
102
+ </style>
103
+
104
+ <!-- Title -->
105
+ <text x="#{@width/2}" y="20" class="title" text-anchor="middle">PostgreSQL Query Execution Plan Flamegraph</text>
106
+ <text x="#{@width/2}" y="35" class="subtitle" text-anchor="middle">Total Execution Time: #{total_time.round(2)}ms</text>
107
+
108
+ <!-- Flamegraph -->
109
+ <g transform="translate(0, 45)">
110
+ SVG
111
+
112
+ # Generate rectangles recursively
113
+ svg += generate_rectangles(flamegraph_data, total_time, 0)
114
+
115
+ svg += <<~SVG
116
+ </g>
117
+ </svg>
118
+ SVG
119
+
120
+ svg
121
+ end
122
+
123
+ def generate_rectangles(node, total_time, y_offset)
124
+ return "" if node[:time] <= 0
125
+
126
+ # Calculate dimensions
127
+ width_ratio = node[:time] / total_time
128
+ rect_width = [@width * width_ratio, @min_width].max
129
+ rect_height = @font_size + 4
130
+
131
+ x_position = (node[:start] / total_time) * @width
132
+ y_position = y_offset
133
+
134
+ # Choose color based on node type
135
+ color = get_node_color(node[:name])
136
+
137
+ # Generate rectangle and text
138
+ svg = <<~SVG
139
+ <rect class="frame"
140
+ x="#{x_position}"
141
+ y="#{y_position}"
142
+ width="#{rect_width}"
143
+ height="#{rect_height}"
144
+ fill="#{color}">
145
+ <title>#{escape_xml(node[:name])}
146
+ Time: #{node[:time].round(2)}ms
147
+ Percentage: #{((node[:time] / total_time) * 100).round(1)}%</title>
148
+ </rect>
149
+ SVG
150
+
151
+ # Add text if rectangle is wide enough
152
+ if rect_width > 50
153
+ text_x = x_position + 4
154
+ text_y = y_position + @font_size + 1
155
+
156
+ # Truncate text if necessary
157
+ display_text = truncate_text(node[:name], rect_width - 8)
158
+
159
+ svg += <<~SVG
160
+ <text class="frame-text" x="#{text_x}" y="#{text_y}">#{escape_xml(display_text)}</text>
161
+ SVG
162
+ end
163
+
164
+ # Generate children
165
+ child_y = y_position + rect_height + 2
166
+ node[:children].each do |child|
167
+ svg += generate_rectangles(child, total_time, child_y)
168
+ end
169
+
170
+ svg
171
+ end
172
+
173
+ def calculate_max_depth(node, current_depth = 0)
174
+ max_child_depth = current_depth
175
+
176
+ node[:children].each do |child|
177
+ child_depth = calculate_max_depth(child, current_depth + 1)
178
+ max_child_depth = [max_child_depth, child_depth].max
179
+ end
180
+
181
+ max_child_depth
182
+ end
183
+
184
+ def get_node_color(node_name)
185
+ # Color code by operation type
186
+ case node_name
187
+ when /Seq Scan/
188
+ '#e74c3c' # Red - potentially slow
189
+ when /Index.*Scan/
190
+ '#2ecc71' # Green - good
191
+ when /Hash Join|Nested Loop|Merge Join/
192
+ '#3498db' # Blue - joins
193
+ when /Sort|Aggregate/
194
+ '#f39c12' # Orange - processing
195
+ when /Result/
196
+ '#95a5a6' # Gray - simple
197
+ else
198
+ # Cycle through colors based on hash
199
+ @colors[node_name.hash.abs % @colors.length]
200
+ end
201
+ end
202
+
203
+ def truncate_text(text, max_width)
204
+ # Rough estimate: 1 character ≈ 7 pixels in monospace
205
+ max_chars = (max_width / 7).to_i
206
+
207
+ if text.length <= max_chars
208
+ text
209
+ else
210
+ text[0, max_chars - 3] + "..."
211
+ end
212
+ end
213
+
214
+ def escape_xml(text)
215
+ text.gsub('&', '&amp;')
216
+ .gsub('<', '&lt;')
217
+ .gsub('>', '&gt;')
218
+ .gsub('"', '&quot;')
219
+ .gsub("'", '&#39;')
220
+ end
221
+ end
@@ -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,9 @@
1
+ require_relative "flamegraph_helper"
2
+ require_relative "test_silencer"
3
+
1
4
  module ResultHelper
5
+ include FlamegraphHelper
6
+
2
7
  private
3
8
 
4
9
  def ruby_style_result?(lang)
@@ -12,7 +17,7 @@ module ResultHelper
12
17
  end
13
18
 
14
19
  def result_block_header(lang)
15
- ruby_style_result?(lang) ? "```ruby RESULT\n" : "```RESULT\n"
20
+ ruby_style_result?(lang) ? "```ruby\n" : "``` {result}\n"
16
21
  end
17
22
 
18
23
  def result_block_regex(lang)
@@ -20,14 +25,21 @@ module ResultHelper
20
25
  # For mermaid, look for existing image tags with .svg extension
21
26
  /^!\[.*\]\(.*\.svg\)$/i
22
27
  elsif ruby_style_result?(lang)
28
+ # For ruby, check for old-style ```ruby RESULT blocks (for backward compatibility during migration)
23
29
  /^```ruby\s+RESULT$/i
24
30
  else
25
- /^```RESULT$/i
31
+ /^```\s*\{result\}$/i
26
32
  end
27
33
  end
28
34
 
29
- def add_result_block(result_output, blank_line_before_new_result)
30
- 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)
31
43
  # For mermaid, add the image tag directly without a result block
32
44
  @output_lines << "\n" if blank_line_before_new_result.nil?
33
45
  @output_lines << result_output
@@ -51,25 +63,34 @@ module ResultHelper
51
63
  end
52
64
 
53
65
  def handle_existing_ruby_result_block(current_line, file_enum)
54
- warn "Found existing '```ruby RESULT' block, passing through."
66
+ TestSilencer.warn_unless_testing("Found existing '```ruby RESULT' or '```ruby' result block, passing through.")
55
67
  @output_lines << current_line
56
68
  @state = :inside_result_block
57
69
  end
58
70
 
59
71
 
60
- def execute_and_add_result(blank_line_before_new_result)
61
- 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)
62
74
 
63
- @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)
64
77
 
65
- result_output = CodeExecutor.execute(@current_code_content, @current_block_lang, @temp_dir, @input_file_path, @current_block_explain)
78
+ result_output = CodeExecutor.execute(@current_code_content, @current_block_lang, @temp_dir, @input_file_path, @current_block_explain, @current_block_flamegraph)
66
79
 
67
80
  # Check if result contains a Dalibo link for psql explain queries
68
- dalibo_link, clean_result = extract_dalibo_link(result_output)
81
+ dalibo_link, result_after_dalibo = extract_dalibo_link(result_output)
82
+
83
+ # Check if result contains a flamegraph link for psql flamegraph queries
84
+ flamegraph_link, clean_result = extract_flamegraph_link(result_after_dalibo)
69
85
 
70
86
  # Add the result block only if result=true (default)
71
87
  if @current_block_result
72
- add_result_block(clean_result || result_output, 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"
73
94
  end
74
95
 
75
96
  # Always add Dalibo link if it exists, even when result=false
@@ -81,30 +102,62 @@ module ResultHelper
81
102
  @output_lines << "\n#{dalibo_link}\n\n"
82
103
  end
83
104
  end
105
+
106
+ # Always add flamegraph link if it exists, even when result=false
107
+ if flamegraph_link
108
+ # Add appropriate spacing based on whether result block was shown
109
+ if @current_block_result || dalibo_link
110
+ @output_lines << "#{flamegraph_link}\n\n"
111
+ else
112
+ @output_lines << "\n#{flamegraph_link}\n\n"
113
+ end
114
+ end
84
115
  end
85
116
 
86
- 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)
87
118
  # Handle run=false case where there are no lines to pass through
88
119
  if lines_to_pass_through.empty?
89
- 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
+ @output_lines << "\n"
126
+ end
90
127
  return
91
128
  end
92
129
 
93
130
  # Check if this is Dalibo content
94
131
  if decision && decision[:dalibo_content]
95
- warn "Found existing Dalibo link for current #{@current_block_lang} block, skipping execution."
132
+ TestSilencer.warn_unless_testing("Found existing Dalibo link for current #{@current_block_lang} block, skipping execution.")
96
133
  @output_lines.concat(lines_to_pass_through)
97
134
  # No additional consumption needed for Dalibo links
98
135
  return
99
136
  end
100
137
 
138
+ # Check if this is flamegraph content
139
+ if decision && decision[:flamegraph_content]
140
+ TestSilencer.warn_unless_testing("Found existing flamegraph for current #{@current_block_lang} block, skipping execution.")
141
+ @output_lines.concat(lines_to_pass_through)
142
+ # No additional consumption needed for flamegraph links
143
+ return
144
+ end
145
+
101
146
  if mermaid_style_result?(@current_block_lang)
102
- warn "Found existing mermaid SVG image for current #{@current_block_lang} block, skipping execution."
147
+ TestSilencer.warn_unless_testing("Found existing mermaid SVG image for current #{@current_block_lang} block, skipping execution.")
103
148
  @output_lines.concat(lines_to_pass_through)
104
149
  # For mermaid, no additional consumption needed since it's just an image line
150
+ elsif ruby_style_result?(@current_block_lang)
151
+ # For ruby blocks with existing old-style RESULT blocks, discard them and output inline results
152
+ TestSilencer.warn_unless_testing("Found existing ruby result for current #{@current_block_lang} block, skipping execution.")
153
+ @output_lines << @current_code_content
154
+ @output_lines << (closing_line || "```\n")
155
+ @output_lines << "\n"
156
+ # Always consume and discard the old-style RESULT block
157
+ consume_and_discard_result_block(file_enum)
105
158
  else
106
- lang_specific_result_type = ruby_style_result?(@current_block_lang) ? "```ruby RESULT" : "```RESULT"
107
- warn "Found existing '#{lang_specific_result_type}' block for current #{@current_block_lang} block, skipping execution."
159
+ lang_specific_result_type = "``` {result}"
160
+ TestSilencer.warn_unless_testing("Found existing '#{lang_specific_result_type}' block for current #{@current_block_lang} block, skipping execution.")
108
161
  @output_lines.concat(lines_to_pass_through)
109
162
  consume_result_block_content(file_enum)
110
163
  end
@@ -116,6 +169,12 @@ module ResultHelper
116
169
  end
117
170
  end
118
171
 
172
+ def consume_and_discard_result_block(file_enum)
173
+ consume_block_lines(file_enum) do |line|
174
+ # Discard the line - don't add to output
175
+ end
176
+ end
177
+
119
178
  def consume_existing_result_block(file_enum, consumed_lines)
120
179
  return if mermaid_style_result?(@current_block_lang)
121
180
 
@@ -124,6 +183,7 @@ module ResultHelper
124
183
  end
125
184
 
126
185
  consume_dalibo_link_if_present(file_enum, consumed_lines)
186
+ consume_flamegraph_link_if_present(file_enum, consumed_lines)
127
187
  end
128
188
 
129
189
 
@@ -135,7 +195,7 @@ module ResultHelper
135
195
  break if is_block_end?(result_block_line)
136
196
  end
137
197
  rescue StopIteration
138
- warn "Warning: End of file reached while consuming result block."
198
+ TestSilencer.warn_unless_testing "Warning: End of file reached while consuming result block."
139
199
  end
140
200
  end
141
201
  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>
@@ -76,3 +76,12 @@ Page cache spills: 0
76
76
  Schema Heap Usage: 0 bytes
77
77
  Statement Heap/Lookaside Usage: 0 bytes
78
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.11
4
+ version: 0.2.0
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.
@@ -104,14 +117,20 @@ files:
104
117
  - lib/dalibo_helper.rb
105
118
  - lib/enum_helper.rb
106
119
  - lib/execution_decider.rb
120
+ - lib/flamegraph_helper.rb
107
121
  - lib/frontmatter_parser.rb
108
122
  - lib/language_configs.rb
123
+ - lib/language_resolver.rb
109
124
  - lib/markdown/run/version.rb
110
125
  - lib/markdown_file_writer.rb
111
126
  - lib/markdown_processor.rb
112
127
  - lib/markdown_run.rb
128
+ - lib/pg_flamegraph_svg.rb
129
+ - lib/postgres_helper.rb
113
130
  - lib/result_helper.rb
131
+ - lib/test_silencer.rb
114
132
  - markdown-run-sample.md
133
+ - markdown-run-sample/markdown-run-sample-flamegraph-20250604-233020-29c2a34ec32b.svg
115
134
  homepage: https://github.com/aurelienbottazini/markdown-run
116
135
  licenses:
117
136
  - MIT
@@ -119,7 +138,6 @@ metadata:
119
138
  homepage_uri: https://github.com/aurelienbottazini/markdown-run
120
139
  source_code_uri: https://github.com/aurelienbottazini/markdown-run
121
140
  changelog_uri: https://github.com/aurelienbottazini/markdown-run/blob/main/CHANGELOG.md
122
- post_install_message:
123
141
  rdoc_options: []
124
142
  require_paths:
125
143
  - lib
@@ -134,8 +152,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
134
152
  - !ruby/object:Gem::Version
135
153
  version: '0'
136
154
  requirements: []
137
- rubygems_version: 3.5.16
138
- signing_key:
155
+ rubygems_version: 3.6.9
139
156
  specification_version: 4
140
157
  summary: Run code blocks in Markdown files
141
158
  test_files: []