diff options
| author | mo khan <mo@mokhan.ca> | 2021-06-05 13:03:57 -0600 |
|---|---|---|
| committer | mo khan <mo@mokhan.ca> | 2021-06-05 13:03:59 -0600 |
| commit | 2473a75913cd70f2a709fa5547aee5bef2eca301 (patch) | |
| tree | 2648d74c48ba5c54b562998881eca5edf0980a6c | |
| parent | ab9eeb30e53ad95d9d1d3a8e538cb9d9a8a478b8 (diff) | |
feat: port HCL2 parser from spandx
* https://github.com/spandx/spandx/blob/dc7ea719adbf2e90753cb92be76e21cddc916557/lib/spandx/terraform/parsers/hcl.rb
| -rw-r--r-- | Gemfile.lock | 2 | ||||
| -rw-r--r-- | hcl2.gemspec | 1 | ||||
| -rw-r--r-- | lib/hcl2.rb | 102 | ||||
| -rw-r--r-- | spec/fixtures/terraform/multiple_providers/.terraform.lock.hcl | 40 | ||||
| -rw-r--r-- | spec/fixtures/terraform/multiple_providers/main.tf | 12 | ||||
| -rw-r--r-- | spec/fixtures/terraform/simple/.terraform.lock.hcl | 21 | ||||
| -rw-r--r-- | spec/fixtures/terraform/simple/main.tf | 15 | ||||
| -rw-r--r-- | spec/parser_spec.rb | 380 | ||||
| -rw-r--r-- | spec/spec_helper.rb | 4 | ||||
| -rw-r--r-- | spec/support/files.rb | 13 |
10 files changed, 590 insertions, 0 deletions
diff --git a/Gemfile.lock b/Gemfile.lock index 4d1aefd..5568bba 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -2,6 +2,7 @@ PATH remote: . specs: hcl2 (0.1.0) + parslet (~> 2.0) GEM remote: https://rubygems.org/ @@ -11,6 +12,7 @@ GEM parallel (1.20.1) parser (3.0.1.1) ast (~> 2.4.1) + parslet (2.0.0) rainbow (3.0.0) rake (13.0.3) regexp_parser (2.1.1) diff --git a/hcl2.gemspec b/hcl2.gemspec index e8c5c70..191670c 100644 --- a/hcl2.gemspec +++ b/hcl2.gemspec @@ -26,4 +26,5 @@ Gem::Specification.new do |spec| spec.bindir = "exe" spec.executables = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) } spec.require_paths = ["lib"] + spec.add_dependency 'parslet', '~> 2.0' end diff --git a/lib/hcl2.rb b/lib/hcl2.rb index fa7b2e7..e6f2fb8 100644 --- a/lib/hcl2.rb +++ b/lib/hcl2.rb @@ -1,7 +1,109 @@ # frozen_string_literal: true +require "parslet" require_relative "hcl2/version" module Hcl2 class Error < StandardError; end + + class Parser < ::Parslet::Parser + rule(:alpha) { match['a-zA-Z'] } + rule(:assign) { str('=') } + rule(:colon) { str(':') } + rule(:comma) { str(',') } + rule(:comment) { (str('#') | str('//')) >> ((str("\n") >> str("\r").maybe).absent? >> any).repeat >> eol } + rule(:crlf) { match('[\r\n]') } + rule(:digit) { match('\d') } + rule(:dot) { str('.') } + rule(:eol) { whitespace? >> crlf.repeat } + rule(:greater_than_or_equal_to) { str('>=') } + rule(:hyphen) { str('-') } + rule(:lbracket) { str('[') } + rule(:lcurly) { str('{') } + rule(:major) { number } + rule(:major_minor) { (number >> dot >> number) } + rule(:major_minor_patch) { number >> dot >> number >> dot >> number } + rule(:multiline_comment) { str('/*') >> (str('*/').absent? >> any).repeat >> str('*/') } + rule(:number) { digit.repeat } + rule(:plus) { str('+') } + rule(:pre_release) { hyphen >> (alpha | digit).repeat } + rule(:pre_release?) { pre_release.maybe } + rule(:quote) { str('"') } + rule(:rbracket) { str(']') } + rule(:rcurly) { str('}') } + rule(:slash) { str('/') } + rule(:space) { match('\s') } + rule(:tilda_wacka) { str('~>') } + rule(:version) { number >> dot >> number >> dot >> number >> pre_release? } + rule(:whitespace) { (multiline_comment | comment | space).repeat } + rule(:whitespace?) { whitespace.maybe } + + rule(:pessimistic_version_constraint) do + tilda_wacka >> whitespace >> ( + major_minor_patch | + major_minor | + major + ) + end + + rule(:greater_than_or_equal_to_version) do + greater_than_or_equal_to >> whitespace >> ( + major_minor_patch | + major_minor | + major + ) + end + + rule(:version_constraint) do + pessimistic_version_constraint | greater_than_or_equal_to_version + end + + rule :version_assignment do + str('version') >> whitespace >> assign >> whitespace >> quote >> version.as(:version) >> quote + end + + rule :constraint_assignment do + str('constraints') >> whitespace >> assign >> whitespace >> quote >> version_constraint.as(:constraints) >> quote + end + + rule :string do + quote >> ( + digit | dot | alpha | str('~> ') | slash | colon | assign | plus + ).repeat(1).as(:value) >> quote + end + + rule :array_item do + whitespace? >> string >> comma.maybe >> eol + end + + rule :array do + lbracket >> eol >> array_item.repeat >> whitespace >> rbracket + end + + rule :argument_value do + (array.as(:values) | string) >> eol + end + + rule :argument do + whitespace >> alpha.repeat(1).as(:name) >> whitespace >> assign >> whitespace >> argument_value + end + + rule :block_body do + lcurly >> crlf >> argument.repeat.as(:arguments) >> rcurly + end + + rule :identifier do + whitespace >> quote >> (alpha | dot | slash).repeat(1).as(:name) >> quote >> whitespace + end + + rule :block do + alpha.repeat(1).as(:type) >> identifier >> block_body + end + + rule :blocks do + whitespace? >> (block >> eol.maybe).repeat(1).as(:blocks) + end + + root(:blocks) + end end diff --git a/spec/fixtures/terraform/multiple_providers/.terraform.lock.hcl b/spec/fixtures/terraform/multiple_providers/.terraform.lock.hcl new file mode 100644 index 0000000..b0ca24f --- /dev/null +++ b/spec/fixtures/terraform/multiple_providers/.terraform.lock.hcl @@ -0,0 +1,40 @@ +# This file is maintained automatically by "terraform init". +# Manual edits may be lost in future updates. + +provider "registry.terraform.io/hashicorp/aws" { + version = "3.40.0" + constraints = "~> 3.27" + hashes = [ + "h1:0r9TS3qACD9xJhrfTPZR7ygoCKDWHRX4c0D5GCyfAu4=", + "zh:2fd824991b19837e200d19b17d8188bf71efb92c36511809484549e77b4045dd", + "zh:47250cb58b3bd6f2698ca17bfb962710542d6adf95637cd560f6119abf97dba2", + "zh:515722a8c8726541b05362ec71331264977603374a2e4d4d64f89940873143ea", + "zh:61b6b7542da2113278c987a0af9f230321f5ed605f1e3098824603cb09ac771b", + "zh:66aad13ada6344b64adbc67abad4f35c414e62838a99f78626befb8b74c760d8", + "zh:7d4436aeb53fa348d7fd3c2ab4a727b03c7c59bfdcdecef4a75237760f3bb3cf", + "zh:a4583891debc49678491510574b1c28bb4fe3f83ed2bb353959c4c1f6f409f1f", + "zh:b8badecea52f6996ae832144560be87e0b7c2da7fe1dcd6e6230969234b2fc55", + "zh:cecf64a085f640c30437ccc31bd964c21004ae8ae00cfbd95fb04037e46b88ca", + "zh:d81dbb9ad8ce5eca4d1fc5a7a06bbb9c47ea8691f1502e94760fa680e20e4afc", + "zh:f0fc724a964c7f8154bc5911d572ee411f5d181414f9b1f09de7ebdacb0d884b", + ] +} + +provider "registry.terraform.io/hashicorp/azurerm" { + version = "2.59.0" + constraints = "~> 2.1" + hashes = [ + "h1:Mp7ECMHocobalN1+ASSKG5dHB7RnhZ6Y0rEEFTT5urA=", + "zh:0996d1c85bccdb15aeb6bc32f763c2d85ff854b33c3c3d62c34859669e05785e", + "zh:37807677e68058381514897ce10dc73a0dd0f503aba98113ac79844d310010e3", + "zh:3bccf9215bdbcc89327582b1d9d2a633c59215ca6452dbb4f9d0a7a661074c5b", + "zh:4801791332ab81e51d1ead47a62e0081ec4c1f23ef0fc2e8b15fef315ecdf07a", + "zh:5bad44816a3eaeb335f665f6eef9b41a403a40e9bddb2db8406ab0e847f639ca", + "zh:64f79c4ddc2bf8384f1a42c4e430ffdc53cb1fbc565bfe1cdc6b075dcdf098e9", + "zh:75c96fcb592ed80cc403944faadda25aeadda7fd6de9162a8d365249b1ec1c17", + "zh:8604558f2f201eefe25f4c611a5d4ef4d7c75338bf2f4a6321da5caa94937947", + "zh:cab930e374d33b3b980c6774f3d0ac3e3d7e1e596aba586d4368d8bcf05cf9c5", + "zh:cf0e78eb1e84b6dd11031283878e392e55801e3acd9c5592309e6f76ebe3a621", + "zh:eba02fcab150775b8b8beeec0c7dbba1585a57f4e97272f48c71021c5e289579", + ] +} diff --git a/spec/fixtures/terraform/multiple_providers/main.tf b/spec/fixtures/terraform/multiple_providers/main.tf new file mode 100644 index 0000000..e2e0818 --- /dev/null +++ b/spec/fixtures/terraform/multiple_providers/main.tf @@ -0,0 +1,12 @@ +terraform { + required_providers { + aws = { + source = "hashicorp/aws" + version = "~> 3.27" + } + azure = { + source = "hashicorp/azurerm" + version = "~> 2.1" + } + } +} diff --git a/spec/fixtures/terraform/simple/.terraform.lock.hcl b/spec/fixtures/terraform/simple/.terraform.lock.hcl new file mode 100644 index 0000000..9a41a4c --- /dev/null +++ b/spec/fixtures/terraform/simple/.terraform.lock.hcl @@ -0,0 +1,21 @@ +# This file is maintained automatically by "terraform init". +# Manual edits may be lost in future updates. + +provider "registry.terraform.io/hashicorp/aws" { + version = "3.39.0" + constraints = "~> 3.27" + hashes = [ + "h1:fjlp3Pd3QsTLghNm7TUh/KnEMM2D3tLb7jsDLs8oWUE=", + "zh:2014b397dd93fa55f2f2d1338c19e5b2b77b025a76a6b1fceea0b8696e984b9c", + "zh:23d59c68ab50148a0f5c911a801734e9934a1fccd41118a8efb5194135cbd360", + "zh:412eab41d4934ca9c47083faa128e4cd585c3bb44ad718e40d67091aebc02f4e", + "zh:4b75e0a259b56d97e66b7d69f3f25bd4cc7be2440c0fe35529f46de7d40a49d3", + "zh:694a32519dcca5bd8605d06481d16883d55160d97c1f4039deb13c6ca8de8427", + "zh:6a0bcef43c2d9a97aeaaac3c5d1d6728dc2464a51a014f118c691c79029d0903", + "zh:6d78fc7c663247ca2a80f276008dcdafece4cac75e2639bbce188c08b796040a", + "zh:78f846a505d7b64b67feed1527d4d2b40130dadaf8e3112113685e148f49b156", + "zh:881bc969432d3ef6ec70f5a762c3415e037904338579b0a360c6818b74d26e59", + "zh:96c1ca80c1d693a3eef80489adb45c076ee8e6878e461d6c29b05388d4b95f48", + "zh:9be5fa342272586fc6e319e20f21c0c5c801b05dcf7d59e473ad0882c9ecfa70", + ] +} diff --git a/spec/fixtures/terraform/simple/main.tf b/spec/fixtures/terraform/simple/main.tf new file mode 100644 index 0000000..b025be8 --- /dev/null +++ b/spec/fixtures/terraform/simple/main.tf @@ -0,0 +1,15 @@ +terraform { + required_providers { + aws = { + source = "hashicorp/aws" + version = "~> 3.27" + } + } + + required_version = ">= 0.14.9" +} + +provider "aws" { + profile = "default" + region = "ca-central-1" +} diff --git a/spec/parser_spec.rb b/spec/parser_spec.rb new file mode 100644 index 0000000..0e356ea --- /dev/null +++ b/spec/parser_spec.rb @@ -0,0 +1,380 @@ +# frozen_string_literal: true + +RSpec.describe Hcl2::Parser do + subject(:parser) { described_class.new } + + describe '#parse' do + subject { parser.parse_with_debug(content) } + + context 'when parsing a single provider' do + let(:content) do + <<~HCL + # This file is maintained automatically by "terraform init". + # Manual edits may be lost in future updates. + + provider "registry.terraform.io/hashicorp/aws" { + version = "3.39.0" + constraints = "~> 3.27" + hashes = [ + "h1:fjlp3Pd3QsTLghNm7TUh/KnEMM2D3tLb7jsDLs8oWUE=", + "zh:2014b397dd93fa55f2f2d1338c19e5b2b77b025a76a6b1fceea0b8696e984b9c", + "zh:23d59c68ab50148a0f5c911a801734e9934a1fccd41118a8efb5194135cbd360", + "zh:412eab41d4934ca9c47083faa128e4cd585c3bb44ad718e40d67091aebc02f4e", + "zh:4b75e0a259b56d97e66b7d69f3f25bd4cc7be2440c0fe35529f46de7d40a49d3", + "zh:694a32519dcca5bd8605d06481d16883d55160d97c1f4039deb13c6ca8de8427", + "zh:6a0bcef43c2d9a97aeaaac3c5d1d6728dc2464a51a014f118c691c79029d0903", + "zh:6d78fc7c663247ca2a80f276008dcdafece4cac75e2639bbce188c08b796040a", + "zh:78f846a505d7b64b67feed1527d4d2b40130dadaf8e3112113685e148f49b156", + "zh:881bc969432d3ef6ec70f5a762c3415e037904338579b0a360c6818b74d26e59", + "zh:96c1ca80c1d693a3eef80489adb45c076ee8e6878e461d6c29b05388d4b95f48", + "zh:9be5fa342272586fc6e319e20f21c0c5c801b05dcf7d59e473ad0882c9ecfa70", + ] + } + + /* + This is a multi-line comment + that spans multiple lines + */ + HCL + end + + specify { expect(subject).to be_truthy } + specify { expect(subject[:blocks][0][:name].to_s).to eql('registry.terraform.io/hashicorp/aws') } + specify { expect(subject[:blocks][0][:type].to_s).to eql('provider') } + + specify do + expect(subject[:blocks][0][:arguments]).to match_array([ + { name: 'version', value: '3.39.0' }, + { name: 'constraints', value: '~> 3.27' }, + { + name: 'hashes', + values: [ + { value: 'h1:fjlp3Pd3QsTLghNm7TUh/KnEMM2D3tLb7jsDLs8oWUE=' }, + { value: 'zh:2014b397dd93fa55f2f2d1338c19e5b2b77b025a76a6b1fceea0b8696e984b9c' }, + { value: 'zh:23d59c68ab50148a0f5c911a801734e9934a1fccd41118a8efb5194135cbd360' }, + { value: 'zh:412eab41d4934ca9c47083faa128e4cd585c3bb44ad718e40d67091aebc02f4e' }, + { value: 'zh:4b75e0a259b56d97e66b7d69f3f25bd4cc7be2440c0fe35529f46de7d40a49d3' }, + { value: 'zh:694a32519dcca5bd8605d06481d16883d55160d97c1f4039deb13c6ca8de8427' }, + { value: 'zh:6a0bcef43c2d9a97aeaaac3c5d1d6728dc2464a51a014f118c691c79029d0903' }, + { value: 'zh:6d78fc7c663247ca2a80f276008dcdafece4cac75e2639bbce188c08b796040a' }, + { value: 'zh:78f846a505d7b64b67feed1527d4d2b40130dadaf8e3112113685e148f49b156' }, + { value: 'zh:881bc969432d3ef6ec70f5a762c3415e037904338579b0a360c6818b74d26e59' }, + { value: 'zh:96c1ca80c1d693a3eef80489adb45c076ee8e6878e461d6c29b05388d4b95f48' }, + { value: 'zh:9be5fa342272586fc6e319e20f21c0c5c801b05dcf7d59e473ad0882c9ecfa70' }, + ] + }, + ]) + end + end + + context 'when parsing multiple provider blocks' do + let(:content) { fixture_file_content('terraform/multiple_providers/.terraform.lock.hcl') } + + specify { expect(subject).to be_truthy } + specify { expect(subject[:blocks][0][:name].to_s).to eql('registry.terraform.io/hashicorp/aws') } + specify { expect(subject[:blocks][0][:type].to_s).to eql('provider') } + specify { expect(subject[:blocks][1][:name].to_s).to eql('registry.terraform.io/hashicorp/azurerm') } + specify { expect(subject[:blocks][1][:type].to_s).to eql('provider') } + + specify do + expect(subject[:blocks][0][:arguments]).to match_array([ + { name: 'version', value: '3.40.0' }, + { name: 'constraints', value: '~> 3.27' }, + { + name: 'hashes', + values: [ + { value: 'h1:0r9TS3qACD9xJhrfTPZR7ygoCKDWHRX4c0D5GCyfAu4=' }, + { value: 'zh:2fd824991b19837e200d19b17d8188bf71efb92c36511809484549e77b4045dd' }, + { value: 'zh:47250cb58b3bd6f2698ca17bfb962710542d6adf95637cd560f6119abf97dba2' }, + { value: 'zh:515722a8c8726541b05362ec71331264977603374a2e4d4d64f89940873143ea' }, + { value: 'zh:61b6b7542da2113278c987a0af9f230321f5ed605f1e3098824603cb09ac771b' }, + { value: 'zh:66aad13ada6344b64adbc67abad4f35c414e62838a99f78626befb8b74c760d8' }, + { value: 'zh:7d4436aeb53fa348d7fd3c2ab4a727b03c7c59bfdcdecef4a75237760f3bb3cf' }, + { value: 'zh:a4583891debc49678491510574b1c28bb4fe3f83ed2bb353959c4c1f6f409f1f' }, + { value: 'zh:b8badecea52f6996ae832144560be87e0b7c2da7fe1dcd6e6230969234b2fc55' }, + { value: 'zh:cecf64a085f640c30437ccc31bd964c21004ae8ae00cfbd95fb04037e46b88ca' }, + { value: 'zh:d81dbb9ad8ce5eca4d1fc5a7a06bbb9c47ea8691f1502e94760fa680e20e4afc' }, + { value: 'zh:f0fc724a964c7f8154bc5911d572ee411f5d181414f9b1f09de7ebdacb0d884b' }, + ] + }, + ]) + end + + specify do + expect(subject[:blocks][1][:arguments]).to match_array([ + { name: 'version', value: '2.59.0' }, + { name: 'constraints', value: '~> 2.1' }, + { + name: 'hashes', + values: [ + { value: 'h1:Mp7ECMHocobalN1+ASSKG5dHB7RnhZ6Y0rEEFTT5urA=' }, + { value: 'zh:0996d1c85bccdb15aeb6bc32f763c2d85ff854b33c3c3d62c34859669e05785e' }, + { value: 'zh:37807677e68058381514897ce10dc73a0dd0f503aba98113ac79844d310010e3' }, + { value: 'zh:3bccf9215bdbcc89327582b1d9d2a633c59215ca6452dbb4f9d0a7a661074c5b' }, + { value: 'zh:4801791332ab81e51d1ead47a62e0081ec4c1f23ef0fc2e8b15fef315ecdf07a' }, + { value: 'zh:5bad44816a3eaeb335f665f6eef9b41a403a40e9bddb2db8406ab0e847f639ca' }, + { value: 'zh:64f79c4ddc2bf8384f1a42c4e430ffdc53cb1fbc565bfe1cdc6b075dcdf098e9' }, + { value: 'zh:75c96fcb592ed80cc403944faadda25aeadda7fd6de9162a8d365249b1ec1c17' }, + { value: 'zh:8604558f2f201eefe25f4c611a5d4ef4d7c75338bf2f4a6321da5caa94937947' }, + { value: 'zh:cab930e374d33b3b980c6774f3d0ac3e3d7e1e596aba586d4368d8bcf05cf9c5' }, + { value: 'zh:cf0e78eb1e84b6dd11031283878e392e55801e3acd9c5592309e6f76ebe3a621' }, + { value: 'zh:eba02fcab150775b8b8beeec0c7dbba1585a57f4e97272f48c71021c5e289579' }, + ] + }, + ]) + end + end + end + + describe '#version_assignment' do + subject { parser.version_assignment } + + [ + 'version = "3.39.0"', + 'version = "3.39.0-alpha"', + 'version = "3.39.0-beta"', + 'version = "3.39.0-d15aad9f"', + 'version = "3.39.0-d15aad9f6ad69c4248a70b11a6534c1c841ec6f9"', + 'version = "3.39.0"', + 'version = "3.39.0-alpha"', + 'version = "3.39.0-beta"', + 'version = "3.39.0-d15aad9f"', + 'version = "3.39.0-d15aad9f6ad69c4248a70b11a6534c1c841ec6f9"', + ].each do |raw| + specify { expect(subject).to parse(raw) } + end + end + + describe '#version' do + [ + '0.1.1-alpha', + '0.1.1-beta', + '1.2.3', + '3.39.0', + '3.39.0-d15aad9f', + '3.39.0-d15aad9f6ad69c4248a70b11a6534c1c841ec6f9', + ].each do |raw| + specify { expect(parser.version).to parse(raw) } + end + end + + describe '#constraint_assignment' do + subject { parser.constraint_assignment } + + [ + 'constraints = ">= 3"', + 'constraints = ">= 3.27"', + 'constraints = ">= 3.27.0"', + 'constraints = "~> 3"', + 'constraints = "~> 3.27"', + 'constraints = "~> 3.27.0"', + ].each do |raw| + specify { expect(subject).to parse(raw) } + end + end + + describe '#version_constraint' do + [ + '~> 3', + '~> 3.27', + '~> 3.27.0', + '>= 1.2.0', + '>= 1.20', + '>= 10', + ].each do |raw| + specify { expect(parser.version_constraint).to parse(raw) } + end + end + + (('a'..'z').to_a + ('A'..'Z').to_a).each do |letter| + specify { expect(parser.alpha).to parse(letter) } + end + + (0..9).each { |digit| specify { expect(parser.digit).to parse(digit.to_s) } } + specify { expect(parser.assign).not_to parse('==') } + specify { expect(parser.assign).to parse('=') } + specify { expect(parser.comment).to parse('# Manual edits may be lost in future updates.') } + specify { expect(parser.comment).to parse('# This file is maintained automatically by "terraform init".') } + specify { expect(parser.comment).to parse('// This file is maintained automatically by "terraform init".') } + specify { expect(parser.crlf).to parse("\n") } + specify { expect(parser.crlf).to parse("\r") } + specify { expect(parser.dot).to parse('.') } + specify { expect(parser.eol).to parse(" \n") } + specify { expect(parser.eol).to parse("\n") } + specify { expect(parser.lcurly).to parse('{') } + specify { expect(parser.number).to parse('123') } + specify { expect(parser.quote).to parse('"') } + specify { expect(parser.rcurly).to parse('}') } + specify { expect(parser.space).to parse(' ') } + specify { expect(parser.whitespace).to parse('# This is a comment') } + specify { expect(parser.whitespace).to parse('// This is a comment') } + specify { expect(parser.string).to parse('"h1:fjlp3Pd3QsTLghNm7TUh/KnEMM2D3tLb7jsDLs8oWUE="') } + specify { expect(parser.string).to parse('"zh:2014b397dd93fa55f2f2d1338c19e5b2b77b025a76a6b1fceea0b8696e984b9c"') } + + specify do + expect(parser.whitespace).to parse(<<~HCL) + /* + This is a multi-line comment + that spans multiple lines + */ + HCL + end + + specify { expect(parser.argument).to parse('constraints = "~> 3.27"') } + + specify do + expect(parser.argument).to parse(<<~HCL.chomp) + hashes = [ + "h1:fjlp3Pd3QsTLghNm7TUh/KnEMM2D3tLb7jsDLs8oWUE=", + "zh:2014b397dd93fa55f2f2d1338c19e5b2b77b025a76a6b1fceea0b8696e984b9c", + "zh:23d59c68ab50148a0f5c911a801734e9934a1fccd41118a8efb5194135cbd360", + "zh:412eab41d4934ca9c47083faa128e4cd585c3bb44ad718e40d67091aebc02f4e", + "zh:4b75e0a259b56d97e66b7d69f3f25bd4cc7be2440c0fe35529f46de7d40a49d3", + "zh:694a32519dcca5bd8605d06481d16883d55160d97c1f4039deb13c6ca8de8427", + "zh:6a0bcef43c2d9a97aeaaac3c5d1d6728dc2464a51a014f118c691c79029d0903", + "zh:6d78fc7c663247ca2a80f276008dcdafece4cac75e2639bbce188c08b796040a", + "zh:78f846a505d7b64b67feed1527d4d2b40130dadaf8e3112113685e148f49b156", + "zh:881bc969432d3ef6ec70f5a762c3415e037904338579b0a360c6818b74d26e59", + "zh:96c1ca80c1d693a3eef80489adb45c076ee8e6878e461d6c29b05388d4b95f48", + "zh:9be5fa342272586fc6e319e20f21c0c5c801b05dcf7d59e473ad0882c9ecfa70", + ] + HCL + end + + specify do + expect(parser.block).to parse(<<~HCL.chomp) + provider "thing" { + argument = "value" + arguments = [ + "value", + "value", + ] + } + HCL + end + + specify do + expect(parser.block_body.parse_with_debug(<<~HCL.chomp)).not_to be_nil + { + argument = "value" + arguments = [ + "value", + "value", + ] + } + HCL + end + + specify { expect(parser.argument).to parse('argument = "value"') } + + specify do + expect(parser.argument).to parse(<<~HCL) + arguments = [ + "a", + "b", + ] + HCL + end + + describe '#blocks' do + subject { parser.blocks.parse_with_debug(hcl) } + + context 'when parsing multiple multi-line empty blocks' do + let(:hcl) do + <<~HCL + provider "thingy" { + } + + provider "other.thingy" { + } + HCL + end + + it 'parses multiple empty blocks' do + expect(subject[:blocks]).to match_array([ + { type: 'provider', name: 'thingy', arguments: [] }, + { type: 'provider', name: 'other.thingy', arguments: [] }, + ]) + end + end + + context 'when parsing multiple multi-line blocks with one argument assignment to a string in the first block' do + let(:hcl) do + <<~HCL + provider "thingy" { + name = "blah" + } + + provider "other.thingy" { + } + HCL + end + + it 'parses multiple empty blocks' do + expect(subject[:blocks]).to match_array([ + { type: 'provider', name: 'thingy', arguments: [{ name: 'name', value: 'blah' }] }, + { type: 'provider', name: 'other.thingy', arguments: [] }, + ]) + end + end + + context 'when parsing multiple multi-line blocks with one argument assignment to a string in the second block' do + let(:hcl) do + <<~HCL + provider "thingy" { + } + + provider "other.thingy" { + name = "blah" + } + HCL + end + + it 'parses multiple empty blocks' do + expect(subject[:blocks]).to match_array([ + { type: 'provider', name: 'thingy', arguments: [] }, + { type: 'provider', name: 'other.thingy', arguments: [{ name: 'name', value: 'blah' }] }, + ]) + end + end + + context 'when parsing a blocks with one assignment to an empty array' do + let(:hcl) do + <<~HCL + provider "thingy" { + names = [ + ] + } + HCL + end + + pending 'parses multiple empty blocks' do + expect(subject[:blocks]).to match_array([ + { type: 'provider', name: 'thingy', arguments: [{ name: 'names', values: [] }] }, + ]) + end + end + + context 'when parsing multiple multi-line blocks with one assignment to a multi-line array' do + let(:hcl) do + <<~HCL + provider "thingy" { + names = [ + "blah" + ] + } + + provider "other.thingy" { + } + HCL + end + + it 'parses multiple empty blocks' do + expect(subject[:blocks]).to match_array([ + { type: 'provider', name: 'thingy', arguments: [{ name: 'names', values: [{ value: 'blah' }] }] }, + { type: 'provider', name: 'other.thingy', arguments: [] }, + ]) + end + end + end +end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 2cd21bf..78ff867 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -1,6 +1,10 @@ # frozen_string_literal: true require "hcl2" +require "parslet/convenience" +require "parslet/rig/rspec" + +Dir["./spec/support/**/*.rb"].sort.each { |f| require f } RSpec.configure do |config| config.example_status_persistence_file_path = ".rspec_status" diff --git a/spec/support/files.rb b/spec/support/files.rb new file mode 100644 index 0000000..81ea2f3 --- /dev/null +++ b/spec/support/files.rb @@ -0,0 +1,13 @@ +# frozen_string_literal: true + +RSpec.configure do |config| + config.include(Module.new do + def fixture_file(path) + Pathname.new(__FILE__).parent.join('../fixtures', path) + end + + def fixture_file_content(path) + fixture_file(path).read + end + end) +end |
