Class: FunWith::Templates::TemplateEvaluator

Inherits:
Object
  • Object
show all
Defined in:
lib/fun_with/templates/template_evaluator.rb

Constant Summary collapse

TEMPLATE_FILE_REGEX =
/\.(fw)?template$/
VARIABLE_SUBSTITUTION_REGEX =
/%(0+)?([a-zA-Z][A-Za-z0-9_]*)(?:\.([a-zA-Z][A-Za-z0-9_]*))?%/
VERBOSE =
false

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(content_or_path, vars = {}) ⇒ TemplateEvaluator

source: absolute filepath of the source template dest: absolute filepath of the destination. The %sequence_variable% must be intact, and the same as the source,

but the overall filename can be different.

content : either a string to be ERB evaluated or a filepath vars : the variables required by the template you’re filling in.



69
70
71
72
73
74
75
76
77
78
79
80
# File 'lib/fun_with/templates/template_evaluator.rb', line 69

def initialize( content_or_path, vars = {} )
  # debugger # if content_or_path =~ /xhtml/
  @vars = vars

  if content_or_path.is_a?( FunWith::Files::FilePath )   #  && content.file?
    @path    = content_or_path
    make_children  # only directly creates first level.  Rest are handled by recursion
  else
    @path = nil
    @content = content_or_path
  end
end

Instance Attribute Details

#childrenObject (readonly)

Returns the value of attribute children.



4
5
6
# File 'lib/fun_with/templates/template_evaluator.rb', line 4

def children
  @children
end

#contentObject (readonly)

Returns the value of attribute content.



4
5
6
# File 'lib/fun_with/templates/template_evaluator.rb', line 4

def content
  @content
end

#parentObject

Returns the value of attribute parent.



5
6
7
# File 'lib/fun_with/templates/template_evaluator.rb', line 5

def parent
  @parent
end

#pathObject (readonly)

Returns the value of attribute path.



4
5
6
# File 'lib/fun_with/templates/template_evaluator.rb', line 4

def path
  @path
end

#varsObject (readonly)

Returns the value of attribute vars.



4
5
6
# File 'lib/fun_with/templates/template_evaluator.rb', line 4

def vars
  @vars
end

Class Method Details

.destination_filename(src_file, src_root, dest_root, vars) ⇒ Object



283
284
285
286
# File 'lib/fun_with/templates/template_evaluator.rb', line 283

def self.destination_filename( src_file, src_root, dest_root, vars )
  relative_path = src_file.relative_path_from( src_root ).gsub( TEMPLATE_FILE_REGEX, "" )
  dest = dest_root.join( relative_path )
end

.result_to_file(src, dst, vars = {}) ⇒ Object

src: Either a file to be read, or a string dst: An output file (exists or not, will be overwritten) vars: A hash: { :var1 => “Value 1”, :var2 => “Value 2”} In the template, these can be accessed in the ERB way: <%= @var1 %>, <%= @var2 %>



277
278
279
280
281
# File 'lib/fun_with/templates/template_evaluator.rb', line 277

def self.result_to_file( src, dst, vars = {} )
  dst = dst.join( src.basename ) if dst.directory?
  
  self.new( src, vars ).result_to_file( dst )
end

.write(src, dest, vars = {}) ⇒ Object

A simple interface for filling in and cloning an entire directory. The entire directory shares a single set of variables… may be made more flexible later, with an ability to specify special behaviors based on file matching regexes?

For now you feed the function the template directory, the destination you want it cloned into. The vars is a hash, with symbols as the variable names. If you declare { :monkey_name => “Bongo” }, you can use <%= @monkey_name %> in any of the templates, and ‘%monkey_name%’ in the filename.

If a file is named with the ‘.template’ or ‘.fwtemplate’ file extension, it will undergo variable substitution.

If the variable is an Array or Range or other enumeratableable object, and the filename has a variable embedded in it, the template will be evaluated once for each item enumerated, leading to multiple destination files. But if the variable name isn’t in the filename, the whole enumerable object gets passed in. I’m not totally happy with the inconsistency.

Filename variables:

A few examples:

- %i%  :  filled in.  If the variable :i is enumerable, then the template gets evaluated multiple times
- %character.name%  :  The object { :character => Character.new("barry") } gets .name() called on it.
- %000k%  :  The number gets leading zeros.
- %hello_world%  : You can use underscores.

Example: chapter-%0000chap_count%.markdown.template_seq

      ==> chapter-0001.markdown
      ==> chapter-0002.markdown
      ==> chapter-0003.markdown
      ...
      ==> chapter-0099.markdown

      In this case, :chap_count would be the key in vars.

