hcl-rb 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
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: []