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.
@@ -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