neapolitan 0.3.0 → 0.4.0

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,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