porthole 0.99.4
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/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
|