templater 0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -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,8 @@
1
+ class String #:nodoc:
2
+
3
+ def realign_indentation
4
+ basis = self.index(/\S/) # find the first non-whitespace character
5
+ return self.to_a.map { |s| s[basis..-1] }.join
6
+ end
7
+
8
+ 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