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.
Files changed (70) hide show
  1. data/LICENSE +20 -0
  2. data/README.md +415 -0
  3. data/Rakefile +89 -0
  4. data/bin/porthole +94 -0
  5. data/lib/porthole.rb +304 -0
  6. data/lib/porthole/context.rb +142 -0
  7. data/lib/porthole/generator.rb +195 -0
  8. data/lib/porthole/parser.rb +263 -0
  9. data/lib/porthole/settings.rb +226 -0
  10. data/lib/porthole/sinatra.rb +205 -0
  11. data/lib/porthole/template.rb +58 -0
  12. data/lib/porthole/version.rb +3 -0
  13. data/lib/rack/bug/panels/mustache_panel.rb +81 -0
  14. data/lib/rack/bug/panels/mustache_panel/mustache_extension.rb +27 -0
  15. data/lib/rack/bug/panels/mustache_panel/view.mustache +46 -0
  16. data/man/porthole.1 +165 -0
  17. data/man/porthole.1.html +213 -0
  18. data/man/porthole.1.ron +127 -0
  19. data/man/porthole.5 +539 -0
  20. data/man/porthole.5.html +422 -0
  21. data/man/porthole.5.ron +324 -0
  22. data/test/autoloading_test.rb +56 -0
  23. data/test/fixtures/comments.porthole +1 -0
  24. data/test/fixtures/comments.rb +14 -0
  25. data/test/fixtures/complex_view.porthole +17 -0
  26. data/test/fixtures/complex_view.rb +34 -0
  27. data/test/fixtures/crazy_recursive.porthole +9 -0
  28. data/test/fixtures/crazy_recursive.rb +31 -0
  29. data/test/fixtures/delimiters.porthole +8 -0
  30. data/test/fixtures/delimiters.rb +23 -0
  31. data/test/fixtures/dot_notation.porthole +10 -0
  32. data/test/fixtures/dot_notation.rb +25 -0
  33. data/test/fixtures/double_section.porthole +7 -0
  34. data/test/fixtures/double_section.rb +14 -0
  35. data/test/fixtures/escaped.porthole +1 -0
  36. data/test/fixtures/escaped.rb +14 -0
  37. data/test/fixtures/inner_partial.porthole +1 -0
  38. data/test/fixtures/inner_partial.txt +1 -0
  39. data/test/fixtures/inverted_section.porthole +7 -0
  40. data/test/fixtures/inverted_section.rb +14 -0
  41. data/test/fixtures/lambda.porthole +7 -0
  42. data/test/fixtures/lambda.rb +31 -0
  43. data/test/fixtures/method_missing.rb +19 -0
  44. data/test/fixtures/namespaced.porthole +1 -0
  45. data/test/fixtures/namespaced.rb +25 -0
  46. data/test/fixtures/nested_objects.porthole +17 -0
  47. data/test/fixtures/nested_objects.rb +35 -0
  48. data/test/fixtures/node.porthole +8 -0
  49. data/test/fixtures/partial_with_module.porthole +4 -0
  50. data/test/fixtures/partial_with_module.rb +37 -0
  51. data/test/fixtures/passenger.conf +5 -0
  52. data/test/fixtures/passenger.rb +27 -0
  53. data/test/fixtures/recursive.porthole +4 -0
  54. data/test/fixtures/recursive.rb +14 -0
  55. data/test/fixtures/simple.porthole +5 -0
  56. data/test/fixtures/simple.rb +26 -0
  57. data/test/fixtures/template_partial.porthole +2 -0
  58. data/test/fixtures/template_partial.rb +18 -0
  59. data/test/fixtures/template_partial.txt +4 -0
  60. data/test/fixtures/unescaped.porthole +1 -0
  61. data/test/fixtures/unescaped.rb +14 -0
  62. data/test/fixtures/utf8.porthole +3 -0
  63. data/test/fixtures/utf8_partial.porthole +1 -0
  64. data/test/helper.rb +7 -0
  65. data/test/parser_test.rb +78 -0
  66. data/test/partial_test.rb +168 -0
  67. data/test/porthole_test.rb +677 -0
  68. data/test/spec_test.rb +68 -0
  69. data/test/template_test.rb +20 -0
  70. 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