inversion 0.12.3 → 0.14.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- checksums.yaml.gz.sig +2 -1
- data.tar.gz.sig +0 -0
- data/ChangeLog +305 -9
- data/Examples.rdoc +134 -0
- data/GettingStarted.rdoc +44 -0
- data/Guide.rdoc +47 -0
- data/History.rdoc +15 -0
- data/Manifest.txt +7 -2
- data/README.rdoc +9 -10
- data/Rakefile +23 -10
- data/Tags.rdoc +561 -0
- data/lib/inversion.rb +2 -2
- data/lib/inversion/renderstate.rb +46 -11
- data/lib/inversion/template.rb +85 -7
- data/lib/inversion/template/attrtag.rb +1 -1
- data/lib/inversion/template/begintag.rb +8 -8
- data/lib/inversion/template/fragmenttag.rb +60 -0
- data/lib/inversion/template/rescuetag.rb +1 -1
- data/spec/{lib/helpers.rb → helpers.rb} +7 -30
- data/spec/inversion/mixins_spec.rb +55 -65
- data/spec/inversion/monkeypatches_spec.rb +2 -12
- data/spec/inversion/parser_spec.rb +34 -44
- data/spec/inversion/renderstate_spec.rb +123 -69
- data/spec/inversion/sinatra_spec.rb +6 -19
- data/spec/inversion/template/attrtag_spec.rb +56 -76
- data/spec/inversion/template/begintag_spec.rb +24 -41
- data/spec/inversion/template/calltag_spec.rb +1 -18
- data/spec/inversion/template/codetag_spec.rb +6 -24
- data/spec/inversion/template/commenttag_spec.rb +9 -27
- data/spec/inversion/template/configtag_spec.rb +5 -16
- data/spec/inversion/template/containertag_spec.rb +4 -21
- data/spec/inversion/template/defaulttag_spec.rb +6 -23
- data/spec/inversion/template/elsetag_spec.rb +9 -26
- data/spec/inversion/template/elsiftag_spec.rb +7 -24
- data/spec/inversion/template/endtag_spec.rb +6 -23
- data/spec/inversion/template/escapetag_spec.rb +10 -25
- data/spec/inversion/template/fortag_spec.rb +20 -37
- data/spec/inversion/template/fragmenttag_spec.rb +40 -0
- data/spec/inversion/template/iftag_spec.rb +23 -40
- data/spec/inversion/template/importtag_spec.rb +8 -25
- data/spec/inversion/template/includetag_spec.rb +27 -42
- data/spec/inversion/template/node_spec.rb +6 -15
- data/spec/inversion/template/pptag_spec.rb +10 -23
- data/spec/inversion/template/publishtag_spec.rb +4 -21
- data/spec/inversion/template/rescuetag_spec.rb +12 -29
- data/spec/inversion/template/subscribetag_spec.rb +8 -25
- data/spec/inversion/template/tag_spec.rb +24 -37
- data/spec/inversion/template/textnode_spec.rb +8 -24
- data/spec/inversion/template/timedeltatag_spec.rb +31 -43
- data/spec/inversion/template/unlesstag_spec.rb +7 -24
- data/spec/inversion/template/uriencodetag_spec.rb +6 -23
- data/spec/inversion/template/yieldtag_spec.rb +3 -20
- data/spec/inversion/template_spec.rb +155 -108
- data/spec/inversion/tilt_spec.rb +7 -16
- data/spec/inversion_spec.rb +7 -22
- metadata +63 -40
- metadata.gz.sig +0 -0
- data/spec/lib/constants.rb +0 -9
data/lib/inversion.rb
CHANGED
@@ -26,10 +26,10 @@ module Inversion
|
|
26
26
|
warn ">>> Inversion requires Ruby 1.9.2 or later. <<<" if RUBY_VERSION < '1.9.2'
|
27
27
|
|
28
28
|
# Library version constant
|
29
|
-
VERSION = '0.
|
29
|
+
VERSION = '0.14.0'
|
30
30
|
|
31
31
|
# Version-control revision constant
|
32
|
-
REVISION = %q$Revision:
|
32
|
+
REVISION = %q$Revision: 9bb165feaf57 $
|
33
33
|
|
34
34
|
|
35
35
|
### Get the Inversion version.
|
@@ -21,8 +21,9 @@ class Inversion::RenderState
|
|
21
21
|
|
22
22
|
### Create a new RenderState::Scope with its initial tag locals set to
|
23
23
|
### +locals+.
|
24
|
-
def initialize( locals={} )
|
24
|
+
def initialize( locals={}, fragments={} )
|
25
25
|
@locals = locals
|
26
|
+
@fragments = fragments
|
26
27
|
end
|
27
28
|
|
28
29
|
|
@@ -41,7 +42,7 @@ class Inversion::RenderState
|
|
41
42
|
### Return a copy of the receiving Scope merged with the given +values+,
|
42
43
|
### which can be either another Scope or a Hash.
|
43
44
|
def +( values )
|
44
|
-
return Scope.new(
|
45
|
+
return Scope.new( self.__locals__.merge(values), self.__fragments__ )
|
45
46
|
end
|
46
47
|
|
47
48
|
|
@@ -52,6 +53,12 @@ class Inversion::RenderState
|
|
52
53
|
alias_method :to_hash, :__locals__
|
53
54
|
|
54
55
|
|
56
|
+
### Returns the Hash of rendered fragments that belong to this scope.
|
57
|
+
def __fragments__
|
58
|
+
return @fragments
|
59
|
+
end
|
60
|
+
|
61
|
+
|
55
62
|
#########
|
56
63
|
protected
|
57
64
|
#########
|
@@ -60,7 +67,7 @@ class Inversion::RenderState
|
|
60
67
|
### and map them into values from the Scope's locals.
|
61
68
|
def method_missing( sym, *args, &block )
|
62
69
|
return super unless sym =~ /^\w+$/
|
63
|
-
@locals[ sym ]
|
70
|
+
return @locals[ sym ].nil? ? @fragments[ sym ] : @locals[ sym ]
|
64
71
|
end
|
65
72
|
|
66
73
|
end # class Scope
|
@@ -104,6 +111,7 @@ class Inversion::RenderState
|
|
104
111
|
# as a Symbol
|
105
112
|
@subscriptions = Hash.new {|hsh, k| hsh[k] = [] } # Auto-vivify to an Array
|
106
113
|
@published_nodes = Hash.new {|hsh, k| hsh[k] = [] }
|
114
|
+
@fragments = Hash.new {|hsh, k| hsh[k] = [] }
|
107
115
|
|
108
116
|
end
|
109
117
|
|
@@ -127,6 +135,9 @@ class Inversion::RenderState
|
|
127
135
|
# Published nodes, keyed by subscription
|
128
136
|
attr_reader :published_nodes
|
129
137
|
|
138
|
+
# Fragment nodes, keyed by fragment name
|
139
|
+
attr_reader :fragments
|
140
|
+
|
130
141
|
# The stack of rendered output destinations, most-recent last.
|
131
142
|
attr_reader :destinations
|
132
143
|
|
@@ -296,14 +307,7 @@ class Inversion::RenderState
|
|
296
307
|
|
297
308
|
### Turn the rendered node structure into the final rendered String.
|
298
309
|
def to_s
|
299
|
-
|
300
|
-
|
301
|
-
if enc = self.options[ :encoding ]
|
302
|
-
self.log.debug "Encoding rendered template parts to %s" % [ enc ]
|
303
|
-
strings.map! {|str| str.encode(enc, invalid: :replace, undef: :replace) }
|
304
|
-
end
|
305
|
-
|
306
|
-
return strings.join
|
310
|
+
return self.stringify_nodes( @output )
|
307
311
|
end
|
308
312
|
|
309
313
|
|
@@ -337,6 +341,23 @@ class Inversion::RenderState
|
|
337
341
|
end
|
338
342
|
|
339
343
|
|
344
|
+
### Add one or more rendered +nodes+ to the state as a reusable fragment associated
|
345
|
+
### with the specified +name+.
|
346
|
+
def add_fragment( name, *nodes )
|
347
|
+
nodes.flatten!
|
348
|
+
self.fragments[ name.to_sym ] = nodes
|
349
|
+
self.scope.__fragments__[ name.to_sym ] = nodes
|
350
|
+
end
|
351
|
+
|
352
|
+
|
353
|
+
### Return the current fragments Hash rendered as Strings.
|
354
|
+
def rendered_fragments
|
355
|
+
return self.fragments.each_with_object( {} ) do |(key, nodes), accum|
|
356
|
+
accum[ key ] = self.stringify_nodes( nodes )
|
357
|
+
end
|
358
|
+
end
|
359
|
+
|
360
|
+
|
340
361
|
### Handle an +exception+ that was raised while appending a node by calling the
|
341
362
|
### #errhandler.
|
342
363
|
def handle_render_error( node, exception )
|
@@ -454,6 +475,20 @@ class Inversion::RenderState
|
|
454
475
|
end
|
455
476
|
|
456
477
|
|
478
|
+
### Return the given +nodes+ as a String in the configured encoding.
|
479
|
+
def stringify_nodes( nodes )
|
480
|
+
self.log.debug "Rendering nodes: %p" % [ nodes ]
|
481
|
+
strings = nodes.flatten.map( &:to_s )
|
482
|
+
|
483
|
+
if enc = self.options[ :encoding ]
|
484
|
+
self.log.debug "Encoding rendered template parts to %s" % [ enc ]
|
485
|
+
strings.map! {|str| str.encode(enc, invalid: :replace, undef: :replace) }
|
486
|
+
end
|
487
|
+
|
488
|
+
return strings.join
|
489
|
+
end
|
490
|
+
|
491
|
+
|
457
492
|
### Handle attribute methods.
|
458
493
|
def method_missing( sym, *args, &block )
|
459
494
|
return super unless sym.to_s =~ /^\w+$/
|
data/lib/inversion/template.rb
CHANGED
@@ -13,9 +13,76 @@ rescue LoadError
|
|
13
13
|
end
|
14
14
|
|
15
15
|
|
16
|
-
# The main template class.
|
17
|
-
#
|
18
|
-
#
|
16
|
+
# The main template class.
|
17
|
+
#
|
18
|
+
# Inversion templates are the primary objects you'll be interacting with. Templates
|
19
|
+
# can be created from a string:
|
20
|
+
#
|
21
|
+
# Inversion::Template.new( template_source )
|
22
|
+
#
|
23
|
+
# or from a file:
|
24
|
+
#
|
25
|
+
# Inversion::Template.load( 'path/to/template.tmpl' )
|
26
|
+
#
|
27
|
+
#
|
28
|
+
# == Template Options
|
29
|
+
#
|
30
|
+
# Inversion supports the {Configurability}[http://rubygems.org/gems/configurability]
|
31
|
+
# API, and registers itself with the +templates+ key. This means you can either add
|
32
|
+
# a +templates+ section to your Configurability config, or call
|
33
|
+
# ::configure yourself with a config Hash (or something that quacks like one).
|
34
|
+
#
|
35
|
+
# To set options on a per-template basis, you can pass an options hash to either
|
36
|
+
# Inversion::Template::load or Inversion::Template::new, or set them from within the template
|
37
|
+
# itself using the {config tag}[rdoc-ref:Tags@config].
|
38
|
+
#
|
39
|
+
# The available options are:
|
40
|
+
#
|
41
|
+
# [:ignore_unknown_tags]
|
42
|
+
# Setting to false causes unknown tags used in templates to raise an
|
43
|
+
# Inversion::ParseError. Defaults to +true+.
|
44
|
+
#
|
45
|
+
# [:on_render_error]
|
46
|
+
# Dictates the behavior of exceptions during rendering. Defaults to +:comment+.
|
47
|
+
#
|
48
|
+
# [:ignore]
|
49
|
+
# Exceptions are silently ignored.
|
50
|
+
# [:comment]
|
51
|
+
# Exceptions are rendered inline as comments.
|
52
|
+
# [:propagate]
|
53
|
+
# Exceptions bubble up to the caller of Inversion::Template#render.
|
54
|
+
#
|
55
|
+
#
|
56
|
+
# [:debugging_comments]
|
57
|
+
# Insert various Inversion parse and render statements while rendering. Defaults to +false+.
|
58
|
+
#
|
59
|
+
# [:comment_start]
|
60
|
+
# When rendering debugging comments, the comment is started with these characters.
|
61
|
+
# Defaults to <code>"<!--"</code>.
|
62
|
+
#
|
63
|
+
# [:comment_end]
|
64
|
+
# When rendering debugging comments, the comment is finished with these characters.
|
65
|
+
# Defaults to <code>"-->"</code>.
|
66
|
+
#
|
67
|
+
# [:template_paths]
|
68
|
+
# An array of filesystem paths to search for templates within, when loaded or
|
69
|
+
# included with a relative path. The current working directory is always the
|
70
|
+
# last checked member of this. Defaults to <code>[]</code>.
|
71
|
+
#
|
72
|
+
# [:escape_format]
|
73
|
+
# The escaping used by tags such as +escape+ and +pp+. Default: +:html+.
|
74
|
+
#
|
75
|
+
# [:strip_tag_lines]
|
76
|
+
# If a tag's presence introduces a blank line into the output, this option
|
77
|
+
# removes it. Defaults to +true+.
|
78
|
+
#
|
79
|
+
# [:stat_delay]
|
80
|
+
# Templates know when they've been altered on disk, and can dynamically
|
81
|
+
# reload themselves in long running applications. Setting this option creates
|
82
|
+
# a purposeful delay between reloads for busy servers. Defaults to +0+
|
83
|
+
# (disabled).
|
84
|
+
#
|
85
|
+
#
|
19
86
|
class Inversion::Template
|
20
87
|
extend Loggability
|
21
88
|
include Inversion::DataUtilities
|
@@ -65,13 +132,15 @@ class Inversion::Template
|
|
65
132
|
}.freeze
|
66
133
|
|
67
134
|
|
68
|
-
|
69
|
-
|
135
|
+
##
|
136
|
+
# Global config
|
70
137
|
class << self; attr_accessor :config; end
|
138
|
+
self.config = DEFAULT_CONFIG.dup
|
71
139
|
|
140
|
+
##
|
72
141
|
# Global template search path
|
73
|
-
@template_paths = []
|
74
142
|
class << self; attr_accessor :template_paths; end
|
143
|
+
self.template_paths = []
|
75
144
|
|
76
145
|
|
77
146
|
### Configure the templating system.
|
@@ -145,8 +214,9 @@ class Inversion::Template
|
|
145
214
|
|
146
215
|
@source = source
|
147
216
|
@node_tree = [] # Parser expects this to always be an Array
|
148
|
-
@options = opts
|
217
|
+
@options = self.class.config.merge( opts )
|
149
218
|
@attributes = {}
|
219
|
+
@fragments = {}
|
150
220
|
@source_file = nil
|
151
221
|
@created_at = Time.now
|
152
222
|
@last_checked = @created_at
|
@@ -159,6 +229,7 @@ class Inversion::Template
|
|
159
229
|
def initialize_copy( other )
|
160
230
|
@options = deep_copy( other.options )
|
161
231
|
@attributes = deep_copy( other.attributes )
|
232
|
+
@fragments = deep_copy( other.fragments )
|
162
233
|
end
|
163
234
|
|
164
235
|
|
@@ -175,6 +246,9 @@ class Inversion::Template
|
|
175
246
|
# The Hash of template attributes
|
176
247
|
attr_reader :attributes
|
177
248
|
|
249
|
+
# The Hash of rendered template fragments
|
250
|
+
attr_reader :fragments
|
251
|
+
|
178
252
|
# The Template's configuration options hash
|
179
253
|
attr_reader :options
|
180
254
|
|
@@ -216,6 +290,8 @@ class Inversion::Template
|
|
216
290
|
opts = self.options
|
217
291
|
opts.merge!( parentstate.options ) if parentstate
|
218
292
|
|
293
|
+
self.fragments.clear
|
294
|
+
|
219
295
|
state = Inversion::RenderState.new( parentstate, self.attributes, opts, &block )
|
220
296
|
|
221
297
|
# self.log.debug " rendering node tree: %p" % [ @node_tree ]
|
@@ -223,6 +299,8 @@ class Inversion::Template
|
|
223
299
|
self.log.info " done rendering template 0x%08x: %0.4fs" %
|
224
300
|
[ self.object_id/2, state.time_elapsed ]
|
225
301
|
|
302
|
+
self.fragments.replace( state.rendered_fragments )
|
303
|
+
|
226
304
|
return state.to_s
|
227
305
|
end
|
228
306
|
alias_method :to_s, :render
|
@@ -82,7 +82,7 @@ class Inversion::Template::AttrTag < Inversion::Template::CodeTag
|
|
82
82
|
value = self.evaluate( renderstate ) # :FIXME: or return value # nil or false?
|
83
83
|
|
84
84
|
# Apply the format if there is one
|
85
|
-
if self.format
|
85
|
+
if self.format && value
|
86
86
|
return self.format % value
|
87
87
|
else
|
88
88
|
return value
|
@@ -20,15 +20,15 @@ require 'inversion/template/rescuetag'
|
|
20
20
|
# <?begin ?><?call employees.length ?><?end?>
|
21
21
|
#
|
22
22
|
# <?begin ?>
|
23
|
-
#
|
24
|
-
#
|
25
|
-
#
|
23
|
+
# <?for employee in employees.all ?>
|
24
|
+
# <?attr employee.name ?> --> <?attr employee.title ?>
|
25
|
+
# <?end for?>
|
26
26
|
# <?rescue DatabaseError => err ?>
|
27
|
-
#
|
28
|
-
#
|
29
|
-
#
|
30
|
-
#
|
31
|
-
#
|
27
|
+
# Oh no!! I can't talk to the database for some reason. The
|
28
|
+
# error was as follows:
|
29
|
+
# <pre>
|
30
|
+
# <?attr err.message ?>
|
31
|
+
# </pre>
|
32
32
|
# <?end?>
|
33
33
|
#
|
34
34
|
class Inversion::Template::BeginTag < Inversion::Template::Tag
|
@@ -0,0 +1,60 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# vim: set noet nosta sw=4 ts=4 :
|
3
|
+
|
4
|
+
require 'inversion/template/tag'
|
5
|
+
require 'inversion/template/containertag'
|
6
|
+
|
7
|
+
|
8
|
+
# Inversion 'fragment' tag.
|
9
|
+
#
|
10
|
+
# This tag provides a way to generate a fragment of content once in a template
|
11
|
+
# as an attribute, and then reuse it later either in the same template or even
|
12
|
+
# outside of it.
|
13
|
+
#
|
14
|
+
# == Syntax
|
15
|
+
#
|
16
|
+
# <?fragment subject ?>Receipt for Order #<?call order.number ?><?end subject ?>
|
17
|
+
#
|
18
|
+
class Inversion::Template::FragmentTag < Inversion::Template::Tag
|
19
|
+
include Inversion::Template::ContainerTag
|
20
|
+
|
21
|
+
|
22
|
+
### Create a new FragmentTag with the given +body+.
|
23
|
+
def initialize( body, line=nil, column=nil )
|
24
|
+
super
|
25
|
+
|
26
|
+
key = self.body[ /^([a-z]\w+)$/ ] or
|
27
|
+
raise Inversion::ParseError,
|
28
|
+
"malformed key: expected simple identifier, got %p" % [ self.body ]
|
29
|
+
@key = key.to_sym
|
30
|
+
end
|
31
|
+
|
32
|
+
|
33
|
+
######
|
34
|
+
public
|
35
|
+
######
|
36
|
+
|
37
|
+
# The fragment key; corresponds to the name of the attribute that will be set
|
38
|
+
# by the rendered contents of the fragment.
|
39
|
+
attr_reader :key
|
40
|
+
|
41
|
+
|
42
|
+
### Render the fragment and store it as an attribute.
|
43
|
+
def render( renderstate )
|
44
|
+
self.log.debug "Publishing %d nodes as %s" % [ self.subnodes.length, self.key ]
|
45
|
+
rendered_nodes = []
|
46
|
+
renderstate.with_destination( rendered_nodes ) do
|
47
|
+
sn = self.render_subnodes( renderstate )
|
48
|
+
self.log.debug " subnodes are: %p" % [ sn ]
|
49
|
+
sn
|
50
|
+
end
|
51
|
+
|
52
|
+
self.log.debug " rendered nodes are: %p" % [ rendered_nodes ]
|
53
|
+
renderstate.add_fragment( self.key, rendered_nodes )
|
54
|
+
|
55
|
+
return nil
|
56
|
+
end
|
57
|
+
|
58
|
+
|
59
|
+
end # class Inversion::Template::FragmentTag
|
60
|
+
|
@@ -42,7 +42,7 @@ class Inversion::Template::RescueTag < Inversion::Template::Tag
|
|
42
42
|
|
43
43
|
|
44
44
|
### Parsing callback -- check to be sure the node tree can have the
|
45
|
-
### '
|
45
|
+
### 'rescue' tag appended to it.
|
46
46
|
def before_appending( parsestate )
|
47
47
|
condtag = parsestate.node_stack.reverse.find do |node|
|
48
48
|
case node
|
@@ -1,40 +1,18 @@
|
|
1
1
|
#!/usr/bin/ruby
|
2
2
|
# coding: utf-8
|
3
3
|
|
4
|
-
BEGIN {
|
5
|
-
require 'pathname'
|
6
|
-
basedir = Pathname.new( __FILE__ ).dirname.parent
|
7
|
-
|
8
|
-
libdir = basedir + "lib"
|
9
|
-
|
10
|
-
$LOAD_PATH.unshift( libdir.to_s ) unless $LOAD_PATH.include?( libdir.to_s )
|
11
|
-
}
|
12
|
-
|
13
4
|
# SimpleCov test coverage reporting; enable this using the :coverage rake task
|
14
|
-
if ENV['COVERAGE']
|
15
|
-
require 'simplecov'
|
16
|
-
SimpleCov.start do
|
17
|
-
add_filter 'spec'
|
18
|
-
add_group "Tags" do |file|
|
19
|
-
file.filename =~ /tag.rb$/
|
20
|
-
end
|
21
|
-
add_group "Needing tests" do |file|
|
22
|
-
file.covered_percent < 90
|
23
|
-
end
|
24
|
-
end
|
25
|
-
end
|
5
|
+
require 'simplecov' if ENV['COVERAGE']
|
26
6
|
|
27
7
|
require 'rspec'
|
28
8
|
require 'loggability'
|
29
9
|
require 'loggability/spechelpers'
|
30
10
|
|
31
11
|
require 'inversion'
|
32
|
-
require 'spec/lib/constants'
|
33
12
|
|
34
13
|
|
35
14
|
### RSpec helper functions.
|
36
15
|
module Inversion::SpecHelpers
|
37
|
-
include Inversion::TestConstants
|
38
16
|
|
39
17
|
###############
|
40
18
|
module_function
|
@@ -52,22 +30,21 @@ module Inversion::SpecHelpers
|
|
52
30
|
return "<?#{name} #{data} ?>"
|
53
31
|
end
|
54
32
|
|
55
|
-
|
56
33
|
end
|
57
34
|
|
58
35
|
|
59
36
|
### Mock with RSpec
|
60
37
|
RSpec.configure do |c|
|
61
|
-
include Inversion::TestConstants
|
62
|
-
include Loggability::SpecHelpers
|
63
38
|
|
64
|
-
c.
|
39
|
+
c.run_all_when_everything_filtered = true
|
40
|
+
c.filter_run :focus
|
41
|
+
c.order = 'random'
|
42
|
+
c.mock_with( :rspec ) do |mock|
|
43
|
+
mock.syntax = :expect
|
44
|
+
end
|
65
45
|
|
66
46
|
c.include( Inversion::SpecHelpers )
|
67
47
|
c.include( Loggability::SpecHelpers )
|
68
|
-
|
69
|
-
c.filter_run_excluding( :ruby_1_9_only => true ) if
|
70
|
-
Inversion::SpecHelpers.vvec( RUBY_VERSION ) < Inversion::SpecHelpers.vvec('1.9.0')
|
71
48
|
end
|
72
49
|
|
73
50
|
# vim: set nosta noet ts=4 sw=4:
|