-
-
Notifications
You must be signed in to change notification settings - Fork 1.6k
/
builder.cr
167 lines (145 loc) · 3.58 KB
/
builder.cr
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
# Builds a tree of YAML nodes.
#
# This builder is similar to `YAML::Builder`, but instead of
# directly emitting the output to an IO it builds a YAML document
# tree in memory.
#
# All "emitting" methods support specifying a "reference" object
# that will be associated to the emitted object,
# so that when that reference object is emitted again an anchor
# and an alias will be created. This generates both more compact
# documents and allows handling recursive data structures.
class YAML::Nodes::Builder
@current : Node
# The document this builder builds.
getter document : Document
def initialize
@document = Document.new
@current = @document
@object_id_to_node = {} of UInt64 => Node
@anchor_count = 0
end
# Emits an alias to the given *anchor*.
#
# ```crystal
# require "yaml"
#
# nodes_builder = YAML::Nodes::Builder.new
#
# nodes_builder.mapping do
# nodes_builder.scalar "foo"
# nodes_builder.alias "key"
# end
#
# yaml = YAML.build do |builder|
# nodes_builder.document.to_yaml builder
# end
#
# yaml # => "---\nfoo: *key\n"
# ```
def alias(anchor : String) : Nil
push_node Alias.new anchor
end
# Emits the scalar `"<<"` followed by an alias to the given *anchor*.
#
# See [YAML Merge](https://yaml.org/type/merge.html).
#
# ```crystal
# require "yaml"
#
# nodes_builder = YAML::Nodes::Builder.new
#
# nodes_builder.mapping do
# nodes_builder.merge "key"
# end
#
# yaml = YAML.build do |builder|
# nodes_builder.document.to_yaml builder
# end
#
# yaml # => "---\n<<: *key\n"
# ```
def merge(anchor : String) : Nil
self.scalar "<<"
self.alias anchor
end
def scalar(value, anchor : String? = nil, tag : String? = nil,
style : YAML::ScalarStyle = YAML::ScalarStyle::ANY,
reference = nil) : Nil
node = Scalar.new(value.to_s)
node.anchor = anchor
node.tag = tag
node.style = style
if register(reference, node)
return
end
push_node(node)
end
def sequence(anchor : String? = nil, tag : String? = nil,
style : YAML::SequenceStyle = YAML::SequenceStyle::ANY,
reference = nil) : Nil
node = Sequence.new
node.anchor = anchor
node.tag = tag
node.style = style
if register(reference, node)
return
end
push_to_stack(node) do
yield
end
end
def mapping(anchor : String? = nil, tag : String? = nil,
style : YAML::MappingStyle = YAML::MappingStyle::ANY,
reference = nil) : Nil
node = Mapping.new
node.anchor = anchor
node.tag = tag
node.style = style
if register(reference, node)
return
end
push_to_stack(node) do
yield
end
end
private def push_node(node)
case current = @current
when Document
current << node
when Sequence
current << node
when Mapping
current << node
end
end
private def push_to_stack(node)
push_node(node)
old_current = @current
@current = node
yield
@current = old_current
end
private def register(object, current_node)
if object.is_a?(Reference)
register_object_id(object.object_id, current_node)
else
false
end
end
private def register_object_id(object_id, current_node)
node = @object_id_to_node[object_id]?
if node
anchor = node.anchor ||= begin
@anchor_count += 1
@anchor_count.to_s
end
node = Alias.new(anchor)
push_node(node)
true
else
@object_id_to_node[object_id] = current_node
false
end
end
end