hcl_data 1.0.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 +7 -0
- data/AGENTS.md +25 -0
- data/CLAUDE.md +1 -0
- data/LICENSE +19 -0
- data/README.md +81 -0
- data/lib/hcl_data/errors.rb +8 -0
- data/lib/hcl_data/generator.rb +48 -0
- data/lib/hcl_data/lexer.rb +122 -0
- data/lib/hcl_data/parser.rb +124 -0
- data/lib/hcl_data/version.rb +7 -0
- data/lib/hcl_data.rb +27 -0
- metadata +55 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: 10aa51e1fe382d2d9eed002a2096c3922924ecbde6850dc26108ffd1f416f4da
|
|
4
|
+
data.tar.gz: eb8245bb4441eafca9a18cbe6cb111573596af40ba992233c76a8df3c331d2d8
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: 7dba19c69393e5d29aa9ea474d45f38b0a8a8dd53224109a81df81431680f393a76c8c80cc29af2de1096dc2250df1c8a717b4ad6a3d64880398ab90deba9fc4
|
|
7
|
+
data.tar.gz: 1a2dbd14fdbfc8a2f1b01969d5d6ec2bb032e556216ec3abe13a0b6421bcc50fdb61d5c8ce3537aed8331a908e98aed7cd17fed0120d5decea638a158c87c968
|
data/AGENTS.md
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# hcl_data
|
|
2
|
+
|
|
3
|
+
A Ruby gem for reading and writing data in HCL (HashiCorp Configuration Language) format.
|
|
4
|
+
Supports the JSON-equivalent subset of HCL grammar: scalars, arrays, maps, and blocks.
|
|
5
|
+
|
|
6
|
+
API: `HclData.dump` / `HclData.load`, like JSON/YAML.
|
|
7
|
+
|
|
8
|
+
## Development approach
|
|
9
|
+
|
|
10
|
+
Strict TDD. Always write a failing test before writing implementation code.
|
|
11
|
+
|
|
12
|
+
1. Write a test that fails
|
|
13
|
+
2. Write the minimum code to make it pass
|
|
14
|
+
3. Refactor if needed
|
|
15
|
+
4. Repeat
|
|
16
|
+
|
|
17
|
+
Never write implementation code without a failing test first.
|
|
18
|
+
|
|
19
|
+
Stage working files with `git add` whenever `bundle exec rake` passes.
|
|
20
|
+
|
|
21
|
+
## Rubocop
|
|
22
|
+
|
|
23
|
+
Avoid relaxing rubocop checks (e.g. raising `Max` limits). If code triggers an offense, refactor to fix it.
|
|
24
|
+
|
|
25
|
+
If/when we do relax checks, do so in a targeted way (e.g. per-class, or per-method).
|
data/CLAUDE.md
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
AGENTS.md
|
data/LICENSE
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
Copyright (c) 2026 Mike Williams <mdub@dogbiscuit.org>
|
|
2
|
+
|
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
4
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
5
|
+
in the Software without restriction, including without limitation the rights
|
|
6
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
7
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
8
|
+
furnished to do so, subject to the following conditions:
|
|
9
|
+
|
|
10
|
+
The above copyright notice and this permission notice shall be included in all
|
|
11
|
+
copies or substantial portions of the Software.
|
|
12
|
+
|
|
13
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
14
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
15
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
16
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
17
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
18
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
19
|
+
SOFTWARE.
|
data/README.md
ADDED
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
# hcl_data
|
|
2
|
+
|
|
3
|
+
[](https://rubygems.org/gems/hcl_data)
|
|
4
|
+
[](https://github.com/mdub/hcl_data/actions/workflows/ci.yml)
|
|
5
|
+
|
|
6
|
+
A Ruby library for reading and writing data in [HCL](https://github.com/hashicorp/hcl) (HashiCorp Configuration Language) format.
|
|
7
|
+
|
|
8
|
+
Supports the JSON-equivalent subset of HCL grammar: scalars, arrays, maps, and blocks.
|
|
9
|
+
|
|
10
|
+
## Installation
|
|
11
|
+
|
|
12
|
+
```ruby
|
|
13
|
+
gem 'hcl_data'
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
## Usage
|
|
17
|
+
|
|
18
|
+
The API follows the `load`/`dump` conventions of `Marshal`, `JSON`, and `YAML`:
|
|
19
|
+
|
|
20
|
+
```ruby
|
|
21
|
+
require 'hcl_data'
|
|
22
|
+
|
|
23
|
+
# parse HCL
|
|
24
|
+
data = HclData.load(<<~HCL)
|
|
25
|
+
name = "web"
|
|
26
|
+
port = 8080
|
|
27
|
+
enabled = true
|
|
28
|
+
|
|
29
|
+
server {
|
|
30
|
+
host = "localhost"
|
|
31
|
+
tags = ["primary", "us-east"]
|
|
32
|
+
}
|
|
33
|
+
HCL
|
|
34
|
+
|
|
35
|
+
# => {"name"=>"web", "port"=>8080, "enabled"=>true,
|
|
36
|
+
# "server"=>{"host"=>"localhost", "tags"=>["primary", "us-east"]}}
|
|
37
|
+
|
|
38
|
+
# generate HCL
|
|
39
|
+
puts HclData.dump(data)
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
### IO support
|
|
43
|
+
|
|
44
|
+
`load` accepts a String or any IO-like object (anything responding to `read`):
|
|
45
|
+
|
|
46
|
+
```ruby
|
|
47
|
+
data = File.open('config.hcl') { |f| HclData.load(f) }
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
`dump` accepts an optional IO as a second argument:
|
|
51
|
+
|
|
52
|
+
```ruby
|
|
53
|
+
File.open('config.hcl', 'w') { |f| HclData.dump(data, f) }
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
### Supported HCL features
|
|
57
|
+
|
|
58
|
+
- String, integer, float, boolean, and null values
|
|
59
|
+
- Arrays
|
|
60
|
+
- Maps (with `=` or `:` separator)
|
|
61
|
+
- Blocks (unlabeled, labeled, nested)
|
|
62
|
+
- Repeated blocks (parsed as arrays)
|
|
63
|
+
- Heredoc strings (`<<EOF`, `<<~EOF`)
|
|
64
|
+
- Comments (`#`, `//`, `/* */`)
|
|
65
|
+
|
|
66
|
+
### Not supported
|
|
67
|
+
|
|
68
|
+
This gem targets HCL as a data format (the JSON-equivalent subset). The following HCL language features are not supported:
|
|
69
|
+
|
|
70
|
+
- String interpolation (`"${var.name}"`)
|
|
71
|
+
- Expressions, operators, and function calls
|
|
72
|
+
- Variable references
|
|
73
|
+
- `for` expressions
|
|
74
|
+
- Splat expressions (`*.id`)
|
|
75
|
+
- Conditional expressions (`condition ? true : false`)
|
|
76
|
+
- Template directives (`%{if ...}`, `%{for ...}`)
|
|
77
|
+
- Dynamic blocks
|
|
78
|
+
|
|
79
|
+
## License
|
|
80
|
+
|
|
81
|
+
MIT
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module HclData
|
|
4
|
+
|
|
5
|
+
# Generates HCL from Ruby data structures.
|
|
6
|
+
class Generator
|
|
7
|
+
|
|
8
|
+
def initialize(data)
|
|
9
|
+
@data = data
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def generate
|
|
13
|
+
generate_body(@data, 0)
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
private
|
|
17
|
+
|
|
18
|
+
def generate_body(data, depth)
|
|
19
|
+
indent = ' ' * depth
|
|
20
|
+
data.map do |key, value|
|
|
21
|
+
if value.is_a?(Hash)
|
|
22
|
+
generate_block(indent, key, value, depth)
|
|
23
|
+
else
|
|
24
|
+
"#{indent}#{key} = #{generate_value(value)}\n"
|
|
25
|
+
end
|
|
26
|
+
end.join
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def generate_block(indent, key, body, depth)
|
|
30
|
+
"#{indent}#{key} {\n#{generate_body(body, depth + 1)}#{indent}}\n"
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def generate_value(value)
|
|
34
|
+
case value
|
|
35
|
+
when nil then 'null'
|
|
36
|
+
when true then 'true'
|
|
37
|
+
when false then 'false'
|
|
38
|
+
when String then value.inspect
|
|
39
|
+
when Numeric then value.to_s
|
|
40
|
+
when Hash then "{ #{value.map { |k, v| "#{k} = #{generate_value(v)}" }.join(', ')} }"
|
|
41
|
+
when Array then "[#{value.map { |v| generate_value(v) }.join(', ')}]"
|
|
42
|
+
else raise ArgumentError, "unsupported value type: #{value.class}"
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
end
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'strscan'
|
|
4
|
+
|
|
5
|
+
module HclData
|
|
6
|
+
|
|
7
|
+
Token = Struct.new(:type, :value)
|
|
8
|
+
|
|
9
|
+
# Tokenizes HCL input.
|
|
10
|
+
class Lexer
|
|
11
|
+
|
|
12
|
+
ESCAPE_SEQUENCES = {
|
|
13
|
+
'"' => '"',
|
|
14
|
+
'\\' => '\\',
|
|
15
|
+
'n' => "\n",
|
|
16
|
+
't' => "\t",
|
|
17
|
+
'r' => "\r"
|
|
18
|
+
}.freeze
|
|
19
|
+
|
|
20
|
+
SINGLE_CHAR_TOKENS = {
|
|
21
|
+
'{' => :LBRACE, '}' => :RBRACE,
|
|
22
|
+
'[' => :LBRACKET, ']' => :RBRACKET,
|
|
23
|
+
'=' => :EQUALS, ',' => :COMMA, ':' => :COLON,
|
|
24
|
+
"\n" => :NEWLINE
|
|
25
|
+
}.freeze
|
|
26
|
+
|
|
27
|
+
def initialize(input)
|
|
28
|
+
@scanner = StringScanner.new(input)
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def tokens
|
|
32
|
+
result = []
|
|
33
|
+
while (token = next_token)
|
|
34
|
+
result << token
|
|
35
|
+
end
|
|
36
|
+
result << Token.new(:EOF, nil)
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
private
|
|
40
|
+
|
|
41
|
+
def next_token
|
|
42
|
+
skip_whitespace_and_comments
|
|
43
|
+
return nil if @scanner.eos?
|
|
44
|
+
|
|
45
|
+
if @scanner.scan('"')
|
|
46
|
+
scan_string
|
|
47
|
+
elsif @scanner.scan(/<<~?-?/)
|
|
48
|
+
scan_heredoc
|
|
49
|
+
elsif @scanner.scan(/[{}\[\]=,:\n]/)
|
|
50
|
+
Token.new(SINGLE_CHAR_TOKENS[@scanner.matched], nil)
|
|
51
|
+
elsif @scanner.scan(/-?\d+\.\d+([eE][+-]?\d+)?/) || @scanner.scan(/-?\d+[eE][+-]?\d+/)
|
|
52
|
+
Token.new(:FLOAT, @scanner.matched.to_f)
|
|
53
|
+
elsif @scanner.scan(/-?\d+/)
|
|
54
|
+
Token.new(:INTEGER, @scanner.matched.to_i)
|
|
55
|
+
elsif @scanner.scan(/[a-zA-Z_][a-zA-Z0-9_-]*/)
|
|
56
|
+
keyword_or_identifier(@scanner.matched)
|
|
57
|
+
else
|
|
58
|
+
ch = @scanner.getch
|
|
59
|
+
raise ParseError, "unexpected character #{ch.inspect}"
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def scan_string
|
|
64
|
+
value = +''
|
|
65
|
+
loop do
|
|
66
|
+
if @scanner.eos?
|
|
67
|
+
raise ParseError, 'unterminated string'
|
|
68
|
+
elsif @scanner.scan('"')
|
|
69
|
+
return Token.new(:STRING, value)
|
|
70
|
+
elsif @scanner.scan(/\\u([0-9a-fA-F]{4})/)
|
|
71
|
+
value << @scanner[1].to_i(16).chr(Encoding::UTF_8)
|
|
72
|
+
elsif @scanner.scan(/\\(.)/)
|
|
73
|
+
value << (ESCAPE_SEQUENCES[@scanner[1]] || @scanner[1])
|
|
74
|
+
else
|
|
75
|
+
@scanner.scan(/[^"\\]+/)
|
|
76
|
+
value << @scanner.matched
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def scan_heredoc
|
|
82
|
+
strip = @scanner.matched.include?('~')
|
|
83
|
+
marker = @scanner.scan(/[A-Z_]+/) or raise ParseError, 'expected heredoc marker'
|
|
84
|
+
@scanner.scan(/[ \t]*\n/) or raise ParseError, 'expected newline after heredoc marker'
|
|
85
|
+
body = @scanner.scan_until(/\n[ \t]*#{marker}[ \t]*$/) or raise ParseError, 'unterminated heredoc'
|
|
86
|
+
value = body.sub(/\n[ \t]*#{marker}[ \t]*\z/, "\n")
|
|
87
|
+
value = strip_heredoc_indent(value) if strip
|
|
88
|
+
Token.new(:STRING, value)
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
def strip_heredoc_indent(text)
|
|
92
|
+
lines = text.split("\n", -1)
|
|
93
|
+
min_indent = lines.reject(&:empty?).map { |l| l[/^ */].length }.min || 0
|
|
94
|
+
lines.map { |l| l.length >= min_indent ? l[min_indent..] : l }.join("\n")
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
def keyword_or_identifier(word)
|
|
98
|
+
case word
|
|
99
|
+
when 'true' then Token.new(:TRUE, true)
|
|
100
|
+
when 'false' then Token.new(:FALSE, false)
|
|
101
|
+
when 'null' then Token.new(:NULL, nil)
|
|
102
|
+
else Token.new(:IDENTIFIER, word)
|
|
103
|
+
end
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
def skip_whitespace_and_comments
|
|
107
|
+
loop do
|
|
108
|
+
@scanner.scan(/[ \t]+/)
|
|
109
|
+
if @scanner.scan(%r{//[^\n]*}) || @scanner.scan(/#[^\n]*/)
|
|
110
|
+
next
|
|
111
|
+
elsif @scanner.scan(%r{/\*})
|
|
112
|
+
@scanner.scan_until(%r{\*/})
|
|
113
|
+
next
|
|
114
|
+
else
|
|
115
|
+
break
|
|
116
|
+
end
|
|
117
|
+
end
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
end
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module HclData
|
|
4
|
+
|
|
5
|
+
# Parses HCL tokens into Ruby data structures.
|
|
6
|
+
class Parser
|
|
7
|
+
|
|
8
|
+
def initialize(input)
|
|
9
|
+
@tokens = Lexer.new(input).tokens
|
|
10
|
+
@pos = 0
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def parse
|
|
14
|
+
parse_body
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
private
|
|
18
|
+
|
|
19
|
+
def parse_body
|
|
20
|
+
result = {}
|
|
21
|
+
skip_newlines
|
|
22
|
+
while current_type == :IDENTIFIER
|
|
23
|
+
key = current.value
|
|
24
|
+
advance
|
|
25
|
+
if current_type == :EQUALS
|
|
26
|
+
advance
|
|
27
|
+
result[key] = parse_expression
|
|
28
|
+
elsif %i[STRING LBRACE].include?(current_type)
|
|
29
|
+
merge_block(result, key, parse_block)
|
|
30
|
+
else
|
|
31
|
+
raise ParseError, "expected EQUALS or LBRACE, got #{current_type}"
|
|
32
|
+
end
|
|
33
|
+
skip_newlines
|
|
34
|
+
end
|
|
35
|
+
result
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def parse_block
|
|
39
|
+
labels = []
|
|
40
|
+
while current_type == :STRING
|
|
41
|
+
labels << current.value
|
|
42
|
+
advance
|
|
43
|
+
end
|
|
44
|
+
expect(:LBRACE)
|
|
45
|
+
body = parse_body
|
|
46
|
+
expect(:RBRACE)
|
|
47
|
+
labels.reverse.reduce(body) { |inner, label| { label => inner } }
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def parse_expression
|
|
51
|
+
case current_type
|
|
52
|
+
when :STRING, :INTEGER, :FLOAT, :TRUE, :FALSE, :NULL
|
|
53
|
+
current.value.tap { advance }
|
|
54
|
+
when :LBRACKET
|
|
55
|
+
parse_array
|
|
56
|
+
when :LBRACE
|
|
57
|
+
parse_map
|
|
58
|
+
else
|
|
59
|
+
raise ParseError, "unexpected token #{current_type}"
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def parse_array
|
|
64
|
+
advance # skip [
|
|
65
|
+
result = []
|
|
66
|
+
skip_newlines
|
|
67
|
+
unless current_type == :RBRACKET
|
|
68
|
+
result << parse_expression
|
|
69
|
+
while %i[COMMA NEWLINE].include?(current_type)
|
|
70
|
+
advance
|
|
71
|
+
skip_newlines
|
|
72
|
+
break if current_type == :RBRACKET
|
|
73
|
+
|
|
74
|
+
result << parse_expression
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
expect(:RBRACKET)
|
|
78
|
+
result
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def parse_map
|
|
82
|
+
advance # skip {
|
|
83
|
+
result = {}
|
|
84
|
+
skip_newlines
|
|
85
|
+
while %i[IDENTIFIER STRING].include?(current_type)
|
|
86
|
+
key = current.value
|
|
87
|
+
advance
|
|
88
|
+
raise ParseError, "expected EQUALS or COLON, got #{current_type}" unless %i[EQUALS COLON].include?(current_type)
|
|
89
|
+
|
|
90
|
+
advance
|
|
91
|
+
result[key] = parse_expression
|
|
92
|
+
advance if current_type == :COMMA
|
|
93
|
+
skip_newlines
|
|
94
|
+
end
|
|
95
|
+
expect(:RBRACE)
|
|
96
|
+
result
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
def merge_block(result, key, value)
|
|
100
|
+
if result.key?(key)
|
|
101
|
+
result[key] = [result[key]] unless result[key].is_a?(Array)
|
|
102
|
+
result[key] << value
|
|
103
|
+
else
|
|
104
|
+
result[key] = value
|
|
105
|
+
end
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
def current = @tokens[@pos]
|
|
109
|
+
def current_type = current&.type
|
|
110
|
+
def advance = @pos += 1
|
|
111
|
+
|
|
112
|
+
def expect(type)
|
|
113
|
+
raise ParseError, "expected #{type}, got #{current_type}" if current_type != type
|
|
114
|
+
|
|
115
|
+
advance
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
def skip_newlines
|
|
119
|
+
advance while current_type == :NEWLINE
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
end
|
data/lib/hcl_data.rb
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'hcl_data/version'
|
|
4
|
+
require 'hcl_data/errors'
|
|
5
|
+
require 'hcl_data/lexer'
|
|
6
|
+
require 'hcl_data/parser'
|
|
7
|
+
require 'hcl_data/generator'
|
|
8
|
+
|
|
9
|
+
# Read and write data in HCL format.
|
|
10
|
+
module HclData
|
|
11
|
+
|
|
12
|
+
def self.load(input)
|
|
13
|
+
input = input.read if input.respond_to?(:read)
|
|
14
|
+
Parser.new(input).parse
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def self.dump(data, io = nil)
|
|
18
|
+
hcl = Generator.new(data).generate
|
|
19
|
+
if io
|
|
20
|
+
io.write(hcl)
|
|
21
|
+
io
|
|
22
|
+
else
|
|
23
|
+
hcl
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
end
|
metadata
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: hcl_data
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 1.0.0
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- Mike Williams
|
|
8
|
+
bindir: bin
|
|
9
|
+
cert_chain: []
|
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
11
|
+
dependencies: []
|
|
12
|
+
description: |
|
|
13
|
+
A library for reading and writing data in HCL (HashiCorp Configuration
|
|
14
|
+
Language) format, supporting the JSON-equivalent subset of the grammar.
|
|
15
|
+
email: mdub@dogbiscuit.org
|
|
16
|
+
executables: []
|
|
17
|
+
extensions: []
|
|
18
|
+
extra_rdoc_files: []
|
|
19
|
+
files:
|
|
20
|
+
- AGENTS.md
|
|
21
|
+
- CLAUDE.md
|
|
22
|
+
- LICENSE
|
|
23
|
+
- README.md
|
|
24
|
+
- lib/hcl_data.rb
|
|
25
|
+
- lib/hcl_data/errors.rb
|
|
26
|
+
- lib/hcl_data/generator.rb
|
|
27
|
+
- lib/hcl_data/lexer.rb
|
|
28
|
+
- lib/hcl_data/parser.rb
|
|
29
|
+
- lib/hcl_data/version.rb
|
|
30
|
+
homepage: https://github.com/mdub/hcl_data
|
|
31
|
+
licenses:
|
|
32
|
+
- MIT
|
|
33
|
+
metadata:
|
|
34
|
+
rubygems_mfa_required: 'true'
|
|
35
|
+
rdoc_options: []
|
|
36
|
+
require_paths:
|
|
37
|
+
- lib
|
|
38
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
39
|
+
requirements:
|
|
40
|
+
- - ">="
|
|
41
|
+
- !ruby/object:Gem::Version
|
|
42
|
+
version: '3.0'
|
|
43
|
+
- - "<"
|
|
44
|
+
- !ruby/object:Gem::Version
|
|
45
|
+
version: '5'
|
|
46
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
47
|
+
requirements:
|
|
48
|
+
- - ">="
|
|
49
|
+
- !ruby/object:Gem::Version
|
|
50
|
+
version: '0'
|
|
51
|
+
requirements: []
|
|
52
|
+
rubygems_version: 3.7.2
|
|
53
|
+
specification_version: 4
|
|
54
|
+
summary: Read and write HCL data
|
|
55
|
+
test_files: []
|