inversion 0.12.3 → 0.14.0
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.
- 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:
|