templater 0.1.6 → 0.2

Sign up to get free protection for your applications and to get access to all the features.
data/README CHANGED
@@ -11,15 +11,9 @@ Hierarchy is pretty simple: manifold has one or many public and private generato
11
11
  by end user. Generators have one or more action that specify what they do, where they take files, how they name resulting
12
12
  files and so forth.
13
13
 
14
-
15
14
  == Idea behind Templater
16
15
 
17
- Templater keeps declarations of generator inside it's body. You vary source and destinations of each action the way you want.
18
- There's no magical %variables% in filenames sprinkled across your generator, generator rules are simple, readable, concise
19
- and are in one place making it easy to take existing generator as example.
20
-
21
- This is how Templater is different from some other code/components generation frameworks.
22
-
16
+ Templater is designed to be flexible and designed to be reflective. Generators created with templater are definitions of what goes where, they provide no interface for the user. This allows you to use templater generators inside your own code, or with the bundled CLI interface, or with your own interface.
23
17
 
24
18
  == Example
25
19
 
@@ -150,6 +144,18 @@ will result in
150
144
 
151
145
  4
152
146
  <%= 2 + 2 %>
147
+
148
+ == Callbacks
149
+
150
+ Sometimes it might be desirable to add a callback to your actions, an example might be to chmod a binary file after it is created.
151
+
152
+ class MyGenerator < Templater::Generator
153
+ template :something, 'something.rb', :after => :chmod
154
+
155
+ def chmod(action)
156
+ File.chmod(action.destination, 0750)
157
+ end
158
+ end
153
159
 
154
160
  == An advanced example
155
161
 
data/Rakefile CHANGED
@@ -3,6 +3,7 @@ require 'rake/gempackagetask'
3
3
  require 'rubygems/specification'
4
4
  require 'rake/rdoctask'
5
5
  require 'date'
6
+ require 'spec/rake/spectask'
6
7
  require File.join(File.dirname(__FILE__), 'lib', 'templater')
7
8
 
8
9
  PLUGIN = "templater"
@@ -34,6 +35,7 @@ spec = Gem::Specification.new do |s|
34
35
 
35
36
  s.add_dependency "highline", ">= 1.4.0"
36
37
  s.add_dependency "diff-lcs", ">= 1.1.2"
38
+ s.add_dependency "extlib", ">= 0.9.5"
37
39
  end
38
40
 
39
41
  Rake::GemPackageTask.new(spec) do |pkg|
@@ -99,3 +101,31 @@ task :rcov do
99
101
  `#{command} 2>&1`
100
102
  end
101
103
  end
104
+
105
+ file_list = FileList['spec/**/*_spec.rb']
106
+
107
+ desc "Run all examples"
108
+ Spec::Rake::SpecTask.new('spec') do |t|
109
+ t.spec_files = file_list
110
+ end
111
+
112
+ namespace :spec do
113
+ desc "Run all examples with RCov"
114
+ Spec::Rake::SpecTask.new('rcov') do |t|
115
+ t.spec_files = file_list
116
+ t.rcov = true
117
+ t.rcov_dir = "doc/coverage"
118
+ t.rcov_opts = ['--exclude', 'spec']
119
+ end
120
+
121
+ desc "Generate an html report"
122
+ Spec::Rake::SpecTask.new('report') do |t|
123
+ t.spec_files = file_list
124
+ t.spec_opts = ["--format", "html:doc/reports/specs.html"]
125
+ t.fail_on_error = false
126
+ end
127
+
128
+ end
129
+
130
+ desc 'Default: run unit tests.'
131
+ task :default => 'spec'
data/lib/templater.rb CHANGED
@@ -1,17 +1,19 @@
1
1
  path = File.dirname(__FILE__) + '/templater/'
2
2
 
3
3
  require 'rubygems'
4
+ require 'extlib'
4
5
  require 'highline'
5
6
  require "highline/import"
6
7
  require 'diff/lcs'
7
8
 
8
9
  require path + 'discovery'
9
10
  require path + 'capture_helpers'
11
+ require path + 'actions/action'
10
12
  require path + 'actions/template'
11
13
  require path + 'actions/file'
12
14
  require path + 'actions/empty_directory'
15
+ require path + 'description'
13
16
  require path + 'generator'
