fun_with_templates 0.0.3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (46) hide show
  1. checksums.yaml +15 -0
  2. data/.document +5 -0
  3. data/CHANGELOG.markdown +6 -0
  4. data/Gemfile +18 -0
  5. data/LICENSE.txt +20 -0
  6. data/README.rdoc +67 -0
  7. data/Rakefile +45 -0
  8. data/VERSION +1 -0
  9. data/lib/fun_with/core_extensions/kernel.rb +18 -0
  10. data/lib/fun_with/templates/directory_builder.rb +36 -0
  11. data/lib/fun_with/templates/filename_var_data.rb +42 -0
  12. data/lib/fun_with/templates/template_evaluator.rb +307 -0
  13. data/lib/fun_with_templates.rb +4 -0
  14. data/test/helper.rb +57 -0
  15. data/test/templates/00/a.rb.template +7 -0
  16. data/test/templates/00/dir/subdir/style.css +4 -0
  17. data/test/templates/00/seqfiles/page%0000i%.html.template +12 -0
  18. data/test/templates/00/seqfiles/page%j%.html.template +12 -0
  19. data/test/templates/00/seqfiles/page_about_%critter_name%.html.template +15 -0
  20. data/test/templates/01/%string.length%/%string%-%string.length%.nontemplate +1 -0
  21. data/test/templates/01/%string.length%/%string%-%string.length%.txt.template +1 -0
  22. data/test/templates/02/%class%.rb.template +6 -0
  23. data/test/templates/03/coordinates.%i%-%j%-%k%.html.template +14 -0
  24. data/test/templates/03/dir%0000i%/dir%0000j%/file%0000k%.py.template +1 -0
  25. data/test/templates/03/index.html.template +22 -0
  26. data/test/templates/epf/book/afterword.markdown +4 -0
  27. data/test/templates/epf/book/chapter-%0000chapter%.markdown.template +4 -0
  28. data/test/templates/epf/book/cover.xhtml.template +13 -0
  29. data/test/templates/epf/book/foreword.markdown +6 -0
  30. data/test/templates/epf/book/images/cover.png +0 -0
  31. data/test/templates/epf/book/stylesheets/stylesheet.css +2 -0
  32. data/test/templates/epf/book/title_page.markdown.template +5 -0
  33. data/test/templates/epf/notes/character.%character.name_for_file%.markdown.template +15 -0
  34. data/test/templates/epf/notes/images/cover.png +0 -0
  35. data/test/templates/epf/notes/stylesheets/stylesheet.css +2 -0
  36. data/test/templates/epf/settings/actions/local_action.rb.example +14 -0
  37. data/test/templates/epf/settings/config.rb.template +52 -0
  38. data/test/templates/epf/settings/htmlizers.rb +83 -0
  39. data/test/templates/epf/settings/wordcount.template +6 -0
  40. data/test/test_directory_builder.rb +24 -0
  41. data/test/test_filename_var_data.rb +51 -0
  42. data/test/test_fun_with_templates.rb +21 -0
  43. data/test/test_parse_filename_vars.rb +25 -0
  44. data/test/test_string_templates.rb +14 -0
  45. data/test/test_write_example_templates.rb +149 -0
  46. metadata +202 -0
