Skip to content

Commit fbfad4f

Browse files
committed
Added to_toml function
1 parent 64d87d2 commit fbfad4f

File tree

4 files changed

+192
-0
lines changed

4 files changed

+192
-0
lines changed

lib/puppet/functions/to_toml.rb

+22
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
# frozen_string_literal: true
2+
3+
require_relative '../../../puppet_x/stdlib/toml_dumper.rb'
4+
5+
# @summary Convert a data structure and output to TOML.
6+
Puppet::Functions.create_function(:to_toml) do
7+
# @param data Data structure which needs to be converted into TOML
8+
# @return [String] Converted data as TOML string
9+
# @example How to output TOML to a file
10+
# file { '/tmp/config.toml':
11+
# ensure => file,
12+
# content => to_toml($myhash),
13+
# }
14+
dispatch :to_toml do
15+
required_param 'Hash', :data
16+
return_type 'String'
17+
end
18+
19+
def to_toml(data)
20+
PuppetX::Stdlib::TomlDumper.new(data).toml_str
21+
end
22+
end

lib/puppet_x/stdlib.rb

+4
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
require 'puppet_x'
2+
3+
# common PuppetX::Stdlib module definition
4+
module PuppetX::Stdlib; end

lib/puppet_x/stdlib/toml_dumper.rb

+138
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,138 @@
1+
# The MIT License (MIT)
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
11+
# all 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
19+
# THE SOFTWARE.
20+
21+
require 'puppet_x/stdlib'
22+
require 'date'
23+
24+
module PuppetX::Stdlib
25+
# The Dumper class was blindly copied from https://github.com/emancu/toml-rb/blob/v2.0.1/lib/toml-rb/dumper.rb
26+
# This allows us to use the `to_toml` function as a `Deferred` function because the `toml-rb` gem is usually
27+
# installed on the agent and the `Deferred` function gets evaluated before the catalog gets applied. This
28+
# makes it in most scenarios impossible to install the gem before it is used.
29+
class TomlDumper
30+
attr_reader :toml_str
31+
32+
def initialize(hash)
33+
@toml_str = ''
34+
35+
visit(hash, [])
36+
end
37+
38+
private
39+
40+
def visit(hash, prefix, extra_brackets = false)
41+
simple_pairs, nested_pairs, table_array_pairs = sort_pairs hash
42+
43+
if prefix.any? && (simple_pairs.any? || hash.empty?)
44+
print_prefix prefix, extra_brackets
45+
end
46+
47+
dump_pairs simple_pairs, nested_pairs, table_array_pairs, prefix
48+
end
49+
50+
def sort_pairs(hash)
51+
nested_pairs = []
52+
simple_pairs = []
53+
table_array_pairs = []
54+
55+
hash.keys.sort.each do |key|
56+
val = hash[key]
57+
element = [key, val]
58+
59+
if val.is_a? Hash
60+
nested_pairs << element
61+
elsif val.is_a?(Array) && val.first.is_a?(Hash)
62+
table_array_pairs << element
63+
else
64+
simple_pairs << element
65+
end
66+
end
67+
68+
[simple_pairs, nested_pairs, table_array_pairs]
69+
end
70+
71+
def dump_pairs(simple, nested, table_array, prefix = [])
72+
# First add simple pairs, under the prefix
73+
dump_simple_pairs simple
74+
dump_nested_pairs nested, prefix
75+
dump_table_array_pairs table_array, prefix
76+
end
77+
78+
def dump_simple_pairs(simple_pairs)
79+
simple_pairs.each do |key, val|
80+
key = quote_key(key) unless bare_key? key
81+
@toml_str << "#{key} = #{to_toml(val)}\n"
82+
end
83+
end
84+
85+
def dump_nested_pairs(nested_pairs, prefix)
86+
nested_pairs.each do |key, val|
87+
key = quote_key(key) unless bare_key? key
88+
89+
visit val, prefix + [key], false
90+
end
91+
end
92+
93+
def dump_table_array_pairs(table_array_pairs, prefix)
94+
table_array_pairs.each do |key, val|
95+
key = quote_key(key) unless bare_key? key
96+
aux_prefix = prefix + [key]
97+
98+
val.each do |child|
99+
print_prefix aux_prefix, true
100+
args = sort_pairs(child) << aux_prefix
101+
102+
dump_pairs(*args)
103+
end
104+
end
105+
end
106+
107+
def print_prefix(prefix, extra_brackets = false)
108+
new_prefix = prefix.join('.')
109+
new_prefix = '[' + new_prefix + ']' if extra_brackets
110+
111+
@toml_str += "[" + new_prefix + "]\n" # rubocop:disable Style/StringLiterals
112+
end
113+
114+
def to_toml(obj)
115+
if obj.is_a?(Time) || obj.is_a?(DateTime)
116+
obj.strftime('%Y-%m-%dT%H:%M:%SZ')
117+
elsif obj.is_a?(Date)
118+
obj.strftime('%Y-%m-%d')
119+
elsif obj.is_a? Regexp
120+
obj.inspect.inspect
121+
elsif obj.is_a? String
122+
obj.inspect.gsub(/\\(#[$@{])/, '\1') # rubocop:disable Style/RegexpLiteral
123+
else
124+
obj.inspect
125+
end
126+
end
127+
128+
def bare_key?(key)
129+
# rubocop:disable Style/RegexpLiteral
130+
!!key.to_s.match(/^[a-zA-Z0-9_-]*$/)
131+
# rubocop:enable Style/RegexpLiteral
132+
end
133+
134+
def quote_key(key)
135+
'"' + key.gsub('"', '\\"') + '"'
136+
end
137+
end
138+
end

spec/functions/to_toml_spec.rb

+28
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
# frozen_string_literal: true
2+
3+
require 'spec_helper'
4+
5+
describe 'to_toml' do
6+
context 'fails on invalid params' do
7+
it { is_expected.not_to eq(nil) }
8+
[
9+
nil,
10+
'',
11+
'foobar',
12+
1,
13+
true,
14+
false,
15+
[],
16+
].each do |value|
17+
it { is_expected.to run.with_params(value).and_raise_error(ArgumentError) }
18+
end
19+
end
20+
21+
context 'returns TOML string on valid params' do
22+
it { is_expected.to run.with_params({}).and_return('') }
23+
it { is_expected.to run.with_params(foo: 'bar').and_return("foo = \"bar\"\n") }
24+
it { is_expected.to run.with_params(foo: { bar: 'baz' }).and_return("[foo]\nbar = \"baz\"\n") }
25+
it { is_expected.to run.with_params(foo: ['bar', 'baz']).and_return("foo = [\"bar\", \"baz\"]\n") }
26+
it { is_expected.to run.with_params(foo: [{ bar: {}, baz: {} }]).and_return("[[foo]]\n[foo.bar]\n[foo.baz]\n") }
27+
end
28+
end

0 commit comments

Comments
 (0)