Module: AutoStacker24::Preprocessor
- Defined in:
- lib/autostacker24/template_preprocessor.rb
Constant Summary collapse
- AT =
Tokens
/\A@/
- NAME =
/\A\w+(::\w+)?/
- LEFT_BRACKET =
/\A\[\s*/
- RIGHT_BRACKET =
/\A\s*\]/
- LEFT_CURLY =
/\A\{/
- RIGHT_CURLY =
/\A\}/
- COMMA =
/\A\s*,\s*/
- KEY =
/\A(\w+)/
- ATTRIB =
/\A(\.\w+)+/
Class Method Summary collapse
-
.interpolate(s) ⇒ Object
interpolates a string with ‘@’ expressions and returns a string or a hash it implements the following non context free grammar in pseudo antlr3.
- .make_function_node(node) ⇒ Object
- .parse(re, s) ⇒ Object
- .parse_attribute(s, name) ⇒ Object
-
.parse_expr(s, embedded = false) ⇒ Object
Parse given string as AutoStacker24 expression, and produce CloudFormation function from it (Fn::GetAtt, Fn::FindInMap, Ref).
- .parse_json(template) ⇒ Object
- .parse_map(s, name) ⇒ Object
- .parse_raw(s) ⇒ Object
- .parse_reference(s, name) ⇒ Object
- .parse_yaml(template) ⇒ Object
- .preprocess(template) ⇒ Object
- .preprocess_json(json) ⇒ Object
- .preprocess_user_data(s) ⇒ Object
- .transform_tags_to_functions(node) ⇒ Object
Class Method Details
.interpolate(s) ⇒ Object
interpolates a string with ‘@’ expressions and returns a string or a hash it implements the following non context free grammar in pseudo antlr3
string : RAW? (expr RAW?)*; expr : ‘@’ ‘name (attr+ | map)? ‘’?; name : ID (‘::’ ID)?; attr : (‘.’ ID)+; map : ‘[’ key (‘,’ key)? ‘]’; key : ID | expr; ID : [a-zA-Z0-9]+; RAW : (~[‘@’]* | FILE)=; FILE : ‘@file://’ [^@s]+ (‘@’ | ‘ ’); FILE_C : ‘@[^] ‘}’;
93 94 95 96 97 98 99 100 101 102 103 104 105 106 |
# File 'lib/autostacker24/template_preprocessor.rb', line 93 def self.interpolate(s) parts = [] while s.length > 0 raw, s = parse_raw(s) parts << raw unless raw.empty? expr, s = parse_expr(s) parts << expr if expr end case parts.length when 0 then '' when 1 then parts[0] else {'Fn::Join' => ['', parts]} end end |
.make_function_node(node) ⇒ Object
39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 |
# File 'lib/autostacker24/template_preprocessor.rb', line 39 def self.make_function_node(node) transformed = Psych::Nodes::Mapping.new() fn = node.tag[1..-1] fn = "Fn::#{fn}" unless fn == 'Ref' # Exception 1: Ref must not be prefixed with Fn:: node.tag = nil transformed.children << Psych::Nodes::Scalar.new(fn) unless fn == 'Fn::GetAtt' # Ecxeption 2: GetAtt has different arguments in short form args = node else resource, attribute = node.value.split('.') args = Psych::Nodes::Sequence.new(fn) args.children << Psych::Nodes::Scalar.new(resource) args.children << Psych::Nodes::Scalar.new(attribute) end transformed.children << args transformed end |
.parse(re, s) ⇒ Object
197 198 199 200 201 |
# File 'lib/autostacker24/template_preprocessor.rb', line 197 def self.parse(re, s) m = re.match(s) return m.to_s, m.post_match if m return nil, s end |
.parse_attribute(s, name) ⇒ Object
170 171 172 173 174 175 176 177 |
# File 'lib/autostacker24/template_preprocessor.rb', line 170 def self.parse_attribute(s, name) attr, s = parse(ATTRIB, s) if attr return {'Fn::GetAtt' => [name, attr[1..-1]]}, s else return nil, s end end |
.parse_expr(s, embedded = false) ⇒ Object
Parse given string as AutoStacker24 expression, and produce CloudFormation function from it (Fn::GetAtt, Fn::FindInMap, Ref).
Parameters:
- s
-
the string to parse
- embedded
-
whether the string is embedded in an AutoStacker24 expression already. Embedded expressions may not start with ‘@{’, only with ‘@’.
137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 |
# File 'lib/autostacker24/template_preprocessor.rb', line 137 def self.parse_expr(s, = false) return nil, s if s.length == 0 at, s = parse(AT, s) raise "expected '@' but got #{s}" unless at curly, s = parse(LEFT_CURLY, s) unless name, s = parse(NAME, s) raise "expected parameter name #{s}" unless name if curly # try attribute, then map, then fallback to simple ref. expr, s = parse_attribute(s, name) expr, s = parse_map(s, name) unless expr expr, s = parse_reference(s, name) unless expr closing_curly, s = parse(RIGHT_CURLY, s) raise "expected '}' but got #{s}" unless closing_curly else # try attribute, then map, then fallback to simple ref. # these are allowed in both embedded and top-level expressions # starting with @... expr, s = parse_attribute(s, name) expr, s = parse_map(s, name) unless expr expr, s = parse_reference(s, name) unless expr end return expr, s end |
.parse_json(template) ⇒ Object
20 21 22 23 24 25 26 |
# File 'lib/autostacker24/template_preprocessor.rb', line 20 def self.parse_json(template) JSON(template) rescue JSON::ParserError => e require 'json/pure' # pure ruby parser has better error diagnostics JSON(template) raise e end |
.parse_map(s, name) ⇒ Object
179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 |
# File 'lib/autostacker24/template_preprocessor.rb', line 179 def self.parse_map(s, name) bracket, s = parse(LEFT_BRACKET, s) return nil, s unless bracket top, s = parse(KEY, s) top, s = parse_expr(s, nested=true) unless top comma, s = parse(COMMA, s) second, s = parse(KEY, s) if comma second, s = parse_expr(s, =true) if comma and second.nil? bracket, s = parse(RIGHT_BRACKET, s) raise "Expected closing ']' #{s}" unless bracket map = [top, second] if second # two arguments found return {'Fn::FindInMap' => [name, top, second]}, s else return {'Fn::FindInMap' => [name + 'Map', {'Ref' => name}, top]}, s end end |
.parse_raw(s) ⇒ Object
108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 |
# File 'lib/autostacker24/template_preprocessor.rb', line 108 def self.parse_raw(s) i = -1 loop do i = s.index('@', i + 1) return s, '' if i.nil? file_match = /\A@file:\/\/([^@\s]+)@?/.match(s[i..-1]) || /\A@\{file:\/\/([^\}]+)\}/.match(s[i..-1]) if file_match # inline file s = s[0, i] + File.read(file_match[1]) + file_match.post_match i -= 1 elsif s[i, 2] =~ /\A@@/ # escape s = s[0, i] + s[i+1..-1] elsif s[i, 2] =~ /\A@(\w|\{)/ return s[0, i], s[i..-1] # return raw, '@...' end end end |
.parse_reference(s, name) ⇒ Object
166 167 168 |
# File 'lib/autostacker24/template_preprocessor.rb', line 166 def self.parse_reference(s, name) return {'Ref' => name}, s end |
.parse_yaml(template) ⇒ Object
28 29 30 |
# File 'lib/autostacker24/template_preprocessor.rb', line 28 def self.parse_yaml(template) (YAML.parse(template)).to_ruby end |
.preprocess(template) ⇒ Object
10 11 12 13 14 15 16 17 18 |
# File 'lib/autostacker24/template_preprocessor.rb', line 10 def self.preprocess(template) if template =~ /\A\s*\/{2}/ preprocess_json(parse_json(template)).to_json elsif template =~ /\A\s*#/ preprocess_json(parse_yaml(template)).to_json else template end end |
.preprocess_json(json) ⇒ Object
57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 |
# File 'lib/autostacker24/template_preprocessor.rb', line 57 def self.preprocess_json(json) if json.is_a?(Hash) json.inject({}) do |m, (k, v)| if k == 'UserData' && v.is_a?(String) m.merge(k => preprocess_user_data(v)) else m.merge(k => preprocess_json(v)) end end elsif json.is_a?(Array) json.map{|v| preprocess_json(v)} elsif json.is_a?(String) interpolate(json) else json end end |
.preprocess_user_data(s) ⇒ Object
75 76 77 |
# File 'lib/autostacker24/template_preprocessor.rb', line 75 def self.preprocess_user_data(s) {'Fn::Base64' => interpolate(s)} end |
.transform_tags_to_functions(node) ⇒ Object
32 33 34 35 36 37 |
# File 'lib/autostacker24/template_preprocessor.rb', line 32 def self.(node) children = node.children children.each_index{|i| children[i] = (children[i])} if children node = make_function_node(node) if node.tag node end |