14
- require path + 'proxy'
15
17
  require path + 'manifold'
16
18
  require path + 'cli/parser'
17
19
  require path + 'cli/manifold'
@@ -40,6 +42,6 @@ module Templater
40
42
  class MalformattedArgumentError < ArgumentError #:nodoc:
41
43
  end
42
44
 
43
- VERSION = '0.1.6'
45
+ VERSION = '0.2'
44
46
 
45
47
  end
@@ -0,0 +1,43 @@
1
+ module Templater
2
+ module Actions
3
+ class Action
4
+
5
+ attr_accessor :generator, :name, :source, :destination, :options
6
+
7
+ def source=(source)
8
+ unless source.blank?
9
+ @source = ::File.expand_path(source, generator.source_root)
10
+ end
11
+ end
12
+
13
+ def destination=(destination)
14
+ unless destination.blank?
15
+ @destination = ::File.expand_path(convert_encoded_instructions(destination), generator.destination_root)
16
+ end
17
+ end
18
+
19
+ # Returns the destination path relative to Dir.pwd. This is useful for prettier output in interfaces
20
+ # where the destination root is Dir.pwd.
21
+ #
22
+ # === Returns
23
+ # String:: The destination relative to Dir.pwd
24
+ def relative_destination
25
+ @destination.relative_path_from(@generator.destination_root)
26
+ end
27
+
28
+ protected
29
+
30
+ def callback(name)
31
+ @generator.send(@options[name], self) if @options[name]
32
+ end
33
+
34
+ def convert_encoded_instructions(filename)
35
+ filename.gsub(/%.*?%/) do |string|
36
+ instruction = string.match(/%(.*?)%/)[1]
37
+ @generator.respond_to?(instruction) ? @generator.send(instruction) : string
38
+ end
39
+ end
40
+
41
+ end
42
+ end
43
+ end
@@ -1,20 +1,19 @@
1
1
  module Templater
2
2
  module Actions
3
- class EmptyDirectory
3
+ class EmptyDirectory < Action
4
4
 
5
- attr_reader :name, :destination
6
-
7
- def initialize(name, destination)
8
- @name, @destination = name, destination
9
- end
10
-
11
- # Returns the destination path relative to Dir.pwd. This is useful for prettier output in interfaces
12
- # where the destination root is Dir.pwd.
13
- #
14
- # === Returns
15
- # String:: The destination relative to Dir.pwd
16
- def relative_destination
17
- @destination.sub(::Dir.pwd + ::File::SEPARATOR, '')
5
+ # Builds a new Directory
6
+ #
7
+ # === Parameters
8
+ # generator<Object>:: The generator
9
+ # name<Symbol>:: The name of this directory
10
+ # destination<String>:: Full path to the destination of this directory
11
+ # options<Hash{Symbol=>Symbol}:: Options, including callbacks.
12
+ def initialize(generator, name, destination, options={})
13
+ self.generator = generator
14
+ self.name = name
15
+ self.destination = destination
16
+ self.options = options
18
17
  end
19
18
 
20
19
  # Returns the contents of the source file as a String
@@ -43,13 +42,16 @@ module Templater
43
42
 
44
43
  # Renders the template and copies it to the destination.
45
44
  def invoke!
45
+ @generator.send(@options[:before], self) if @options[:before]
46
46
  ::FileUtils.mkdir_p(destination)
47
+ @generator.send(@options[:after], self) if @options[:after]
47
48
  end
48
49
 
49
50
  # removes the destination file
50
51
  def revoke!
51
52
  ::FileUtils.rm_rf(::File.expand_path(destination))
52
53
  end
54
+
53
55
  end # EmptyDirectory
54
56
  end # Actions
55
57
  end # Templater
@@ -1,28 +1,21 @@
1
1
  module Templater
2
2
  module Actions
3
- class File
3
+ class File < Action
4
4
 
5
- attr_accessor :name, :source, :destination
6
-
7
- # Builds a new file, given the name of the file and its source and destination.
5
+ # Builds a new file.
8
6
  #
9
7
  # === Parameters
