mustache_render 0.0.1 → 0.0.3
Sign up to get free protection for your applications and to get access to all the features.
- data/MIT-LICENSE +1 -1
- data/README.rdoc +20 -1
- data/lib/generators/mustache_render/migration/install_generator.rb +53 -0
- data/lib/generators/mustache_render/migration/templates/active_record/migration.rb +37 -0
- data/lib/generators/mustache_render/migration/templates/models/active_record/mustache_render_folder.rb +6 -0
- data/lib/generators/mustache_render/migration/templates/models/active_record/mustache_render_template.rb +6 -0
- data/lib/mustache_render.rb +26 -0
- data/lib/mustache_render/config.rb +22 -0
- data/lib/mustache_render/controllers/mustache_render/manager/base_controller.rb +16 -0
- data/lib/mustache_render/controllers/mustache_render/manager/folders_controller.rb +46 -0
- data/lib/mustache_render/controllers/mustache_render/manager/templates_controller.rb +53 -0
- data/lib/mustache_render/core_ext/base_controller_ext.rb +41 -0
- data/lib/mustache_render/models/mustache_render_folder_mixin.rb +128 -0
- data/lib/mustache_render/models/mustache_render_template_mixin.rb +44 -0
- data/lib/mustache_render/mustache.rb +314 -0
- data/lib/mustache_render/mustache/context.rb +144 -0
- data/lib/mustache_render/mustache/generator.rb +197 -0
- data/lib/mustache_render/mustache/parser.rb +265 -0
- data/lib/mustache_render/mustache/settings.rb +234 -0
- data/lib/mustache_render/mustache/template.rb +60 -0
- data/lib/mustache_render/version.rb +8 -1
- data/lib/mustache_render/views/layouts/mustache_render/manager/base.html.erb +14 -0
- data/lib/mustache_render/views/mustache_render/manager/folders/_form.html.erb +31 -0
- data/lib/mustache_render/views/mustache_render/manager/folders/edit.html.erb +11 -0
- data/lib/mustache_render/views/mustache_render/manager/folders/index.html.erb +9 -0
- data/lib/mustache_render/views/mustache_render/manager/folders/new.html.erb +8 -0
- data/lib/mustache_render/views/mustache_render/manager/folders/show.html.erb +44 -0
- data/lib/mustache_render/views/mustache_render/manager/templates/_form.html.erb +36 -0
- data/lib/mustache_render/views/mustache_render/manager/templates/edit.html.erb +15 -0
- data/lib/mustache_render/views/mustache_render/manager/templates/new.html.erb +12 -0
- data/lib/mustache_render/views/mustache_render/manager/templates/show.html.erb +33 -0
- metadata +106 -30
@@ -0,0 +1,44 @@
|
|
1
|
+
# -*- encoding : utf-8 -*-
|
2
|
+
module MustacheRender::Models
|
3
|
+
module MustacheRenderTemplateMixin
|
4
|
+
def self.included(base)
|
5
|
+
base.class_eval do
|
6
|
+
table_name = 'mustache_render_templates'
|
7
|
+
|
8
|
+
attr_accessible :folder_id, :name, :note, :content
|
9
|
+
|
10
|
+
belongs_to :folder, :class_name => 'MustacheRenderFolder'
|
11
|
+
|
12
|
+
validates_presence_of :folder_id
|
13
|
+
validates_presence_of :name
|
14
|
+
|
15
|
+
extend ClassMethods
|
16
|
+
include InstanceMethods
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
module ClassMethods
|
21
|
+
#
|
22
|
+
# TODO: add cache here!
|
23
|
+
#
|
24
|
+
def find_with_full_path(name)
|
25
|
+
# 首先获取文件夹的名称, 然后获取文件名
|
26
|
+
tmp_paths = name.to_s.split('/')
|
27
|
+
|
28
|
+
template_name = tmp_paths.pop.to_s
|
29
|
+
|
30
|
+
folder_full_path = "#{tmp_paths.join('/')}"
|
31
|
+
folder = ::MustacheRenderFolder.find_by_full_path(folder_full_path)
|
32
|
+
|
33
|
+
self.find_by_folder_id_and_name(folder.try(:id), template_name)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
module InstanceMethods
|
38
|
+
def full_path
|
39
|
+
"#{self.folder.try :full_path}/#{self.name}"
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
@@ -0,0 +1,314 @@
|
|
1
|
+
require 'mustache_render/mustache/template'
|
2
|
+
require 'mustache_render/mustache/context'
|
3
|
+
require 'mustache_render/mustache/settings'
|
4
|
+
|
5
|
+
# Mustache is the base class from which your Mustache subclasses
|
6
|
+
# should inherit (though it can be used on its own).
|
7
|
+
#
|
8
|
+
# The typical Mustache workflow is as follows:
|
9
|
+
#
|
10
|
+
# * Create a Mustache subclass: class Stats < Mustache
|
11
|
+
# * Create a template: stats.mustache
|
12
|
+
# * Instantiate an instance: view = Stats.new
|
13
|
+
# * Render that instance: view.render
|
14
|
+
#
|
15
|
+
# You can skip the instantiation by calling `Stats.render` directly.
|
16
|
+
#
|
17
|
+
# While Mustache will do its best to load and render a template for
|
18
|
+
# you, this process is completely customizable using a few options.
|
19
|
+
#
|
20
|
+
# All settings can be overriden at the class level.
|
21
|
+
#
|
22
|
+
# For example, going with the above example, we can use
|
23
|
+
# `Stats.template_path = "/usr/local/templates"` to specify the path
|
24
|
+
# Mustache uses to find templates.
|
25
|
+
#
|
26
|
+
# Here are the available options:
|
27
|
+
#
|
28
|
+
# * template_path
|
29
|
+
#
|
30
|
+
# The `template_path` setting determines the path Mustache uses when
|
31
|
+
# looking for a template. By default it is "."
|
32
|
+
# Setting it to /usr/local/templates, for example, means (given all
|
33
|
+
# other settings are default) a Mustache subclass `Stats` will try to
|
34
|
+
# load /usr/local/templates/stats.mustache
|
35
|
+
#
|
36
|
+
# * template_extension
|
37
|
+
#
|
38
|
+
# The `template_extension` is the extension Mustache uses when looking
|
39
|
+
# for template files. By default it is "mustache"
|
40
|
+
#
|
41
|
+
# * template_file
|
42
|
+
#
|
43
|
+
# You can tell Mustache exactly which template to us with this
|
44
|
+
# setting. It can be a relative or absolute path.
|
45
|
+
#
|
46
|
+
# * template
|
47
|
+
#
|
48
|
+
# Sometimes you want Mustache to render a string, not a file. In those
|
49
|
+
# cases you may set the `template` setting. For example:
|
50
|
+
#
|
51
|
+
# >> Mustache.render("Hello {{planet}}", :planet => "World!")
|
52
|
+
# => "Hello World!"
|
53
|
+
#
|
54
|
+
# The `template` setting is also available on instances.
|
55
|
+
#
|
56
|
+
# view = Mustache.new
|
57
|
+
# view.template = "Hi, {{person}}!"
|
58
|
+
# view[:person] = 'Mom'
|
59
|
+
# view.render # => Hi, mom!
|
60
|
+
#
|
61
|
+
# * view_namespace
|
62
|
+
#
|
63
|
+
# To make life easy on those developing Mustache plugins for web frameworks or
|
64
|
+
# other libraries, Mustache will attempt to load view classes (i.e. Mustache
|
65
|
+
# subclasses) using the `view_class` class method. The `view_namespace` tells
|
66
|
+
# Mustache under which constant view classes live. By default it is `Object`.
|
67
|
+
#
|
68
|
+
# * view_path
|
69
|
+
#
|
70
|
+
# Similar to `template_path`, the `view_path` option tells Mustache where to look
|
71
|
+
# for files containing view classes when using the `view_class` method.
|
72
|
+
#
|
73
|
+
module MustacheRender
|
74
|
+
class Mustache
|
75
|
+
|
76
|
+
#
|
77
|
+
# Public API
|
78
|
+
#
|
79
|
+
|
80
|
+
# Instantiates an instance of this class and calls `render` with
|
81
|
+
# the passed args.
|
82
|
+
#
|
83
|
+
# Returns a rendered String version of a template
|
84
|
+
def self.render(*args)
|
85
|
+
new.render(*args)
|
86
|
+
end
|
87
|
+
|
88
|
+
class << self
|
89
|
+
alias_method :to_html, :render
|
90
|
+
alias_method :to_text, :render
|
91
|
+
end
|
92
|
+
|
93
|
+
# Parses our fancy pants template file and returns normal file with
|
94
|
+
# all special {{tags}} and {{#sections}}replaced{{/sections}}.
|
95
|
+
#
|
96
|
+
# data - A String template or a Hash context. If a Hash is given,
|
97
|
+
# we'll try to figure out the template from the class.
|
98
|
+
# ctx - A Hash context if `data` is a String template.
|
99
|
+
#
|
100
|
+
# Examples
|
101
|
+
#
|
102
|
+
# @view.render("Hi {{thing}}!", :thing => :world)
|
103
|
+
#
|
104
|
+
# View.template = "Hi {{thing}}!"
|
105
|
+
# @view = View.new
|
106
|
+
# @view.render(:thing => :world)
|
107
|
+
#
|
108
|
+
# Returns a rendered String version of a template
|
109
|
+
def render(data = template, ctx = {})
|
110
|
+
if data.is_a? Hash
|
111
|
+
ctx = data
|
112
|
+
tpl = templateify(template)
|
113
|
+
elsif data.is_a? Symbol
|
114
|
+
self.template_name = data
|
115
|
+
tpl = templateify(template)
|
116
|
+
else
|
117
|
+
tpl = templateify(data)
|
118
|
+
end
|
119
|
+
|
120
|
+
return tpl.render(context) if ctx == {}
|
121
|
+
|
122
|
+
begin
|
123
|
+
context.push(ctx)
|
124
|
+
tpl.render(context)
|
125
|
+
ensure
|
126
|
+
context.pop
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
alias_method :to_html, :render
|
131
|
+
alias_method :to_text, :render
|
132
|
+
|
133
|
+
# Context accessors.
|
134
|
+
#
|
135
|
+
# view = Mustache.new
|
136
|
+
# view[:name] = "Jon"
|
137
|
+
# view.template = "Hi, {{name}}!"
|
138
|
+
# view.render # => "Hi, Jon!"
|
139
|
+
def [](key)
|
140
|
+
context[key.to_sym]
|
141
|
+
end
|
142
|
+
|
143
|
+
def []=(key, value)
|
144
|
+
context[key.to_sym] = value
|
145
|
+
end
|
146
|
+
|
147
|
+
# A helper method which gives access to the context at a given time.
|
148
|
+
# Kind of a hack for now, but useful when you're in an iterating section
|
149
|
+
# and want access to the hash currently being iterated over.
|
150
|
+
def context
|
151
|
+
@context ||= Context.new(self)
|
152
|
+
end
|
153
|
+
|
154
|
+
# Given a file name and an optional context, attempts to load and
|
155
|
+
# render the file as a template.
|
156
|
+
def self.render_file(name, context = {})
|
157
|
+
render(partial(name), context)
|
158
|
+
end
|
159
|
+
|
160
|
+
# Given a file name and an optional context, attempts to load and
|
161
|
+
# render the file as a template.
|
162
|
+
def render_file(name, context = {})
|
163
|
+
self.class.render_file(name, context)
|
164
|
+
end
|
165
|
+
|
166
|
+
def self.read_template(name)
|
167
|
+
db_template = ::MustacheRenderTemplate.find_with_full_path(name)
|
168
|
+
db_template.try :content
|
169
|
+
end
|
170
|
+
|
171
|
+
# Given a name, attempts to read a file and return the contents as a
|
172
|
+
# string. The file is not rendered, so it might contain
|
173
|
+
# {{mustaches}}.
|
174
|
+
#
|
175
|
+
# Call `render` if you need to process it.
|
176
|
+
def self.partial(name)
|
177
|
+
self.read_template(name)
|
178
|
+
|
179
|
+
# File.read("#{template_path}/#{name}.#{template_extension}")
|
180
|
+
end
|
181
|
+
|
182
|
+
# Override this in your subclass if you want to do fun things like
|
183
|
+
# reading templates from a database. It will be rendered by the
|
184
|
+
# context, so all you need to do is return a string.
|
185
|
+
def partial(name)
|
186
|
+
self.class.partial(name)
|
187
|
+
end
|
188
|
+
|
189
|
+
# Override this to provide custom escaping.
|
190
|
+
#
|
191
|
+
# class PersonView < Mustache
|
192
|
+
# def escapeHTML(str)
|
193
|
+
# my_html_escape_method(str)
|
194
|
+
# end
|
195
|
+
# end
|
196
|
+
#
|
197
|
+
# Returns a String
|
198
|
+
def escapeHTML(str)
|
199
|
+
CGI.escapeHTML(str)
|
200
|
+
end
|
201
|
+
|
202
|
+
|
203
|
+
#
|
204
|
+
# Private API
|
205
|
+
#
|
206
|
+
|
207
|
+
# When given a symbol or string representing a class, will try to produce an
|
208
|
+
# appropriate view class.
|
209
|
+
# e.g.
|
210
|
+
# Mustache.view_namespace = Hurl::Views
|
211
|
+
# Mustache.view_class(:Partial) # => Hurl::Views::Partial
|
212
|
+
def self.view_class(name)
|
213
|
+
if name != classify(name.to_s)
|
214
|
+
name = classify(name.to_s)
|
215
|
+
end
|
216
|
+
|
217
|
+
# Emptiness begets emptiness.
|
218
|
+
if name.to_s == ''
|
219
|
+
return Mustache
|
220
|
+
end
|
221
|
+
|
222
|
+
file_name = underscore(name)
|
223
|
+
|
224
|
+
name = "#{view_namespace}::#{name}"
|
225
|
+
|
226
|
+
if const = const_get!(name)
|
227
|
+
const
|
228
|
+
elsif File.exists?(file = "#{view_path}/#{file_name}.rb")
|
229
|
+
require "#{file}".chomp('.rb')
|
230
|
+
const_get!(name) || Mustache
|
231
|
+
else
|
232
|
+
Mustache
|
233
|
+
end
|
234
|
+
end
|
235
|
+
|
236
|
+
# Supercharged version of Module#const_get.
|
237
|
+
#
|
238
|
+
# Always searches under Object and can find constants by their full name,
|
239
|
+
# e.g. Mustache::Views::Index
|
240
|
+
#
|
241
|
+
# name - The full constant name to find.
|
242
|
+
#
|
243
|
+
# Returns the constant if found
|
244
|
+
# Returns nil if nothing is found
|
245
|
+
def self.const_get!(name)
|
246
|
+
name.split('::').inject(Object) do |klass, name|
|
247
|
+
klass.const_get(name)
|
248
|
+
end
|
249
|
+
rescue NameError
|
250
|
+
nil
|
251
|
+
end
|
252
|
+
|
253
|
+
# Has this template already been compiled? Compilation is somewhat
|
254
|
+
# expensive so it may be useful to check this before attempting it.
|
255
|
+
def self.compiled?
|
256
|
+
@template.is_a? Template
|
257
|
+
end
|
258
|
+
|
259
|
+
# Has this instance or its class already compiled a template?
|
260
|
+
def compiled?
|
261
|
+
(@template && @template.is_a?(Template)) || self.class.compiled?
|
262
|
+
end
|
263
|
+
|
264
|
+
# template_partial => TemplatePartial
|
265
|
+
# template/partial => Template::Partial
|
266
|
+
def self.classify(underscored)
|
267
|
+
underscored.split('/').map do |namespace|
|
268
|
+
namespace.split(/[-_]/).map do |part|
|
269
|
+
part[0] = part[0].chr.upcase; part
|
270
|
+
end.join
|
271
|
+
end.join('::')
|
272
|
+
end
|
273
|
+
|
274
|
+
# TemplatePartial => template_partial
|
275
|
+
# Template::Partial => template/partial
|
276
|
+
# Takes a string but defaults to using the current class' name.
|
277
|
+
def self.underscore(classified = name)
|
278
|
+
classified = name if classified.to_s.empty?
|
279
|
+
classified = superclass.name if classified.to_s.empty?
|
280
|
+
|
281
|
+
string = classified.dup.split("#{view_namespace}::").last
|
282
|
+
|
283
|
+
string.split('::').map do |part|
|
284
|
+
part[0] = part[0].chr.downcase
|
285
|
+
part.gsub(/[A-Z]/) { |s| "_#{s.downcase}"}
|
286
|
+
end.join('/')
|
287
|
+
end
|
288
|
+
|
289
|
+
# Turns a string into a Mustache::Template. If passed a Template,
|
290
|
+
# returns it.
|
291
|
+
def self.templateify(obj)
|
292
|
+
if obj.is_a?(Template)
|
293
|
+
obj
|
294
|
+
else
|
295
|
+
Template.new(obj.to_s)
|
296
|
+
end
|
297
|
+
end
|
298
|
+
|
299
|
+
def templateify(obj)
|
300
|
+
self.class.templateify(obj)
|
301
|
+
end
|
302
|
+
|
303
|
+
# Return the value of the configuration setting on the superclass, or return
|
304
|
+
# the default.
|
305
|
+
#
|
306
|
+
# attr_name - Symbol name of the attribute. It should match the instance variable.
|
307
|
+
# default - Default value to use if the superclass does not respond.
|
308
|
+
#
|
309
|
+
# Returns the inherited or default configuration setting.
|
310
|
+
def self.inheritable_config_for(attr_name, default)
|
311
|
+
superclass.respond_to?(attr_name) ? superclass.send(attr_name) : default
|
312
|
+
end
|
313
|
+
end
|
314
|
+
end
|
@@ -0,0 +1,144 @@
|
|
1
|
+
module MustacheRender
|
2
|
+
class Mustache
|
3
|
+
# A ContextMiss is raised whenever a tag's target can not be found
|
4
|
+
# in the current context if `Mustache#raise_on_context_miss?` is
|
5
|
+
# set to true.
|
6
|
+
#
|
7
|
+
# For example, if your View class does not respond to `music` but
|
8
|
+
# your template contains a `{{music}}` tag this exception will be raised.
|
9
|
+
#
|
10
|
+
# By default it is not raised. See Mustache.raise_on_context_miss.
|
11
|
+
class ContextMiss < RuntimeError; end
|
12
|
+
|
13
|
+
# A Context represents the context which a Mustache template is
|
14
|
+
# executed within. All Mustache tags reference keys in the Context.
|
15
|
+
class Context
|
16
|
+
# Expect to be passed an instance of `Mustache`.
|
17
|
+
def initialize(mustache)
|
18
|
+
@stack = [mustache]
|
19
|
+
end
|
20
|
+
|
21
|
+
# A {{>partial}} tag translates into a call to the context's
|
22
|
+
# `partial` method, which would be this sucker right here.
|
23
|
+
#
|
24
|
+
# If the Mustache view handling the rendering (e.g. the view
|
25
|
+
# representing your profile page or some other template) responds
|
26
|
+
# to `partial`, we call it and render the result.
|
27
|
+
def partial(name, indentation = '')
|
28
|
+
# Look for the first Mustache in the stack.
|
29
|
+
mustache = mustache_in_stack
|
30
|
+
|
31
|
+
# Indent the partial template by the given indentation.
|
32
|
+
part = mustache.partial(name).to_s.gsub(/^/, indentation)
|
33
|
+
|
34
|
+
# Call the Mustache's `partial` method and render the result.
|
35
|
+
result = mustache.render(part, self)
|
36
|
+
end
|
37
|
+
|
38
|
+
# Find the first Mustache in the stack. If we're being rendered
|
39
|
+
# inside a Mustache object as a context, we'll use that one.
|
40
|
+
def mustache_in_stack
|
41
|
+
@stack.detect { |frame| frame.is_a?(Mustache) }
|
42
|
+
end
|
43
|
+
|
44
|
+
# Allows customization of how Mustache escapes things.
|
45
|
+
#
|
46
|
+
# Returns a String.
|
47
|
+
def escapeHTML(str)
|
48
|
+
mustache_in_stack.escapeHTML(str)
|
49
|
+
end
|
50
|
+
|
51
|
+
# Adds a new object to the context's internal stack.
|
52
|
+
#
|
53
|
+
# Returns the Context.
|
54
|
+
def push(new)
|
55
|
+
@stack.unshift(new)
|
56
|
+
self
|
57
|
+
end
|
58
|
+
alias_method :update, :push
|
59
|
+
|
60
|
+
# Removes the most recently added object from the context's
|
61
|
+
# internal stack.
|
62
|
+
#
|
63
|
+
# Returns the Context.
|
64
|
+
def pop
|
65
|
+
@stack.shift
|
66
|
+
self
|
67
|
+
end
|
68
|
+
|
69
|
+
# Can be used to add a value to the context in a hash-like way.
|
70
|
+
#
|
71
|
+
# context[:name] = "Chris"
|
72
|
+
def []=(name, value)
|
73
|
+
push(name => value)
|
74
|
+
end
|
75
|
+
|
76
|
+
# Alias for `fetch`.
|
77
|
+
def [](name)
|
78
|
+
fetch(name, nil)
|
79
|
+
end
|
80
|
+
|
81
|
+
# Do we know about a particular key? In other words, will calling
|
82
|
+
# `context[key]` give us a result that was set. Basically.
|
83
|
+
def has_key?(key)
|
84
|
+
!!fetch(key)
|
85
|
+
rescue ContextMiss
|
86
|
+
false
|
87
|
+
end
|
88
|
+
|
89
|
+
# Similar to Hash#fetch, finds a value by `name` in the context's
|
90
|
+
# stack. You may specify the default return value by passing a
|
91
|
+
# second parameter.
|
92
|
+
#
|
93
|
+
# If no second parameter is passed (or raise_on_context_miss is
|
94
|
+
# set to true), will raise a ContextMiss exception on miss.
|
95
|
+
def fetch(name, default = :__raise)
|
96
|
+
@stack.each do |frame|
|
97
|
+
# Prevent infinite recursion.
|
98
|
+
next if frame == self
|
99
|
+
|
100
|
+
value = find(frame, name, :__missing)
|
101
|
+
if value != :__missing
|
102
|
+
return value
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
if default == :__raise || mustache_in_stack.raise_on_context_miss?
|
107
|
+
raise ContextMiss.new("Can't find #{name} in #{@stack.inspect}")
|
108
|
+
else
|
109
|
+
default
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
# Finds a key in an object, using whatever method is most
|
114
|
+
# appropriate. If the object is a hash, does a simple hash lookup.
|
115
|
+
# If it's an object that responds to the key as a method call,
|
116
|
+
# invokes that method. You get the idea.
|
117
|
+
#
|
118
|
+
# obj - The object to perform the lookup on.
|
119
|
+
# key - The key whose value you want.
|
120
|
+
# default - An optional default value, to return if the
|
121
|
+
# key is not found.
|
122
|
+
#
|
123
|
+
# Returns the value of key in obj if it is found and default otherwise.
|
124
|
+
def find(obj, key, default = nil)
|
125
|
+
hash = obj.respond_to?(:has_key?)
|
126
|
+
|
127
|
+
if hash && obj.has_key?(key)
|
128
|
+
obj[key]
|
129
|
+
elsif hash && obj.has_key?(key.to_s)
|
130
|
+
obj[key.to_s]
|
131
|
+
elsif !hash && obj.respond_to?(key)
|
132
|
+
meth = obj.method(key) rescue proc { obj.send(key) }
|
133
|
+
if meth.arity == 1
|
134
|
+
meth.to_proc
|
135
|
+
else
|
136
|
+
meth[]
|
137
|
+
end
|
138
|
+
else
|
139
|
+
default
|
140
|
+
end
|
141
|
+
end
|
142
|
+
end
|
143
|
+
end
|
144
|
+
end
|