neapolitan 0.3.0 → 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,39 @@
1
+ module Neapolitan
2
+
3
+ # Encapsulates a template rendering.
4
+ #
5
+ class Rendering
6
+
7
+ #
8
+ def initialize(renders, metadata)
9
+ @renders = renders
10
+ @summary = renders.first
11
+ @output = renders.join("\n")
12
+ @metadata = metadata
13
+ end
14
+
15
+ #
16
+ def to_s
17
+ @output
18
+ end
19
+
20
+ # Renderings of each part.
21
+ def to_a
22
+ @renders
23
+ end
24
+
25
+ # Summary is the rendering of the first part.
26
+ def summary
27
+ @summary
28
+ end
29
+
30
+ #
31
+ def metadata
32
+ @metadata
33
+ end
34
+
35
+ # for temporary backward comptability
36
+ alias_method :header, :metadata
37
+ end
38
+
39
+ end
@@ -0,0 +1,269 @@
1
+ module Neapolitan
2
+
3
+ # Template class is the main interface class.
4
+ #
5
+ class Template
6
+
7
+ # Backend templating system used. Either `malt` or `tilt`.
8
+ #
9
+ # Note that Neapolitan is designed with Malt in mind, but Tilt
10
+ # should work fine in most cases too. Keep in mind that Tilt
11
+ # uses `yield` to render block content, where as Malt uses `content`.
12
+ attr :system
13
+
14
+ # Template text.
15
+ attr :text
16
+
17
+ # File name of template, if given.
18
+ attr :file
19
+
20
+ # Default format(s) for undecorated parts. If not otherwise set the
21
+ # part is rendered exactly as given (which usually means `html`).
22
+ attr :default
23
+
24
+ # Template format(s) to apply to all sections. Typically this
25
+ # will be a polyglot template format that injects data and performs
26
+ # conditional rendering, such as `erb` or `liquid`. But it can
27
+ # contain multiple formats.
28
+ #
29
+ # @example
30
+ # stencil 'erb'
31
+ #
32
+ attr :stencil
33
+
34
+ # Post-formatting to be applied to all parts. These formats are applied
35
+ # after the part specific formats. This can be useful for post-formatting
36
+ # such as `rubypants`, a SmartyPants formatter.
37
+ #
38
+ attr :finish
39
+
40
+ # Data provided in template header. Also known in some circles as
41
+ # <i>YAML front-matter</i>.
42
+ attr :metadata
43
+
44
+ # @deprecated
45
+ alias_method :header, :metadata
46
+
47
+ # Unrendered template parts.
48
+ attr :parts
49
+
50
+ #
51
+ def initialize(source, options={})
52
+ case source
53
+ when ::File
54
+ @file = source.path #name
55
+ @text = source.read
56
+ source.close
57
+ when ::IO
58
+ @text = source.read
59
+ @file = options[:file]
60
+ source.close
61
+ when ::String
62
+ @text = source
63
+ @file = options[:file]
64
+ when Hash
65
+ options = source
66
+ source = nil
67
+ @file = options[:file]
68
+ @text = File.read(@file)
69
+ end
70
+
71
+ @select = Neapolitan.select
72
+ @reject = Neapolitan.reject
73
+
74
+ @system = options[:system] || Neapolitan.system || 'malt'
75
+
76
+ require @system.to_s
77
+
78
+ @default = options[:default] || 'html' # FIXME: 'text'
79
+ @stencil = options[:stencil]
80
+ @finish = options[:finish]
81
+
82
+ parse
83
+ end
84
+
85
+ #
86
+ def inspect
87
+ if file
88
+ "<#{self.class}: @file='#{file}'>"
89
+ else
90
+ "<#{self.class}: @text='#{text[0,10]}...'>"
91
+ end
92
+ end
93
+
94
+ # Apply complex formating rules to parts.
95
+ #
96
+ # Here is an example of how formatting is determined
97
+ # when no formatting block is given.
98
+ #
99
+ # template.format do |part|
100
+ # if part.specific.empty?
101
+ # part.stencil + part.default + part.finish
102
+ # else
103
+ # part.stencil + part.specific + part.finish
104
+ # end
105
+ # end
106
+ #
107
+ def format(&block)
108
+ @format = block if block
109
+ @format
110
+ end
111
+
112
+ # TODO: filter common and default
113
+
114
+ # Reject formats, limiting the template to only the remaining supported
115
+ # formats.
116
+ def reject(&block)
117
+ @reject = block if block
118
+ @reject
119
+ end
120
+
121
+ # TODO: filter common and default
122
+
123
+ # Select formats, limit the template to only the specified formats.
124
+ def select(&block)
125
+ @select = block if block
126
+ @select
127
+ end
128
+
129
+ # Render document.
130
+ #
131
+ # @return [Rendering]
132
+ # The encapsulation of templates completed rendering.
133
+ #
134
+ def render(data={}, &content)
135
+
136
+ # TODO: is this content block buiness here needed any more?
137
+ #if !content
138
+ # case data
139
+ # when Hash
140
+ # yld = data.delete('yield')
141
+ # content = Proc.new{ yld } if yld
142
+ # end
143
+ # content = Proc.new{''} unless content
144
+ #end
145
+
146
+ # apply stencil whole-clothe
147
+ #body = apply_stencil(@body, scope, locals, &content)
148
+
149
+ #parts = parse_parts(body)
150
+
151
+ case data
152
+ when Hash
153
+ scope = Object.new
154
+ locals = @metadata.merge(data.rekey)
155
+ else
156
+ scope = data
157
+ locals = @metadata
158
+ end
159
+
160
+ rendered_parts = parts.map{ |part| render_part(part, scope, locals, &content) }
161
+
162
+ Rendering.new(rendered_parts, @metadata)
163
+ end
164
+
165
+ # Save template to disk.
166
+ #
167
+ # @overload save(data={}, &content)
168
+ # Name of file is the same as the given template
169
+ # file less it's extension.
170
+ #
171
+ # @param [Hash] data
172
+ #
173
+ # @return nothing
174
+ #
175
+ # @overload save(file, data={}, &content)
176
+ #
177
+ # @param [String] file to save as
178
+ #
179
+ # @param [Hash] data
180
+ #
181
+ # @return nothing
182
+ def save(*args, &content)
183
+ data = Hash===args.last ? args.pop : {}
184
+ path = args.first
185
+
186
+ rendering = render(data, &content)
187
+
188
+ path = path || rendering.metadata['output']
189
+ path = path || path.chomp(File.extname(file))
190
+
191
+ path = Dir.pwd unless path
192
+ if File.directory?(path)
193
+ file = File.join(path, file.chomp(File.extname(file)) + extension)
194
+ else
195
+ file = path
196
+ end
197
+
198
+ if $DRYRUN
199
+ $stderr << "[DRYRUN] write #{fname}"
200
+ else
201
+ File.open(file, 'w'){ |f| f << rendering.to_s }
202
+ end
203
+ end
204
+
205
+ private
206
+
207
+ # TODO: Should a stencil be applied once to the entire document?
208
+ # While it would be nice, b/c it would speed things up a bit, it
209
+ # could present an issue with the `---` dividers and would be useless
210
+ # for certain formats like Haml. So probably not.
211
+
212
+ # Apply stencil whole-clothe.
213
+ #def apply_stencil(body, scope, locals, &content)
214
+ # return body unless stencil
215
+ # factory.render(body, stencil, scope, locals, &content)
216
+ #end
217
+
218
+ # Parse template document into metadata and parts.
219
+ def parse
220
+ parts = text.split(/^\-\-\-/)
221
+
222
+ if parts.size == 1
223
+ data = {}
224
+ #@parts << Part.new(sect[0]) #, *[@stencil, @default].compact.flatten)
225
+ else
226
+ parts.shift if parts.first.strip.empty?
227
+ data = YAML::load(parts.first)
228
+ if Hash === data
229
+ parts.shift
230
+ else
231
+ data = {}
232
+ end
233
+ end
234
+
235
+ parse_metadata(data)
236
+
237
+ @parts = parts.map{ |part| Part.parse(self, part) }
238
+ end
239
+
240
+ #
241
+ def parse_metadata(data)
242
+ @default = data.delete('default') if data.key?('default')
243
+ @stencil = data.delete('stencil') if data.key?('stencil')
244
+ @finish = data.delete('finish') if data.key?('finish')
245
+ @metadata = data
246
+ end
247
+
248
+ # Render a part.
249
+ def render_part(part, scope, locals={}, &content)
250
+ formats = part.formatting(&@format)
251
+
252
+ formats = formats.flatten.compact.uniq
253
+
254
+ formats.reject!(&@reject) if @reject
255
+ formats.select!(&@select) if @select
256
+
257
+ formats.inject(part.text) do |text, format|
258
+ factory.render(text, format, scope, locals, &content)
259
+ end
260
+ end
261
+
262
+ # Get cached {Factory} instance.
263
+ def factory
264
+ @factory ||= Factory.new(:tilt=>@tilt)
265
+ end
266
+
267
+ end
268
+
269
+ end
@@ -0,0 +1,17 @@
1
+ module Neapolitan
2
+
3
+ # Access to project metadata.
4
+ def self.metadata
5
+ @metadata ||= (
6
+ require 'yaml'
7
+ YAML.load(File.new(File.dirname(__FILE__) + '/neapolitan.yml'))
8
+ )
9
+ end
10
+
11
+ # Access project metadata as constants.
12
+ def self.const_missing(name)
13
+ key = name.to_s.downcase
14
+ metadata[key] || super(name)
15
+ end
16
+
17
+ end