rhcl 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,182 @@
1
+ class Rhcl::Parse
2
+ options no_result_var
3
+ rule
4
+ objectlist : objectitem
5
+ {
6
+ val[0]
7
+ }
8
+ | objectlist objectitem
9
+ {
10
+ val[0].deep_merge(val[1])
11
+ }
12
+
13
+ object: LEFTBRACE objectlist RIGHTBRACE
14
+ {
15
+ val[1]
16
+ }
17
+ | LEFTBRACE RIGHTBRACE
18
+ {
19
+ {}
20
+ }
21
+
22
+ objectitem: IDENTIFIER EQUAL number
23
+ {
24
+ {val[0] => val[2]}
25
+ }
26
+ | IDENTIFIER EQUAL BOOL
27
+ {
28
+ {val[0] => val[2]}
29
+ }
30
+ | IDENTIFIER EQUAL STRING
31
+ {
32
+ {val[0] => val[2]}
33
+ }
34
+ | IDENTIFIER EQUAL object
35
+ {
36
+ {val[0] => val[2]}
37
+ }
38
+ | IDENTIFIER EQUAL list
39
+ {
40
+ {val[0] => val[2]}
41
+ }
42
+ | block
43
+ {
44
+ val[0]
45
+ }
46
+
47
+ block: blockId object
48
+ {
49
+ {val[0] => val[1]}
50
+ }
51
+ | blockId block
52
+ {
53
+ {val[0] => val[1]}
54
+ }
55
+
56
+
57
+ blockId: IDENTIFIER
58
+ {
59
+ val[0]
60
+ }
61
+ | STRING
62
+ {
63
+ val[0]
64
+ }
65
+
66
+ list: LEFTBRACKET listitems RIGHTBRACKET
67
+ {
68
+ val[1]
69
+ }
70
+ | LEFTBRACKET RIGHTBRACKET
71
+ {
72
+ []
73
+ }
74
+
75
+ listitems: listitem
76
+ {
77
+ [val[0]]
78
+ }
79
+ | listitems COMMA listitem
80
+ {
81
+ val[0] + [val[2]]
82
+ }
83
+
84
+ listitem: number
85
+ {
86
+ val[0]
87
+ }
88
+ | STRING
89
+ {
90
+ val[0]
91
+ }
92
+
93
+ number: INTEGER
94
+ {
95
+ val[0]
96
+ }
97
+ | FLOAT
98
+ {
99
+ val[0]
100
+ }
101
+
102
+ ---- header
103
+
104
+ require 'strscan'
105
+
106
+ ---- inner
107
+
108
+ def initialize(obj)
109
+ src = obj.is_a?(IO) ? obj.read : obj.to_s
110
+ @ss = StringScanner.new(src)
111
+ end
112
+
113
+ def scan
114
+ tok = nil
115
+
116
+ until @ss.eos?
117
+ if (tok = @ss.scan /\s+/)
118
+ # nothing to do
119
+ elsif (tok = @ss.scan(/#/))
120
+ @ss.scan_until(/\n/)
121
+ elsif (tok = @ss.scan(%r|/|))
122
+ case @ss.getch
123
+ when '/'
124
+ @ss.scan_until(/(\n|\z)/)
125
+ when '*'
126
+ nested = 1
127
+
128
+ until nested.zero?
129
+ case @ss.scan_until(%r{(/\*|\*/|\z)})
130
+ when %r|/\*\z|
131
+ nested += 1
132
+ when %r|\*/\z|
133
+ nested -= 1
134
+ else
135
+ break
136
+ end
137
+ end
138
+ else
139
+ raise "comment expected, got '#{tok}'"
140
+ end
141
+ elsif (tok = @ss.scan(/-?\d+\.\d+/))
142
+ yield [:FLOAT, tok.to_f]
143
+ elsif (tok = @ss.scan(/-?\d+/))
144
+ yield [:INTEGER, tok.to_i]
145
+ elsif (tok = @ss.scan(/,/))
146
+ yield [:COMMA, tok]
147
+ elsif (tok = @ss.scan(/\=/))
148
+ yield [:EQUAL, tok]
149
+ elsif (tok = @ss.scan(/\[/))
150
+ yield [:LEFTBRACKET, tok]
151
+ elsif (tok = @ss.scan(/\]/))
152
+ yield [:RIGHTBRACKET, tok]
153
+ elsif (tok = @ss.scan(/\{/))
154
+ yield [:LEFTBRACE, tok]
155
+ elsif (tok = @ss.scan(/\}/))
156
+ yield [:RIGHTBRACE, tok]
157
+ elsif (tok = @ss.scan(/"/))
158
+ yield [:STRING, (@ss.scan_until(/("|\z)/) || '').sub(/"\z/, '')]
159
+ else
160
+ identifier = (@ss.scan_until(/(\s|\z)/) || '').sub(/\s\z/, '')
161
+ token_type = :IDENTIFIER
162
+
163
+ if ['true', 'false'].include?(identifier)
164
+ identifier = !!(identifier =~ /true/)
165
+ token_type = :BOOL
166
+ end
167
+
168
+ yield [token_type, identifier]
169
+ end
170
+ end
171
+
172
+ yield [false, '$end']
173
+ end
174
+ private :scan
175
+
176
+ def parse
177
+ yyparse self, :scan
178
+ end
179
+
180
+ def self.parse(obj)
181
+ self.new(obj).parse
182
+ end
@@ -0,0 +1,3 @@
1
+ module Rhcl
2
+ VERSION = '0.0.1'
3
+ end
@@ -0,0 +1,26 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'rhcl/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = 'rhcl'
8
+ spec.version = Rhcl::VERSION
9
+ spec.authors = ['Genki Sugawara']
10
+ spec.email = ['sgwr_dts@yahoo.co.jp']
11
+ spec.summary = %q{Pure Ruby HCL parser}
12
+ spec.description = %q{Pure Ruby HCL parser}
13
+ spec.homepage = ''
14
+ spec.license = 'MIT'
15
+
16
+ spec.files = `git ls-files -z`.split("\x0")
17
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
+ spec.require_paths = ['lib']
20
+
21
+ spec.add_dependency 'deep_merge'
22
+ spec.add_development_dependency 'bundler', '~> 1.5'
23
+ spec.add_development_dependency 'rake'
24
+ spec.add_development_dependency 'racc'
25
+ spec.add_development_dependency 'rspec', '>= 3.0.0'
26
+ end
@@ -0,0 +1,275 @@
1
+ describe Rhcl do
2
+ it 'basic' do
3
+ dumped = Rhcl.dump(
4
+ {"foo" => "bar"}
5
+ )
6
+
7
+ expect(dumped).to eq 'foo = "bar"'
8
+ end
9
+
10
+ it 'decode_policy' do
11
+ dumped = Rhcl.dump(
12
+ {"key"=>
13
+ {""=>{"policy"=>"read"},
14
+ "foo/"=>{"policy"=>"write"},
15
+ "foo/bar/"=>{"policy"=>"read"},
16
+ "foo/bar/baz"=>{"policy"=>"deny"}}}
17
+ )
18
+
19
+ expect(dumped).to eq <<-EOS.strip
20
+ key {
21
+ "" {
22
+ policy = "read"
23
+ }
24
+ "foo/" {
25
+ policy = "write"
26
+ }
27
+ "foo/bar/" {
28
+ policy = "read"
29
+ }
30
+ "foo/bar/baz" {
31
+ policy = "deny"
32
+ }
33
+ }
34
+ EOS
35
+ end
36
+
37
+ it 'decode_tf_variable' do
38
+ dumped = Rhcl.dump(
39
+ {"variable"=>
40
+ {"foo"=>{"default"=>"bar", "description"=>"bar"},
41
+ "amis"=>{"default"=>{"east"=>"foo"}}}}
42
+ )
43
+
44
+ expect(dumped).to eq <<-EOS.strip
45
+ variable {
46
+ foo {
47
+ default = "bar"
48
+ description = "bar"
49
+ }
50
+ amis {
51
+ default {
52
+ east = "foo"
53
+ }
54
+ }
55
+ }
56
+ EOS
57
+ end
58
+
59
+ it 'empty' do
60
+ dumped = Rhcl.dump(
61
+ {"resource"=>{"aws_instance"=>{"db"=>{}}}}
62
+ )
63
+
64
+ expect(dumped).to eq <<-EOS.strip
65
+ resource {
66
+ aws_instance {
67
+ db {
68
+
69
+ }
70
+ }
71
+ }
72
+ EOS
73
+ end
74
+
75
+ it 'flat' do
76
+ dumped = Rhcl.dump(
77
+ {"foo"=>"bar", "Key"=>7}
78
+ )
79
+
80
+ expect(dumped).to eq <<-EOS.strip
81
+ foo = "bar"
82
+ Key = 7
83
+ EOS
84
+ end
85
+
86
+ it 'structure' do
87
+ dumped = Rhcl.dump(
88
+ {"foo"=>{"baz"=>{"key"=>7, "foo"=>"bar"}}}
89
+ )
90
+
91
+ expect(dumped).to eq <<-EOS.strip
92
+ foo {
93
+ baz {
94
+ key = 7
95
+ foo = "bar"
96
+ }
97
+ }
98
+ EOS
99
+ end
100
+
101
+ it 'structure2' do
102
+ dumped = Rhcl.dump(
103
+ {"foo"=>{"baz"=>{"key"=>7, "foo"=>"bar"}, "key"=>7}}
104
+ )
105
+
106
+ expect(dumped).to eq <<-EOS.strip
107
+ foo {
108
+ baz {
109
+ key = 7
110
+ foo = "bar"
111
+ }
112
+ key = 7
113
+ }
114
+ EOS
115
+ end
116
+
117
+ it 'structure_flat' do
118
+ dumped = Rhcl.dump(
119
+ {"foo"=>{"baz"=>{"key"=>7, "foo"=>"bar"}}}
120
+ )
121
+
122
+ expect(dumped).to eq <<-EOS.strip
123
+ foo {
124
+ baz {
125
+ key = 7
126
+ foo = "bar"
127
+ }
128
+ }
129
+ EOS
130
+ end
131
+
132
+ it 'structure_flatmap' do
133
+ dumped = Rhcl.dump(
134
+ {"foo"=>{"key"=>7, "foo"=>"bar"}}
135
+ )
136
+
137
+ expect(dumped).to eq <<-EOS.strip
138
+ foo {
139
+ key = 7
140
+ foo = "bar"
141
+ }
142
+ EOS
143
+ end
144
+
145
+ it 'structure_multi' do
146
+ dumped = Rhcl.dump(
147
+ {"foo"=>{"baz"=>{"key"=>7}, "bar"=>{"key"=>12}}}
148
+ )
149
+
150
+ expect(dumped).to eq <<-EOS.strip
151
+ foo {
152
+ baz {
153
+ key = 7
154
+ }
155
+ bar {
156
+ key = 12
157
+ }
158
+ }
159
+ EOS
160
+ end
161
+
162
+ it 'array' do
163
+ dumped = Rhcl.dump(
164
+ {"foo"=>[1, 2, "baz"], "bar"=>"baz"}
165
+ )
166
+
167
+ expect(dumped).to eq <<-EOS.strip
168
+ foo = [1, 2, "baz"]
169
+ bar = "baz"
170
+ EOS
171
+ end
172
+
173
+ it 'object' do
174
+ dumped = Rhcl.dump(
175
+ {"foo"=>{"bar"=>[1, 2, "baz"]}}
176
+ )
177
+
178
+ expect(dumped).to eq <<-EOS.strip
179
+ foo {
180
+ bar = [1, 2, "baz"]
181
+ }
182
+ EOS
183
+ end
184
+
185
+ it 'types' do
186
+ dumped = Rhcl.dump(
187
+ {"foo"=>"bar",
188
+ "bar"=>7,
189
+ "baz"=>[1, 2, 3],
190
+ "foo2"=>-12,
191
+ "bar2"=>3.14159,
192
+ "bar3"=>-3.14159,
193
+ "hoge"=>true,
194
+ "fuga"=>false}
195
+ )
196
+
197
+ expect(dumped).to eq <<-EOS.strip
198
+ foo = "bar"
199
+ bar = 7
200
+ baz = [1, 2, 3]
201
+ foo2 = -12
202
+ bar2 = 3.14159
203
+ bar3 = -3.14159
204
+ hoge = true
205
+ fuga = false
206
+ EOS
207
+ end
208
+
209
+ it 'complex' do
210
+ dumped = Rhcl.dump(
211
+ {"variable"=>{"foo"=>{"default"=>"bar", "description"=>"bar"}},
212
+ "provider"=>
213
+ {"aws"=>{"access_key"=>"foo", "secret_key"=>"bar"},
214
+ "do"=>{"api_key"=>"${var.foo}"}},
215
+ "resource"=>
216
+ {"aws_security_group"=>{"firewall"=>{"count"=>5}},
217
+ "aws_instance"=>
218
+ {"web"=>
219
+ {"ami"=>"${var.foo}",
220
+ "security_groups"=>["foo", "${aws_security_group.firewall.foo}"],
221
+ "network_interface"=>
222
+ {"device_index"=>0, "description"=>"Main network interface"}},
223
+ "db"=>
224
+ {"security_groups"=>"${aws_security_group.firewall.*.id}",
225
+ "VPC"=>"foo",
226
+ "depends_on"=>["aws_instance.web"]}}},
227
+ "output"=>{"web_ip"=>{"value"=>"${aws_instance.web.private_ip}"}}}
228
+ )
229
+
230
+ expect(dumped).to eq <<-EOS.strip
231
+ variable {
232
+ foo {
233
+ default = "bar"
234
+ description = "bar"
235
+ }
236
+ }
237
+ provider {
238
+ aws {
239
+ access_key = "foo"
240
+ secret_key = "bar"
241
+ }
242
+ do {
243
+ api_key = "${var.foo}"
244
+ }
245
+ }
246
+ resource {
247
+ aws_security_group {
248
+ firewall {
249
+ count = 5
250
+ }
251
+ }
252
+ aws_instance {
253
+ web {
254
+ ami = "${var.foo}"
255
+ security_groups = ["foo", "${aws_security_group.firewall.foo}"]
256
+ network_interface {
257
+ device_index = 0
258
+ description = "Main network interface"
259
+ }
260
+ }
261
+ db {
262
+ security_groups = "${aws_security_group.firewall.*.id}"
263
+ VPC = "foo"
264
+ depends_on = ["aws_instance.web"]
265
+ }
266
+ }
267
+ }
268
+ output {
269
+ web_ip {
270
+ value = "${aws_instance.web.private_ip}"
271
+ }
272
+ }
273
+ EOS
274
+ end
275
+ end