Location via proxy:   [ UP ]  
[Report a bug]   [Manage cookies]                
Skip to content

Commit 7f4fe77

Browse files
committed
Move mermaid rendering into its own file
1 parent 05401da commit 7f4fe77

File tree

3 files changed

+130
-52
lines changed

3 files changed

+130
-52
lines changed

lib/syntax_tree.rb

Lines changed: 32 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
# frozen_string_literal: true
22

3-
require "cgi"
43
require "etc"
54
require "json"
65
require "pp"
@@ -71,19 +70,6 @@ module SyntaxTree
7170
# that Syntax Tree can format arbitrary parts of a document.
7271
DEFAULT_INDENTATION = 0
7372

74-
# This is a hook provided so that plugins can register themselves as the
75-
# handler for a particular file type.
76-
def self.register_handler(extension, handler)
77-
HANDLERS[extension] = handler
78-
end
79-
80-
# Parses the given source and returns the syntax tree.
81-
def self.parse(source)
82-
parser = Parser.new(source)
83-
response = parser.parse
84-
response unless parser.error?
85-
end
86-
8773
# Parses the given source and returns the formatted source.
8874
def self.format(
8975
source,
@@ -98,13 +84,39 @@ def self.format(
9884
formatter.output.join
9985
end
10086

87+
# Indexes the given source code to return a list of all class, module, and
88+
# method definitions. Used to quickly provide indexing capability for IDEs or
89+
# documentation generation.
90+
def self.index(source)
91+
Index.index(source)
92+
end
93+
94+
# Indexes the given file to return a list of all class, module, and method
95+
# definitions. Used to quickly provide indexing capability for IDEs or
96+
# documentation generation.
97+
def self.index_file(filepath)
98+
Index.index_file(filepath)
99+
end
100+
101101
# A convenience method for creating a new mutation visitor.
102102
def self.mutation
103103
visitor = Visitor::MutationVisitor.new
104104
yield visitor
105105
visitor
106106
end
107107

108+
# Parses the given source and returns the syntax tree.
109+
def self.parse(source)
110+
parser = Parser.new(source)
111+
response = parser.parse
112+
response unless parser.error?
113+
end
114+
115+
# Parses the given file and returns the syntax tree.
116+
def self.parse_file(filepath)
117+
parse(read(filepath))
118+
end
119+
108120
# Returns the source from the given filepath taking into account any potential
109121
# magic encoding comments.
110122
def self.read(filepath)
@@ -120,23 +132,15 @@ def self.read(filepath)
120132
File.read(filepath, encoding: encoding)
121133
end
122134

135+
# This is a hook provided so that plugins can register themselves as the
136+
# handler for a particular file type.
137+
def self.register_handler(extension, handler)
138+
HANDLERS[extension] = handler
139+
end
140+
123141
# Searches through the given source using the given pattern and yields each
124142
# node in the tree that matches the pattern to the given block.
125143
def self.search(source, query, &block)
126144
Search.new(Pattern.new(query).compile).scan(parse(source), &block)
127145
end
128-
129-
# Indexes the given source code to return a list of all class, module, and
130-
# method definitions. Used to quickly provide indexing capability for IDEs or
131-
# documentation generation.
132-
def self.index(source)
133-
Index.index(source)
134-
end
135-
136-
# Indexes the given file to return a list of all class, module, and method
137-
# definitions. Used to quickly provide indexing capability for IDEs or
138-
# documentation generation.
139-
def self.index_file(filepath)
140-
Index.index_file(filepath)
141-
end
142146
end

lib/syntax_tree/mermaid.rb

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
# frozen_string_literal: true
2+
3+
require "cgi"
4+
5+
module SyntaxTree
6+
# This module is responsible for rendering mermaid flow charts.
7+
module Mermaid
8+
class Node
9+
SHAPES = %i[circle rectangle stadium].freeze
10+
11+
attr_reader :id, :label, :shape
12+
13+
def initialize(id, label, shape)
14+
raise unless SHAPES.include?(shape)
15+
16+
@id = id
17+
@label = label
18+
@shape = shape
19+
end
20+
21+
def render
22+
left_bound, right_bound =
23+
case shape
24+
when :circle
25+
["((", "))"]
26+
when :rectangle
27+
["[", "]"]
28+
when :stadium
29+
["([", "])"]
30+
end
31+
32+
" #{id}#{left_bound}\"#{CGI.escapeHTML(label)}\"#{right_bound}"
33+
end
34+
end
35+
36+
class Edge
37+
TYPES = %i[directed].freeze
38+
39+
attr_reader :from, :to, :label, :type
40+
41+
def initialize(from, to, label, type)
42+
raise unless TYPES.include?(type)
43+
44+
@from = from
45+
@to = to
46+
@label = label
47+
@type = type
48+
end
49+
50+
def render
51+
case type
52+
when :directed
53+
" #{from.id} -- \"#{CGI.escapeHTML(label)}\" --> #{to.id}"
54+
end
55+
end
56+
end
57+
58+
class FlowChart
59+
attr_reader :nodes, :edges
60+
61+
def initialize
62+
@nodes = {}
63+
@edges = []
64+
end
65+
66+
def edge(from, to, label, type = :directed)
67+
edges << Edge.new(from, to, label, type)
68+
end
69+
70+
def node(id, label, shape = :rectangle)
71+
nodes[id] = Node.new(id, label, shape)
72+
end
73+
74+
def render
75+
output = StringIO.new
76+
output.puts("flowchart TD")
77+
78+
nodes.each_value { |node| output.puts(node.render) }
79+
edges.each { |edge| output.puts(edge.render) }
80+
81+
output.string
82+
end
83+
end
84+
end
85+
end

lib/syntax_tree/visitor/mermaid_visitor.rb

Lines changed: 13 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -4,18 +4,16 @@ module SyntaxTree
44
class Visitor
55
# This visitor transforms the AST into a mermaid flow chart.
66
class MermaidVisitor < FieldVisitor
7-
attr_reader :output, :target
7+
attr_reader :flowchart, :target
88

99
def initialize
10-
@output = StringIO.new
11-
@output.puts("flowchart TD")
12-
10+
@flowchart = Mermaid::FlowChart.new
1311
@target = nil
1412
end
1513

1614
def visit_program(node)
1715
super
18-
output.string
16+
flowchart.render
1917
end
2018

2119
private
@@ -26,19 +24,13 @@ def comments(node)
2624

2725
def field(name, value)
2826
case value
29-
when Node
30-
node_id = visit(value)
31-
output.puts(" #{target} -- \"#{name}\" --> #{node_id}")
32-
when String
33-
node_id = "#{target}_#{name}"
34-
output.puts(" #{node_id}([#{CGI.escapeHTML(value.inspect)}])")
35-
output.puts(" #{target} -- \"#{name}\" --> #{node_id}")
3627
when nil
3728
# skip
29+
when Node
30+
flowchart.edge(target, visit(value), name)
3831
else
39-
node_id = "#{target}_#{name}"
40-
output.puts(" #{node_id}([\"#{CGI.escapeHTML(value.inspect)}\"])")
41-
output.puts(" #{target} -- \"#{name}\" --> #{node_id}")
32+
to = flowchart.node("#{target.id}_#{name}", value.inspect, :stadium)
33+
flowchart.edge(target, to, name)
4234
end
4335
end
4436

@@ -52,11 +44,8 @@ def node(node, type)
5244
previous_target = target
5345

5446
begin
55-
@target = "node_#{node.object_id}"
56-
47+
@target = flowchart.node("node_#{node.object_id}", type)
5748
yield
58-
59-
output.puts(" #{@target}[\"#{type}\"]")
6049
@target
6150
ensure
6251
@target = previous_target
@@ -65,11 +54,11 @@ def node(node, type)
6554

6655
def pairs(name, values)
6756
values.each_with_index do |(key, value), index|
68-
node_id = "#{target}_#{name}_#{index}"
69-
output.puts(" #{node_id}((\"&nbsp;\"))")
70-
output.puts(" #{target} -- \"#{name}[#{index}]\" --> #{node_id}")
71-
output.puts(" #{node_id} -- \"[0]\" --> #{visit(key)}")
72-
output.puts(" #{node_id} -- \"[1]\" --> #{visit(value)}") if value
57+
to = flowchart.node("#{target.id}_#{name}_#{index}", " ", :circle)
58+
59+
flowchart.edge(target, to, "#{name}[#{index}]")
60+
flowchart.edge(to, visit(key), "[0]")
61+
flowchart.edge(to, visit(value), "[1]") if value
7362
end
7463
end
7564

0 commit comments

Comments
 (0)