porthole 0.99.4
Sign up to get free protection for your applications and to get access to all the features.
- data/LICENSE +20 -0
- data/README.md +415 -0
- data/Rakefile +89 -0
- data/bin/porthole +94 -0
- data/lib/porthole.rb +304 -0
- data/lib/porthole/context.rb +142 -0
- data/lib/porthole/generator.rb +195 -0
- data/lib/porthole/parser.rb +263 -0
- data/lib/porthole/settings.rb +226 -0
- data/lib/porthole/sinatra.rb +205 -0
- data/lib/porthole/template.rb +58 -0
- data/lib/porthole/version.rb +3 -0
- data/lib/rack/bug/panels/mustache_panel.rb +81 -0
- data/lib/rack/bug/panels/mustache_panel/mustache_extension.rb +27 -0
- data/lib/rack/bug/panels/mustache_panel/view.mustache +46 -0
- data/man/porthole.1 +165 -0
- data/man/porthole.1.html +213 -0
- data/man/porthole.1.ron +127 -0
- data/man/porthole.5 +539 -0
- data/man/porthole.5.html +422 -0
- data/man/porthole.5.ron +324 -0
- data/test/autoloading_test.rb +56 -0
- data/test/fixtures/comments.porthole +1 -0
- data/test/fixtures/comments.rb +14 -0
- data/test/fixtures/complex_view.porthole +17 -0
- data/test/fixtures/complex_view.rb +34 -0
- data/test/fixtures/crazy_recursive.porthole +9 -0
- data/test/fixtures/crazy_recursive.rb +31 -0
- data/test/fixtures/delimiters.porthole +8 -0
- data/test/fixtures/delimiters.rb +23 -0
- data/test/fixtures/dot_notation.porthole +10 -0
- data/test/fixtures/dot_notation.rb +25 -0
- data/test/fixtures/double_section.porthole +7 -0
- data/test/fixtures/double_section.rb +14 -0
- data/test/fixtures/escaped.porthole +1 -0
- data/test/fixtures/escaped.rb +14 -0
- data/test/fixtures/inner_partial.porthole +1 -0
- data/test/fixtures/inner_partial.txt +1 -0
- data/test/fixtures/inverted_section.porthole +7 -0
- data/test/fixtures/inverted_section.rb +14 -0
- data/test/fixtures/lambda.porthole +7 -0
- data/test/fixtures/lambda.rb +31 -0
- data/test/fixtures/method_missing.rb +19 -0
- data/test/fixtures/namespaced.porthole +1 -0
- data/test/fixtures/namespaced.rb +25 -0
- data/test/fixtures/nested_objects.porthole +17 -0
- data/test/fixtures/nested_objects.rb +35 -0
- data/test/fixtures/node.porthole +8 -0
- data/test/fixtures/partial_with_module.porthole +4 -0
- data/test/fixtures/partial_with_module.rb +37 -0
- data/test/fixtures/passenger.conf +5 -0
- data/test/fixtures/passenger.rb +27 -0
- data/test/fixtures/recursive.porthole +4 -0
- data/test/fixtures/recursive.rb +14 -0
- data/test/fixtures/simple.porthole +5 -0
- data/test/fixtures/simple.rb +26 -0
- data/test/fixtures/template_partial.porthole +2 -0
- data/test/fixtures/template_partial.rb +18 -0
- data/test/fixtures/template_partial.txt +4 -0
- data/test/fixtures/unescaped.porthole +1 -0
- data/test/fixtures/unescaped.rb +14 -0
- data/test/fixtures/utf8.porthole +3 -0
- data/test/fixtures/utf8_partial.porthole +1 -0
- data/test/helper.rb +7 -0
- data/test/parser_test.rb +78 -0
- data/test/partial_test.rb +168 -0
- data/test/porthole_test.rb +677 -0
- data/test/spec_test.rb +68 -0
- data/test/template_test.rb +20 -0
- metadata +127 -0
@@ -0,0 +1,226 @@
|
|
1
|
+
# Settings which can be configured for all view classes, a single
|
2
|
+
# view class, or a single Porthole instance.
|
3
|
+
class Porthole
|
4
|
+
|
5
|
+
#
|
6
|
+
# Template Path
|
7
|
+
#
|
8
|
+
|
9
|
+
# The template path informs your Porthole view where to look for its
|
10
|
+
# corresponding template. By default it's the current directory (".")
|
11
|
+
#
|
12
|
+
# A class named Stat with a template_path of "app/templates" will look
|
13
|
+
# for "app/templates/stat.porthole"
|
14
|
+
|
15
|
+
def self.template_path
|
16
|
+
@template_path ||= inheritable_config_for :template_path, '.'
|
17
|
+
end
|
18
|
+
|
19
|
+
def self.template_path=(path)
|
20
|
+
@template_path = File.expand_path(path)
|
21
|
+
@template = nil
|
22
|
+
end
|
23
|
+
|
24
|
+
def template_path
|
25
|
+
@template_path ||= self.class.template_path
|
26
|
+
end
|
27
|
+
|
28
|
+
def template_path=(path)
|
29
|
+
@template_path = File.expand_path(path)
|
30
|
+
@template = nil
|
31
|
+
end
|
32
|
+
|
33
|
+
# Alias for `template_path`
|
34
|
+
def self.path
|
35
|
+
template_path
|
36
|
+
end
|
37
|
+
alias_method :path, :template_path
|
38
|
+
|
39
|
+
# Alias for `template_path`
|
40
|
+
def self.path=(path)
|
41
|
+
self.template_path = path
|
42
|
+
end
|
43
|
+
alias_method :path=, :template_path=
|
44
|
+
|
45
|
+
|
46
|
+
#
|
47
|
+
# Template Extension
|
48
|
+
#
|
49
|
+
|
50
|
+
# A Porthole template's default extension is 'porthole', but this can be changed.
|
51
|
+
|
52
|
+
def self.template_extension
|
53
|
+
@template_extension ||= inheritable_config_for :template_extension, 'porthole'
|
54
|
+
end
|
55
|
+
|
56
|
+
def self.template_extension=(template_extension)
|
57
|
+
@template_extension = template_extension
|
58
|
+
@template = nil
|
59
|
+
end
|
60
|
+
|
61
|
+
def template_extension
|
62
|
+
@template_extension ||= self.class.template_extension
|
63
|
+
end
|
64
|
+
|
65
|
+
def template_extension=(template_extension)
|
66
|
+
@template_extension = template_extension
|
67
|
+
@template = nil
|
68
|
+
end
|
69
|
+
|
70
|
+
|
71
|
+
#
|
72
|
+
# Template Name
|
73
|
+
#
|
74
|
+
|
75
|
+
# The template name is the Porthole template file without any
|
76
|
+
# extension or other information. Defaults to `class_name`.
|
77
|
+
#
|
78
|
+
# You may want to change this if your class is named Stat but you want
|
79
|
+
# to re-use another template.
|
80
|
+
#
|
81
|
+
# class Stat
|
82
|
+
# self.template_name = "graphs" # use graphs.porthole
|
83
|
+
# end
|
84
|
+
|
85
|
+
def self.template_name
|
86
|
+
@template_name || underscore
|
87
|
+
end
|
88
|
+
|
89
|
+
def self.template_name=(template_name)
|
90
|
+
@template_name = template_name
|
91
|
+
@template = nil
|
92
|
+
end
|
93
|
+
|
94
|
+
def template_name
|
95
|
+
@template_name ||= self.class.template_name
|
96
|
+
end
|
97
|
+
|
98
|
+
def template_name=(template_name)
|
99
|
+
@template_name = template_name
|
100
|
+
@template = nil
|
101
|
+
end
|
102
|
+
|
103
|
+
|
104
|
+
#
|
105
|
+
# Template File
|
106
|
+
#
|
107
|
+
|
108
|
+
# The template file is the absolute path of the file Porthole will
|
109
|
+
# use as its template. By default it's ./class_name.porthole
|
110
|
+
|
111
|
+
def self.template_file
|
112
|
+
@template_file || "#{path}/#{template_name}.#{template_extension}"
|
113
|
+
end
|
114
|
+
|
115
|
+
def self.template_file=(template_file)
|
116
|
+
@template_file = template_file
|
117
|
+
@template = nil
|
118
|
+
end
|
119
|
+
|
120
|
+
# The template file is the absolute path of the file Porthole will
|
121
|
+
# use as its template. By default it's ./class_name.porthole
|
122
|
+
def template_file
|
123
|
+
@template_file || "#{path}/#{template_name}.#{template_extension}"
|
124
|
+
end
|
125
|
+
|
126
|
+
def template_file=(template_file)
|
127
|
+
@template_file = template_file
|
128
|
+
@template = nil
|
129
|
+
end
|
130
|
+
|
131
|
+
|
132
|
+
#
|
133
|
+
# Template
|
134
|
+
#
|
135
|
+
|
136
|
+
# The template is the actual string Porthole uses as its template.
|
137
|
+
# There is a bit of magic here: what we get back is actually a
|
138
|
+
# Porthole::Template object, but you can still safely use `template=`
|
139
|
+
# with a string.
|
140
|
+
|
141
|
+
def self.template
|
142
|
+
@template ||= templateify(File.read(template_file))
|
143
|
+
end
|
144
|
+
|
145
|
+
def self.template=(template)
|
146
|
+
@template = templateify(template)
|
147
|
+
end
|
148
|
+
|
149
|
+
# The template can be set at the instance level.
|
150
|
+
def template
|
151
|
+
return @template if @template
|
152
|
+
|
153
|
+
# If they sent any instance-level options use that instead of the class's.
|
154
|
+
if @template_path || @template_extension || @template_name || @template_file
|
155
|
+
@template = templateify(File.read(template_file))
|
156
|
+
else
|
157
|
+
@template = self.class.template
|
158
|
+
end
|
159
|
+
end
|
160
|
+
|
161
|
+
def template=(template)
|
162
|
+
@template = templateify(template)
|
163
|
+
end
|
164
|
+
|
165
|
+
|
166
|
+
#
|
167
|
+
# Raise on context miss
|
168
|
+
#
|
169
|
+
|
170
|
+
# Should an exception be raised when we cannot find a corresponding method
|
171
|
+
# or key in the current context? By default this is false to emulate ctemplate's
|
172
|
+
# behavior, but it may be useful to enable when debugging or developing.
|
173
|
+
#
|
174
|
+
# If set to true and there is a context miss, `Porthole::ContextMiss` will
|
175
|
+
# be raised.
|
176
|
+
|
177
|
+
def self.raise_on_context_miss?
|
178
|
+
@raise_on_context_miss
|
179
|
+
end
|
180
|
+
|
181
|
+
def self.raise_on_context_miss=(boolean)
|
182
|
+
@raise_on_context_miss = boolean
|
183
|
+
end
|
184
|
+
|
185
|
+
# Instance level version of `Porthole.raise_on_context_miss?`
|
186
|
+
def raise_on_context_miss?
|
187
|
+
self.class.raise_on_context_miss? || @raise_on_context_miss
|
188
|
+
end
|
189
|
+
|
190
|
+
def raise_on_context_miss=(boolean)
|
191
|
+
@raise_on_context_miss = boolean
|
192
|
+
end
|
193
|
+
|
194
|
+
|
195
|
+
#
|
196
|
+
# View Namespace
|
197
|
+
#
|
198
|
+
|
199
|
+
# The constant under which Porthole will look for views when autoloading.
|
200
|
+
# By default the view namespace is `Object`, but it might be nice to set
|
201
|
+
# it to something like `Hurl::Views` if your app's main namespace is `Hurl`.
|
202
|
+
|
203
|
+
def self.view_namespace
|
204
|
+
@view_namespace ||= inheritable_config_for(:view_namespace, Object)
|
205
|
+
end
|
206
|
+
|
207
|
+
def self.view_namespace=(namespace)
|
208
|
+
@view_namespace = namespace
|
209
|
+
end
|
210
|
+
|
211
|
+
|
212
|
+
#
|
213
|
+
# View Path
|
214
|
+
#
|
215
|
+
|
216
|
+
# Porthole searches the view path for .rb files to require when asked to find a
|
217
|
+
# view class. Defaults to "."
|
218
|
+
|
219
|
+
def self.view_path
|
220
|
+
@view_path ||= inheritable_config_for(:view_path, '.')
|
221
|
+
end
|
222
|
+
|
223
|
+
def self.view_path=(path)
|
224
|
+
@view_path = path
|
225
|
+
end
|
226
|
+
end
|
@@ -0,0 +1,205 @@
|
|
1
|
+
require 'sinatra/base'
|
2
|
+
require 'porthole'
|
3
|
+
|
4
|
+
class Porthole
|
5
|
+
# Support for Porthole in your Sinatra app.
|
6
|
+
#
|
7
|
+
# require 'porthole/sinatra'
|
8
|
+
#
|
9
|
+
# class Hurl < Sinatra::Base
|
10
|
+
# register Porthole::Sinatra
|
11
|
+
#
|
12
|
+
# set :porthole, {
|
13
|
+
# # Should be the path to your .porthole template files.
|
14
|
+
# :templates => "path/to/porthole/templates",
|
15
|
+
#
|
16
|
+
# # Should be the path to your .rb Porthole view files.
|
17
|
+
# :views => "path/to/porthole/views",
|
18
|
+
#
|
19
|
+
# # This tells Porthole where to look for the Views module,
|
20
|
+
# # under which your View classes should live. By default it's
|
21
|
+
# # the class of your app - in this case `Hurl`. That is, for an :index
|
22
|
+
# # view Porthole will expect Hurl::Views::Index by default.
|
23
|
+
# # If our Sinatra::Base subclass was instead Hurl::App,
|
24
|
+
# # we'd want to do `set :namespace, Hurl::App`
|
25
|
+
# :namespace => Hurl
|
26
|
+
# }
|
27
|
+
#
|
28
|
+
# get '/stats' do
|
29
|
+
# porthole :stats
|
30
|
+
# end
|
31
|
+
# end
|
32
|
+
#
|
33
|
+
# As noted above, Porthole will look for `Hurl::Views::Index` when
|
34
|
+
# `porthole :index` is called.
|
35
|
+
#
|
36
|
+
# If no `Views::Stats` class exists Porthole will render the template
|
37
|
+
# file directly.
|
38
|
+
#
|
39
|
+
# You can indeed use layouts with this library. Where you'd normally
|
40
|
+
# <%= yield %> you instead {{{yield}}} - the body of the subview is
|
41
|
+
# set to the `yield` variable and made available to you.
|
42
|
+
#
|
43
|
+
# If you don't want the Sinatra extension to look up your view class,
|
44
|
+
# maybe because you've already loaded it or you're pulling it in from
|
45
|
+
# a gem, you can hand the `porthole` helper a Porthole subclass directly:
|
46
|
+
#
|
47
|
+
# # Assuming `class Omnigollum::Login < Porthole`
|
48
|
+
# get '/login' do
|
49
|
+
# @title = "Log In"
|
50
|
+
# require 'lib/omnigollum/views/login'
|
51
|
+
# porthole Omnigollum::Login
|
52
|
+
# end
|
53
|
+
#
|
54
|
+
module Sinatra
|
55
|
+
module Helpers
|
56
|
+
# Call this in your Sinatra routes.
|
57
|
+
def porthole(template, options={}, locals={})
|
58
|
+
# Locals can be passed as options under the :locals key.
|
59
|
+
locals.update(options.delete(:locals) || {})
|
60
|
+
|
61
|
+
# Grab any user-defined settings.
|
62
|
+
if settings.respond_to?(:porthole)
|
63
|
+
options = settings.send(:porthole).merge(options)
|
64
|
+
end
|
65
|
+
|
66
|
+
# If they aren't explicitly disabling layouts, try to find
|
67
|
+
# one.
|
68
|
+
if options[:layout] != false
|
69
|
+
# Let the user pass in a layout name.
|
70
|
+
layout_name = options[:layout]
|
71
|
+
|
72
|
+
# If all they said was `true` (or nothing), default to :layout.
|
73
|
+
layout_name = :layout if layout_name == true || !layout_name
|
74
|
+
|
75
|
+
# If they passed a layout name use that.
|
76
|
+
layout = porthole_class(layout_name, options)
|
77
|
+
|
78
|
+
# If it's just an anonymous subclass then don't bother, otherwise
|
79
|
+
# give us a layout instance.
|
80
|
+
if layout.name && layout.name.empty?
|
81
|
+
layout = nil
|
82
|
+
else
|
83
|
+
layout = layout.new
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
# If instead of a symbol they gave us a Porthole class,
|
88
|
+
# use that for rendering.
|
89
|
+
klass = template if template.is_a?(Class) && template < Porthole
|
90
|
+
|
91
|
+
# Find and cache the view class we want if we don't have
|
92
|
+
# one yet. This ensures the compiled template is cached,
|
93
|
+
# too - no looking up and compiling templates on each page
|
94
|
+
# load.
|
95
|
+
if klass.nil?
|
96
|
+
klass = porthole_class(template, options)
|
97
|
+
end
|
98
|
+
|
99
|
+
# Does the view subclass the layout? If so we'll use the
|
100
|
+
# view to render the layout so you can override layout
|
101
|
+
# methods in your view - tricky.
|
102
|
+
view_subclasses_layout = klass < layout.class if layout
|
103
|
+
|
104
|
+
# Create a new instance for playing with.
|
105
|
+
instance = klass.new
|
106
|
+
|
107
|
+
# Copy instance variables set in Sinatra to the view
|
108
|
+
instance_variables.each do |name|
|
109
|
+
instance.instance_variable_set(name, instance_variable_get(name))
|
110
|
+
end
|
111
|
+
|
112
|
+
# Render with locals.
|
113
|
+
rendered = instance.render(instance.template, locals)
|
114
|
+
|
115
|
+
# Now render the layout with the view we just rendered, if we
|
116
|
+
# need to.
|
117
|
+
if layout && view_subclasses_layout
|
118
|
+
rendered = instance.render(layout.template, :yield => rendered)
|
119
|
+
elsif layout
|
120
|
+
rendered = layout.render(layout.template, :yield => rendered)
|
121
|
+
end
|
122
|
+
|
123
|
+
# That's it.
|
124
|
+
rendered
|
125
|
+
end
|
126
|
+
|
127
|
+
# Returns a View class for a given template name.
|
128
|
+
def porthole_class(template, options = {})
|
129
|
+
@template_cache.fetch(:porthole, template) do
|
130
|
+
compile_porthole(template, options)
|
131
|
+
end
|
132
|
+
end
|
133
|
+
|
134
|
+
# Given a view name and settings, finds and prepares an
|
135
|
+
# appropriate view class for this view.
|
136
|
+
def compile_porthole(view, options = {})
|
137
|
+
options[:templates] ||= settings.views if settings.respond_to?(:views)
|
138
|
+
options[:namespace] ||= self.class
|
139
|
+
|
140
|
+
unless options[:namespace].to_s.include? 'Views'
|
141
|
+
options[:namespace] = options[:namespace].const_get(:Views)
|
142
|
+
end
|
143
|
+
|
144
|
+
factory = Class.new(Porthole) do
|
145
|
+
self.view_namespace = options[:namespace]
|
146
|
+
self.view_path = options[:views]
|
147
|
+
end
|
148
|
+
|
149
|
+
# If we were handed :"positions.atom" or some such as the
|
150
|
+
# template name, we need to remember the extension.
|
151
|
+
if view.to_s.include?('.')
|
152
|
+
view, ext = view.to_s.split('.')
|
153
|
+
end
|
154
|
+
|
155
|
+
# Try to find the view class for a given view, e.g.
|
156
|
+
# :view => Hurl::Views::Index.
|
157
|
+
klass = factory.view_class(view)
|
158
|
+
klass.view_namespace = options[:namespace]
|
159
|
+
klass.view_path = options[:views]
|
160
|
+
|
161
|
+
# If there is no view class, issue a warning and use the one
|
162
|
+
# we just generated to cache the compiled template.
|
163
|
+
if klass == Porthole
|
164
|
+
warn "No view class found for #{view} in #{factory.view_path}"
|
165
|
+
klass = factory
|
166
|
+
|
167
|
+
# If this is a generic view class make sure we set the
|
168
|
+
# template name as it was given. That is, an anonymous
|
169
|
+
# subclass of Porthole won't know how to find the
|
170
|
+
# "index.porthole" template unless we tell it to.
|
171
|
+
klass.template_name = view.to_s
|
172
|
+
elsif ext
|
173
|
+
# We got an ext (like "atom"), so look for an "Atom" class
|
174
|
+
# under the current View's namespace.
|
175
|
+
#
|
176
|
+
# So if our template was "positions.atom", try to find
|
177
|
+
# Positions::Atom.
|
178
|
+
if klass.const_defined?(ext_class = ext.capitalize)
|
179
|
+
# Found Positions::Atom - set it
|
180
|
+
klass = klass.const_get(ext_class)
|
181
|
+
else
|
182
|
+
# Didn't find Positions::Atom - create it by creating an
|
183
|
+
# anonymous subclass of Positions and setting that to
|
184
|
+
# Positions::Atom.
|
185
|
+
new_class = Class.new(klass)
|
186
|
+
new_class.template_name = "#{view}.#{ext}"
|
187
|
+
klass.const_set(ext_class, new_class)
|
188
|
+
klass = new_class
|
189
|
+
end
|
190
|
+
end
|
191
|
+
|
192
|
+
# Set the template path and return our class.
|
193
|
+
klass.template_path = options[:templates] if options[:templates]
|
194
|
+
klass
|
195
|
+
end
|
196
|
+
end
|
197
|
+
|
198
|
+
# Called when you `register Porthole::Sinatra` in your Sinatra app.
|
199
|
+
def self.registered(app)
|
200
|
+
app.helpers Porthole::Sinatra::Helpers
|
201
|
+
end
|
202
|
+
end
|
203
|
+
end
|
204
|
+
|
205
|
+
Sinatra.register Porthole::Sinatra
|
@@ -0,0 +1,58 @@
|
|
1
|
+
require 'cgi'
|
2
|
+
|
3
|
+
require 'porthole/parser'
|
4
|
+
require 'porthole/generator'
|
5
|
+
|
6
|
+
class Porthole
|
7
|
+
# A Template represents a Porthole template. It compiles and caches
|
8
|
+
# a raw string template into something usable.
|
9
|
+
#
|
10
|
+
# The idea is this: when handed a Porthole template, convert it into
|
11
|
+
# a Ruby string by transforming Porthole tags into interpolated
|
12
|
+
# Ruby.
|
13
|
+
#
|
14
|
+
# You shouldn't use this class directly, instead:
|
15
|
+
#
|
16
|
+
# >> Porthole.render(template, hash)
|
17
|
+
class Template
|
18
|
+
attr_reader :source
|
19
|
+
|
20
|
+
# Expects a Porthole template as a string along with a template
|
21
|
+
# path, which it uses to find partials.
|
22
|
+
def initialize(source)
|
23
|
+
@source = source
|
24
|
+
end
|
25
|
+
|
26
|
+
# Renders the `@source` Porthole template using the given
|
27
|
+
# `context`, which should be a simple hash keyed with symbols.
|
28
|
+
#
|
29
|
+
# The first time a template is rendered, this method is overriden
|
30
|
+
# and from then on it is "compiled". Subsequent calls will skip
|
31
|
+
# the compilation step and run the Ruby version of the template
|
32
|
+
# directly.
|
33
|
+
def render(context)
|
34
|
+
# Compile our Porthole template into a Ruby string
|
35
|
+
compiled = "def render(ctx) #{compile} end"
|
36
|
+
|
37
|
+
# Here we rewrite ourself with the interpolated Ruby version of
|
38
|
+
# our Porthole template so subsequent calls are very fast and
|
39
|
+
# can skip the compilation stage.
|
40
|
+
instance_eval(compiled, __FILE__, __LINE__ - 1)
|
41
|
+
|
42
|
+
# Call the newly rewritten version of #render
|
43
|
+
render(context)
|
44
|
+
end
|
45
|
+
|
46
|
+
# Does the dirty work of transforming a Porthole template into an
|
47
|
+
# interpolation-friendly Ruby string.
|
48
|
+
def compile(src = @source)
|
49
|
+
Generator.new.compile(tokens(src))
|
50
|
+
end
|
51
|
+
alias_method :to_s, :compile
|
52
|
+
|
53
|
+
# Returns an array of tokens for a given template.
|
54
|
+
def tokens(src = @source)
|
55
|
+
Parser.new.compile(src)
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|