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.
Files changed (32) hide show
  1. data/MIT-LICENSE +1 -1
  2. data/README.rdoc +20 -1
  3. data/lib/generators/mustache_render/migration/install_generator.rb +53 -0
  4. data/lib/generators/mustache_render/migration/templates/active_record/migration.rb +37 -0
  5. data/lib/generators/mustache_render/migration/templates/models/active_record/mustache_render_folder.rb +6 -0
  6. data/lib/generators/mustache_render/migration/templates/models/active_record/mustache_render_template.rb +6 -0
  7. data/lib/mustache_render.rb +26 -0
  8. data/lib/mustache_render/config.rb +22 -0
  9. data/lib/mustache_render/controllers/mustache_render/manager/base_controller.rb +16 -0
  10. data/lib/mustache_render/controllers/mustache_render/manager/folders_controller.rb +46 -0
  11. data/lib/mustache_render/controllers/mustache_render/manager/templates_controller.rb +53 -0
  12. data/lib/mustache_render/core_ext/base_controller_ext.rb +41 -0
  13. data/lib/mustache_render/models/mustache_render_folder_mixin.rb +128 -0
  14. data/lib/mustache_render/models/mustache_render_template_mixin.rb +44 -0
  15. data/lib/mustache_render/mustache.rb +314 -0
  16. data/lib/mustache_render/mustache/context.rb +144 -0
  17. data/lib/mustache_render/mustache/generator.rb +197 -0
  18. data/lib/mustache_render/mustache/parser.rb +265 -0
  19. data/lib/mustache_render/mustache/settings.rb +234 -0
  20. data/lib/mustache_render/mustache/template.rb +60 -0
  21. data/lib/mustache_render/version.rb +8 -1
  22. data/lib/mustache_render/views/layouts/mustache_render/manager/base.html.erb +14 -0
  23. data/lib/mustache_render/views/mustache_render/manager/folders/_form.html.erb +31 -0
  24. data/lib/mustache_render/views/mustache_render/manager/folders/edit.html.erb +11 -0
  25. data/lib/mustache_render/views/mustache_render/manager/folders/index.html.erb +9 -0
  26. data/lib/mustache_render/views/mustache_render/manager/folders/new.html.erb +8 -0
  27. data/lib/mustache_render/views/mustache_render/manager/folders/show.html.erb +44 -0
  28. data/lib/mustache_render/views/mustache_render/manager/templates/_form.html.erb +36 -0
  29. data/lib/mustache_render/views/mustache_render/manager/templates/edit.html.erb +15 -0
  30. data/lib/mustache_render/views/mustache_render/manager/templates/new.html.erb +12 -0
  31. data/lib/mustache_render/views/mustache_render/manager/templates/show.html.erb +33 -0
  32. 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