Example (no leading 0s):  chapter%i%.html.template
                          ==> chapter1.html
                          ==> chapter2.html
                          ==> chapter3.html
                          ...
                          ==> chapter99.html

The point of the leading zeros is to specify that the file’s number will be padded with zeros.

When declaring this in vars, you can use any of the following:

vars = {:i => 0..19} (will generate 0 - 19)
vars = {:i => %w(ocelot mouse kitten puppy velociraptor)}  # @i will be filled in with the given strings.  leading 0s will be ignored.

The directory structure is cloned.

Files named with any other extension will simply be copied as-is.



60
61
62
# File 'lib/fun_with/templates/template_evaluator.rb', line 60

def self.write( src, dest, vars = {} )
  self.new( src, vars ).write( dest )
end

Instance Method Details

#destination(dest_root = nil) ⇒ Object

if no destination root is given, then a relative path from the src_root is given if this calculated dest is a directory, while the template @path is a file, then the template’s basename is appended to the dest and filled in



181
182
183
184
185
186
187
188
# File 'lib/fun_with/templates/template_evaluator.rb', line 181

def destination( dest_root = nil )
  dest = dest_root ? dest_root.join( self.relative_path_from_root ) : self.relative_path_from_root
  
  dest = dest.join( @path.basename ) if dest.directory? && @path.file?
    
  dest = dest.gsub( TEMPLATE_FILE_REGEX, "" )
  FilenameVarData.fill_in_path( dest, parse_filename_vars, @vars )
end

#each_node {|_self| ... } ⇒ Object

Yields:

  • (_self)

Yield Parameters:



167
168
169
170
171
172
173
174
175
# File 'lib/fun_with/templates/template_evaluator.rb', line 167

def each_node( &block )
  yield self

  for child in self.children
    child.each_node do |node|
      yield node
    end
  end
end

#each_node_with_destination(dest_root = :temp, &block) ⇒ Object



190
191
192
193
194
195
196
197
198
199
200
201
# File 'lib/fun_with/templates/template_evaluator.rb', line 190

def each_node_with_destination( dest_root = :temp, &block )
  dest_root = FunWith::Files::FilePath.tmpdir if dest_root == :temp
    
  self.each_node do |node|
    dst = node.destination( dest_root )
     if dst       # if the filename needs variable replacing
       yield [node, dst]
     else   
       warn( "Warning: file #{node.path} was not returned.") if FunWith::Templates.gem_verbose?
     end
  end
end

#is_template?(filename) ⇒ Boolean

Returns:

  • (Boolean)


288
289
290
# File 'lib/fun_with/templates/template_evaluator.rb', line 288

def is_template?( filename )
  !!((filename) =~ TEMPLATE_FILE_REGEX )
end

#loop_over(var, &block) ⇒ Object



301
302
303
304
# File 'lib/fun_with/templates/template_evaluator.rb', line 301

def loop_over( var, &block )
  var = [var] unless loopable_object?( var )
  var.each(&block)
end

#loopable_object?(obj) ⇒ Boolean

Returns:

  • (Boolean)


292
293
294
295
# File 'lib/fun_with/templates/template_evaluator.rb', line 292

def loopable_object?( obj )
  obj.is_a?( Array ) || obj.is_a?( Range )
  # obj.respond_to?(:each) && !obj.is_a?(String) && !obj.is_a?(Hash)
end

#loopable_variables?(var_info, vars) ⇒ Boolean

Returns:

  • (Boolean)


297
298
299
# File 'lib/fun_with/templates/template_evaluator.rb', line 297

def loopable_variables?( var_info, vars )
  var_info.map(&:name).detect{ |name| loopable_object?( vars[name]) }
end

#make_childrenObject



82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
# File 'lib/fun_with/templates/template_evaluator.rb', line 82

def make_children
  @children = []
  
  # TODO: Fix fwf so that recursiveness can be turned off.
  if @path.directory?
    child_paths = @path.glob(:all, :recursive => false).select{|entry| entry.dirname == @path}
  elsif parse_filename_vars.fwf_blank? || ! loopable_variables?( parse_filename_vars, @vars ) # The current template is leaf?
    child_paths = []
  else # we need to make the pathname variants
    child_paths = [@path]   
  end
  
  for entry in child_paths
    combos = var_combos( parse_filename_vars( entry ), @vars )
    
    if combos.fwf_blank?   # Then you don't need to go any deeper?
      child = TemplateEvaluator.new( entry, @vars.clone )
      child.parent = self
      @children << child
    else
      for narrowed_var_set in combos
        narrowed_var_set.inspect
      
        child = TemplateEvaluator.new( entry, @vars.clone.merge( narrowed_var_set ) )
        child.parent = self
        @children << child
      end
    end
  end
