forked from jnicklas/templater
-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathREADME
More file actions
235 lines (157 loc) · 8 KB
/
Copy pathREADME
File metadata and controls
235 lines (157 loc) · 8 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
= Templater
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:
- Actions (File copying routines, templates generation and directories creation routines).
- Generators (set of rules).
- Manifolds (generator suites).
- The command line interface.
Hierarchy is pretty simple: manifold has one or many public and private generators. Public ones are supposed to be called
by end user. Generators have one or more action that specify what they do, where they take files, how they name resulting
files and so forth.
== Idea behind Templater
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.
== Example
This is how to create a very simple system for generating things:
module MyGenerators
extend Templater::Manifold
class BlogGenerator < Templater::Generator
# directory this generator uses as source root when searching
# for files, directories, templates
def self.source_root
File.join(File.dirname(__FILE__), 'templates')
end
# uses blog.rbt template (note conventional trailing t)
# placing resulting file to blog.rb relatively to
# destination root
template :blog, 'blog.rb'
# does simple copy of me.jpg placing resulting file to me.jpg relatively to
# destination root
file :me, 'me.jpg'
# creates empty directory public/javascripts relatively to
# destination root
empty_directory :javascripts, File.join("public", "javascripts")
end
class WikiGenerator < Templater::Generator
def self.source_root
File.join(File.dirname(__FILE__), 'templates')
end
template :wiki, 'wiki.rb'
file :img, 'wiki.jpg'
end
# The generators are added to the manifold, and assigned the names 'wiki' and 'blog'.
# So you can call them <script name> blog merb-blog-in-10-minutes and
# <script name> blog merb-wiki-in-10-minutes, respectively
add :blog, BlogGenerator
add :wiki, WikiGenerator
end
# registers manifold with command line interface
MyGenerators.run_cli Dir.pwd, 'my_generators', '0.1', ARGV
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. +empty_directory+ action creates empty directory under destination root.
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.
== Invoking other generators
Generators can invoke other generators, a WikiBlog generator that creates both a Wiki and a Blog could look like this:
module MyGenerators
extend Templater::Manifold
class WikiBlogGenerator < Templater::Generator
invoke :wiki
invoke :blog
end
add :wiki_blog, WikiBlogGenerator
end
It needs no source_root, since it has no actions. The generators are invoked by their name in the manifold, not by their class name; this gives the system a great deal of flexibility.
== Automatically adding actions
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.
class MyGenerator < Templater::Generator
def self.source_root
File.join(File.dirname(__FILE__), 'templates')
end
glob!
end
This will search the source root and add all files as actions.
== Templates
Templates are processed using generator instance scope as binding, so every instance method available on your
generator is available in template body.
There are a lot of ways of adding templates:
template :one_argument, 'source_and_destination.rb'
template :two_arguments, 'source.rb', 'destination.rb'
template :block do
source('source.rb')
destination(some_instance_method)
end
template :expression, 'source.rb' '%some_instance_method%.rb'
In the last example, the characters enclosed in percentage signs will be replaced with the results of the instance method +some_instance_method+
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.
<% if name %>
puts "My name is <%= name %>"
<% else %>
puts "I have no name"
<% end %>
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.
<%= 2 + 2 %>
<%%= 2 + 2 %>
will result in
4
<%= 2 + 2 %>
== Callbacks
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.
class MyGenerator < Templater::Generator
template :something, 'something.rb', :after => :chmod
def chmod(action)
File.chmod(action.destination, 0750)
end
end
== An advanced example
A generator for creating a model class, such as it used by Merb or Rails, could look like this:
module Merb::Generators
class ModelGenerator < ComponentGenerator
def self.source_root
File.join(super, 'model')
end
# description end users see next to generator name
desc <<-DESC
This is a model generator
DESC
# options generator takes, their metadata, like description or arguments type
option :testing_framework, :desc => 'Specify which testing framework to use (spec, test_unit)'
option :orm, :desc => 'Specify which Object-Relation Mapper to use (none, activerecord, datamapper, sequel)'
# you may use shortcuts for first 4 option positions
first_argument :name, :required => true
second_argument :attributes, :as => :hash, :default => {}
invoke :migration do |generator|
generator.new(destination_root, options.merge(:model => true), name, attributes)
end
template :model, :orm => :none do
source('model.rbt')
destination('app/models/' + file_name + '.rb')
end
template :model_activerecord, :orm => :activerecord do
source('model_activerecord.rbt')
destination('app/models/' + file_name + '.rb')
end
template :model_datamapper, :orm => :datamapper do
source('model_datamapper.rbt')
destination('app/models/' + file_name + '.rb')
end
template :model_sequel, :orm => :sequel do
source('model_sequel.rbt')
destination('app/models/' + file_name + '.rb')
end
template :spec, :testing_framework => :rspec do
source('spec.rbt')
destination('spec/models/' + file_name + '_spec.rb')
end
template :test_unit, :testing_framework => :test_unit do
source('test_unit.rbt')
destination('test/models/' + file_name + '_test.rb')
end
def class_name
self.name.camel_case
end
def test_class_name
self.class_name + "Test"
end
def file_name
self.name.snake_case
end
end
add :model, ModelGenerator
end