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
data/bin/porthole
ADDED
@@ -0,0 +1,94 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'yaml'
|
4
|
+
require 'optparse'
|
5
|
+
|
6
|
+
require 'porthole'
|
7
|
+
require 'porthole/version'
|
8
|
+
|
9
|
+
class Porthole
|
10
|
+
class CLI
|
11
|
+
# Return a structure describing the options.
|
12
|
+
def self.parse_options(args)
|
13
|
+
opts = OptionParser.new do |opts|
|
14
|
+
opts.banner = "Usage: porthole [-c] [-t] [-r library] FILE ..."
|
15
|
+
|
16
|
+
opts.separator " "
|
17
|
+
|
18
|
+
opts.separator "Examples:"
|
19
|
+
opts.separator " $ porthole data.yml template.porthole"
|
20
|
+
opts.separator " $ cat data.yml | porthole - template.porthole"
|
21
|
+
opts.separator " $ porthole -c template.porthole"
|
22
|
+
|
23
|
+
opts.separator " "
|
24
|
+
|
25
|
+
opts.separator " See porthole(1) or " +
|
26
|
+
"http://porthole.github.com/porthole.1.html"
|
27
|
+
opts.separator " for more details."
|
28
|
+
|
29
|
+
opts.separator " "
|
30
|
+
opts.separator "Options:"
|
31
|
+
|
32
|
+
opts.on("-c", "--compile FILE",
|
33
|
+
"Print the compiled Ruby for a given template.") do |file|
|
34
|
+
puts Porthole::Template.new(File.read(file)).compile
|
35
|
+
exit
|
36
|
+
end
|
37
|
+
|
38
|
+
opts.on("-t", "--tokens FILE",
|
39
|
+
"Print the tokenized form of a given template.") do |file|
|
40
|
+
require 'pp'
|
41
|
+
pp Porthole::Template.new(File.read(file)).tokens
|
42
|
+
exit
|
43
|
+
end
|
44
|
+
|
45
|
+
opts.on('-r', '--require LIB', 'Require a Ruby library before running.') do |lib|
|
46
|
+
require lib
|
47
|
+
end
|
48
|
+
|
49
|
+
opts.separator "Common Options:"
|
50
|
+
|
51
|
+
opts.on("-v", "--version", "Print the version") do |v|
|
52
|
+
puts "Porthole v#{Porthole::Version}"
|
53
|
+
exit
|
54
|
+
end
|
55
|
+
|
56
|
+
opts.on_tail("-h", "--help", "Show this message") do
|
57
|
+
puts opts
|
58
|
+
exit
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
opts.separator ""
|
63
|
+
|
64
|
+
opts.parse!(args)
|
65
|
+
end
|
66
|
+
|
67
|
+
# Does the dirty work of reading files from STDIN and the command
|
68
|
+
# line then processing them. The meat of this script, if you will.
|
69
|
+
def self.process_files(input_stream)
|
70
|
+
doc = input_stream.read
|
71
|
+
|
72
|
+
if doc =~ /^(\s*---(.+)---\s*)/m
|
73
|
+
yaml = $2.strip
|
74
|
+
template = doc.sub($1, '')
|
75
|
+
|
76
|
+
YAML.each_document(yaml) do |data|
|
77
|
+
puts Porthole.render(template, data)
|
78
|
+
end
|
79
|
+
else
|
80
|
+
puts Porthole.render(doc)
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
# Help is the default.
|
87
|
+
ARGV << '-h' if ARGV.empty? && $stdin.tty?
|
88
|
+
|
89
|
+
# Process options
|
90
|
+
Porthole::CLI.parse_options(ARGV) if $stdin.tty?
|
91
|
+
|
92
|
+
# Still here - process ARGF
|
93
|
+
Porthole::CLI.process_files(ARGF)
|
94
|
+
|
data/lib/porthole.rb
ADDED
@@ -0,0 +1,304 @@
|
|
1
|
+
require 'porthole/template'
|
2
|
+
require 'porthole/context'
|
3
|
+
require 'porthole/settings'
|
4
|
+
|
5
|
+
# Porthole is the base class from which your Porthole subclasses
|
6
|
+
# should inherit (though it can be used on its own).
|
7
|
+
#
|
8
|
+
# The typical Porthole workflow is as follows:
|
9
|
+
#
|
10
|
+
# * Create a Porthole subclass: class Stats < Porthole
|
11
|
+
# * Create a template: stats.porthole
|
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 Porthole 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
|
+
# Porthole uses to find templates.
|
25
|
+
#
|
26
|
+
# Here are the available options:
|
27
|
+
#
|
28
|
+
# * template_path
|
29
|
+
#
|
30
|
+
# The `template_path` setting determines the path Porthole 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 Porthole subclass `Stats` will try to
|
34
|
+
# load /usr/local/templates/stats.porthole
|
35
|
+
#
|
36
|
+
# * template_extension
|
37
|
+
#
|
38
|
+
# The `template_extension` is the extension Porthole uses when looking
|
39
|
+
# for template files. By default it is "porthole"
|
40
|
+
#
|
41
|
+
# * template_file
|
42
|
+
#
|
43
|
+
# You can tell Porthole exactly which template to use with this
|
44
|
+
# setting. It can be a relative or absolute path.
|
45
|
+
#
|
46
|
+
# * template
|
47
|
+
#
|
48
|
+
# Sometimes you want Porthole to render a string, not a file. In those
|
49
|
+
# cases you may set the `template` setting. For example:
|
50
|
+
#
|
51
|
+
# >> Porthole.render("Hello {{planet}}", :planet => "World!")
|
52
|
+
# => "Hello World!"
|
53
|
+
#
|
54
|
+
# The `template` setting is also available on instances.
|
55
|
+
#
|
56
|
+
# view = Porthole.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 Porthole plugins for web frameworks or
|
64
|
+
# other libraries, Porthole will attempt to load view classes (i.e. Porthole
|
65
|
+
# subclasses) using the `view_class` class method. The `view_namespace` tells
|
66
|
+
# Porthole 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 Porthole where to look
|
71
|
+
# for files containing view classes when using the `view_class` method.
|
72
|
+
#
|
73
|
+
class Porthole
|
74
|
+
|
75
|
+
#
|
76
|
+
# Public API
|
77
|
+
#
|
78
|
+
|
79
|
+
# Instantiates an instance of this class and calls `render` with
|
80
|
+
# the passed args.
|
81
|
+
#
|
82
|
+
# Returns a rendered String version of a template
|
83
|
+
def self.render(*args)
|
84
|
+
new.render(*args)
|
85
|
+
end
|
86
|
+
|
87
|
+
class << self
|
88
|
+
alias_method :to_html, :render
|
89
|
+
alias_method :to_text, :render
|
90
|
+
end
|
91
|
+
|
92
|
+
# Parses our fancy pants template file and returns normal file with
|
93
|
+
# all special {{tags}} and {{#sections}}replaced{{/sections}}.
|
94
|
+
#
|
95
|
+
# data - A String template or a Hash context. If a Hash is given,
|
96
|
+
# we'll try to figure out the template from the class.
|
97
|
+
# ctx - A Hash context if `data` is a String template.
|
98
|
+
#
|
99
|
+
# Examples
|
100
|
+
#
|
101
|
+
# @view.render("Hi {{thing}}!", :thing => :world)
|
102
|
+
#
|
103
|
+
# View.template = "Hi {{thing}}!"
|
104
|
+
# @view = View.new
|
105
|
+
# @view.render(:thing => :world)
|
106
|
+
#
|
107
|
+
# Returns a rendered String version of a template
|
108
|
+
def render(data = template, ctx = {})
|
109
|
+
if data.is_a? Hash
|
110
|
+
ctx = data
|
111
|
+
tpl = templateify(template)
|
112
|
+
elsif data.is_a? Symbol
|
113
|
+
self.template_name = data
|
114
|
+
tpl = templateify(template)
|
115
|
+
else
|
116
|
+
tpl = templateify(data)
|
117
|
+
end
|
118
|
+
|
119
|
+
return tpl.render(context) if ctx == {}
|
120
|
+
|
121
|
+
begin
|
122
|
+
context.push(ctx)
|
123
|
+
tpl.render(context)
|
124
|
+
ensure
|
125
|
+
context.pop
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
alias_method :to_html, :render
|
130
|
+
alias_method :to_text, :render
|
131
|
+
|
132
|
+
# Context accessors.
|
133
|
+
#
|
134
|
+
# view = Porthole.new
|
135
|
+
# view[:name] = "Jon"
|
136
|
+
# view.template = "Hi, {{name}}!"
|
137
|
+
# view.render # => "Hi, Jon!"
|
138
|
+
def [](key)
|
139
|
+
context[key.to_sym]
|
140
|
+
end
|
141
|
+
|
142
|
+
def []=(key, value)
|
143
|
+
context[key.to_sym] = value
|
144
|
+
end
|
145
|
+
|
146
|
+
# A helper method which gives access to the context at a given time.
|
147
|
+
# Kind of a hack for now, but useful when you're in an iterating section
|
148
|
+
# and want access to the hash currently being iterated over.
|
149
|
+
def context
|
150
|
+
@context ||= Context.new(self)
|
151
|
+
end
|
152
|
+
|
153
|
+
# Given a file name and an optional context, attempts to load and
|
154
|
+
# render the file as a template.
|
155
|
+
def self.render_file(name, context = {})
|
156
|
+
render(partial(name), context)
|
157
|
+
end
|
158
|
+
|
159
|
+
# Given a file name and an optional context, attempts to load and
|
160
|
+
# render the file as a template.
|
161
|
+
def render_file(name, context = {})
|
162
|
+
self.class.render_file(name, context)
|
163
|
+
end
|
164
|
+
|
165
|
+
# Given a name, attempts to read a file and return the contents as a
|
166
|
+
# string. The file is not rendered, so it might contain
|
167
|
+
# {{portholes}}.
|
168
|
+
#
|
169
|
+
# Call `render` if you need to process it.
|
170
|
+
def self.partial(name)
|
171
|
+
File.read("#{template_path}/#{name}.#{template_extension}")
|
172
|
+
end
|
173
|
+
|
174
|
+
# Override this in your subclass if you want to do fun things like
|
175
|
+
# reading templates from a database. It will be rendered by the
|
176
|
+
# context, so all you need to do is return a string.
|
177
|
+
def partial(name)
|
178
|
+
self.class.partial(name)
|
179
|
+
end
|
180
|
+
|
181
|
+
# Override this to provide custom escaping.
|
182
|
+
#
|
183
|
+
# class PersonView < Porthole
|
184
|
+
# def escapeHTML(str)
|
185
|
+
# my_html_escape_method(str)
|
186
|
+
# end
|
187
|
+
# end
|
188
|
+
#
|
189
|
+
# Returns a String
|
190
|
+
def escapeHTML(str)
|
191
|
+
CGI.escapeHTML(str)
|
192
|
+
end
|
193
|
+
|
194
|
+
|
195
|
+
#
|
196
|
+
# Private API
|
197
|
+
#
|
198
|
+
|
199
|
+
# When given a symbol or string representing a class, will try to produce an
|
200
|
+
# appropriate view class.
|
201
|
+
# e.g.
|
202
|
+
# Porthole.view_namespace = Hurl::Views
|
203
|
+
# Porthole.view_class(:Partial) # => Hurl::Views::Partial
|
204
|
+
def self.view_class(name)
|
205
|
+
if name != classify(name.to_s)
|
206
|
+
name = classify(name.to_s)
|
207
|
+
end
|
208
|
+
|
209
|
+
# Emptiness begets emptiness.
|
210
|
+
if name.to_s == ''
|
211
|
+
return Porthole
|
212
|
+
end
|
213
|
+
|
214
|
+
file_name = underscore(name)
|
215
|
+
name = "#{view_namespace}::#{name}"
|
216
|
+
|
217
|
+
if const = const_get!(name)
|
218
|
+
const
|
219
|
+
elsif File.exists?(file = "#{view_path}/#{file_name}.rb")
|
220
|
+
require "#{file}".chomp('.rb')
|
221
|
+
const_get!(name) || Porthole
|
222
|
+
else
|
223
|
+
Porthole
|
224
|
+
end
|
225
|
+
end
|
226
|
+
|
227
|
+
# Supercharged version of Module#const_get.
|
228
|
+
#
|
229
|
+
# Always searches under Object and can find constants by their full name,
|
230
|
+
# e.g. Porthole::Views::Index
|
231
|
+
#
|
232
|
+
# name - The full constant name to find.
|
233
|
+
#
|
234
|
+
# Returns the constant if found
|
235
|
+
# Returns nil if nothing is found
|
236
|
+
def self.const_get!(name)
|
237
|
+
name.split('::').inject(Object) do |klass, cname|
|
238
|
+
klass.const_get(cname)
|
239
|
+
end
|
240
|
+
rescue NameError
|
241
|
+
nil
|
242
|
+
end
|
243
|
+
|
244
|
+
# Has this template already been compiled? Compilation is somewhat
|
245
|
+
# expensive so it may be useful to check this before attempting it.
|
246
|
+
def self.compiled?
|
247
|
+
@template.is_a? Template
|
248
|
+
end
|
249
|
+
|
250
|
+
# Has this instance or its class already compiled a template?
|
251
|
+
def compiled?
|
252
|
+
(@template && @template.is_a?(Template)) || self.class.compiled?
|
253
|
+
end
|
254
|
+
|
255
|
+
# template_partial => TemplatePartial
|
256
|
+
# template/partial => Template::Partial
|
257
|
+
def self.classify(underscored)
|
258
|
+
underscored.split('/').map do |namespace|
|
259
|
+
namespace.split(/[-_]/).map do |part|
|
260
|
+
part[0] = part[0].chr.upcase; part
|
261
|
+
end.join
|
262
|
+
end.join('::')
|
263
|
+
end
|
264
|
+
|
265
|
+
# TemplatePartial => template_partial
|
266
|
+
# Template::Partial => template/partial
|
267
|
+
# Takes a string but defaults to using the current class' name.
|
268
|
+
def self.underscore(classified = name)
|
269
|
+
classified = name if classified.to_s.empty?
|
270
|
+
classified = superclass.name if classified.to_s.empty?
|
271
|
+
|
272
|
+
string = classified.dup.split("#{view_namespace}::").last
|
273
|
+
|
274
|
+
string.split('::').map do |part|
|
275
|
+
part[0] = part[0].chr.downcase
|
276
|
+
part.gsub(/[A-Z]/) { |s| "_#{s.downcase}"}
|
277
|
+
end.join('/')
|
278
|
+
end
|
279
|
+
|
280
|
+
# Turns a string into a Porthole::Template. If passed a Template,
|
281
|
+
# returns it.
|
282
|
+
def self.templateify(obj)
|
283
|
+
if obj.is_a?(Template)
|
284
|
+
obj
|
285
|
+
else
|
286
|
+
Template.new(obj.to_s)
|
287
|
+
end
|
288
|
+
end
|
289
|
+
|
290
|
+
def templateify(obj)
|
291
|
+
self.class.templateify(obj)
|
292
|
+
end
|
293
|
+
|
294
|
+
# Return the value of the configuration setting on the superclass, or return
|
295
|
+
# the default.
|
296
|
+
#
|
297
|
+
# attr_name - Symbol name of the attribute. It should match the instance variable.
|
298
|
+
# default - Default value to use if the superclass does not respond.
|
299
|
+
#
|
300
|
+
# Returns the inherited or default configuration setting.
|
301
|
+
def self.inheritable_config_for(attr_name, default)
|
302
|
+
superclass.respond_to?(attr_name) ? superclass.send(attr_name) : default
|
303
|
+
end
|
304
|
+
end
|
@@ -0,0 +1,142 @@
|
|
1
|
+
class Porthole
|
2
|
+
# A ContextMiss is raised whenever a tag's target can not be found
|
3
|
+
# in the current context if `Porthole#raise_on_context_miss?` is
|
4
|
+
# set to true.
|
5
|
+
#
|
6
|
+
# For example, if your View class does not respond to `music` but
|
7
|
+
# your template contains a `{{music}}` tag this exception will be raised.
|
8
|
+
#
|
9
|
+
# By default it is not raised. See Porthole.raise_on_context_miss.
|
10
|
+
class ContextMiss < RuntimeError; end
|
11
|
+
|
12
|
+
# A Context represents the context which a Porthole template is
|
13
|
+
# executed within. All Porthole tags reference keys in the Context.
|
14
|
+
class Context
|
15
|
+
# Expect to be passed an instance of `Porthole`.
|
16
|
+
def initialize(porthole)
|
17
|
+
@stack = [porthole]
|
18
|
+
end
|
19
|
+
|
20
|
+
# A {{>partial}} tag translates into a call to the context's
|
21
|
+
# `partial` method, which would be this sucker right here.
|
22
|
+
#
|
23
|
+
# If the Porthole view handling the rendering (e.g. the view
|
24
|
+
# representing your profile page or some other template) responds
|
25
|
+
# to `partial`, we call it and render the result.
|
26
|
+
def partial(name, indentation = '')
|
27
|
+
# Look for the first Porthole in the stack.
|
28
|
+
porthole = porthole_in_stack
|
29
|
+
|
30
|
+
# Indent the partial template by the given indentation.
|
31
|
+
part = porthole.partial(name).to_s.gsub(/^/, indentation)
|
32
|
+
|
33
|
+
# Call the Porthole's `partial` method and render the result.
|
34
|
+
result = porthole.render(part, self)
|
35
|
+
end
|
36
|
+
|
37
|
+
# Find the first Porthole in the stack. If we're being rendered
|
38
|
+
# inside a Porthole object as a context, we'll use that one.
|
39
|
+
def porthole_in_stack
|
40
|
+
@stack.detect { |frame| frame.is_a?(Porthole) }
|
41
|
+
end
|
42
|
+
|
43
|
+
# Allows customization of how Porthole escapes things.
|
44
|
+
#
|
45
|
+
# Returns a String.
|
46
|
+
def escapeHTML(str)
|
47
|
+
porthole_in_stack.escapeHTML(str)
|
48
|
+
end
|
49
|
+
|
50
|
+
# Adds a new object to the context's internal stack.
|
51
|
+
#
|
52
|
+
# Returns the Context.
|
53
|
+
def push(new)
|
54
|
+
@stack.unshift(new)
|
55
|
+
self
|
56
|
+
end
|
57
|
+
alias_method :update, :push
|
58
|
+
|
59
|
+
# Removes the most recently added object from the context's
|
60
|
+
# internal stack.
|
61
|
+
#
|
62
|
+
# Returns the Context.
|
63
|
+
def pop
|
64
|
+
@stack.shift
|
65
|
+
self
|
66
|
+
end
|
67
|
+
|
68
|
+
# Can be used to add a value to the context in a hash-like way.
|
69
|
+
#
|
70
|
+
# context[:name] = "Chris"
|
71
|
+
def []=(name, value)
|
72
|
+
push(name => value)
|
73
|
+
end
|
74
|
+
|
75
|
+
# Alias for `fetch`.
|
76
|
+
def [](name)
|
77
|
+
fetch(name, nil)
|
78
|
+
end
|
79
|
+
|
80
|
+
# Do we know about a particular key? In other words, will calling
|
81
|
+
# `context[key]` give us a result that was set. Basically.
|
82
|
+
def has_key?(key)
|
83
|
+
!!fetch(key)
|
84
|
+
rescue ContextMiss
|
85
|
+
false
|
86
|
+
end
|
87
|
+
|
88
|
+
# Similar to Hash#fetch, finds a value by `name` in the context's
|
89
|
+
# stack. You may specify the default return value by passing a
|
90
|
+
# second parameter.
|
91
|
+
#
|
92
|
+
# If no second parameter is passed (or raise_on_context_miss is
|
93
|
+
# set to true), will raise a ContextMiss exception on miss.
|
94
|
+
def fetch(name, default = :__raise)
|
95
|
+
@stack.each do |frame|
|
96
|
+
# Prevent infinite recursion.
|
97
|
+
next if frame == self
|
98
|
+
|
99
|
+
value = find(frame, name, :__missing)
|
100
|
+
if value != :__missing
|
101
|
+
return value
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
if default == :__raise || porthole_in_stack.raise_on_context_miss?
|
106
|
+
raise ContextMiss.new("Can't find #{name} in #{@stack.inspect}")
|
107
|
+
else
|
108
|
+
default
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
# Finds a key in an object, using whatever method is most
|
113
|
+
# appropriate. If the object is a hash, does a simple hash lookup.
|
114
|
+
# If it's an object that responds to the key as a method call,
|
115
|
+
# invokes that method. You get the idea.
|
116
|
+
#
|
117
|
+
# obj - The object to perform the lookup on.
|
118
|
+
# key - The key whose value you want.
|
119
|
+
# default - An optional default value, to return if the
|
120
|
+
# key is not found.
|
121
|
+
#
|
122
|
+
# Returns the value of key in obj if it is found and default otherwise.
|
123
|
+
def find(obj, key, default = nil)
|
124
|
+
hash = obj.respond_to?(:has_key?)
|
125
|
+
|
126
|
+
if hash && obj.has_key?(key)
|
127
|
+
obj[key]
|
128
|
+
elsif hash && obj.has_key?(key.to_s)
|
129
|
+
obj[key.to_s]
|
130
|
+
elsif !hash && obj.respond_to?(key)
|
131
|
+
meth = obj.method(key) rescue proc { obj.send(key) }
|
132
|
+
if meth.arity == 1
|
133
|
+
meth.to_proc
|
134
|
+
else
|
135
|
+
meth[]
|
136
|
+
end
|
137
|
+
else
|
138
|
+
default
|
139
|
+
end
|
140
|
+
end
|
141
|
+
end
|
142
|
+
end
|