checksums.yaml ADDED
@@ -0,0 +1,15 @@
1
+ ---
2
+ !binary "U0hBMQ==":
3
+ metadata.gz: !binary |-
4
+ ZDQ2ZTFmYzJhZTUxMDRhYWE1OTNhODUxZTkyYTQ2MmU0ODU4OGE1ZQ==
5
+ data.tar.gz: !binary |-
6
+ OTQ0ZDAyYmJjODYxNDZmZDE1NjY4ZDgxZmE5M2ExODdiYjM4YTA2MQ==
7
+ SHA512:
8
+ metadata.gz: !binary |-
9
+ MTZmNWE5MTE4YTlmZjRjNmNiZjgzYTc3ZDQzYTcxMTI3MGJkOWFhYTE5ZGI5
10
+ OWZjMjFkZjhjNGMxYmI2MTZkNDQzM2NlNzhlN2NjNTgwMjViMTRmMTM3ZTcw
11
+ ZDhmZDBkMDBkMDViMDFjZjk4MmY5ZjEzYTU3YThjNDIxODQxZGY=
12
+ data.tar.gz: !binary |-
13
+ NGI3MjNlZDI0ZjczYWE2MzczZmZmNDEyZWVkODdmZmIxOWU3ZjEwOTRhN2Q3
14
+ YmFiNWVjMDJjNzJmNTk5YWY3MzY3MzRmYjAxNDFlZWE3OTNjYmY2Y2Y0NjZm
15
+ MTFiYTc4OTA2ODkyZDFlYWQyMWUyNjA1OTA1YTY4NmI2NzMwNjI=
data/.document ADDED
@@ -0,0 +1,5 @@
1
+ lib/**/*.rb
2
+ bin/*
3
+ -
4
+ features/**/*.feature
5
+ LICENSE.txt
@@ -0,0 +1,6 @@
1
+ v. 0.0.3
2
+ ========
3
+
4
+ * Now with a changelog
5
+ * New dependencies on `fun_with_gems`, `fun_with_testing`.
6
+ * Now skips over files/directories when variable embedded in filename doesn't exist (is nil).
data/Gemfile ADDED
@@ -0,0 +1,18 @@
1
+ source "http://rubygems.org"
2
+ # Add dependencies required to use your gem here.
3
+ # Example:
4
+ # gem "activesupport", ">= 2.3.5"
5
+
6
+ # Add dependencies to develop your gem here.
7
+ # Include everything needed to run rake, tests, features, etc.
8
+ group :development do
9
+ gem "shoulda", "~> 3.5"
10
+ gem "rdoc", "~> 3.12"
11
+ gem "bundler", "~> 1.5"
12
+ gem "jeweler", "~> 2.0"
13
+ gem "debugger", "~> 1.6"
14
+ gem "fun_with_testing", "~> 0.0"
15
+ end
16
+
17
+ gem 'fun_with_gems', "~> 0.0"
18
+ gem 'fun_with_string_colors'
data/LICENSE.txt ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2013 Bryce Anderson
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.rdoc ADDED
@@ -0,0 +1,67 @@
1
+ = fun_with_templates
2
+
3
+ A simple interface for filling in and cloning an entire directory. The entire directory shares a single set of variables.
4
+
5
+ For now you feed the function the template directory, the destination you want it cloned into.
6
+ The vars is a hash, with symbols as the variable names. If you declare { :monkey_name => "Bongo" },
7
+ you can use <%= @monkey_name %> in any of the templates, and '%monkey_name%' in the filename.
8
+
9
+ If a file is named with the '.template' or '.fwtemplate' file extension, it will undergo variable substitution.
10
+
11
+ 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. If the variable name isn't in the filename, the whole enumerable object gets passed in. This is weird, but it gives one advantage: in the templates where you don't mention the variable in the filename, you can access the whole range.
12
+
13
+ Filename variables:
14
+
15
+ A few examples:
16
+ - %i% : filled in. If the variable :i is enumerable, then the template gets evaluated multiple times
17
+ - %character.name% : The object { :character => Character.new("barry") } gets .name() called on it.
18
+ You can also make :character a hash with the :name key ( {:character => {:name => "barry" } } ), and 'barry' will get substituted in.
19
+ - %000k% : The number gets leading zeros.
20
+ - %hello_world% : You can use underscores.
21
+
22
+ Example: chapter-%0000chap_count%.markdown.template_seq
23
+ ==> chapter-0001.markdown
24
+ ==> chapter-0002.markdown
25
+ ==> chapter-0003.markdown
26
+ ...
27
+ ==> chapter-0099.markdown
28
+
29
+ In this case, :chap_count would be the key in vars.
30
+
31
+ Example (no leading 0s): chapter%i%.html.template
32
+ ==> chapter1.html
33
+ ==> chapter2.html
34
+ ==> chapter3.html
35
+ ...
36
+ ==> chapter99.html
37
+
38
+ The point of the leading zeros is to specify that the file's number will be padded with that number of zeros.
39
+
40
+ When declaring this in vars, you can use any of the following:
41
+ vars = {:i => 0..19} (will generate 0 - 19)
42
+ vars = {:i => %w(ocelot mouse kitten puppy velociraptor)} # @i will be filled in with the given strings. leading 0s will be ignored, because the items in the array aren't numbers.
43
+
44
+ The directory structure is cloned, though variable substitution can be done in directories as well as files. If the variable embedded in the directory is an Array or Range object, it will create a separate directory for each item. Beware combinatorial explosions.
45
+
46
+ Files named with extensions other than .fwtemplate and .template will simply be copied as-is.
47
+
48
+
49
+ == Feature ideas
50
+
51
+ .. may be made more flexible later, with an ability to specify special behaviors based on file-matching regexes? Also, maybe having template evaluators other than ERB would be a good idea. I wonder if there's demand for it.
52
+
53
+ == Contributing to fun_with_templates
54
+
55
+ * Check out the latest master to make sure the feature hasn't been implemented or the bug hasn't been fixed yet.
56
+ * Check out the issue tracker to make sure someone already hasn't requested it and/or contributed it.
57
+ * Fork the project.
58
+ * Start a feature/bugfix branch.
59
+ * Commit and push until you are happy with your contribution.
60
+ * Make sure to add tests for it. This is important so I don't break it in a future version unintentionally.
61
+ * Please try not to mess with the Rakefile, version, or history. If you want to have your own version, or is otherwise necessary, that is fine, but please isolate to its own commit so I can cherry-pick around it.
62
+
63
+ == Copyright
64
+
65
+ Copyright (c) 2013 Bryce Anderson. See LICENSE.txt for
66
+ further details.
67
+
data/Rakefile ADDED
@@ -0,0 +1,45 @@
1
+ # encoding: utf-8
2
+
3
+ require 'rubygems'
4
+ require 'bundler'
5
+ begin
6
+ Bundler.setup(:default, :development)
7
+ rescue Bundler::BundlerError => e
8
+ $stderr.puts e.message
9
+ $stderr.puts "Run `bundle install` to install missing gems"
10
+ exit e.status_code
11
+ end
12
+ require 'rake'
13
+
14
+ require 'jeweler'
15
+ Jeweler::Tasks.new do |gem|
16
+ # gem is a Gem::Specification... see http://docs.rubygems.org/read/chapter/20 for more options
17
+ gem.name = "fun_with_templates"
18
+ gem.homepage = "http://github.com/darthschmoo/fun_with_templates"
19
+ gem.license = "MIT"
20
+ gem.summary = "Templates made stupid"
21
+ gem.description = "A simple approach to 'fill in the blank' file templates, which may be useful for some tasks."
22
+ gem.email = "keeputahweird@gmail.com"
23
+ gem.authors = ["Bryce Anderson"]
24
+ # dependencies defined in Gemfile
25
+ end
26
+ Jeweler::RubygemsDotOrgTasks.new
27
+
28
+ require 'rake/testtask'
29
+ Rake::TestTask.new(:test) do |test|
30
+ test.libs << 'lib' << 'test'
31
+ test.pattern = 'test/**/test_*.rb'
32
+ test.verbose = true
33
+ end
34
+
35
+ task :default => :test
36
+
37
+ require 'rdoc/task'
38
+ Rake::RDocTask.new do |rdoc|
39
+ version = File.exist?('VERSION') ? File.read('VERSION') : ""
40
+
41
+ rdoc.rdoc_dir = 'rdoc'
42
+ rdoc.title = "fun_with_templates #{version}"
43
+ rdoc.rdoc_files.include('README*')
44
+ rdoc.rdoc_files.include('lib/**/*.rb')
45
+ end
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.0.3
@@ -0,0 +1,18 @@
1
+ module Kernel
2
+ def template_evaluator_set_local_vars( locals = {}, &block )
3
+ old_local_vars = {}
4
+
5
+ for k, v in locals
6
+ var = :"@#{k}"
7
+ old_local_vars[k] = instance_variable_get( var )
8
+ instance_variable_set( var, v )
9
+ end
10
+
11
+ yield
12
+ ensure # make all as it once was
13
+ for k, v in old_local_vars
14
+ var = :"@#{k}"
15
+ instance_variable_set( var, v )
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,36 @@
1
+ # Add-on to FunWith::Files # Not going to implement just now. Having trouble deciding what it ought to do.
2
+ # module FunWith
3
+ # module Files
4
+ # class DirectoryBuilder
5
+ # def template( *args, &block )
6
+ # vars = args.last.is_a?(Hash) ? args.pop : {}
7
+ #
8
+ # if args.length == 2
9
+ # src = args.first
10
+ # dst = args.last
11
+ # elsif args.length == 1
12
+ # src = args.first
13
+ # dst = self.current_path
14
+ # else
15
+ # raise ArgumentError.new( "Wrong number of arguments: template(src, dst, variables)" )
16
+ # end
17
+ #
18
+ # if block_given?
19
+ # collector = FunWith::Templates::VarCollector.collect do |c|
20
+ # yield c
21
+ # end
22
+ #
23
+ # vars.merge!( collector )
24
+ # end
25
+ #
26
+ # FunWith::Templates::TemplateEvaluator.result_to_file( src, dst, vars )
27
+ #
28
+ # # if src.directory?
29
+ # # FunWith::Templates::TemplateEvaluator.evaluate( src, dest, vars )
30
+ # # elsif src.file?
31
+ # # FunWith::Templates::TemplateEvaluator.result_to_file( src, dest, vars )
32
+ # # end
33
+ # end
34
+ # end
35
+ # end
36
+ # end
@@ -0,0 +1,42 @@
1
+ module FunWith
2
+ module Templates
3
+ class FilenameVarData
4
+ attr_accessor :num_format, :var_name, :method_to_call
5
+
6
+ def initialize( var_name, method_to_call = nil, num_format = nil )
7
+ @num_format = num_format
8
+ @var_name = var_name.to_sym
9
+ @method_to_call = method_to_call.to_sym if method_to_call
10
+ end
11
+
12
+ alias :name :var_name
13
+
14
+ def original_string
15
+ "%#{self.num_format}#{self.name}" + (self.method_to_call ? ".#{method_to_call}" : "") + "%"
16
+ end
17
+
18
+ def fill_in_path( template_path, substitution )
19
+ return nil if substitution.nil?
20
+ substitution = call_method_on( substitution )
21
+ substitution = self.num_format ? sprintf("%0#{self.num_format.length}i", substitution ) : substitution
22
+
23
+ template_path.gsub( original_string, substitution.to_s )
24
+ end
25
+
26
+ # If necessary. If
27
+ def call_method_on( obj )
28
+ return obj if self.method_to_call.nil?
29
+ return obj[self.method_to_call] if obj.is_a?(Hash) && obj.has_key?(self.method_to_call)
30
+ return obj.send( self.method_to_call )
31
+ end
32
+
33
+ def self.fill_in_path( template_path, var_data, vars )
34
+ var_data.each do |var_dat|
35
+ template_path = var_dat.fill_in_path( template_path, vars[var_dat.name] )
36
+ end
37
+
38
+ template_path
39
+ end
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,307 @@
1
+ module FunWith
2
+ module Templates
3
+ class TemplateEvaluator
4
+ attr_reader :content, :vars, :path, :children
5
+ attr_accessor :parent
6
+
7
+ TEMPLATE_FILE_REGEX = /\.(fw)?template$/
8
+ VARIABLE_SUBSTITUTION_REGEX = /%(0+)?([a-zA-Z][A-Za-z0-9_]*)(?:\.([a-zA-Z][A-Za-z0-9_]*))?%/
9
+ VERBOSE = false
10
+
11
+ # A simple interface for filling in and cloning an entire directory.
12
+ # The entire directory shares a single set of variables... may be made
13
+ # more flexible later, with an ability to specify special behaviors
14
+ # based on file matching regexes?
15
+ #
16
+ # For now you feed the function the template directory, the destination you want it cloned into.
17
+ # The vars is a hash, with symbols as the variable names. If you declare { :monkey_name => "Bongo" },
18
+ # you can use <%= @monkey_name %> in any of the templates, and '%monkey_name%' in the filename.
19
+ #
20
+ # If a file is named with the '.template' or '.fwtemplate' file extension, it will undergo variable substitution.
21
+ #
22
+ # If the variable is an Array or Range or other enumeratableable object, and the filename has a variable embedded
23
+ # in it, the template will be evaluated once for each item enumerated, leading to multiple destination files.
24
+ # But if the variable name isn't in the filename, the whole enumerable object gets passed in. I'm not totally
25
+ # happy with the inconsistency.
26
+ #
27
+ # Filename variables:
28
+ #
29
+ # A few examples:
30
+ # - %i% : filled in. If the variable :i is enumerable, then the template gets evaluated multiple times
31
+ # - %character.name% : The object { :character => Character.new("barry") } gets .name() called on it.
32
+ # - %000k% : The number gets leading zeros.
33
+ # - %hello_world% : You can use underscores.
34
+ #
35
+ # Example: chapter-%0000chap_count%.markdown.template_seq
36
+ # ==> chapter-0001.markdown
37
+ # ==> chapter-0002.markdown
38
+ # ==> chapter-0003.markdown
39
+ # ...
40
+ # ==> chapter-0099.markdown
41
+ #
42
+ # In this case, :chap_count would be the key in vars.
43
+ #
44
+ # Example (no leading 0s): chapter%i%.html.template
45
+ # ==> chapter1.html
46
+ # ==> chapter2.html
47
+ # ==> chapter3.html
48
+ # ...
49
+ # ==> chapter99.html
50
+ #
51
+ # The point of the leading zeros is to specify that the file's number will be padded with zeros.
52
+ #
53
+ # When declaring this in vars, you can use any of the following:
54
+ # vars = {:i => 0..19} (will generate 0 - 19)
55
+ # vars = {:i => %w(ocelot mouse kitten puppy velociraptor)} # @i will be filled in with the given strings. leading 0s will be ignored.
56
+ #
57
+ # The directory structure is cloned.
58
+ #
59
+ # Files named with any other extension will simply be copied as-is.
60
+ def self.write( src, dest, vars = {} )
61
+ self.new( src, vars ).write( dest )
62
+ end
63
+
64
+ # source: absolute filepath of the source template
65
+ # dest: absolute filepath of the destination. The %sequence_variable% must be intact, and the same as the source,
66
+ # but the overall filename can be different.
67
+ # content : either a string to be ERB evaluated or a filepath
68
+ # vars : the variables required by the template you're filling in.
69
+ def initialize( content_or_path, vars = {} )
70
+ # debugger # if content_or_path =~ /xhtml/
71
+ @vars = vars
72
+
73
+ if content_or_path.is_a?( FunWith::Files::FilePath ) # && content.file?
74
+ @path = content_or_path
75
+ make_children # only directly creates first level. Rest are handled by recursion
76
+ else
77
+ @path = nil
78
+ @content = content_or_path
79
+ end
80
+ end
81
+
82
+ def make_children
83
+ @children = []
84
+
85
+ # TODO: Fix fwf so that recursiveness can be turned off.
86
+ if @path.directory?
87
+ child_paths = @path.glob(:all, :recursive => false).select{|entry| entry.dirname == @path}
88
+ elsif parse_filename_vars.fwf_blank? || ! loopable_variables?( parse_filename_vars, @vars ) # The current template is leaf?
89
+ child_paths = []
90
+ else # we need to make the pathname variants
91
+ child_paths = [@path]
92
+ end
93
+
94
+ for entry in child_paths
95
+ combos = var_combos( parse_filename_vars( entry ), @vars )
96
+
97
+ if combos.fwf_blank? # Then you don't need to go any deeper?
98
+ child = TemplateEvaluator.new( entry, @vars.clone )
99
+ child.parent = self
100
+ @children << child
101
+ else
102
+ for narrowed_var_set in combos
103
+ narrowed_var_set.inspect
104
+
105
+ child = TemplateEvaluator.new( entry, @vars.clone.merge( narrowed_var_set ) )
106
+ child.parent = self
107
+ @children << child
108
+ end
109
+ end
110
+ end
111
+ end
112
+
113
+ # given the vars and a list of which entries to loop over
114
+ #
115
+ # Should yield combinations of entries in the multi_entry variables.
116
+ # for example
117
+ # comboize( [ :i, :j, :k], { :i => 0..2, :j => 0..2, :k => 0..2 } )
118
+ # would yield { :i => 0, :j => 0, :k => 0 }
119
+ # then { :i => 0, :j => 0, :k => 1 }
120
+ # then { :i => 0, :j => 0, :k => 2 }
121
+ # then { :i => 0, :j => 1, :k => 0 } ...
122
+ #
123
+ # The results can just be merged into the cloned variable set for a given child.
124
+ def var_combos( var_data, vars, &block )
125
+ return [] if var_data.fwf_blank?
126
+ var_name = var_data.shift
127
+ if var_name.nil?
128
+ raise "Recursed too far!"
129
+ elsif var_data.fwf_blank? # last variable in the list, so start yielding
130
+ combos = []
131
+ loop_over( vars[ var_name.name ] ) do |item|
132
+ hash = { var_name.name => item }
133
+ combos << hash
134
+ end
135
+ return combos
136
+ else # recurse into other variables
137
+ # Order doesn't matter, so take the results of the next recursion, pop from the front,
138
+ # create a subarray with the variations, and push to the back. Stop when the key is found.
139
+ partial_combos = var_combos( var_data, vars )
140
+
141
+ until partial_combos.length == 0 || partial_combos.first.keys.include?( var_name.name )
142
+ hash = partial_combos.shift
143
+ filled_combos = []
144
+
145
+ loop_over( vars[var_name.name] ) do |item|
146
+ h = hash.clone
147
+ h[var_name.name] = item
148
+ filled_combos << h
149
+ end
150
+
151
+ partial_combos += filled_combos
152
+ end
153
+
154
+ return partial_combos
155
+ end
156
+ end
157
+
158
+
159
+ def src_root
160
+ self.parent ? self.parent.src_root : @path
161
+ end
162
+
163
+ def relative_path_from_root
164
+ @path.relative_path_from( self.src_root )
165
+ end
166
+
167
+ def each_node( &block )
168
+ yield self
169
+
170
+ for child in self.children
171
+ child.each_node do |node|
172
+ yield node
173
+ end
174
+ end
175
+ end
176
+
177
+
178
+ # if no destination root is given, then a relative path from the src_root is given
179
+ # if this calculated dest is a directory, while the template @path is a file, then
180
+ # the template's basename is appended to the dest and filled in
181
+ def destination( dest_root = nil )
182
+ dest = dest_root ? dest_root.join( self.relative_path_from_root ) : self.relative_path_from_root
183
+
184
+ dest = dest.join( @path.basename ) if dest.directory? && @path.file?
185
+
186
+ dest = dest.gsub( TEMPLATE_FILE_REGEX, "" )
187
+ FilenameVarData.fill_in_path( dest, parse_filename_vars, @vars )
188
+ end
189
+
190
+ def each_node_with_destination( dest_root = :temp, &block )
191
+ dest_root = FunWith::Files::FilePath.tmpdir if dest_root == :temp
192
+
193
+ self.each_node do |node|
194
+ dst = node.destination( dest_root )
195
+ if dst # if the filename needs variable replacing
196
+ yield [node, dst]
197
+ else
198
+ warn( "Warning: file #{node.path} was not returned.") if FunWith::Templates.gem_verbose?
199
+ end
200
+ end
201
+ end
202
+
203
+ def write( dest = :temp )
204
+ dest = FunWith::Files::FilePath.tmpdir if dest == :temp
205
+
206
+ self.each_node_with_destination( dest ) do |node, destination|
207
+ if node.path.directory?
208
+ destination.touch_dir
209
+ else
210
+ destination.write( node.result )
211
+ end
212
+ end
213
+
214
+ dest
215
+ end
216
+
217
+ def content
218
+ @content || ( ( @path && @path.file?) ? @path.read : "ERROR: 'content() meaningless for directory" )
219
+ end
220
+
221
+ # only called on leaf/files
222
+ def result
223
+ if @path.nil? || is_template?( @path )
224
+ begin
225
+ # formerly @template_evaluator_current_content. Don't know if removing the @ makes a diff.
226
+ template_evaluator_current_content = self.content # In case someone using the templates uses @content
227
+ template_evaluator_set_local_vars( @vars ) do
228
+ ERB.new( template_evaluator_current_content ).result( binding )
229
+ end
230
+ rescue Exception => e
231
+ warn( "Template #{ @path } could not be filled in properly (using vars: #{@vars.inspect}). Returning error as result." )
232
+ result = ["FunWith::Templates::TemplateEvaluator ERROR"]
233
+ result << ""
234
+ result << "path: #{@path}"
235
+ result << ""
236
+ result << "vars: #{@vars.inspect}"
237
+ result << ""
238
+ result << "#{e.class}: #{e.message}"
239
+ result += e.backtrace.map{|line| "\t#{line}" }
240
+
241
+ FunWith::Templates.say_if_verbose( result.join("\n") )
242
+ result.join("\n")
243
+ end
244
+ elsif @path.file?
245
+ # just copy if it's not a template
246
+ @path.read
247
+ end
248
+ end
249
+
250
+ def result_to_file( dest )
251
+ dest = dest.fwf_filepath.expand
252
+ dest.write( self.result )
253
+ end
254
+
255
+ # if the var found in the filename isn't included in the set of variables given (@vars), no substitution will be performed
256
+ def parse_filename_vars( path = @path )
257
+ var_matches = path.scan( VARIABLE_SUBSTITUTION_REGEX )
258
+
259
+ return [] if var_matches.fwf_blank?
260
+
261
+ var_matches.inject([]) do |memo, var_match|
262
+ # only return the matches where the name of the variable is in @vars
263
+ if @vars.keys.include?( var_match[1].to_sym )
264
+ memo << FilenameVarData.new( var_match[1], var_match[2], var_match[0] )
265
+ end
266
+
267
+ memo
268
+ end
269
+ end
270
+
271
+
272
+ # src: Either a file to be read, or a string
273
+ # dst: An output file (exists or not, will be overwritten)
274
+ # vars: A hash: { :var1 => "Value 1", :var2 => "Value 2"}
275
+ # In the template, these can be accessed in the ERB way:
276
+ # <%= @var1 %>, <%= @var2 %>
277
+ def self.result_to_file( src, dst, vars = {} )
278
+ dst = dst.join( src.basename ) if dst.directory?
279
+
280
+ self.new( src, vars ).result_to_file( dst )
281
+ end
282
+
283
+ def self.destination_filename( src_file, src_root, dest_root, vars )
284
+ relative_path = src_file.relative_path_from( src_root ).gsub( TEMPLATE_FILE_REGEX, "" )
285
+ dest = dest_root.join( relative_path )
286
+ end
287
+
288
+ def is_template?( filename )
289
+ !!((filename) =~ TEMPLATE_FILE_REGEX )
290
+ end
291
+
292
+ def loopable_object?( obj )
293
+ obj.is_a?( Array ) || obj.is_a?( Range )
294
+ # obj.respond_to?(:each) && !obj.is_a?(String) && !obj.is_a?(Hash)
295
+ end
296
+
297
+ def loopable_variables?( var_info, vars )
298
+ var_info.map(&:name).detect{ |name| loopable_object?( vars[name]) }
299
+ end
300
+
301
+ def loop_over( var, &block )
302
+ var = [var] unless loopable_object?( var )
303
+ var.each(&block)
304
+ end
305
+ end
306
+ end
307
+ end