templater 0.1

Sign up to get free protection for your applications and to get access to all the features.
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2008 Jonas Nicklas
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README ADDED
@@ -0,0 +1,199 @@
1
+ = Templater
2
+
3
+ Templater is a system for generating files. Templater has the ability to both copy files from A to B and also to render templates using ERB. Templater consists of four parts:
4
+
5
+ - Actions (Files and Templates)
6
+ - Generators
7
+ - Manifolds
8
+ - The command line interface
9
+
10
+ Each manifold has many generator, and each generator has many actions.
11
+
12
+ == Example
13
+
14
+ This is how to create a very simple system for generating things:
15
+
16
+ module MyGenerators
17
+
18
+ extend Templater::Manifold
19
+
20
+ class BlogGenerator < Templater::Generator
21
+
22
+ def self.source_root
23
+ File.join(File.dirname(__FILE__), 'templates')
24
+ end
25
+
26
+ template :blog, 'blog.rb'
27
+ file :me, 'me.jpg'
28
+
29
+ end
30
+
31
+ class WikiGenerator < Templater::Generator
32
+
33
+ def self.source_root
34
+ File.join(File.dirname(__FILE__), 'templates')
35
+ end
36
+
37
+ template :wiki, 'wiki.rb'
38
+ file :img, 'wiki.jpg'
39
+
40
+ end
41
+
42
+ add :blog, BlogGenerator
43
+ add :wiki, WikiGenerator
44
+
45
+ end
46
+
47
+ MyGenerators.run_cli Dir.pwd, 'my_generators', '0.1', ARGV
48
+
49
+ The generator classes override the source_root method to specify where templates will be located. All subclasses of Templater::Generator that have any actions must do this. The +template+ and +file+ methods add actions to the generator. In the first case, a template that is rendered with ERB and then put in its destination location, in the other case a file that is copied.
50
+
51
+ The generators are added to the manifold, and assigned the names 'wiki' and 'blog'.
52
+
53
+ Neither manifolds or generators actually do anything by themselves, they are just abstract represenations. The last line invokes the command-line-interface, which fetches the desired generator, tells it to render its templates and checks with the user if there are any problems. The generators can easily be used without the command-line-interface, so it is easy to construct an alternative interface.
54
+
55
+ == Invoking other generators
56
+
57
+ Generators can invoke other generators, a WikiBlog generator that creates both a Wiki and a Blog could look like this:
58
+
59
+ module MyGenerators
60
+
61
+ extend Templater::Manifold
62
+
63
+ class WikiBlogGenerator < Templater::Generator
64
+
65
+ invoke :wiki
66
+ invoke :blog
67
+
68
+ end
69
+
70
+ add :wiki_blog, WikiBlogGenerator
71
+
72
+ end
73
+
74
+ It needs to source_root, since it has no actions. Not here that the generators are invoked by their name in the manifold, *not* by their class name. This gives the system a great deal of flexibility.
75
+
76
+ == Automatically adding actions
77
+
78
+ It can get tedious to declare each action, instead you can search in a given directory and automatically add all files to your generator, this is done with the glob! function.
79
+
80
+ class MyGenerator < Templater::Generator
81
+
82
+ def self.source_root
83
+ File.join(File.dirname(__FILE__), 'templates')
84
+ end
85
+
86
+ glob!
87
+
88
+ end
89
+
90
+ This will search the source root and add all files as actions.
91
+
92
+ == Templates
93
+
94
+ There are a lot of ways of adding templates:
95
+
96
+ template :one_argument, 'source_and_destination.rb'
97
+
98
+ template :two_arguments, 'source.rb', 'destination.rb'
99
+
100
+ template :block do
101
+ source('source.rb')
102
+ destination(some_instance_method)
103
+ end
104
+
105
+ template :expression, 'source.rb' '%some_instance_method%.rb'
106
+
107
+ In the last example, the characters enclosed in percentage signs will be replaced with the results of the instance method +some_instance_method+
108
+
109
+ Inside the templates normal ERB can be used. The templates are rendered in the same context as the generator instance, so generator instance methods can be called from inside the template.
110
+
111
+ <% if name %>
112
+ puts "My name is <%= name %>"
113
+ <% else %>
114
+ puts "I have no name"
115
+ <% end %>
116
+
117
+ IF you need to render templates where the result should contain actual erb, simply use a double percentage sign, this will prevent the statement from being executed.
118
+
119
+ <%= 2 + 2 %>
120
+ <%%= 2 + 2 %>
121
+
122
+ will result in
123
+
124
+ 4
125
+ <%= 2 + 2 %>
126
+
127
+ == An advanced example
128
+
129
+ A generator for creating a model class, such as it used by Merb or Rails, could look like this:
130
+
131
+ module Merb::Generators
132
+
133
+ class ModelGenerator < ComponentGenerator
134
+
135
+ def self.source_root
136
+ File.join(super, 'model')
137
+ end
138
+
139
+ desc <<-DESC
140
+ This is a model generator
141
+ DESC
142
+
143
+ option :testing_framework, :desc => 'Specify which testing framework to use (spec, test_unit)'
144
+ option :orm, :desc => 'Specify which Object-Relation Mapper to use (none, activerecord, datamapper, sequel)'
145
+
146
+ first_argument :name, :required => true
147
+ second_argument :attributes, :as => :hash, :default => {}
148
+
149
+ invoke :migration do |generator|
150
+ generator.new(destination_root, options.merge(:model => true), name, attributes)
151
+ end
152
+
153
+ template :model, :orm => :none do
154
+ source('model.rbt')
155
+ destination('app/models/' + file_name + '.rb')
156
+ end
157
+
158
+ template :model_activerecord, :orm => :activerecord do
159
+ source('model_activerecord.rbt')
160
+ destination('app/models/' + file_name + '.rb')
161
+ end
162
+
163
+ template :model_datamapper, :orm => :datamapper do
164
+ source('model_datamapper.rbt')
165
+ destination('app/models/' + file_name + '.rb')
166
+ end
167
+
168
+ template :model_sequel, :orm => :sequel do
169
+ source('model_sequel.rbt')
170
+ destination('app/models/' + file_name + '.rb')
171
+ end
172
+
173
+ template :spec, :testing_framework => :rspec do
174
+ source('spec.rbt')
175
+ destination('spec/models/' + file_name + '_spec.rb')
176
+ end
177
+
178
+ template :test_unit, :testing_framework => :test_unit do
179
+ source('test_unit.rbt')
180
+ destination('test/models/' + file_name + '_test.rb')
181
+ end
182
+
183
+ def class_name
184
+ self.name.camel_case
185
+ end
186
+
187
+ def test_class_name
188
+ self.class_name + "Test"
189
+ end
190
+
191
+ def file_name
192
+ self.name.snake_case
193
+ end
194
+
195
+ end
196
+
197
+ add :model, ModelGenerator
198
+
199
+ end
data/Rakefile ADDED
@@ -0,0 +1,69 @@
1
+ require 'rubygems'
2
+ require 'rake/gempackagetask'
3
+ require 'rubygems/specification'
4
+ require 'rake/rdoctask'
5
+ require 'date'
6
+
7
+ PLUGIN = "templater"
8
+ NAME = "templater"
9
+ GEM_VERSION = "0.1"
10
+ AUTHOR = "Jonas Nicklas"
11
+ EMAIL = "jonas.nicklas@gmail.com"
12
+ HOMEPAGE = "http://templater.rubyforge.org/"
13
+ SUMMARY = "File generation system"
14
+
15
+ spec = Gem::Specification.new do |s|
16
+ s.name = NAME
17
+ s.version = GEM_VERSION
18
+ s.platform = Gem::Platform::RUBY
19
+ s.has_rdoc = true
20
+ s.extra_rdoc_files = ["README", "LICENSE", 'TODO']
21
+ s.summary = SUMMARY
22
+ s.description = s.summary
23
+ s.author = AUTHOR
24
+ s.email = EMAIL
25
+ s.homepage = HOMEPAGE
26
+ s.require_path = 'lib'
27
+ s.autorequire = PLUGIN
28
+ s.files = %w(LICENSE README Rakefile TODO) + Dir.glob("{lib,spec}/**/*")
29
+
30
+ s.add_dependency "highline", ">= 1.4.0"
31
+ s.add_dependency "diff-lcs", ">= 1.1.2"
32
+ # Templater uses facets only for a single instance_exec. This dependency might be a bit stupid.
33
+ s.add_dependency "facets"
34
+ end
35
+
36
+ Rake::GemPackageTask.new(spec) do |pkg|
37
+ pkg.gem_spec = spec
38
+ end
39
+
40
+ desc "install the plugin locally"
41
+ task :install => [:package] do
42
+ sh %{sudo gem install pkg/#{NAME}-#{VERSION} --no-update-sources}
43
+ end
44
+
45
+ desc "create a gemspec file"
46
+ task :make_spec do
47
+ File.open("#{GEM}.gemspec", "w") do |file|
48
+ file.puts spec.to_ruby
49
+ end
50
+ end
51
+
52
+ namespace :jruby do
53
+
54
+ desc "Run :package and install the resulting .gem with jruby"
55
+ task :install => :package do
56
+ sh %{#{SUDO} jruby -S gem install pkg/#{NAME}-#{Merb::VERSION}.gem --no-rdoc --no-ri}
57
+ end
58
+
59
+ end
60
+
61
+ desc 'Generate documentation for Templater.'
62
+ Rake::RDocTask.new(:doc) do |rdoc|
63
+ rdoc.rdoc_dir = 'doc'
64
+ rdoc.title = 'Templater'
65
+ rdoc.options << '--line-numbers' << '--inline-source'
66
+ rdoc.rdoc_files.include('README')
67
+ rdoc.rdoc_files.include('LICENSE')
68
+ rdoc.rdoc_files.include('lib/**/*.rb')
69
+ end
data/TODO ADDED
@@ -0,0 +1,5 @@
1
+ TODO:
2
+ Fix LICENSE with your name
3
+ Fix Rakefile with your name and contact info
4
+ Add your code to lib/templater.rb
5
+ Add your Merb rake tasks to lib/templater/merbtasks.rb
data/lib/templater.rb ADDED
@@ -0,0 +1,44 @@
1
+ path = File.dirname(__FILE__) + '/templater/'
2
+
3
+ require 'rubygems'
4
+ require 'highline'
5
+ require "highline/import"
6
+ require 'diff/lcs'
7
+ require 'facets'
8
+
9
+
10
+ require path + 'capture_helpers'
11
+ require path + 'template'
12
+ require path + 'file'
13
+ require path + 'generator'
14
+ require path + 'proxy'
15
+ require path + 'manifold'
16
+ require path + 'cli/parser'
17
+ require path + 'cli/manifold'
18
+ require path + 'cli/generator'
19
+ require path + 'core_ext/string'
20
+
21
+ require 'erb'
22
+
23
+ module Templater
24
+
25
+ class TemplaterError < StandardError #:nodoc:
26
+ end
27
+ class GeneratorError < TemplaterError #:nodoc:
28
+ end
29
+ class SourceNotSpecifiedError < TemplaterError #:nodoc:
30
+ end
31
+ class ArgumentError < GeneratorError #:nodoc:
32
+ end
33
+ class TooManyArgumentsError < ArgumentError #:nodoc:
34
+ end
35
+ class TooFewArgumentsError < ArgumentError #:nodoc:
36
+ end
37
+ class JustTheRightAmountOfArgumentsError < ArgumentError #:nodoc:
38
+ end
39
+ class MalformattedArgumentError < ArgumentError #:nodoc:
40
+ end
41
+
42
+ VERSION = '0.1'
43
+
44
+ end
@@ -0,0 +1,62 @@
1
+ module Templater
2
+
3
+ # Stolen from merb-core. Merb-core is licensed under the MIT license as follows:
4
+
5
+ # Copyright (c) 2008 Ezra Zygmuntowicz
6
+ #
7
+ # Permission is hereby granted, free of charge, to any person obtaining
8
+ # a copy of this software and associated documentation files (the
9
+ # "Software"), to deal in the Software without restriction, including
10
+ # without limitation the rights to use, copy, modify, merge, publish,
11
+ # distribute, sublicense, and/or sell copies of the Software, and to
12
+ # permit persons to whom the Software is furnished to do so, subject to
13
+ # the following conditions:
14
+ #
15
+ # The above copyright notice and this permission notice shall be
16
+ # included in all copies or substantial portions of the Software.
17
+ #
18
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
19
+ # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
20
+ # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
21
+ # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
22
+ # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
23
+ # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
24
+ # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
25
+
26
+ # TODO: this should be specced in some way.
27
+ module CaptureHelpers #:nodoc:
28
+
29
+ def _erb_buffer( the_binding )
30
+ @_buffer = eval( "_erbout", the_binding, __FILE__, __LINE__)
31
+ end
32
+
33
+ def capture(*args, &block)
34
+ # get the buffer from the block's binding
35
+ buffer = _erb_buffer( block.binding ) rescue nil
36
+
37
+ # If there is no buffer, just call the block and get the contents
38
+ if buffer.nil?
39
+ block.call(*args)
40
+ # If there is a buffer, execute the block, then extract its contents
41
+ else
42
+ pos = buffer.length
43
+ block.call(*args)
44
+
45
+ # extract the block
46
+ data = buffer[pos..-1]
47
+
48
+ # replace it in the original with empty string
49
+ buffer[pos..-1] = ''
50
+
51
+ data
52
+ end
53
+ end
54
+
55
+ # DOC
56
+ def concat(string, binding)
57
+ _erb_buffer(binding) << string
58
+ end
59
+
60
+ end
61
+
62
+ end
@@ -0,0 +1,162 @@
1
+ module Templater
2
+
3
+ module CLI
4
+
5
+ class Generator
6
+
7
+ def initialize(generator_name, destination_root, manifold, name, version)
8
+ @destination_root, @manifold, @name, @version = destination_root, manifold, name, version
9
+ @generator_name = generator_name
10
+ @generator_class = @manifold.generator(@generator_name)
11
+ end
12
+
13
+ def version
14
+ puts @version
15
+ exit
16
+ end
17
+
18
+ # outputs a helpful message and quits
19
+ def help
20
+ puts "Usage: #{@name} #{@generator_name} [options] [args]"
21
+ puts ''
22
+ puts @generator_class.desc
23
+ puts ''
24
+ puts @options[:opts]
25
+ puts ''
26
+ exit
27
+ end
28
+
29
+ def run(arguments)
30
+ generator_class = @generator_class # FIXME: closure wizardry, there has got to be a better way than this?
31
+ @options = Templater::CLI::Parser.parse(arguments) do |opts, options|
32
+ opts.separator "Options specific for this generator:"
33
+ # the reason this is reversed is so that the 'main' generator will always have the last word
34
+ # on the description of the option
35
+ generator_class.generators.reverse.each do |generator|
36
+ # Loop through this generator's options and add them as valid command line options
37
+ # so that they show up in help messages and such
38
+ generator.options.each do |option|
39
+ name = option[:name].to_s.gsub('_', '-')
40
+ if option[:options][:as] == :boolean
41
+ opts.on("--#{name}", option[:options][:desc]) do |s|
42
+ options[option[:name]] = s
43
+ end
44
+ else
45
+ opts.on("--#{name} OPTION", option[:options][:desc]) do |s|
46
+ options[option[:name]] = s.to_sym
47
+ end
48
+ end
49
+ end
50
+ end
51
+ end
52
+
53
+ self.help if @options[:help]
54
+ self.help if arguments.first == 'help'
55
+ self.version if @options[:version]
56
+
57
+ # Try to instantiate a generator, if the arguments to it were incorrect: show a help message
58
+ begin
59
+ @generator = @generator_class.new(@destination_root, @options, *arguments)
60
+ rescue Templater::ArgumentError
61
+ self.help
62
+ end
63
+
64
+ if @options[:pretend]
65
+ puts "Generating with #{@generator_name} generator (just pretending):"
66
+ else
67
+ puts "Generating with #{@generator_name} generator:"
68
+ end
69
+ step_through_templates
70
+ end
71
+
72
+ def step_through_templates
73
+ @generator.actions.each do |action|
74
+ if action.identical?
75
+ say_status('identical', action, :blue)
76
+ elsif action.exists?
77
+ if @options[:force]
78
+ say_status('forced', action, :yellow)
79
+ action.invoke! unless @options[:pretend]
80
+ elsif @options[:skip]
81
+ say_status('skipped', action, :yellow)
82
+ else
83
+ say_status('conflict', action, :red)
84
+ conflict_menu(action)
85
+ end
86
+ else
87
+ say_status('added', action, :green)
88
+ action.invoke! unless @options[:pretend]
89
+ end
90
+ end
91
+ end
92
+
93
+ protected
94
+
95
+ def conflict_menu(template)
96
+ choose do |menu|
97
+ menu.prompt = "How do you wish to proceed with this file?"
98
+
99
+ menu.choice(:skip) do
100
+ say("Skipped file")
101
+ end
102
+ menu.choice(:overwrite) do
103
+ say("Overwritten")
104
+ template.invoke! unless @options[:pretend]
105
+ end
106
+ menu.choice(:render) do
107
+ puts "Rendering " + template.relative_destination
108
+ puts ""
109
+ # outputs each line of the file with the row number prepended
110
+ template.render.to_a.each_with_index do |line, i|
111
+ puts((i+1).to_s.rjust(4) + ': ' + line)
112
+ end
113
+ puts ""
114
+ puts ""
115
+ conflict_menu(template)
116
+ end
117
+ menu.choice(:diff) do
118
+ puts "Showing differences for " + template.relative_destination
119
+ puts ""
120
+
121
+ diffs = Diff::LCS.diff(File.read(template.destination).to_s.to_a, template.render.to_a).first
122
+
123
+ diffs.each do |diff|
124
+ output_diff_line(diff)
125
+ end
126
+
127
+ puts ""
128
+ puts ""
129
+ conflict_menu(template)
130
+ end
131
+ menu.choice(:abort) do
132
+ say("Aborted!")
133
+ exit
134
+ end
135
+ end
136
+ end
137
+
138
+ def say_status(status, template, color = nil)
139
+ status_flag = "[#{status.to_s.upcase}]".rjust(12)
140
+ if color and not @options[:no_color]
141
+ say "<%= color('#{status_flag}', :#{color}) %> " + template.relative_destination
142
+ else
143
+ say "#{status_flag} " + template.relative_destination
144
+ end
145
+ end
146
+
147
+ def output_diff_line(diff)
148
+ case diff.action
149
+ when '-'
150
+ say "<%= color('- #{diff.element.chomp}', :red) %>"
151
+ when '+'
152
+ say "<%= color('+ #{diff.element.chomp}', :green) %>"
153
+ else
154
+ say "#{diff.action} #{diff.element.chomp}"
155
+ end
156
+ end
157
+
158
+ end
159
+
160
+ end
161
+
162
+ end