10
- # name<Symbol>:: The name of this template
11
- # source<String>:: Full path to the source of this template
12
- # destination<String>:: Full path to the destination of this template
13
- def initialize(name, source, destination)
14
- @name = name
15
- @source = source
16
- @destination = destination
17
- end
18
-
19
- # Returns the destination path relative to Dir.pwd. This is useful for prettier output in interfaces
20
- # where the destination root is Dir.pwd.
21
- #
22
- # === Returns
23
- # String:: The destination relative to Dir.pwd
24
- def relative_destination
25
- @destination.sub(::Dir.pwd + ::File::SEPARATOR, '')
8
+ # generator<Object>:: The generator
9
+ # name<Symbol>:: The name of this file
10
+ # source<String>:: Full path to the source of this file
11
+ # destination<String>:: Full path to the destination of this file
12
+ # options<Hash{Symbol=>Symbol}:: Options, including callbacks.
13
+ def initialize(generator, name, source, destination, options={})
14
+ self.generator = generator
15
+ self.name = name
16
+ self.source = source
17
+ self.destination = destination
18
+ self.options = options
26
19
  end
27
20
 
28
21
  # Returns the contents of the source file as a String
@@ -51,15 +44,17 @@ module Templater
51
44
 
52
45
  # Renders the template and copies it to the destination.
53
46
  def invoke!
47
+ callback(:before)
54
48
  ::FileUtils.mkdir_p(::File.dirname(destination))
55
49
  ::FileUtils.copy_file(source, destination)
50
+ callback(:after)
56
51
  end
57
52
 
58
53
  # removes the destination file
59
54
  def revoke!
60
55
  ::FileUtils.rm(destination, :force => true)
61
56
  end
62
-
57
+
63
58
  end
64
59
  end
65
60
  end
@@ -1,32 +1,21 @@
1
1
  module Templater
2
2
  module Actions
3
- class Template
3
+ class Template < Action
4
4
 
5
- attr_accessor :context, :name, :source, :destination, :options
6
-
7
- # Builds a new template, given the context (e.g. binding) in which the template will be rendered
8
- # (usually a generator), the name of the template and its source and destination.
5
+ # Builds a new template.
9
6
  #
10
7
  # === Parameters
11
- # context<Object>:: Context for rendering
8
+ # generator<Object>:: Context for rendering
12
9
  # name<Symbol>:: The name of this template
13
10
  # source<String>:: Full path to the source of this template
14
11
  # destination<String>:: Full path to the destination of this template
15
- # render<Boolean>:: If set to false, will do a copy instead of rendering.
16
- def initialize(context, name, source, destination)
17
- @context = context
18
- @name = name
19
- @source = source
20
- @destination = destination
21
- end
22
-
23
- # Returns the destination path relative to Dir.pwd. This is useful for prettier output in interfaces
24
- # where the destination root is Dir.pwd.
25
- #
26
- # === Returns
27
- # String:: The destination relative to Dir.pwd
28
- def relative_destination
29
- @destination.sub(::Dir.pwd + ::File::SEPARATOR, '')
12
+ # options<Hash{Symbol=>Symbol}:: Options, including callbacks.
13
+ def initialize(generator, name, source, destination, options={})
14
+ self.generator = generator
15
+ self.name = name
16
+ self.source = source
17
+ self.destination = destination
18
+ self.options = options
30
19
  end
31
20
 
32
21
  # Renders the template using ERB and returns the result as a String.
@@ -34,7 +23,7 @@ module Templater
34
23
  # === Returns
35
24
  # String:: The rendered template.
36
25
  def render
37
- ERB.new(::File.read(source), nil, '-').result(context.send(:binding))
26
+ ERB.new(::File.read(source), nil, '-').result(generator.send(:binding))
38
27
  end
39
28
 
40
29
  # Checks if the destination file already exists.
@@ -55,8 +44,10 @@ module Templater
55
44
 
56
45
  # Renders the template and copies it to the destination.
57
46
  def invoke!
47
+ @generator.send(@options[:before], self) if @options[:before]
58
48
  ::FileUtils.mkdir_p(::File.dirname(destination))
59
49
  ::File.open(destination, 'w') {|f| f.write render }
50
+ @generator.send(@options[:after], self) if @options[:after]
60
51
  end
61
52
 
62
53
  # removes the destination file
@@ -5,8 +5,11 @@ module Templater
5
5
  class Generator
6
6
 
7
7
  def initialize(generator_name, generator_class, destination_root, name, version)