end

#parse_filename_vars(path = @path) ⇒ Object

if the var found in the filename isn’t included in the set of variables given (@vars), no substitution will be performed



256
257
258
259
260
261
262
263
264
265
266
267
268
269
# File 'lib/fun_with/templates/template_evaluator.rb', line 256

def parse_filename_vars( path = @path )
  var_matches = path.scan( VARIABLE_SUBSTITUTION_REGEX )
  
  return [] if var_matches.fwf_blank?
  
  var_matches.inject([]) do |memo, var_match|
    # only return the matches where the name of the variable is in @vars
    if @vars.keys.include?( var_match[1].to_sym )
      memo << FilenameVarData.new( var_match[1], var_match[2], var_match[0] )
    end
    
    memo
  end
end

#relative_path_from_rootObject



163
164
165
# File 'lib/fun_with/templates/template_evaluator.rb', line 163

def relative_path_from_root
  @path.relative_path_from( self.src_root )
end

#resultObject

only called on leaf/files



222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
# File 'lib/fun_with/templates/template_evaluator.rb', line 222

def result
  if @path.nil? || is_template?( @path )
    begin
      # formerly @template_evaluator_current_content.  Don't know if removing the @ makes a diff.
      template_evaluator_current_content = self.content  # In case someone using the templates uses @content
      template_evaluator_set_local_vars( @vars ) do
        ERB.new( template_evaluator_current_content ).result( binding )
      end
    rescue Exception => e
      warn( "Template #{ @path } could not be filled in properly (using vars: #{@vars.inspect}).  Returning error as result." )
      result = ["FunWith::Templates::TemplateEvaluator ERROR"]
      result << ""
      result << "path: #{@path}"
      result << ""
      result << "vars: #{@vars.inspect}"
      result << ""
      result << "#{e.class}: #{e.message}"
      result += e.backtrace.map{|line| "\t#{line}" }
      
      FunWith::Templates.say_if_verbose( result.join("\n") )
      result.join("\n")
    end
  elsif @path.file?
    # just copy if it's not a template
    @path.read
  end
end

#result_to_file(dest) ⇒ Object



250
251
252
253
# File 'lib/fun_with/templates/template_evaluator.rb', line 250

def result_to_file( dest )
  dest = dest.fwf_filepath.expand
  dest.write( self.result )
end

#src_rootObject



159
160
161
# File 'lib/fun_with/templates/template_evaluator.rb', line 159

def src_root
  self.parent ? self.parent.src_root : @path
end

#var_combos(var_data, vars, &block) ⇒ Object

given the vars and a list of which entries to loop over

Should yield combinations of entries in the multi_entry variables. for example comboize( [ :i, :j, :k], { :i => 0..2, :j => 0..2, :k => 0..2 } ) would yield { :i => 0, :j => 0, :k => 0 }

then { :i => 0, :j => 0, :k => 1 }
then { :i => 0, :j => 0, :k => 2 }
then { :i => 0, :j => 1, :k => 0 } ...

The results can just be merged into the cloned variable set for a given child.



124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
# File 'lib/fun_with/templates/template_evaluator.rb', line 124

def var_combos( var_data, vars, &block )
  return [] if var_data.fwf_blank?
  var_name = var_data.shift
  if var_name.nil?
    raise "Recursed too far!"
  elsif var_data.fwf_blank?    # last variable in the list, so start yielding
    combos = []
    loop_over( vars[ var_name.name ] ) do |item|
      hash = { var_name.name => item }
      combos << hash
    end
    return combos
  else                         # recurse into other variables
    # Order doesn't matter, so take the results of the next recursion, pop from the front,
    # create a subarray with the variations, and push to the back.  Stop when the key is found.
    partial_combos = var_combos( var_data, vars )
    
    until partial_combos.length == 0 || partial_combos.first.keys.include?( var_name.name )
      hash = partial_combos.shift
      filled_combos = []
      
      loop_over( vars[var_name.name] ) do |item|
        h = hash.clone
        h[var_name.name] = item
        filled_combos << h
      end
      
      partial_combos += filled_combos
    end
    
    return partial_combos
  end
end

#write(dest = :temp) ⇒ Object



203
204
205
206
207
208
209
210
211
212
213
214
215
# File 'lib/fun_with/templates/template_evaluator.rb', line 203

def write( dest = :temp )
  dest = FunWith::Files::FilePath.tmpdir if dest == :temp
  
  self.each_node_with_destination( dest ) do |node, destination|
    if node.path.directory?
      destination.touch_dir
    else
      destination.write( node.result )
    end
  end
  
  dest
end