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.
- data/.ruby +72 -0
- data/HISTORY.rdoc +17 -1
- data/LICENSE.txt +29 -0
- data/README.rdoc +10 -8
- data/demo/01_overview.rdoc +86 -0
- data/demo/02_faq.rdoc +51 -0
- data/demo/applique/env.rb +12 -0
- data/demo/fixtures/example.html +39 -0
- data/demo/fixtures/example.np +31 -0
- data/demo/fixtures/example.yaml +5 -0
- data/lib/neapolitan/cli.rb +66 -0
- data/lib/neapolitan/core_ext.rb +2 -0
- data/lib/neapolitan/factory.rb +123 -0
- data/lib/neapolitan/part.rb +89 -0
- data/lib/neapolitan/rendering.rb +39 -0
- data/lib/neapolitan/template.rb +269 -0
- data/lib/neapolitan/version.rb +17 -0
- data/lib/neapolitan.rb +57 -406
- data/lib/neapolitan.yml +72 -0
- metadata +121 -123
- data/LICENSE +0 -205
- data/lib/neapolitan/meta/data.rb +0 -26
- data/lib/neapolitan/meta/package +0 -12
- data/lib/neapolitan/meta/profile +0 -23
- data/meta/data.rb +0 -26
- data/meta/package +0 -12
- data/meta/profile +0 -23
@@ -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
|