templater 0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/LICENSE +20 -0
- data/README +199 -0
- data/Rakefile +69 -0
- data/TODO +5 -0
- data/lib/templater.rb +44 -0
- data/lib/templater/capture_helpers.rb +62 -0
- data/lib/templater/cli/generator.rb +162 -0
- data/lib/templater/cli/manifold.rb +57 -0
- data/lib/templater/cli/parser.rb +88 -0
- data/lib/templater/core_ext/string.rb +8 -0
- data/lib/templater/file.rb +58 -0
- data/lib/templater/generator.rb +560 -0
- data/lib/templater/manifold.rb +72 -0
- data/lib/templater/proxy.rb +66 -0
- data/lib/templater/template.rb +67 -0
- data/spec/core_ext/string_spec.rb +39 -0
- data/spec/file_spec.rb +74 -0
- data/spec/generator_spec.rb +1011 -0
- data/spec/manifold_spec.rb +77 -0
- data/spec/results/erb.rbs +1 -0
- data/spec/results/file.rbs +1 -0
- data/spec/results/random.rbs +1 -0
- data/spec/results/simple_erb.rbs +1 -0
- data/spec/spec_helper.rb +15 -0
- data/spec/template_spec.rb +124 -0
- data/spec/templater_spec.rb +7 -0
- data/spec/templates/erb.rbt +1 -0
- data/spec/templates/glob/README +1 -0
- data/spec/templates/glob/arg.js +3 -0
- data/spec/templates/glob/subfolder/jessica_alba.jpg +1 -0
- data/spec/templates/glob/subfolder/monkey.rb +1 -0
- data/spec/templates/glob/test.rb +1 -0
- data/spec/templates/literals_erb.rbt +1 -0
- data/spec/templates/simple.rbt +1 -0
- data/spec/templates/simple_erb.rbt +1 -0
- metadata +123 -0
@@ -0,0 +1,57 @@
|
|
1
|
+
module Templater
|
2
|
+
|
3
|
+
module CLI
|
4
|
+
|
5
|
+
class Manifold
|
6
|
+
|
7
|
+
def initialize(destination_root, manifold, name, version)
|
8
|
+
@destination_root, @manifold, @name, @version = destination_root, manifold, name, version
|
9
|
+
end
|
10
|
+
|
11
|
+
def version
|
12
|
+
puts @version
|
13
|
+
exit
|
14
|
+
end
|
15
|
+
|
16
|
+
def self.run(destination_root, manifold, name, version, arguments)
|
17
|
+
|
18
|
+
if arguments.first and not arguments.first =~ /^-/ and not arguments.first == "help"
|
19
|
+
generator_name = arguments.shift
|
20
|
+
if manifold.generator(generator_name)
|
21
|
+
Generator.new(generator_name, destination_root, manifold, name, version).run(arguments)
|
22
|
+
else
|
23
|
+
Manifold.new(destination_root, manifold, name, version).run(arguments)
|
24
|
+
end
|
25
|
+
else
|
26
|
+
Manifold.new(destination_root, manifold, name, version).run(arguments)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def run(arguments)
|
31
|
+
@options = Templater::CLI::Parser.parse(arguments)
|
32
|
+
self.help
|
33
|
+
end
|
34
|
+
|
35
|
+
# outputs a helpful message and quits
|
36
|
+
def help
|
37
|
+
puts "Usage: #{@name} generator_name [options] [args]"
|
38
|
+
puts ''
|
39
|
+
puts @manifold.desc
|
40
|
+
puts ''
|
41
|
+
puts 'Available Generators'
|
42
|
+
@manifold.generators.each do |name, generator|
|
43
|
+
print " "
|
44
|
+
print name.to_s.ljust(33)
|
45
|
+
print generator.desc.to_a.first.chomp if generator.desc
|
46
|
+
print "\n"
|
47
|
+
end
|
48
|
+
puts @options[:opts]
|
49
|
+
puts ''
|
50
|
+
exit
|
51
|
+
end
|
52
|
+
|
53
|
+
end
|
54
|
+
|
55
|
+
end
|
56
|
+
|
57
|
+
end
|
@@ -0,0 +1,88 @@
|
|
1
|
+
require 'optparse'
|
2
|
+
require 'optparse/time'
|
3
|
+
|
4
|
+
module Templater
|
5
|
+
|
6
|
+
module CLI
|
7
|
+
|
8
|
+
class Parser #:nodoc:
|
9
|
+
|
10
|
+
def self.parse(args)
|
11
|
+
# The options specified on the command line will be collected in *options*.
|
12
|
+
# We set default values here.
|
13
|
+
options = {}
|
14
|
+
options[:pretend] = false
|
15
|
+
options[:force] = false
|
16
|
+
options[:skip] = false
|
17
|
+
options[:quiet] = false
|
18
|
+
options[:verbose] = false
|
19
|
+
options[:help] = false
|
20
|
+
options[:version] = false
|
21
|
+
|
22
|
+
opts = OptionParser.new do |opts|
|
23
|
+
|
24
|
+
opts.banner = ""
|
25
|
+
|
26
|
+
if block_given?
|
27
|
+
yield opts, options
|
28
|
+
opts.separator ""
|
29
|
+
end
|
30
|
+
|
31
|
+
opts.separator "General options:"
|
32
|
+
|
33
|
+
opts.on("-p", "--pretend", "Run, but do not make any changes.") do |s|
|
34
|
+
options[:pretend] = s
|
35
|
+
end
|
36
|
+
|
37
|
+
opts.on("-f", "--force", "Overwrite files that already exist.") do |s|
|
38
|
+
options[:force] = s
|
39
|
+
end
|
40
|
+
|
41
|
+
opts.on("-s", "--skip", "Skip files that already exist.") do |s|
|
42
|
+
options[:skip] = s
|
43
|
+
end
|
44
|
+
|
45
|
+
opts.on("-a", "--ask", "Ask about each file before generating it.") do |s|
|
46
|
+
options[:ask] = s
|
47
|
+
end
|
48
|
+
|
49
|
+
opts.on("-d", "--delete", "Delete files that have previously been generated with this generator.") do |s|
|
50
|
+
options[:skip] = s
|
51
|
+
end
|
52
|
+
|
53
|
+
opts.on("--no-color", "Don't colorize the output") do
|
54
|
+
options[:no_color] = true
|
55
|
+
end
|
56
|
+
|
57
|
+
# these could be implemented in the future, but they are not used right now.
|
58
|
+
#opts.on("-q", "--quiet", "Suppress normal output.") do |q|
|
59
|
+
# options[:quit] = q
|
60
|
+
#end
|
61
|
+
#
|
62
|
+
#opts.on("-v", "--verbose", "Run verbosely") do |v|
|
63
|
+
# options[:verbose] = v
|
64
|
+
#end
|
65
|
+
|
66
|
+
opts.on("-h", "--help", "Show this message") do
|
67
|
+
options[:help] = true
|
68
|
+
end
|
69
|
+
|
70
|
+
opts.on("--version", "Show the version") do
|
71
|
+
options[:version] = true
|
72
|
+
end
|
73
|
+
|
74
|
+
end
|
75
|
+
|
76
|
+
def opts.inspect; "<#OptionParser #{object_id}>"; end
|
77
|
+
|
78
|
+
options[:opts] = opts
|
79
|
+
|
80
|
+
opts.parse!(args)
|
81
|
+
options
|
82
|
+
end
|
83
|
+
|
84
|
+
end
|
85
|
+
|
86
|
+
end
|
87
|
+
|
88
|
+
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
module Templater
|
2
|
+
class File
|
3
|
+
|
4
|
+
attr_accessor :name, :source, :destination
|
5
|
+
|
6
|
+
# Builds a new file, given the name of the file and its source and destination.
|
7
|
+
#
|
8
|
+
# === Parameters
|
9
|
+
# name<Symbol>:: The name of this template
|
10
|
+
# source<String>:: Full path to the source of this template
|
11
|
+
# destination<String>:: Full path to the destination of this template
|
12
|
+
def initialize(name, source, destination)
|
13
|
+
@name = name
|
14
|
+
@source = source
|
15
|
+
@destination = destination
|
16
|
+
end
|
17
|
+
|
18
|
+
# Returns the destination path relative to Dir.pwd. This is useful for prettier output in interfaces
|
19
|
+
# where the destination root is Dir.pwd.
|
20
|
+
#
|
21
|
+
# === Returns
|
22
|
+
# String:: The destination relative to Dir.pwd
|
23
|
+
def relative_destination
|
24
|
+
@destination.sub(::Dir.pwd + '/', '')
|
25
|
+
end
|
26
|
+
|
27
|
+
# Returns the contents of the source file as a String
|
28
|
+
#
|
29
|
+
# === Returns
|
30
|
+
# String:: The source file.
|
31
|
+
def render
|
32
|
+
::File.read(source)
|
33
|
+
end
|
34
|
+
|
35
|
+
# Checks if the destination file already exists.
|
36
|
+
#
|
37
|
+
# === Returns
|
38
|
+
# Boolean:: true if the file exists, false otherwise.
|
39
|
+
def exists?
|
40
|
+
::File.exists?(destination)
|
41
|
+
end
|
42
|
+
|
43
|
+
# Checks if the content of the file at the destination is identical to the rendered result.
|
44
|
+
#
|
45
|
+
# === Returns
|
46
|
+
# Boolean:: true if it is identical, false otherwise.
|
47
|
+
def identical?
|
48
|
+
exists? && ::FileUtils.identical?(source, destination)
|
49
|
+
end
|
50
|
+
|
51
|
+
# Renders the template and copies it to the destination.
|
52
|
+
def invoke!
|
53
|
+
::FileUtils.mkdir_p(::File.dirname(destination))
|
54
|
+
::FileUtils.copy_file(source, destination)
|
55
|
+
end
|
56
|
+
|
57
|
+
end
|
58
|
+
end
|
@@ -0,0 +1,560 @@
|
|
1
|
+
module Templater
|
2
|
+
|
3
|
+
class Generator
|
4
|
+
|
5
|
+
include Templater::CaptureHelpers
|
6
|
+
|
7
|
+
class << self
|
8
|
+
|
9
|
+
attr_accessor :manifold
|
10
|
+
|
11
|
+
# Returns an array of hashes, where each hash describes a single argument.
|
12
|
+
#
|
13
|
+
# === Returns
|
14
|
+
# Array[Hash{Symbol=>Object}]:: A list of arguments
|
15
|
+
def arguments; @arguments ||= []; end
|
16
|
+
|
17
|
+
# Returns an array of options, where each hash describes a single option.
|
18
|
+
#
|
19
|
+
# === Returns
|
20
|
+
# Array[Hash{Symbol=>Object}]:: A list of options
|
21
|
+
def options; @options ||= []; end
|
22
|
+
|
23
|
+
# Returns an array of hashes, where each hash describes a single template.
|
24
|
+
#
|
25
|
+
# === Returns
|
26
|
+
# Array[Hash{Symbol=>Object}]:: A list of template
|
27
|
+
def templates; @templates ||= []; end
|
28
|
+
|
29
|
+
# Returns an array of hashes, where each hash describes a single file.
|
30
|
+
#
|
31
|
+
# === Returns
|
32
|
+
# Array[Hash{Symbol=>Object}]:: A list of files
|
33
|
+
def files; @files ||= []; end
|
34
|
+
|
35
|
+
# Returns an array of hashes, where each hash describes a single invocation.
|
36
|
+
#
|
37
|
+
# === Returns
|
38
|
+
# Array[Hash{Symbol=>Object}]:: A list of invocations
|
39
|
+
def invocations; @invocations ||= []; end
|
40
|
+
|
41
|
+
# A shorthand method for adding the first argument, see +Templater::Generator.argument+
|
42
|
+
def first_argument(*args); argument(0, *args); end
|
43
|
+
|
44
|
+
# A shorthand method for adding the second argument, see +Templater::Generator.argument+
|
45
|
+
def second_argument(*args); argument(1, *args); end
|
46
|
+
|
47
|
+
# A shorthand method for adding the third argument, see +Templater::Generator.argument+
|
48
|
+
def third_argument(*args); argument(2, *args); end
|
49
|
+
|
50
|
+
# A shorthand method for adding the fourth argument, see +Templater::Generator.argument+
|
51
|
+
def fourth_argument(*args); argument(3, *args); end
|
52
|
+
|
53
|
+
# If the argument is omitted, simply returns the description for this generator, otherwise
|
54
|
+
# sets the description to the passed string.
|
55
|
+
#
|
56
|
+
# === Parameters
|
57
|
+
# text<String>:: A description
|
58
|
+
#
|
59
|
+
# === Returns
|
60
|
+
# String:: The description for this generator
|
61
|
+
def desc(text = nil)
|
62
|
+
@text = text.realign_indentation if text
|
63
|
+
return @text
|
64
|
+
end
|
65
|
+
|
66
|
+
# Assign a name to the n:th argument that this generator takes. An accessor
|
67
|
+
# with that name will automatically be added to the generator. Options can be provided
|
68
|
+
# to ensure the argument conforms to certain requirements. If a block is provided, when an
|
69
|
+
# argument is assigned, the block is called with that value and if :invalid is thrown, a proper
|
70
|
+
# error is raised
|
71
|
+
#
|
72
|
+
# === Parameters
|
73
|
+
# n<Integer>:: The index of the argument that this describes
|
74
|
+
# name<Symbol>:: The name of this argument, an accessor with this name will be created for the argument
|
75
|
+
# options<Hash>:: Options for this argument
|
76
|
+
# &block<Proc>:: Is evaluated on assignment to check the validity of the argument
|
77
|
+
#
|
78
|
+
# ==== Options (opts)
|
79
|
+
# :default<Object>:: Specify a default value for this argument
|
80
|
+
# :as<Symbol>:: If set to :hash or :array, this argument will 'consume' all remaining arguments and bundle them
|
81
|
+
# Use this only for the last argument to this generator.
|
82
|
+
# :required<Boolean>:: If set to true, the generator will throw an error if it initialized without this argument
|
83
|
+
# :desc<Symbol>:: Provide a description for this argument
|
84
|
+
def argument(n, name, options={}, &block)
|
85
|
+
self.arguments[n] = {
|
86
|
+
:name => name,
|
87
|
+
:options => options,
|
88
|
+
:block => block
|
89
|
+
}
|
90
|
+
class_eval <<-CLASS
|
91
|
+
def #{name}
|
92
|
+
get_argument(#{n})
|
93
|
+
end
|
94
|
+
|
95
|
+
def #{name}=(arg)
|
96
|
+
set_argument(#{n}, arg)
|
97
|
+
end
|
98
|
+
CLASS
|
99
|
+
end
|
100
|
+
|
101
|
+
# Adds an accessor with the given name to this generator, also automatically fills that value through
|
102
|
+
# the options hash that is provided when the generator is initialized.
|
103
|
+
#
|
104
|
+
# === Parameters
|
105
|
+
# name<Symbol>:: The name of this option, an accessor with this name will be created for the option
|
106
|
+
# options<Hash>:: Options for this option (how meta!)
|
107
|
+
#
|
108
|
+
# ==== Options (opts)
|
109
|
+
# :default<Object>:: Specify a default value for this option
|
110
|
+
# :as<Symbol>:: If set to :boolean provides a hint to the interface using this generator.
|
111
|
+
# :desc<Symbol>:: Provide a description for this option
|
112
|
+
def option(name, options={})
|
113
|
+
self.options << {
|
114
|
+
:name => name.to_sym,
|
115
|
+
:options => options
|
116
|
+
}
|
117
|
+
class_eval <<-CLASS
|
118
|
+
def #{name}
|
119
|
+
get_option(:#{name})
|
120
|
+
end
|
121
|
+
|
122
|
+
def #{name}=(arg)
|
123
|
+
set_option(:#{name}, arg)
|
124
|
+
end
|
125
|
+
CLASS
|
126
|
+
end
|
127
|
+
|
128
|
+
# Adds an invocation of another generator to this generator. This allows the interface to invoke
|
129
|
+
# any templates in that target generator. This requires that the generator is part of a manifold. The name
|
130
|
+
# provided is the name of the target generator in this generator's manifold.
|
131
|
+
#
|
132
|
+
# A hash of options can be passed, all of these options are matched against the options passed to the
|
133
|
+
# generator.
|
134
|
+
#
|
135
|
+
# If a block is given, the generator class is passed to the block, and it is expected that the
|
136
|
+
# block yields an instance. Otherwise the target generator is instantiated with the same options and
|
137
|
+
# arguments as this generator.
|
138
|
+
#
|
139
|
+
# === Parameters
|
140
|
+
# name<Symbol>:: The name in the manifold of the generator that is to be invoked
|
141
|
+
# options<Hash>:: A hash of requirements that are matched against the generator options
|
142
|
+
# &block<Proc>:: A block to execute when the generator is instantiated
|
143
|
+
#
|
144
|
+
# ==== Examples
|
145
|
+
#
|
146
|
+
# class MyGenerator < Templater::Generator
|
147
|
+
# invoke :other_generator
|
148
|
+
# end
|
149
|
+
#
|
150
|
+
# class MyGenerator < Templater::Generator
|
151
|
+
# def random
|
152
|
+
# rand(100000).to_s
|
153
|
+
# end
|
154
|
+
#
|
155
|
+
# # invoke :other_generator with some
|
156
|
+
# invoke :other_generator do |generator|
|
157
|
+
# generator.new(destination_root, options, random)
|
158
|
+
# end
|
159
|
+
# end
|
160
|
+
#
|
161
|
+
# class MyGenerator < Templater::Generator
|
162
|
+
# option :animal
|
163
|
+
# # other_generator will be invoked only if the option 'animal' is set to 'bear'
|
164
|
+
# invoke :other_generator, :amimal => :bear
|
165
|
+
# end
|
166
|
+
def invoke(name, options={}, &block)
|
167
|
+
self.invocations << {
|
168
|
+
:name => name,
|
169
|
+
:options => options,
|
170
|
+
:block => block
|
171
|
+
}
|
172
|
+
end
|
173
|
+
|
174
|
+
# Adds a template to this generator. Templates are named and can later be retrieved by that name.
|
175
|
+
# Templates have a source and a destination. When a template is invoked, the source file is rendered,
|
176
|
+
# passing through ERB, and the result is copied to the destination. Source and destination can be
|
177
|
+
# specified in different ways, and are always assumed to be relative to source_root and destination_root.
|
178
|
+
#
|
179
|
+
# If only a destination is given, the source is assumed to be the same destination, only appending the
|
180
|
+
# letter 't', so a destination of 'app/model.rb', would assume a source of 'app/model.rbt'
|
181
|
+
#
|
182
|
+
# Source and destination can be set in a block, which makes it possible to call instance methods to
|
183
|
+
# determine the correct source and/or desination.
|
184
|
+
#
|
185
|
+
# A hash of options can be passed, all of these options are matched against the options passed to the
|
186
|
+
# generator.
|
187
|
+
#
|
188
|
+
# === Parameters
|
189
|
+
# name<Symbol>:: The name of this template
|
190
|
+
# source<String>:: The source template, can be omitted
|
191
|
+
# destination<String>:: The destination where the result will be put.
|
192
|
+
# options<Hash>:: Options for this template
|
193
|
+
# &block<Proc>:: A block to execute when the generator is instantiated
|
194
|
+
#
|
195
|
+
# ==== Examples
|
196
|
+
#
|
197
|
+
# class MyGenerator < Templater::Generator
|
198
|
+
# def random
|
199
|
+
# rand(100000).to_s
|
200
|
+
# end
|
201
|
+
#
|
202
|
+
# template :one, 'template.rb' # source will be inferred as 'template.rbt'
|
203
|
+
# template :two, 'source.rbt', 'template.rb' # source expicitly given
|
204
|
+
# template :three do
|
205
|
+
# source('source.rbt')
|
206
|
+
# destination("#{random}.rb")
|
207
|
+
# end
|
208
|
+
# end
|
209
|
+
def template(name, *args, &block)
|
210
|
+
options = args.last.is_a?(Hash) ? args.pop : {}
|
211
|
+
source, destination = args
|
212
|
+
source, destination = source + 't', source if args.size == 1
|
213
|
+
|
214
|
+
self.templates << {
|
215
|
+
:name => name,
|
216
|
+
:options => options,
|
217
|
+
:source => source,
|
218
|
+
:destination => destination,
|
219
|
+
:block => block,
|
220
|
+
:render => true
|
221
|
+
}
|
222
|
+
end
|
223
|
+
|
224
|
+
# Adds a template that is not rendered using ERB, but copied directly. Unlike Templater::Generator.template
|
225
|
+
# this will not append a 't' to the source, otherwise it works identically.
|
226
|
+
#
|
227
|
+
# === Parameters
|
228
|
+
# name<Symbol>:: The name of this template
|
229
|
+
# source<String>:: The source template, can be omitted
|
230
|
+
# destination<String>:: The destination where the result will be put.
|
231
|
+
# options<Hash>:: Options for this template
|
232
|
+
# &block<Proc>:: A block to execute when the generator is instantiated
|
233
|
+
def file(name, *args, &block)
|
234
|
+
options = args.last.is_a?(Hash) ? args.pop : {}
|
235
|
+
source, destination = args
|
236
|
+
source, destination = source, source if args.size == 1
|
237
|
+
|
238
|
+
self.files << {
|
239
|
+
:name => name,
|
240
|
+
:options => options,
|
241
|
+
:source => source,
|
242
|
+
:destination => destination,
|
243
|
+
:block => block,
|
244
|
+
:render => false
|
245
|
+
}
|
246
|
+
end
|
247
|
+
|
248
|
+
# An easy way to add many templates to a generator, each item in the list is added as a
|
249
|
+
# template. The provided list can be either an array of Strings or a Here-Doc with templates
|
250
|
+
# on individual lines.
|
251
|
+
#
|
252
|
+
# === Parameters
|
253
|
+
# list<String|Array>:: A list of templates to be added to this generator
|
254
|
+
#
|
255
|
+
# === Examples
|
256
|
+
#
|
257
|
+
# class MyGenerator < Templater::Generator
|
258
|
+
# template_list <<-LIST
|
259
|
+
# path/to/template1.rb
|
260
|
+
# another/template.css
|
261
|
+
# LIST
|
262
|
+
# template_list ['a/third/template.rb', 'and/a/fourth.js']
|
263
|
+
# end
|
264
|
+
def template_list(list)
|
265
|
+
list.to_a.each do |item|
|
266
|
+
item = item.to_s.chomp.strip
|
267
|
+
self.template(item.gsub(/[\.\/]/, '_').to_sym, item)
|
268
|
+
end
|
269
|
+
end
|
270
|
+
|
271
|
+
# An easy way to add many non-rendering templates to a generator. The provided list can be either an
|
272
|
+
# array of Strings or a Here-Doc with templates on individual lines.
|
273
|
+
#
|
274
|
+
# === Parameters
|
275
|
+
# list<String|Array>:: A list of non-rendering templates to be added to this generator
|
276
|
+
#
|
277
|
+
# === Examples
|
278
|
+
#
|
279
|
+
# class MyGenerator < Templater::Generator
|
280
|
+
# file_list <<-LIST
|
281
|
+
# path/to/file.jpg
|
282
|
+
# another/file.html.erb
|
283
|
+
# LIST
|
284
|
+
# file_list ['a/third/file.gif', 'and/a/fourth.rb']
|
285
|
+
# end
|
286
|
+
def file_list(list)
|
287
|
+
list.to_a.each do |item|
|
288
|
+
item = item.to_s.chomp.strip
|
289
|
+
self.file(item.gsub(/[\.\/]/, '_').to_sym, item)
|
290
|
+
end
|
291
|
+
end
|
292
|
+
|
293
|
+
# Search a directory for templates and files and add them to this generator. Any file
|
294
|
+
# whose extension matches one of those provided in the template_extensions parameter
|
295
|
+
# is considered a template and will be rendered with ERB, all others are considered
|
296
|
+
# normal files and are simply copied.
|
297
|
+
#
|
298
|
+
# A hash of options can be passed which will be assigned to each file and template.
|
299
|
+
# All of these options are matched against the options passed to the generator.
|
300
|
+
#
|
301
|
+
# === Parameters
|
302
|
+
# source<String>:: The directory to search in, relative to the source_root, if omitted
|
303
|
+
# the source root itself is searched.
|
304
|
+
# template_destination<Array[String]>:: A list of extensions. If a file has one of these
|
305
|
+
# extensions, it is considered a template and will be rendered with ERB.
|
306
|
+
# options<Hash{Symbol=>Object}>:: A list of options.
|
307
|
+
def glob!(dir = nil, template_extensions = %w(rb css js erb html yml), options={})
|
308
|
+
::Dir[::File.join(source_root, dir.to_s, '**/*')].each do |action|
|
309
|
+
unless ::File.directory?(action)
|
310
|
+
action = action.sub("#{source_root}/", '')
|
311
|
+
if template_extensions.include?(::File.extname(action)[1..-1])
|
312
|
+
template(action.downcase.gsub(/[^a-z0-9]+/, '_').to_sym, action, action)
|
313
|
+
else
|
314
|
+
file(action.downcase.gsub(/[^a-z0-9]+/, '_').to_sym, action, action)
|
315
|
+
end
|
316
|
+
end
|
317
|
+
end
|
318
|
+
end
|
319
|
+
|
320
|
+
# Returns a list of the classes of all generators (recursively) that are invoked together with this one.
|
321
|
+
#
|
322
|
+
# === Returns
|
323
|
+
# Array[Templater::Generator]:: an array of generator classes.
|
324
|
+
def generators
|
325
|
+
if manifold
|
326
|
+
generators = invocations.map do |i|
|
327
|
+
generator = manifold.generator(i[:name])
|
328
|
+
generator ? generator.generators : nil
|
329
|
+
end
|
330
|
+
generators.unshift(self).flatten.compact
|
331
|
+
else
|
332
|
+
[self]
|
333
|
+
end
|
334
|
+
end
|
335
|
+
|
336
|
+
# This should return the directory where source templates are located. This method must be overridden in
|
337
|
+
# any Generator inheriting from Templater::Source.
|
338
|
+
#
|
339
|
+
# === Raises
|
340
|
+
# Templater::SourceNotSpecifiedError:: Always raises this error, so be sure to override this method.
|
341
|
+
def source_root
|
342
|
+
raise Templater::SourceNotSpecifiedError, "Subclasses of Templater::Generator must override the source_root method, to specify where source templates are located."
|
343
|
+
end
|
344
|
+
|
345
|
+
end
|
346
|
+
|
347
|
+
attr_accessor :destination_root, :arguments, :options
|
348
|
+
|
349
|
+
# Create a new generator. Checks the list of arguments agains the requirements set using +argument+.
|
350
|
+
#
|
351
|
+
# === Parameters
|
352
|
+
# destination_root<String>:: The destination, where the generated files will be put.
|
353
|
+
# options<Hash{Symbol => Symbol}>:: Options given to this generator.
|
354
|
+
# *arguments<String>:: The list of arguments. These must match the declared requirements.
|
355
|
+
#
|
356
|
+
# === Raises
|
357
|
+
# Templater::ArgumentError:: If the arguments are invalid
|
358
|
+
def initialize(destination_root, options = {}, *args)
|
359
|
+
# FIXME: options as a second argument is kinda stupid, since it forces silly syntax, but since *args
|
360
|
+
# might contain hashes, I can't come up with another way of making this unambiguous.
|
361
|
+
@destination_root = destination_root
|
362
|
+
@arguments = []
|
363
|
+
@options = options
|
364
|
+
|
365
|
+
self.class.options.each do |option|
|
366
|
+
@options[option[:name]] ||= option[:options][:default]
|
367
|
+
end
|
368
|
+
|
369
|
+
extract_arguments(*args)
|
370
|
+
|
371
|
+
valid_arguments?
|
372
|
+
end
|
373
|
+
|
374
|
+
# Finds and returns the template of the given name. If that template's options don't match the generator
|
375
|
+
# options, returns nil.
|
376
|
+
#
|
377
|
+
# === Parameters
|
378
|
+
# name<Symbol>:: The name of the template to look up.
|
379
|
+
#
|
380
|
+
# === Returns
|
381
|
+
# Templater::Template:: The found template.
|
382
|
+
def template(name)
|
383
|
+
self.templates.find { |t| t.name == name }
|
384
|
+
end
|
385
|
+
|
386
|
+
# Finds and returns the file of the given name. If that file's options don't match the generator
|
387
|
+
# options, returns nil.
|
388
|
+
#
|
389
|
+
# === Parameters
|
390
|
+
# name<Symbol>:: The name of the file to look up.
|
391
|
+
#
|
392
|
+
# === Returns
|
393
|
+
# Templater::File:: The found file.
|
394
|
+
def file(name)
|
395
|
+
self.files.find { |f| f.name == name }
|
396
|
+
end
|
397
|
+
|
398
|
+
# Finds and returns all templates whose options match the generator options.
|
399
|
+
#
|
400
|
+
# === Returns
|
401
|
+
# [Templater::Template]:: The found templates.
|
402
|
+
def templates
|
403
|
+
templates = self.class.templates.map do |t|
|
404
|
+
template = Templater::TemplateProxy.new(t[:name], t[:source], t[:destination], &t[:block]).to_template(self)
|
405
|
+
match_options?(t[:options]) ? template : nil
|
406
|
+
end
|
407
|
+
templates.compact
|
408
|
+
end
|
409
|
+
|
410
|
+
# Finds and returns all files whose options match the generator options.
|
411
|
+
#
|
412
|
+
# === Returns
|
413
|
+
# [Templater::File]:: The found files.
|
414
|
+
def files
|
415
|
+
files = self.class.files.map do |t|
|
416
|
+
file = Templater::FileProxy.new(t[:name], t[:source], t[:destination], &t[:block]).to_file(self)
|
417
|
+
match_options?(t[:options]) ? file : nil
|
418
|
+
end
|
419
|
+
files.compact
|
420
|
+
end
|
421
|
+
|
422
|
+
# Finds and returns all templates whose options match the generator options.
|
423
|
+
#
|
424
|
+
# === Returns
|
425
|
+
# [Templater::Generator]:: The found templates.
|
426
|
+
def invocations
|
427
|
+
if self.class.manifold
|
428
|
+
invocations = self.class.invocations.map do |invocation|
|
429
|
+
generator = self.class.manifold.generator(invocation[:name])
|
430
|
+
if generator and invocation[:block]
|
431
|
+
instance_exec(generator, &invocation[:block])
|
432
|
+
elsif generator and match_options?(invocation[:options])
|
433
|
+
generator.new(destination_root, options, *@arguments)
|
434
|
+
end
|
435
|
+
end
|
436
|
+
invocations.compact
|
437
|
+
else
|
438
|
+
[]
|
439
|
+
end
|
440
|
+
end
|
441
|
+
|
442
|
+
# Finds and returns all templates and files for this generators and any of those generators it invokes,
|
443
|
+
# whose options match that generator's options.
|
444
|
+
#
|
445
|
+
# === Returns
|
446
|
+
# [Templater::File, Templater::Template]:: The found templates and files.
|
447
|
+
def actions
|
448
|
+
actions = templates + files
|
449
|
+
actions += invocations.map { |i| i.actions }
|
450
|
+
actions.flatten
|
451
|
+
end
|
452
|
+
|
453
|
+
# Invokes the templates for this generator
|
454
|
+
def invoke!
|
455
|
+
templates.each { |t| t.invoke! }
|
456
|
+
end
|
457
|
+
|
458
|
+
# Returns this generator's source root
|
459
|
+
#
|
460
|
+
# === Returns
|
461
|
+
# String:: The source root of this generator.
|
462
|
+
#
|
463
|
+
# === Raises
|
464
|
+
# Templater::SourceNotSpecifiedError:: IF the source_root class method has not been overridden.
|
465
|
+
def source_root
|
466
|
+
self.class.source_root
|
467
|
+
end
|
468
|
+
|
469
|
+
# Returns the destination root that is given to the generator on initialization. If the generator is a
|
470
|
+
# command line program, this would usually be Dir.pwd.
|
471
|
+
#
|
472
|
+
# === Returns
|
473
|
+
# String:: The destination root
|
474
|
+
def destination_root
|
475
|
+
@destination_root # just here so it can be documented.
|
476
|
+
end
|
477
|
+
|
478
|
+
protected
|
479
|
+
|
480
|
+
def set_argument(n, arg)
|
481
|
+
argument = self.class.arguments[n]
|
482
|
+
valid_argument?(arg, argument[:options], &argument[:block])
|
483
|
+
@arguments[n] = arg
|
484
|
+
end
|
485
|
+
|
486
|
+
def get_argument(n)
|
487
|
+
@arguments[n] || self.class.arguments[n][:options][:default]
|
488
|
+
end
|
489
|
+
|
490
|
+
def set_option(name, arg)
|
491
|
+
@options[name] = arg
|
492
|
+
end
|
493
|
+
|
494
|
+
def get_option(name)
|
495
|
+
@options[name]
|
496
|
+
end
|
497
|
+
|
498
|
+
def match_options?(options)
|
499
|
+
options.all? { |key, value| get_option(key) == value }
|
500
|
+
end
|
501
|
+
|
502
|
+
def valid_argument?(arg, options, &block)
|
503
|
+
if arg.nil? and options[:required]
|
504
|
+
raise Templater::TooFewArgumentsError
|
505
|
+
elsif not arg.nil?
|
506
|
+
if options[:as] == :hash and not arg.is_a?(Hash)
|
507
|
+
raise Templater::MalformattedArgumentError, "Expected the argument to be a Hash, but was '#{arg.inspect}'"
|
508
|
+
elsif options[:as] == :array and not arg.is_a?(Array)
|
509
|
+
raise Templater::MalformattedArgumentError, "Expected the argument to be an Array, but was '#{arg.inspect}'"
|
510
|
+
end
|
511
|
+
|
512
|
+
invalid = catch :invalid do
|
513
|
+
yield if block_given?
|
514
|
+
throw :invalid, :not_invalid
|
515
|
+
end
|
516
|
+
raise Templater::ArgumentError, invalid unless invalid == :not_invalid
|
517
|
+
end
|
518
|
+
end
|
519
|
+
|
520
|
+
def valid_arguments?
|
521
|
+
self.class.arguments.each_with_index do |arg, i|
|
522
|
+
valid_argument?(@arguments[i], arg[:options], &arg[:block])
|
523
|
+
end
|
524
|
+
end
|
525
|
+
|
526
|
+
# from a list of arguments, walk through that list and assign it to this generator, taking into account
|
527
|
+
# that an argument could be a hash or array that consumes the remaining arguments.
|
528
|
+
def extract_arguments(*args)
|
529
|
+
args.each_with_index do |arg, i|
|
530
|
+
expected = self.class.arguments[i]
|
531
|
+
raise Templater::TooManyArgumentsError, "This generator does not take this many Arguments" if expected.nil?
|
532
|
+
|
533
|
+
# When one of the arguments has :as set to :hash or :list, the remaining arguments should be consumed
|
534
|
+
# and converted to a Hash or an Array respectively
|
535
|
+
case expected[:options][:as]
|
536
|
+
when :hash
|
537
|
+
if arg.is_a?(String)
|
538
|
+
pairs = args[i..-1]
|
539
|
+
|
540
|
+
hash = pairs.inject({}) do |h, pair|
|
541
|
+
key, value = pair.split(':')
|
542
|
+
raise Templater::MalformattedArgumentError, "Expected '#{arg.inspect}' to be a key/value pair" unless key and value
|
543
|
+
h[key] = value
|
544
|
+
h
|
545
|
+
end
|
546
|
+
|
547
|
+
set_argument(i, hash) and return
|
548
|
+
else
|
549
|
+
set_argument(i, arg)
|
550
|
+
end
|
551
|
+
when :array
|
552
|
+
set_argument(i, args[i..-1].flatten) and return
|
553
|
+
else
|
554
|
+
set_argument(i, arg)
|
555
|
+
end
|
556
|
+
end
|
557
|
+
end
|
558
|
+
end
|
559
|
+
|
560
|
+
end
|