lib/lono/template/template.rb in lono-3.5.0 vs lib/lono/template/template.rb in lono-4.0.0
- old
+ new
@@ -1,251 +1,59 @@
require 'erb'
require 'json'
require 'base64'
class Lono::Template::Template
- include Lono::Template::Helpers
- include Lono::CurrentRegion
include ERB::Util
+ # Main template DSL methods are: source and variables
+ #
+ # template "example-2" do
+ # source "example"
+ # variables(test: 1)
+ # end
+ #
+ attr_reader :name
def initialize(name, block=nil, options={})
# Taking care to name instance variables with _ in front because we load the
# variables from config/variables and those instance variables can clobber these
# instance variables
- @_name = name
- @_options = options
- @_detected_format = options[:detected_format]
- @_block = block
- @_config_path = "#{Lono.root}/config"
- @_source = default_source(name)
+ @name = name
+ @block = block
+ @options = options
+ @source_path = default_source_path(name)
end
- def default_source(name)
- "#{Lono.root}/templates/#{name}.#{@_detected_format}" # defaults to name, source method overrides
- end
-
- def build
- load_variables
- load_custom_helpers
- instance_eval(&@_block) if @_block
- template = IO.read(@_source)
- erb_result(@_source, template)
- end
-
- def load_variables
- load_variables_folder("base")
- load_variables_folder(Lono.env)
- end
-
- # Load the variables defined in config/variables/* to make available in the
- # template blocks in config/templates/*.
- #
- # Example:
- #
- # `config/variables/base/variables.rb`:
- # @foo = 123
- #
- # `config/templates/base/resources.rb`:
- # template "mytemplate.yml" do
- # source "mytemplate.yml.erb"
- # variables(foo: @foo)
- # end
- #
- # NOTE: Only able to make instance variables avaialble with instance_eval
- # Wasnt able to make local variables available.
- def load_variables_folder(folder)
- paths = Dir.glob("#{@_config_path}/variables/#{folder}/**/*")
- paths.select{ |e| File.file? e }.each do |path|
- instance_eval(IO.read(path))
- end
- end
-
- # Load custom helper methods from the user's infra repo
- def load_custom_helpers
- Dir.glob("#{Lono.root}/helpers/**/*_helper.rb").each do |path|
- filename = path.sub(%r{.*/},'').sub('.rb','')
- module_name = filename.classify
-
- require path
- self.class.send :include, module_name.constantize
- end
- end
-
+ # Returns path, example: ./app/templates/example.yml
def source(path)
- @_source = path[0..0] == '/' ? path : "#{Lono.root}/templates/#{path}"
- @_source += ".#{@_detected_format}"
+ @source_path = path[0..0] == '/' ? path : "#{Lono.config.templates_path}/#{path}"
+ @source_path += ".yml"
end
def variables(vars={})
vars.each do |var,value|
- instance_variable_set("@#{var}", value)
+ context.instance_variable_set("@#{var}", value)
end
end
- def erb_result(path, template)
- begin
- ERB.new(template, nil, "-").result(binding)
- rescue Exception => e
- puts e
- puts e.backtrace if ENV['DEBUG']
-
- # how to know where ERB stopped? - https://www.ruby-forum.com/topic/182051
- # syntax errors have the (erb):xxx info in e.message
- # undefined variables have (erb):xxx info in e.backtrac
- error_info = e.message.split("\n").grep(/\(erb\)/)[0]
- error_info ||= e.backtrace.grep(/\(erb\)/)[0]
- raise unless error_info # unable to find the (erb):xxx: error line
- line = error_info.split(':')[1].to_i
- puts "Error evaluating ERB template on line #{line.to_s.colorize(:red)} of: #{path.sub(/^\.\//, '').colorize(:green)}"
-
- template_lines = template.split("\n")
- context = 5 # lines of context
- top, bottom = [line-context-1, 0].max, line+context-1
- spacing = template_lines.size.to_s.size
- template_lines[top..bottom].each_with_index do |line_content, index|
- line_number = top+index+1
- if line_number == line
- printf("%#{spacing}d %s\n".colorize(:red), line_number, line_content)
- else
- printf("%#{spacing}d %s\n", line_number, line_content)
- end
- end
- exit 1 unless ENV['TEST']
- end
+ # internal methods
+ def default_source_path(name)
+ "#{Lono.config.templates_path}/#{name}.yml" # defaults to name, source method overrides
end
- def transform_array(arr)
- arr.map! {|x| x =~ /=>/ ? x : x.inspect }
- arr.join(',')
- end
+ def build
+ instance_eval(&@block) if @block
- # transform each line of bash script to array with cloudformation template objects
- def transform(data)
- data = evaluate(data)
- if data[-1].is_a?(String)
- data[0..-2] + ["#{data[-1]}\n"]
+ if File.exist?(@source_path)
+ RenderMePretty.result(@source_path, context: context)
else
- data + ["\n"]
+ puts "ERROR: #{@source_path} does not exist, but it was used as a template source.".colorize(:red)
+ exit 1
end
end
- # Input:
- # String
- # Output:
- # Array of parse positions
- #
- # The positions of tokens taking into account when brackets start and close,
- # handles nested brackets.
- def bracket_positions(line)
- positions,pair,count = [],[],0
-
- line.split('').each_with_index do |char,i|
- pair << i if pair.empty?
-
- first_pair_char = line[pair[0]]
- if first_pair_char == '{' # object logic
- if char == '{'
- count += 1
- end
-
- if char == '}'
- count -= 1
- if count == 0
- pair << i
- positions << pair
- pair = []
- end
- end
- else # string logic
- lookahead = line[i+1]
- if lookahead == '{'
- pair << i
- positions << pair
- pair = []
- end
- end
- end # end of loop
-
- # for string logic when lookahead does not contain a object token
- # need to clear out what's left to match the final pair
- if !pair.empty?
- pair << line.size - 1
- positions << pair
- end
-
- positions
- end
-
- # Input:
- # Array - bracket_positions
- # Ouput:
- # Array - positions that can be use to determine what to parse
- def parse_positions(line)
- positions = bracket_positions(line)
- positions.flatten
- end
-
- # Input
- # String line of code to decompose into chunks, some can be transformed into objects
- # Output
- # Array of strings, some can be transformed into objects
- #
- # Example:
- # line = 'a{b}c{d{d}d}e' # nested brackets
- # template.decompose(line).should == ['a','{b}','c','{d{d}d}','e']
- def decompose(line)
- positions = parse_positions(line)
- return [line] if positions.empty?
-
- result = []
- str = ''
- until positions.empty?
- left = positions.shift
- right = positions.shift
- token = line[left..right]
- # if cfn object, add to the result set but after clearing out
- # the temp str that is being built up when the token is just a string
- if cfn_object?(token)
- unless str.empty? # first token might be a object
- result << str
- str = ''
- end
- result << token
- else
- str << token # keeps building up the string
- end
- end
-
- # at the of the loop there's a leftover string, unless the last token
- # is an object
- result << str unless str.empty?
-
- result
- end
-
- def cfn_object?(s)
- exact = %w[Ref]
- pattern = %w[Fn::]
- exact_match = !!exact.detect {|word| s.include?(word)}
- pattern_match = !!pattern.detect {|p| s =~ Regexp.new(p)}
- (exact_match || pattern_match) && s =~ /^{/ && s =~ /=>/
- end
-
- def recompose(decomposition)
- decomposition.map { |s| cfn_object?(s) ? eval(s) : s }
- end
-
- def evaluate(line)
- recompose(decompose(line))
- end
-
- # For simple just parameters files that can also be generated with lono, the CFN
- # Fn::Base64 function is not available and as lono is not being used in the context
- # of CloudFormation. So this can be used in it's place.
- def encode_base64(text)
- Base64.strict_encode64(text).strip
- end
-
- def name
- @_name
+ # Context for ERB rendering.
+ # This is where we control what references get passed to the ERB rendering.
+ def context
+ @context ||= Lono::Template::Context.new(@options)
end
end