inversion 0.12.3 → 0.14.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (59) hide show
  1. checksums.yaml +4 -4
  2. checksums.yaml.gz.sig +2 -1
  3. data.tar.gz.sig +0 -0
  4. data/ChangeLog +305 -9
  5. data/Examples.rdoc +134 -0
  6. data/GettingStarted.rdoc +44 -0
  7. data/Guide.rdoc +47 -0
  8. data/History.rdoc +15 -0
  9. data/Manifest.txt +7 -2
  10. data/README.rdoc +9 -10
  11. data/Rakefile +23 -10
  12. data/Tags.rdoc +561 -0
  13. data/lib/inversion.rb +2 -2
  14. data/lib/inversion/renderstate.rb +46 -11
  15. data/lib/inversion/template.rb +85 -7
  16. data/lib/inversion/template/attrtag.rb +1 -1
  17. data/lib/inversion/template/begintag.rb +8 -8
  18. data/lib/inversion/template/fragmenttag.rb +60 -0
  19. data/lib/inversion/template/rescuetag.rb +1 -1
  20. data/spec/{lib/helpers.rb → helpers.rb} +7 -30
  21. data/spec/inversion/mixins_spec.rb +55 -65
  22. data/spec/inversion/monkeypatches_spec.rb +2 -12
  23. data/spec/inversion/parser_spec.rb +34 -44
  24. data/spec/inversion/renderstate_spec.rb +123 -69
  25. data/spec/inversion/sinatra_spec.rb +6 -19
  26. data/spec/inversion/template/attrtag_spec.rb +56 -76
  27. data/spec/inversion/template/begintag_spec.rb +24 -41
  28. data/spec/inversion/template/calltag_spec.rb +1 -18
  29. data/spec/inversion/template/codetag_spec.rb +6 -24
  30. data/spec/inversion/template/commenttag_spec.rb +9 -27
  31. data/spec/inversion/template/configtag_spec.rb +5 -16
  32. data/spec/inversion/template/containertag_spec.rb +4 -21
  33. data/spec/inversion/template/defaulttag_spec.rb +6 -23
  34. data/spec/inversion/template/elsetag_spec.rb +9 -26
  35. data/spec/inversion/template/elsiftag_spec.rb +7 -24
  36. data/spec/inversion/template/endtag_spec.rb +6 -23
  37. data/spec/inversion/template/escapetag_spec.rb +10 -25
  38. data/spec/inversion/template/fortag_spec.rb +20 -37
  39. data/spec/inversion/template/fragmenttag_spec.rb +40 -0
  40. data/spec/inversion/template/iftag_spec.rb +23 -40
  41. data/spec/inversion/template/importtag_spec.rb +8 -25
  42. data/spec/inversion/template/includetag_spec.rb +27 -42
  43. data/spec/inversion/template/node_spec.rb +6 -15
  44. data/spec/inversion/template/pptag_spec.rb +10 -23
  45. data/spec/inversion/template/publishtag_spec.rb +4 -21
  46. data/spec/inversion/template/rescuetag_spec.rb +12 -29
  47. data/spec/inversion/template/subscribetag_spec.rb +8 -25
  48. data/spec/inversion/template/tag_spec.rb +24 -37
  49. data/spec/inversion/template/textnode_spec.rb +8 -24
  50. data/spec/inversion/template/timedeltatag_spec.rb +31 -43
  51. data/spec/inversion/template/unlesstag_spec.rb +7 -24
  52. data/spec/inversion/template/uriencodetag_spec.rb +6 -23
  53. data/spec/inversion/template/yieldtag_spec.rb +3 -20
  54. data/spec/inversion/template_spec.rb +155 -108
  55. data/spec/inversion/tilt_spec.rb +7 -16
  56. data/spec/inversion_spec.rb +7 -22
  57. metadata +63 -40
  58. metadata.gz.sig +0 -0
  59. 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.12.3'
29
+ VERSION = '0.14.0'
30
30
 
31
31
  # Version-control revision constant
32
- REVISION = %q$Revision: 100ed23eb2ba $
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( @locals.merge(values) )
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
- strings = @output.flatten.map( &:to_s )
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+$/
@@ -13,9 +13,76 @@ rescue LoadError
13
13
  end
14
14
 
15
15
 
16
- # The main template class. Instances of this class are created by parsing template
17
- # source and combining the resulting node tree with a set of attributes that
18
- # can be used to populate it when rendered.
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
- ### Global config
69
- @config = DEFAULT_CONFIG.dup
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
- # <?for employee in employees.all ?>
24
- # <?attr employee.name ?> --> <?attr employee.title ?>
25
- # <?end for?>
23
+ # <?for employee in employees.all ?>
24
+ # <?attr employee.name ?> --> <?attr employee.title ?>
25
+ # <?end for?>
26
26
  # <?rescue DatabaseError => err ?>
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>
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
- ### 'else' tag appended to it.
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.mock_with :rspec
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: