porthole 0.99.4

Sign up to get free protection for your applications and to get access to all the features.
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