hocon 0.0.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.
- data/LICENSE +202 -0
- data/README.md +19 -0
- data/lib/hocon.rb +2 -0
- data/lib/hocon/config_error.rb +12 -0
- data/lib/hocon/config_factory.rb +9 -0
- data/lib/hocon/config_object.rb +4 -0
- data/lib/hocon/config_parse_options.rb +53 -0
- data/lib/hocon/config_render_options.rb +46 -0
- data/lib/hocon/config_syntax.rb +7 -0
- data/lib/hocon/config_value_type.rb +26 -0
- data/lib/hocon/impl.rb +5 -0
- data/lib/hocon/impl/abstract_config_object.rb +64 -0
- data/lib/hocon/impl/abstract_config_value.rb +130 -0
- data/lib/hocon/impl/config_concatenation.rb +136 -0
- data/lib/hocon/impl/config_float.rb +9 -0
- data/lib/hocon/impl/config_impl.rb +10 -0
- data/lib/hocon/impl/config_impl_util.rb +78 -0
- data/lib/hocon/impl/config_int.rb +31 -0
- data/lib/hocon/impl/config_number.rb +27 -0
- data/lib/hocon/impl/config_string.rb +37 -0
- data/lib/hocon/impl/full_includer.rb +4 -0
- data/lib/hocon/impl/origin_type.rb +9 -0
- data/lib/hocon/impl/parseable.rb +151 -0
- data/lib/hocon/impl/parser.rb +882 -0
- data/lib/hocon/impl/path.rb +59 -0
- data/lib/hocon/impl/path_builder.rb +36 -0
- data/lib/hocon/impl/resolve_status.rb +18 -0
- data/lib/hocon/impl/simple_config.rb +11 -0
- data/lib/hocon/impl/simple_config_list.rb +70 -0
- data/lib/hocon/impl/simple_config_object.rb +178 -0
- data/lib/hocon/impl/simple_config_origin.rb +174 -0
- data/lib/hocon/impl/simple_include_context.rb +7 -0
- data/lib/hocon/impl/simple_includer.rb +19 -0
- data/lib/hocon/impl/token.rb +32 -0
- data/lib/hocon/impl/token_type.rb +42 -0
- data/lib/hocon/impl/tokenizer.rb +370 -0
- data/lib/hocon/impl/tokens.rb +157 -0
- data/lib/hocon/impl/unmergeable.rb +4 -0
- metadata +84 -0
@@ -0,0 +1,59 @@
|
|
1
|
+
require 'hocon/impl'
|
2
|
+
require 'stringio'
|
3
|
+
|
4
|
+
class Hocon::Impl::Path
|
5
|
+
# this doesn't have a very precise meaning, just to reduce
|
6
|
+
# noise from quotes in the rendered path for average cases
|
7
|
+
def self.has_funky_chars?(s)
|
8
|
+
length = s.length
|
9
|
+
if length == 0
|
10
|
+
return false
|
11
|
+
end
|
12
|
+
|
13
|
+
# if the path starts with something that could be a number,
|
14
|
+
# we need to quote it because the number could be invalid,
|
15
|
+
# for example it could be a hyphen with no digit afterward
|
16
|
+
# or the exponent "e" notation could be mangled.
|
17
|
+
first = s[0]
|
18
|
+
unless first =~ /[[:alpha:]]/
|
19
|
+
return true
|
20
|
+
end
|
21
|
+
|
22
|
+
s.chars.each do |c|
|
23
|
+
unless (c =~ /[[:alnum:]]/) || (c == '-') || (c == '_')
|
24
|
+
return true
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
false
|
29
|
+
end
|
30
|
+
|
31
|
+
def initialize(first, remainder)
|
32
|
+
@first = first
|
33
|
+
@remainder = remainder
|
34
|
+
end
|
35
|
+
attr_reader :first, :remainder
|
36
|
+
|
37
|
+
#
|
38
|
+
# toString() is a debugging-oriented version while this is an
|
39
|
+
# error-message-oriented human-readable one.
|
40
|
+
#
|
41
|
+
def render
|
42
|
+
sb = StringIO.new
|
43
|
+
append_to_string_builder(sb)
|
44
|
+
sb.string
|
45
|
+
end
|
46
|
+
|
47
|
+
def append_to_string_builder(sb)
|
48
|
+
if self.class.has_funky_chars?(@first) || @first.empty?
|
49
|
+
sb << ConfigImplUtil.render_json_string(@first)
|
50
|
+
else
|
51
|
+
sb << @first
|
52
|
+
end
|
53
|
+
|
54
|
+
unless @remainder.nil?
|
55
|
+
sb << "."
|
56
|
+
@remainder.append_to_string_builder(sb)
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
require 'hocon/impl'
|
2
|
+
require 'hocon/impl/path'
|
3
|
+
|
4
|
+
class Hocon::Impl::PathBuilder
|
5
|
+
Path = Hocon::Impl::Path
|
6
|
+
|
7
|
+
def initialize
|
8
|
+
@keys = []
|
9
|
+
@result = nil
|
10
|
+
end
|
11
|
+
|
12
|
+
def check_can_append
|
13
|
+
if @result
|
14
|
+
raise ConfigBugError, "Adding to PathBuilder after getting result"
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
def append_key(key)
|
19
|
+
check_can_append
|
20
|
+
@keys.push(key)
|
21
|
+
end
|
22
|
+
|
23
|
+
def result
|
24
|
+
# note: if keys is empty, we want to return null, which is a valid
|
25
|
+
# empty path
|
26
|
+
if @result.nil?
|
27
|
+
remainder = nil
|
28
|
+
while !@keys.empty?
|
29
|
+
key = @keys.pop
|
30
|
+
remainder = Path.new(key, remainder)
|
31
|
+
end
|
32
|
+
@result = remainder
|
33
|
+
end
|
34
|
+
@result
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
require 'hocon/impl'
|
2
|
+
|
3
|
+
class Hocon::Impl::ResolveStatus
|
4
|
+
UNRESOLVED = 0
|
5
|
+
RESOLVED = 1
|
6
|
+
|
7
|
+
def self.from_values(values)
|
8
|
+
if values.any? { |v| v.resolve_status == UNRESOLVED }
|
9
|
+
UNRESOLVED
|
10
|
+
else
|
11
|
+
RESOLVED
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
def self.from_boolean(resolved)
|
16
|
+
resolved ? RESOLVED : UNRESOLVED
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,70 @@
|
|
1
|
+
require 'hocon/impl'
|
2
|
+
require 'hocon/impl/resolve_status'
|
3
|
+
require 'hocon/config_value_type'
|
4
|
+
|
5
|
+
class Hocon::Impl::SimpleConfigList < Hocon::Impl::AbstractConfigValue
|
6
|
+
ResolveStatus = Hocon::Impl::ResolveStatus
|
7
|
+
|
8
|
+
def initialize(origin, value, status = ResolveStatus.from_values(value))
|
9
|
+
super(origin)
|
10
|
+
@value = value
|
11
|
+
@resolved = (status == ResolveStatus::RESOLVED)
|
12
|
+
|
13
|
+
# kind of an expensive debug check (makes this constructor pointless)
|
14
|
+
if status != ResolveStatus.from_values(value)
|
15
|
+
raise ConfigBugError, "SimpleConfigList created with wrong resolve status: #{self}"
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def value_type
|
20
|
+
Hocon::ConfigValueType::LIST
|
21
|
+
end
|
22
|
+
|
23
|
+
def unwrapped
|
24
|
+
@value.map { |v| v.unwrapped }
|
25
|
+
end
|
26
|
+
|
27
|
+
def render_value_to_sb(sb, indent_size, at_root, options)
|
28
|
+
if @value.empty?
|
29
|
+
sb << "[]"
|
30
|
+
else
|
31
|
+
sb << "["
|
32
|
+
if options.formatted?
|
33
|
+
sb << "\n"
|
34
|
+
end
|
35
|
+
@value.each do |v|
|
36
|
+
if options.origin_comments?
|
37
|
+
indent(sb, indent_size + 1, options)
|
38
|
+
sb << "# "
|
39
|
+
sb << v.origin.description
|
40
|
+
sb << "\n"
|
41
|
+
end
|
42
|
+
if options.comments?
|
43
|
+
v.origin.comments.each do |comment|
|
44
|
+
sb << "# "
|
45
|
+
sb << comment
|
46
|
+
sb << "\n"
|
47
|
+
end
|
48
|
+
end
|
49
|
+
indent(sb, indent_size + 1, options)
|
50
|
+
|
51
|
+
v.render_value_to_sb(sb, indent_size + 1, at_root, options)
|
52
|
+
sb << ","
|
53
|
+
if options.formatted?
|
54
|
+
sb << "\n"
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
# couldn't figure out a better way to chop characters off of the end of
|
59
|
+
# the StringIO. This relies on making sure that, prior to returning the
|
60
|
+
# final string, we take a substring that ends at sb.pos.
|
61
|
+
sb.pos = sb.pos - 1 # chop or newline
|
62
|
+
if options.formatted?
|
63
|
+
sb.pos = sb.pos - 1 # also chop comma
|
64
|
+
sb << "\n"
|
65
|
+
indent(sb, indent_size, options)
|
66
|
+
end
|
67
|
+
sb << "]"
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
@@ -0,0 +1,178 @@
|
|
1
|
+
require 'hocon/impl'
|
2
|
+
require 'hocon/impl/simple_config_origin'
|
3
|
+
require 'hocon/impl/abstract_config_object'
|
4
|
+
require 'hocon/impl/resolve_status'
|
5
|
+
require 'set'
|
6
|
+
|
7
|
+
class Hocon::Impl::SimpleConfigObject < Hocon::Impl::AbstractConfigObject
|
8
|
+
def self.empty_missing(base_origin)
|
9
|
+
self.new(
|
10
|
+
Hocon::Impl::SimpleConfigOrigin.new_simple("#{base_origin.description} (not found)"),
|
11
|
+
{})
|
12
|
+
end
|
13
|
+
|
14
|
+
def initialize(origin, value,
|
15
|
+
status = Hocon::Impl::ResolveStatus.from_values(value.values),
|
16
|
+
ignores_fallbacks = false)
|
17
|
+
super(origin)
|
18
|
+
if value.nil?
|
19
|
+
raise ConfigBugError, "creating config object with null map"
|
20
|
+
end
|
21
|
+
@value = value
|
22
|
+
@resolved = (status == Hocon::Impl::ResolveStatus::RESOLVED)
|
23
|
+
@ignores_fallbacks = ignores_fallbacks
|
24
|
+
|
25
|
+
# Kind of an expensive debug check. Comment out?
|
26
|
+
if status != Hocon::Impl::ResolveStatus.from_values(value.values)
|
27
|
+
raise ConfigBugError, "Wrong resolved status on #{self}"
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
attr_reader :value
|
32
|
+
|
33
|
+
def new_copy_with_status(new_status, new_origin, new_ignores_fallbacks = nil)
|
34
|
+
Hocon::Impl::SimpleConfigObject.new(new_origin,
|
35
|
+
@value, new_status, new_ignores_fallbacks)
|
36
|
+
end
|
37
|
+
|
38
|
+
def ignores_fallbacks?
|
39
|
+
@ignores_fallbacks
|
40
|
+
end
|
41
|
+
|
42
|
+
def unwrapped
|
43
|
+
@value.merge(@value) { |k,v| v.unwrapped }
|
44
|
+
end
|
45
|
+
|
46
|
+
def merged_with_object(abstract_fallback)
|
47
|
+
require_not_ignoring_fallbacks
|
48
|
+
|
49
|
+
unless abstract_fallback.is_a?(Hocon::Impl::SimpleConfigObject)
|
50
|
+
raise ConfigBugError, "should not be reached (merging non-SimpleConfigObject)"
|
51
|
+
end
|
52
|
+
|
53
|
+
fallback = abstract_fallback
|
54
|
+
changed = false
|
55
|
+
all_resolved = true
|
56
|
+
merged = {}
|
57
|
+
all_keys = key_set.union(fallback.key_set)
|
58
|
+
all_keys.each do |key|
|
59
|
+
first = @value[key]
|
60
|
+
second = fallback.value[key]
|
61
|
+
kept =
|
62
|
+
if first.nil?
|
63
|
+
second
|
64
|
+
elsif second.nil?
|
65
|
+
first
|
66
|
+
else
|
67
|
+
first.with_fallback(second)
|
68
|
+
end
|
69
|
+
merged[key] = kept
|
70
|
+
|
71
|
+
if first != kept
|
72
|
+
changed = true
|
73
|
+
end
|
74
|
+
|
75
|
+
if kept.resolve_status == Hocon::Impl::ResolveStatus::UNRESOLVED
|
76
|
+
all_resolved = false
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
new_resolve_status = Hocon::Impl::ResolveStatus.from_boolean(all_resolved)
|
81
|
+
new_ignores_fallbacks = fallback.ignores_fallbacks?
|
82
|
+
|
83
|
+
if changed
|
84
|
+
Hocon::Impl::SimpleConfigObject.new(merge_origins([self, fallback]),
|
85
|
+
merged, new_resolve_status,
|
86
|
+
new_ignores_fallbacks)
|
87
|
+
elsif (new_resolve_status != resolve_status) || (new_ignores_fallbacks != ignores_fallbacks?)
|
88
|
+
newCopy(new_resolve_status, origin, new_ignores_fallbacks)
|
89
|
+
else
|
90
|
+
self
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
def render_value_to_sb(sb, indent_size, at_root, options)
|
95
|
+
if empty?
|
96
|
+
sb << "{}"
|
97
|
+
else
|
98
|
+
outer_braces = options.json? || !at_root
|
99
|
+
|
100
|
+
inner_indent =
|
101
|
+
if outer_braces
|
102
|
+
sb << "{"
|
103
|
+
if options.formatted?
|
104
|
+
sb << "\n"
|
105
|
+
end
|
106
|
+
indent_size + 1
|
107
|
+
else
|
108
|
+
indent_size
|
109
|
+
end
|
110
|
+
|
111
|
+
separator_count = 0
|
112
|
+
key_set.each do |k|
|
113
|
+
v = @value[k]
|
114
|
+
|
115
|
+
if options.origin_comments?
|
116
|
+
indent(sb, inner_indent, options)
|
117
|
+
sb << "# "
|
118
|
+
sb << v.origin.description
|
119
|
+
sb << "\n"
|
120
|
+
end
|
121
|
+
if options.comments?
|
122
|
+
v.origin.comments.each do |comment|
|
123
|
+
indent(sb, inner_indent, options)
|
124
|
+
sb << "#"
|
125
|
+
if !comment.start_with?(" ")
|
126
|
+
sb << " "
|
127
|
+
end
|
128
|
+
sb << comment
|
129
|
+
sb << "\n"
|
130
|
+
end
|
131
|
+
end
|
132
|
+
indent(sb, inner_indent, options)
|
133
|
+
v.render_to_sb(sb, inner_indent, false, k.to_s, options)
|
134
|
+
|
135
|
+
if options.formatted?
|
136
|
+
if options.json?
|
137
|
+
sb << ","
|
138
|
+
separator_count = 2
|
139
|
+
else
|
140
|
+
separator_count = 1
|
141
|
+
end
|
142
|
+
sb << "\n"
|
143
|
+
else
|
144
|
+
sb << ","
|
145
|
+
separator_count = 1
|
146
|
+
end
|
147
|
+
end
|
148
|
+
# chop last commas/newlines
|
149
|
+
# couldn't figure out a better way to chop characters off of the end of
|
150
|
+
# the StringIO. This relies on making sure that, prior to returning the
|
151
|
+
# final string, we take a substring that ends at sb.pos.
|
152
|
+
sb.pos = sb.pos - separator_count
|
153
|
+
|
154
|
+
if outer_braces
|
155
|
+
if options.formatted?
|
156
|
+
sb << "\n" # put a newline back
|
157
|
+
if outer_braces
|
158
|
+
indent(sb, indent_size, options)
|
159
|
+
end
|
160
|
+
end
|
161
|
+
sb << "}"
|
162
|
+
end
|
163
|
+
end
|
164
|
+
if at_root && options.formatted?
|
165
|
+
sb << "\n"
|
166
|
+
end
|
167
|
+
end
|
168
|
+
|
169
|
+
|
170
|
+
def key_set
|
171
|
+
Set.new(@value.keys)
|
172
|
+
end
|
173
|
+
|
174
|
+
def empty?
|
175
|
+
@value.empty?
|
176
|
+
end
|
177
|
+
|
178
|
+
end
|
@@ -0,0 +1,174 @@
|
|
1
|
+
require 'uri'
|
2
|
+
require 'hocon/impl'
|
3
|
+
require 'hocon/impl/origin_type'
|
4
|
+
|
5
|
+
class Hocon::Impl::SimpleConfigOrigin
|
6
|
+
|
7
|
+
MERGE_OF_PREFIX = "merge of "
|
8
|
+
|
9
|
+
def self.new_file(file_path)
|
10
|
+
url = URI.join('file:///', file_path)
|
11
|
+
self.new(file_path, -1, -1,
|
12
|
+
Hocon::Impl::OriginType::FILE,
|
13
|
+
url, nil)
|
14
|
+
end
|
15
|
+
|
16
|
+
def self.new_simple(description)
|
17
|
+
self.new(description, -1, -1,
|
18
|
+
Hocon::Impl::OriginType::GENERIC,
|
19
|
+
nil, nil)
|
20
|
+
end
|
21
|
+
|
22
|
+
def self.remove_merge_of_prefix(desc)
|
23
|
+
if desc.start_with?(MERGE_OF_PREFIX)
|
24
|
+
desc = desc[MERGE_OF_PREFIX.length, desc.length - 1]
|
25
|
+
end
|
26
|
+
desc
|
27
|
+
end
|
28
|
+
|
29
|
+
def self.merge_two(a, b)
|
30
|
+
merged_desc = nil
|
31
|
+
merged_start_line = nil
|
32
|
+
merged_end_line = nil
|
33
|
+
merged_comments = nil
|
34
|
+
|
35
|
+
merged_type =
|
36
|
+
if a.origin_type == b.origin_type
|
37
|
+
a.origin_type
|
38
|
+
else
|
39
|
+
Hocon::Impl::OriginType.GENERIC
|
40
|
+
end
|
41
|
+
|
42
|
+
# first use the "description" field which has no line numbers
|
43
|
+
# cluttering it.
|
44
|
+
a_desc = remove_merge_of_prefix(a.description)
|
45
|
+
b_desc = remove_merge_of_prefix(b.description)
|
46
|
+
|
47
|
+
if a_desc == b_desc
|
48
|
+
merged_desc = a_desc
|
49
|
+
if a.line_number < 0
|
50
|
+
merged_start_line = b.line_number
|
51
|
+
elsif b.line_number < 0
|
52
|
+
merged_start_line = a.line_number
|
53
|
+
else
|
54
|
+
merged_start_line = [a.line_number, b.line_number].min
|
55
|
+
end
|
56
|
+
|
57
|
+
merged_end_line = [a.end_line_number, b.end_line_number].max
|
58
|
+
else
|
59
|
+
# this whole merge song-and-dance was intended to avoid this case
|
60
|
+
# whenever possible, but we've lost. Now we have to lose some
|
61
|
+
# structured information and cram into a string.
|
62
|
+
#
|
63
|
+
# description() method includes line numbers, so use it instead
|
64
|
+
# of description field.
|
65
|
+
a_full = remove_merge_of_prefix(a.description)
|
66
|
+
b_full = remove_merge_of_prefix(b.description)
|
67
|
+
|
68
|
+
merged_desc = "#{MERGE_OF_PREFIX}#{a_full},#{b_full}"
|
69
|
+
merged_start_line = -1
|
70
|
+
merged_end_line = -1
|
71
|
+
end
|
72
|
+
|
73
|
+
merged_url =
|
74
|
+
if Hocon::Impl::ConfigImplUtil.equals_handling_nil?(a.url_or_nil, b.url_or_nil)
|
75
|
+
a.url_or_nil
|
76
|
+
else
|
77
|
+
nil
|
78
|
+
end
|
79
|
+
|
80
|
+
if Hocon::Impl::ConfigImplUtil.equals_handling_nil?(a.comments_or_nil, b.comments_or_nil)
|
81
|
+
merged_comments = a.comments_or_nil
|
82
|
+
else
|
83
|
+
merged_comments = []
|
84
|
+
if a.comments_or_nil
|
85
|
+
merged_comments.concat(a.comments_or_nil)
|
86
|
+
end
|
87
|
+
if b.comments_or_nil
|
88
|
+
merged_comments.concat(b.comments_or_nil)
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
Hocon::Impl::SimpleConfigOrigin.new(
|
93
|
+
merged_desc, merged_start_line, merged_end_line,
|
94
|
+
merged_type, merged_url, merged_comments)
|
95
|
+
end
|
96
|
+
|
97
|
+
def self.merge_origins(stack)
|
98
|
+
if stack.empty?
|
99
|
+
raise ConfigBugError, "can't merge empty list of origins"
|
100
|
+
elsif stack.length == 1
|
101
|
+
stack[0]
|
102
|
+
elsif stack.length == 2
|
103
|
+
merge_two(stack[0], stack[1])
|
104
|
+
else
|
105
|
+
remaining = stack.clone
|
106
|
+
while remaining.length > 2
|
107
|
+
merged = merge_three(remaining[0], remaining[1], remaining[2])
|
108
|
+
remaining.pop
|
109
|
+
remaining.pop
|
110
|
+
remaining.pop
|
111
|
+
end
|
112
|
+
|
113
|
+
# should be down to either 1 or 2
|
114
|
+
merge_origins(remaining)
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
|
119
|
+
def initialize(description, line_number, end_line_number,
|
120
|
+
origin_type, url, comments)
|
121
|
+
if !description
|
122
|
+
raise ArgumentError, "description may not be nil"
|
123
|
+
end
|
124
|
+
|
125
|
+
@description = description
|
126
|
+
@line_number = line_number
|
127
|
+
@end_line_number = end_line_number
|
128
|
+
@origin_type = origin_type
|
129
|
+
@url_or_nil = url
|
130
|
+
@comments_or_nil = comments
|
131
|
+
end
|
132
|
+
|
133
|
+
attr_reader :description, :line_number, :end_line_number, :origin_type,
|
134
|
+
:url_or_nil, :comments_or_nil
|
135
|
+
|
136
|
+
def set_line_number(line_number)
|
137
|
+
if (line_number == @line_number) and
|
138
|
+
(line_number == @end_line_number)
|
139
|
+
self
|
140
|
+
else
|
141
|
+
Hocon::Impl::SimpleConfigOrigin.new(
|
142
|
+
@description, line_number, line_number,
|
143
|
+
@origin_type, @url_or_nil, @comments_or_nil)
|
144
|
+
end
|
145
|
+
end
|
146
|
+
|
147
|
+
def set_comments(comments)
|
148
|
+
if Hocon::Impl::ConfigImplUtil.equals_handling_nil?(comments, @comments_or_nil)
|
149
|
+
self
|
150
|
+
else
|
151
|
+
Hocon::Impl::SimpleConfigOrigin.new(
|
152
|
+
@description, @line_number, @end_line_number,
|
153
|
+
@origin_type, @url_or_nil, comments)
|
154
|
+
end
|
155
|
+
end
|
156
|
+
|
157
|
+
def prepend_comments(comments)
|
158
|
+
if Hocon::Impl::ConfigImplUtil.equals_handling_nil?(comments, @comments_or_nil)
|
159
|
+
self
|
160
|
+
elsif @comments_or_nil.nil?
|
161
|
+
set_comments(comments)
|
162
|
+
else
|
163
|
+
merged = []
|
164
|
+
merged.concat(comments)
|
165
|
+
merged.concat(@comments_or_nil)
|
166
|
+
set_comments(merged)
|
167
|
+
end
|
168
|
+
end
|
169
|
+
|
170
|
+
def comments
|
171
|
+
@comments_or_nil || []
|
172
|
+
end
|
173
|
+
|
174
|
+
end
|