hcl-rb 0.1.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.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 5112fcbbb036a494cf40ca905f073e0390d49d9c2bc6a09631b6f565cc42b1a1
4
+ data.tar.gz: 04bca627bf636706076f49c19b6bfbf8a2146b690c2eed1e76f6ee8d8be6d917
5
+ SHA512:
6
+ metadata.gz: bf9fd4abdf12d5467a38079d41b4c002bd01cc00d9f1f264d79d7237649f67179b58a352d98efe82037262dfbd4e4a17d463c62843dda0e90cc0f0434e9bbf1c
7
+ data.tar.gz: c4a391a9ef43ab2c9f18fbd1aaa5816162e2640e263b7e2418e7b5196b77f39fbf8eb71d49aaa833eea37a982d3f60da9d0f4853203cced9dd89e2f666a4a834
data/bin/console ADDED
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "bundler/setup"
4
+ require "hcl"
5
+
6
+ # You can add fixtures and/or initialization code here to make experimenting
7
+ # with your gem easier. You can also use a different console, if you like.
8
+
9
+ # (If you use this, don't forget to add pry to your Gemfile!)
10
+ # require "pry"
11
+ # Pry.start
12
+
13
+ require "irb"
14
+ IRB.start(__FILE__)
data/bin/setup ADDED
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle install
7
+
8
+ # Do any other automated setup that you need to do here
@@ -0,0 +1,28 @@
1
+ class HCL::ASTVisitor
2
+ @@types = [
3
+ :document,
4
+ :kv_key,
5
+ :string,
6
+ :key_string,
7
+ :object,
8
+ :comment,
9
+ :integer,
10
+ :boolean,
11
+ :float,
12
+ :heredoc,
13
+ :list,
14
+ :object
15
+ ]
16
+
17
+ def visit(ast)
18
+ return nil unless ast
19
+
20
+ raise "AST object must be Hash" unless Hash === ast
21
+
22
+ type = @@types.find { |type| ast.key? type }
23
+ raise "Couldn't determine AST object type" unless type
24
+
25
+ method_name = "visit_#{type}"
26
+ send(method_name, ast)
27
+ end
28
+ end
@@ -0,0 +1,469 @@
1
+ class HCL::Decoder < HCL::ASTVisitor
2
+ def initialize
3
+ end
4
+
5
+ def decode(ast)
6
+ visit(ast)
7
+ end
8
+
9
+ def visit_document(ast)
10
+ doc = ast[:document]
11
+ if Array === doc
12
+ visit({object: doc})
13
+ else
14
+ doc
15
+ end || {}
16
+ end
17
+
18
+ def conv_key(key)
19
+ if Hash === key
20
+ if key.key? :string
21
+ key[:string]
22
+ elsif Hash === key[:key]
23
+ key[:key][:string]
24
+ else
25
+ key[:key]
26
+ end
27
+ else
28
+ key
29
+ end.to_s
30
+ end
31
+
32
+ def visit_kv_key(ast)
33
+ key = conv_key(ast[:kv_key])
34
+ value = visit(ast[:value])
35
+
36
+ extra_keys = ast[:keys]
37
+ if extra_keys.nil? || extra_keys == []
38
+ { key => value }
39
+ else
40
+ rest = extra_keys.reverse.inject(value) do |h, k|
41
+ { conv_key(k) => h }
42
+ end
43
+ { key => rest }
44
+ end
45
+ end
46
+
47
+ def newline?(b)
48
+ b == "\n".ord || b == "\r".ord
49
+ end
50
+
51
+ SIMPLE_ESCAPES = [
52
+ ["a", "\a"],
53
+ ["b", "\b"],
54
+ ["f", "\f"],
55
+ ["n", "\n"],
56
+ ["r", "\r"],
57
+ ["t", "\t"],
58
+ ["v", "\v"],
59
+ ["\\", "\\"],
60
+ ["'", "'"],
61
+ ["\"", "\""]
62
+ ].map { |a, b| [a.ord, b] }.to_h
63
+
64
+ def undump(str)
65
+ chunks = nil
66
+ chunk_start = 0
67
+ braces = 0
68
+ dollar = false
69
+ hil = false
70
+ bytes = str.bytes
71
+
72
+ io = StringIO.new(str)
73
+ b = io.getbyte
74
+
75
+ until b.nil?
76
+ if braces == 0 and dollar and b == "{".ord
77
+ braces += 1
78
+ hil = true
79
+ elsif braces > 0 and b == "{".ord
80
+ braces += 1
81
+ end
82
+
83
+ if braces > 0 and b == "}".ord
84
+ braces -= 1
85
+ end
86
+
87
+ dollar = false
88
+ if braces == 0 and b == "$".ord
89
+ dollar = true
90
+ end
91
+
92
+ if b == "\\".ord
93
+ chunks = [] if chunks.nil?
94
+
95
+ if chunk_start != io.pos-1
96
+ chunks << str[chunk_start..io.pos-2]
97
+ end
98
+
99
+ s = nil
100
+
101
+ b = io.getbyte
102
+
103
+ escape = SIMPLE_ESCAPES[b]
104
+
105
+ if escape != nil
106
+ b = io.getbyte
107
+ s = escape
108
+ elsif newline? b
109
+ raise "string literal not terminated" if braces == 0
110
+ b = io.getbyte while newline? b
111
+ s = "\n"
112
+ elsif b == "x".ord
113
+ c1 = nil
114
+ c2 = nil
115
+
116
+ b = io.getbyte || 0
117
+
118
+ c1 = b.chr.hex
119
+ raise "invalid hexadecimal escape sequence" if c1.zero?
120
+
121
+ b = io.getbyte || 0
122
+
123
+ c2 = b.chr.hex
124
+ raise "invalid hexadecimal escape sequence" if c2.zero?
125
+
126
+ b = io.getbyte
127
+ s = (c1*16 + c2).to_s(16)
128
+ elsif b == "u".ord || b == "U".ord
129
+ size = if b == "U".ord then 8 else 4 end
130
+
131
+ b = io.getbyte # Skip "u".
132
+
133
+ codepoint = 0
134
+ hexdigits = 0
135
+ while hexdigits < size
136
+ hex = b && b.chr.hex
137
+ raise "UTF-8 escape sequence contained invalid character:(#{b.chr})" unless hex
138
+
139
+ hexdigits += 1
140
+ codepoint = codepoint * 16 + hex
141
+
142
+ raise "UTF-8 escape sequence too large" if codepoint > 0x10FFFF
143
+
144
+ b = io.getbyte
145
+ end
146
+
147
+ s = codepoint.chr(Encoding::UTF_8)
148
+ else
149
+ cb = b && b.chr.to_i
150
+
151
+ raise "invalid escape sequence" if cb.nil?
152
+
153
+ b = io.getbyte
154
+
155
+ if b != nil
156
+ c2 = b.chr.to_i
157
+
158
+ if b == "0".ord || c2 != 0
159
+ cb = 10 * cb + c2
160
+ b = io.getbyte
161
+
162
+ if b != nil
163
+ c3 = b.chr.to_i
164
+
165
+ if b == "0".ord || c3 != 0
166
+ cb = 10 * cb + c3
167
+
168
+ raise "invalid decimal escape sequence" if cb > 255
169
+
170
+ b = io.getbyte
171
+ end
172
+ end
173
+ end
174
+ end
175
+
176
+ s = cb.chr
177
+ end
178
+
179
+ chunks << s if s != nil
180
+
181
+ chunk_start = if b.nil? then io.pos else io.pos - 1 end
182
+ elsif b.nil? || (newline? b && braces == 0)
183
+ raise "unfinished string"
184
+ else
185
+ b = io.getbyte
186
+ end
187
+ end
188
+
189
+ raise "expected terminating brace" if b.nil? and braces != 0
190
+
191
+ if chunks != nil
192
+ # Put last chunk into buffer.
193
+ if chunk_start != io.pos
194
+ chunks << str[chunk_start..io.pos-1]
195
+ end
196
+
197
+ chunks.join
198
+ else
199
+ # There were no escape sequences.
200
+ str
201
+ end
202
+ end
203
+
204
+ def visit_string(ast)
205
+ string = ast[:string]
206
+ if string == []
207
+ ""
208
+ else
209
+ undump(string.to_s)
210
+ end
211
+ end
212
+
213
+ def visit_key_string(ast)
214
+ ast[:key_string].to_s
215
+ end
216
+
217
+ def visit_comment(ast)
218
+ end
219
+
220
+ def visit_integer(ast)
221
+ ast[:integer].to_i
222
+ end
223
+
224
+ def visit_boolean(ast)
225
+ case ast[:boolean]
226
+ when "true" then
227
+ true
228
+ when "false"
229
+ false
230
+ else raise "unknown boolean #{ast}"
231
+ end
232
+ end
233
+
234
+ def visit_float(ast)
235
+ ast[:float].to_f
236
+ end
237
+
238
+ def visit_heredoc(ast)
239
+ doc = ast[:heredoc]
240
+ tag = doc[:tag]
241
+ content = doc[:doc].to_s.gsub(/#{tag}$/, "")[1..-1]
242
+
243
+ case doc[:backticks].to_s
244
+ when "<<" then
245
+ content
246
+ when "<<-" then
247
+ # We need to unindent each line based on the indentation level of the marker
248
+ lines = content.split("\n")
249
+ indent = lines.last
250
+
251
+ indented = true
252
+ lines.each do |line|
253
+ if indent.size > line.size
254
+ indented = false
255
+ break
256
+ end
257
+
258
+ prefix_found = line.delete_prefix(indent) != line
259
+
260
+ unless prefix_found
261
+ indented = false
262
+ break
263
+ end
264
+ end
265
+
266
+ # If all lines are not at least as indented as the terminating mark, return the
267
+ # heredoc as is, but trim the leading space from the marker on the final line.
268
+ unless indented
269
+ return content.sub(/\s+\Z/, "") + "\n"
270
+ end
271
+
272
+ unindented_lines = []
273
+ lines.each do |line|
274
+ unindented_lines << line.delete_prefix(indent)
275
+ end
276
+ unindented_lines.join("\n")
277
+ else raise "unknown backticks #{backticks}"
278
+ end
279
+ end
280
+
281
+ def visit_list(ast)
282
+ return [] if ast[:list].nil?
283
+
284
+ list = if Hash === ast[:list]
285
+ [ast[:list]]
286
+ else
287
+ ast[:list]
288
+ end
289
+
290
+ list.map do |a|
291
+ value = a[:value]
292
+ value = if Array === value
293
+ it = value.reject { |i| i.key? :comment }
294
+ raise "extra list value" unless it.size == 1
295
+ it.first
296
+ else
297
+ value
298
+ end
299
+ visit(value)
300
+ end.reject(&:nil?)
301
+ end
302
+
303
+ def recurse_objects(a, b, keys)
304
+ a_child = a.dup
305
+ b_child = b.dup
306
+
307
+ keys.each do |key|
308
+ a_child = a_child[key]
309
+ b_child = b_child[key]
310
+ end
311
+
312
+ return a_child, b_child
313
+ end
314
+
315
+ def hash_or_array(obj)
316
+ Hash === obj or Array === obj
317
+ end
318
+
319
+ def common_nested_keys(a, b)
320
+ a_child = a
321
+ b_child = b
322
+
323
+ keys = []
324
+ finished = false
325
+
326
+ until finished
327
+ break if Array === a_child or Array === b_child
328
+ break if a_child.nil? or b_child.nil?
329
+ break if a_child.length > 1 or b_child.length > 1
330
+
331
+ a_child.each do |k, v|
332
+ finished = true if b_child[k].nil?
333
+ finished = true if (not hash_or_array(a_child[k]) or not hash_or_array(b_child[k]))
334
+ break if finished
335
+
336
+ keys << k
337
+ a_child = a_child[k]
338
+ b_child = b_child[k]
339
+ break
340
+ end
341
+ end
342
+
343
+ return keys, a_child, b_child
344
+ end
345
+
346
+ def objects_share_keys?(a, b, keys)
347
+ b.each_key do |k|
348
+ return true if a.key? k
349
+ end
350
+
351
+ false
352
+ end
353
+
354
+ def set_object_nested(this, value, keys)
355
+ raise "no keys" unless keys.size > 0
356
+
357
+ if keys.size == 1
358
+ this[keys.last] = value
359
+ return
360
+ end
361
+
362
+ this_parent = this[keys.first]
363
+
364
+ keys.drop(1).each do |key|
365
+ this_parent = this_parent[key]
366
+ end
367
+
368
+ this_parent[keys.last] = value
369
+ end
370
+
371
+ def expand_objects(this, other, keys)
372
+ this_child, other_child = recurse_objects(this, other, keys)
373
+
374
+ set_object_nested(this, [this_child, other_child], keys)
375
+ end
376
+
377
+ def merge_object_lists(this, other, keys)
378
+ this_child, other_child = recurse_objects(this, other, keys)
379
+
380
+ if Hash === this_child && Array === other_child
381
+ object = this_child
382
+ list = other_child
383
+ elsif Array === this_child && Hash === other_child
384
+ object = other_child
385
+ list = this_child
386
+ else
387
+ raise "not both lists" unless Array === this_child && Array == other_child
388
+ end
389
+
390
+ if list.nil?
391
+ this_child.each_value do |v|
392
+ other_child << v
393
+ end
394
+
395
+ set_object_nested(this, other_child, keys)
396
+ else
397
+ list << object
398
+ set_object_nested(this, list, keys)
399
+ end
400
+ end
401
+
402
+ def merge_objects(this, other)
403
+ raise "merge_objects was called on the same object" if this == other
404
+ raise "merge_objects was called with non-objects" unless Hash === this && Hash === other
405
+
406
+ other.each do |k, v|
407
+ tmp = this[k]
408
+ if tmp != nil
409
+ if Hash === tmp and Hash === v
410
+ merge_objects(tmp, v)
411
+ else
412
+ this[k] = v
413
+ end
414
+ else
415
+ this[k] = v
416
+ end
417
+ end
418
+ end
419
+
420
+ def visit_object(ast)
421
+ object = ast[:object]
422
+ return {} if object == ""
423
+
424
+ pairs = ast[:object].map { |a| visit(a) }.reject(&:nil?)
425
+
426
+ pairs.inject({}) do |result, object|
427
+ first_key = object.sort.first[0]
428
+ existing = result[first_key]
429
+ value = object[first_key]
430
+ expand = false
431
+
432
+ if existing != nil
433
+ if Array === existing
434
+ existing << value
435
+ else
436
+ if Hash === existing
437
+ if not Hash === existing
438
+ expand = true
439
+ else
440
+ keys, a, b = common_nested_keys(existing, value)
441
+ if Array === a or Array === b
442
+ merge_object_lists(existing, value, keys)
443
+ elsif objects_share_keys?(a, b, keys)
444
+ if keys.size == 0
445
+ expand = true
446
+ else
447
+ expand_objects(existing, value, keys)
448
+ end
449
+ else
450
+ merge_objects(existing, value)
451
+ result[first_key] = existing
452
+ end
453
+ end
454
+ else
455
+ expand = true
456
+ end
457
+
458
+ if expand
459
+ result[first_key] = [existing, value]
460
+ end
461
+ end
462
+ else
463
+ result[first_key] = value
464
+ end
465
+
466
+ result
467
+ end
468
+ end
469
+ end
@@ -0,0 +1,26 @@
1
+ class HCL::Generator
2
+ attr_reader :body, :doc
3
+
4
+ def initialize(doc)
5
+ # Ensure all the to_hcl methods are injected into the base Ruby classes
6
+ # used by HCL.
7
+ self.class.inject!
8
+
9
+ @doc = doc
10
+ @body = doc.to_hcl
11
+
12
+ return @body
13
+ end
14
+
15
+ # Whether or not the injections have already been done.
16
+ @@injected = false
17
+ # Inject to_hcl methods into the Ruby classes used by HCL (booleans,
18
+ # String, Numeric, Array). You can add to_hcl methods to your own classes
19
+ # to allow them to be easily serialized by the generator (and it will shout
20
+ # if something doesn't have a to_hcl method).
21
+ def self.inject!
22
+ return if @@injected
23
+ require 'hcl/monkey_patch'
24
+ @@injected = true
25
+ end
26
+ end
@@ -0,0 +1,91 @@
1
+ module HCL
2
+ def self.escape_key(key)
3
+ str = key.to_s
4
+ pos = str =~ /[^a-zA-Z0-9_\-]/
5
+
6
+ return str if pos.nil?
7
+
8
+ str.dump
9
+ end
10
+ end
11
+
12
+ class Object
13
+ def hcl_object?
14
+ self.kind_of?(Hash)
15
+ end
16
+ def hcl_list?
17
+ self.kind_of?(Array) && self.first.hcl_object?
18
+ end
19
+ end
20
+
21
+ class Hash
22
+ def to_hcl(indent = 0)
23
+ return "" if self.empty?
24
+ hcl = ""
25
+ spaces = " " * indent
26
+
27
+ self.each do |k, v|
28
+ next if v.hcl_object?
29
+ next if v.hcl_list? and v.size > 0 and v.first.hcl_object?
30
+
31
+ hcl << spaces
32
+ hcl << HCL.escape_key(k) << " = "
33
+ hcl << v.to_hcl(indent + 4)
34
+ hcl << "\n"
35
+ end
36
+
37
+ self.each do |k, v|
38
+ if v.hcl_object?
39
+ key = HCL.escape_key(k)
40
+ hcl << spaces
41
+ hcl << key << " {\n"
42
+ hcl << v.to_hcl(indent + 4)
43
+ hcl << spaces << "}\n"
44
+ end
45
+ if v.hcl_list? and v.size > 0 and v.first.hcl_object?
46
+ key = HCL.escape_key(k)
47
+ hcl << spaces
48
+ hcl << key << " = ["
49
+ v.each do |i|
50
+ if i.hcl_object?
51
+ hcl << "\n" << spaces << "{\n"
52
+ else
53
+ hcl << spaces
54
+ end
55
+ hcl << i.to_hcl(indent + 4)
56
+ if i.hcl_object?
57
+ hcl << spaces << "},\n"
58
+ end
59
+ end
60
+ hcl << spaces << "]\n"
61
+ end
62
+ end
63
+
64
+ hcl
65
+ end
66
+ end
67
+ class Array
68
+ def to_hcl(indent = 0)
69
+ "[" + self.map {|v| v.to_hcl(indent) }.join(", ") + "]"
70
+ end
71
+ end
72
+ class TrueClass
73
+ def to_hcl(indent = 0); "true"; end
74
+ end
75
+ class FalseClass
76
+ def to_hcl(indent = 0); "false"; end
77
+ end
78
+ class String
79
+ def to_hcl(indent = 0); self.inspect; end
80
+ end
81
+ class Numeric
82
+ def to_hcl(indent = 0); self.to_s; end
83
+ end
84
+ class Symbol
85
+ def to_hcl(indent = 0); HCL.escape_key(self.to_s); end
86
+ end
87
+ class DateTime
88
+ def to_hcl(indent = 0)
89
+ self.to_time.utc.strftime("%Y-%m-%dT%H:%M:%SZ")
90
+ end
91
+ end
data/lib/hcl/parser.rb ADDED
@@ -0,0 +1,17 @@
1
+ class HCL::Parser
2
+ def initialize(src)
3
+ @src = src
4
+ @parslet = HCL::Parslet.new
5
+ end
6
+
7
+ def parse
8
+ ast = begin
9
+ @parslet.parse(@src)
10
+ rescue Parslet::ParseFailed => error
11
+ puts error.parse_failure_cause.ascii_tree
12
+ raise
13
+ end
14
+
15
+ HCL::Decoder.new.decode(ast)
16
+ end
17
+ end
@@ -0,0 +1,162 @@
1
+ require "parslet"
2
+
3
+ class HCL::Parslet < Parslet::Parser
4
+ rule(:document) {
5
+ all_space >>
6
+ ((key_value >> all_space) | comment_line).repeat.as(:document) >>
7
+ all_space
8
+ }
9
+ root :document
10
+
11
+ rule(:value) {
12
+ boolean.as(:boolean) |
13
+ list |
14
+ object |
15
+ float.as(:float) |
16
+ scientific.as(:float) |
17
+ integer.as(:integer) |
18
+ string |
19
+ key.as(:key_string) |
20
+ heredoc.as(:heredoc)
21
+ }
22
+
23
+ rule(:trailing_comma?) {
24
+ (all_space >> str(",").maybe).maybe
25
+ }
26
+
27
+ rule (:dood) {
28
+ }
29
+
30
+ rule(:object) {
31
+ str("{") >> list_comments >> all_space >>
32
+ ( ( key_value >> list_comments >> all_space ).repeat ).maybe.as(:object) >>
33
+ str("}") >> list_comments
34
+ }
35
+
36
+ rule(:sign) { str("-") }
37
+ rule(:sign?) { sign.maybe }
38
+
39
+ rule(:integer) {
40
+ str("0") | sign? >>
41
+ (match["1-9"] >> match["0-9"].repeat)
42
+ }
43
+
44
+ rule(:exponent) {
45
+ match["eE"] >> match["+\\-"].maybe >> match["0-9"].repeat
46
+ }
47
+
48
+ rule(:scientific) {
49
+ sign? >>
50
+ (match["0-9"] >> match["0-9"].repeat) >> exponent
51
+ }
52
+
53
+ rule(:float) {
54
+ sign? >>
55
+ (match["0-9"] >> match["0-9"].repeat).maybe >> str(".") >>
56
+ (match["0-9"] >> match["0-9"].repeat) >> exponent.maybe
57
+ }
58
+
59
+ rule(:key) {
60
+ string | (match["\\w_\\-"] >> match["\\w\\d_\\-.:"].repeat)
61
+ }
62
+
63
+ rule(:key_value) {
64
+ space >> key.as(:kv_key) >> space >>
65
+ ((key.as(:key) >> space).repeat.as(:keys) >> object.as(:value) | (str("=") >> space >> value.as(:value))) >> trailing_comma?
66
+ }
67
+
68
+ rule (:sq_string) {
69
+ str("'") >> match["^'\\n"].repeat.maybe.as(:string) >> str("'")
70
+ }
71
+
72
+ rule (:string_inner) {
73
+ match["^\"\\\\"] | escape
74
+ }
75
+
76
+ rule(:dq_string) {
77
+ str('"') >> (hil | (str("${").absent? >> string_inner)).repeat.as(:string) >> str('"')
78
+ }
79
+
80
+ rule(:string) {
81
+ sq_string | dq_string
82
+ }
83
+
84
+ rule(:hil_inner) {
85
+ brace | match["^\\\\}"] | escape
86
+ }
87
+
88
+ rule(:hil) {
89
+ str("${") >> hil_inner.repeat.maybe >> str("}")
90
+ }
91
+
92
+ rule (:brace) {
93
+ str("{") >> hil_inner.repeat.maybe >> str("}")
94
+ }
95
+
96
+ rule(:heredoc) {
97
+ space >>
98
+ backticks.as(:backticks) >>
99
+ tag.capture(:tag).as(:tag) >> doc.as(:doc)
100
+ }
101
+
102
+ rule(:hex) {
103
+ match["0-9a-fA-F"]
104
+ }
105
+
106
+ rule(:escape) {
107
+ str("\\") >> (match["bfnrt\"\\\\"] |
108
+ (str("u") >> hex.repeat(4,4)) |
109
+ (str("U") >> hex.repeat(8,8)))
110
+ }
111
+
112
+ # the tag that delimits the heredoc
113
+ rule(:tag) { match['\\w\\d'].repeat(1) }
114
+ # the doc itself, ends when tag is found at start of line
115
+ rule(:doc) { gobble_eol >> doc_line }
116
+ # a doc_line is either the stop tag followed by nothing
117
+ # or just any kind of line.
118
+ rule(:doc_line) {
119
+ ((space >> end_tag).absent? >> gobble_eol).repeat >> space >> end_tag
120
+ }
121
+ rule(:end_tag) { dynamic { |s,c| str(c.captures[:tag]) } }
122
+ # eats anything until an end of line is found
123
+ rule(:gobble_eol) { (newline.absent? >> any).repeat >> newline }
124
+
125
+ rule(:backticks) { str('<<') >> str("-").maybe }
126
+
127
+ rule(:boolean) { str("true") | str("false") }
128
+
129
+ rule(:space) { match[" \t"].repeat }
130
+ rule(:all_space) { match[" \t\r\n"].repeat }
131
+ rule(:newline) { str("\r").maybe >> str("\n") | str("\r") >> str("\n").maybe }
132
+ rule(:eof) { any.absent? }
133
+ rule(:newline_or_eof) { newline | eof }
134
+
135
+ rule(:comment_line) { comment >> all_space }
136
+ rule(:single_comment) { (str("#") | str("//")) >> match["^\n"].repeat >> newline_or_eof }
137
+ rule(:multiline_comment) { str("/*") >> (str("*/").absent? >> any).repeat >> str("*/") }
138
+ rule(:comment) { (single_comment | multiline_comment).as(:comment) }
139
+
140
+ # Finding comments in multiline lists requires accepting a bunch of
141
+ # possible newlines and stuff before the comment
142
+ rule(:list_comments) { (all_space >> comment_line).repeat }
143
+
144
+ rule(:list) {
145
+ str("[") >> all_space >> list_comments >>
146
+ ( list_comments >> # Match any comments on first line
147
+ dood >>
148
+ (all_space >> str(",")).maybe >> # possible trailing comma
149
+ all_space >> list_comments # Grab any remaining comments just in case
150
+ ).maybe.as(:list) >> str("]")
151
+ }
152
+
153
+ rule (:dood) {
154
+ all_space >> (value >> list_comments).as(:value) >>
155
+ (
156
+ # Separator followed by any comments
157
+ all_space >> str(",") >> (list_comments >>
158
+ # Value followed by any comments
159
+ all_space >> value).as(:value) >> list_comments >> all_space
160
+ ).repeat
161
+ }
162
+ end
@@ -0,0 +1,3 @@
1
+ module HCL
2
+ VERSION = "0.1.0"
3
+ end
data/lib/hcl.rb ADDED
@@ -0,0 +1,16 @@
1
+ require "hcl/version"
2
+ require "hcl/ast_visitor"
3
+ require "hcl/decoder"
4
+ require "hcl/generator"
5
+ require "hcl/parser"
6
+ require "hcl/parslet"
7
+
8
+ module HCL
9
+ def self.load(source)
10
+ HCL::Parser.new(source).parse
11
+ end
12
+
13
+ def self.load_file(path)
14
+ HCL::Parser.new(File.read(path)).parse
15
+ end
16
+ end
metadata ADDED
@@ -0,0 +1,98 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: hcl-rb
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Ruin0x11
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2019-05-08 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: bundler
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '2.0'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '2.0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rake
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '10.0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '10.0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: parslet
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '1.8'
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '1.8'
55
+ description:
56
+ email:
57
+ - ipickering2@gmail.com
58
+ executables:
59
+ - console
60
+ - setup
61
+ extensions: []
62
+ extra_rdoc_files: []
63
+ files:
64
+ - bin/console
65
+ - bin/setup
66
+ - lib/hcl.rb
67
+ - lib/hcl/ast_visitor.rb
68
+ - lib/hcl/decoder.rb
69
+ - lib/hcl/generator.rb
70
+ - lib/hcl/monkey_patch.rb
71
+ - lib/hcl/parser.rb
72
+ - lib/hcl/parslet.rb
73
+ - lib/hcl/version.rb
74
+ homepage: https://www.github.com/Ruin0x11/hcl-rb
75
+ licenses:
76
+ - MIT
77
+ metadata:
78
+ source_code_uri: https://www.github.com/Ruin0x11/hcl-rb
79
+ post_install_message:
80
+ rdoc_options: []
81
+ require_paths:
82
+ - lib
83
+ required_ruby_version: !ruby/object:Gem::Requirement
84
+ requirements:
85
+ - - ">="
86
+ - !ruby/object:Gem::Version
87
+ version: '0'
88
+ required_rubygems_version: !ruby/object:Gem::Requirement
89
+ requirements:
90
+ - - ">="
91
+ - !ruby/object:Gem::Version
92
+ version: '0'
93
+ requirements: []
94
+ rubygems_version: 3.0.3
95
+ signing_key:
96
+ specification_version: 4
97
+ summary: A ruby parser for HCL (Hashicorp Configuration Language).
98
+ test_files: []