8
- @destination_root, @generator_name, @generator_class = destination_root, generator_name, generator_class
9
- @name, @version = name, version
8
+ @generator_name = generator_name
9
+ @destination_root = destination_root
10
+ @generator_class = generator_class
11
+ @name = name
12
+ @version = version
10
13
  end
11
14
 
12
15
  def version
@@ -27,6 +30,7 @@ module Templater
27
30
 
28
31
  def run(arguments)
29
32
  generator_class = @generator_class # FIXME: closure wizardry, there has got to be a better way than this?
33
+
30
34
  @options = Templater::CLI::Parser.parse(arguments) do |opts, options|
31
35
  opts.separator "Options specific for this generator:"
32
36
  # the reason this is reversed is so that the 'main' generator will always have the last word
@@ -35,14 +39,14 @@ module Templater
35
39
  # Loop through this generator's options and add them as valid command line options
36
40
  # so that they show up in help messages and such
37
41
  generator.options.each do |option|
38
- name = option[:name].to_s.gsub('_', '-')
39
- if option[:options][:as] == :boolean
40
- opts.on("--#{name}", option[:options][:desc]) do |s|
41
- options[option[:name]] = s
42
+ name = option.name.to_s.gsub('_', '-')
43
+ if option.options[:as] == :boolean
44
+ opts.on("--#{name}", option.options[:desc]) do |s|
45
+ options[option.name] = s
42
46
  end
43
47
  else
44
- opts.on("--#{name} OPTION", option[:options][:desc]) do |s|
45
- options[option[:name]] = s.gsub('-', '_').to_sym
48
+ opts.on("--#{name} OPTION", option.options[:desc]) do |s|
49
+ options[option.name] = s.gsub('-', '_').to_sym
46
50
  end
47
51
  end
48
52
  end
@@ -72,7 +76,7 @@ module Templater
72
76
  end
73
77
 
74
78
  def step_through_templates
75
- @generator.actions.each do |action|
79
+ @generator.all_actions.each do |action|
76
80
  if @options[:delete]
77
81
  action.revoke! unless @options[:pretend]
78
82
  say_status('deleted', action, :red)
@@ -1,4 +1,4 @@
1
- class String #:nodoc:
1
+ class String
2
2
 
3
3
  def realign_indentation
4
4
  basis = self.index(/\S/) # find the first non-whitespace character
@@ -0,0 +1,61 @@
1
+ module Templater
2
+
3
+ class Description
4
+ attr_accessor :name, :options, :block
5
+
6
+ def initialize(name, options={}, &block)
7
+ @name = name
8
+ @options = options
9
+ @block = block
10
+ end
11
+ end
12
+
13
+ class ActionDescription < Description
14
+
15
+ def compile(generator)
16
+ @block.call(generator)
17
+ end
18
+
19
+ end
20
+
21
+ class ArgumentDescription < Description
22
+
23
+ # Checks if the given argument is valid according to this description
24
+ #
25
+ # === Parameters
26
+ # argument<Object>:: Checks if the given argument is valid.
27
+ # === Returns
28
+ # Boolean:: Validity of the argument
29
+ def valid?(argument)
30
+ if argument.nil? and options[:required]
31
+ raise Templater::TooFewArgumentsError
32
+ elsif not argument.nil?
33
+ if options[:as] == :hash and not argument.is_a?(Hash)
34
+ raise Templater::MalformattedArgumentError, "Expected the argument to be a Hash, but was '#{argument.inspect}'"
35
+ elsif options[:as] == :array and not argument.is_a?(Array)
36
+ raise Templater::MalformattedArgumentError, "Expected the argument to be an Array, but was '#{argument.inspect}'"
37
+ end
38
+
39
+ invalid = catch :invalid do
40
+ block.call(argument) if block
41
+ throw :invalid, :not_invalid
42
+ end
43
+ raise Templater::ArgumentError, invalid unless invalid == :not_invalid
44
+ end
45
+ end
46
+
47
+ end
48
+
49
+ class InvocationDescription < Description
50
+
51
+ def get(generator)
52
+ klass = generator.class.manifold.generator(name)
53
+ if klass and block
54
+ generator.instance_exec(klass, &block)
55
+ elsif klass
56
+ klass.new(generator.destination_root, generator.options, *generator.arguments)
57
+ end
58
+ end
59
+
60
+ end
61
+ end