inversion 0.0.1

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.
Files changed (78) hide show
  1. data.tar.gz.sig +2 -0
  2. data/.gemtest +0 -0
  3. data/ChangeLog +836 -0
  4. data/History.md +4 -0
  5. data/Manifest.txt +74 -0
  6. data/README.rdoc +171 -0
  7. data/Rakefile +55 -0
  8. data/bin/inversion +276 -0
  9. data/lib/inversion.rb +98 -0
  10. data/lib/inversion/exceptions.rb +21 -0
  11. data/lib/inversion/mixins.rb +236 -0
  12. data/lib/inversion/monkeypatches.rb +20 -0
  13. data/lib/inversion/renderstate.rb +337 -0
  14. data/lib/inversion/sinatra.rb +35 -0
  15. data/lib/inversion/template.rb +250 -0
  16. data/lib/inversion/template/attrtag.rb +120 -0
  17. data/lib/inversion/template/calltag.rb +16 -0
  18. data/lib/inversion/template/codetag.rb +164 -0
  19. data/lib/inversion/template/commenttag.rb +54 -0
  20. data/lib/inversion/template/conditionaltag.rb +49 -0
  21. data/lib/inversion/template/configtag.rb +60 -0
  22. data/lib/inversion/template/containertag.rb +45 -0
  23. data/lib/inversion/template/elsetag.rb +62 -0
  24. data/lib/inversion/template/elsiftag.rb +49 -0
  25. data/lib/inversion/template/endtag.rb +55 -0
  26. data/lib/inversion/template/escapetag.rb +26 -0
  27. data/lib/inversion/template/fortag.rb +120 -0
  28. data/lib/inversion/template/iftag.rb +69 -0
  29. data/lib/inversion/template/importtag.rb +70 -0
  30. data/lib/inversion/template/includetag.rb +51 -0
  31. data/lib/inversion/template/node.rb +102 -0
  32. data/lib/inversion/template/parser.rb +297 -0
  33. data/lib/inversion/template/pptag.rb +28 -0
  34. data/lib/inversion/template/publishtag.rb +72 -0
  35. data/lib/inversion/template/subscribetag.rb +88 -0
  36. data/lib/inversion/template/tag.rb +150 -0
  37. data/lib/inversion/template/textnode.rb +43 -0
  38. data/lib/inversion/template/unlesstag.rb +60 -0
  39. data/lib/inversion/template/uriencodetag.rb +30 -0
  40. data/lib/inversion/template/yieldtag.rb +51 -0
  41. data/lib/inversion/tilt.rb +82 -0
  42. data/lib/inversion/utils.rb +235 -0
  43. data/spec/data/sinatra/hello.inversion +1 -0
  44. data/spec/inversion/mixins_spec.rb +177 -0
  45. data/spec/inversion/monkeypatches_spec.rb +35 -0
  46. data/spec/inversion/renderstate_spec.rb +291 -0
  47. data/spec/inversion/sinatra_spec.rb +59 -0
  48. data/spec/inversion/template/attrtag_spec.rb +216 -0
  49. data/spec/inversion/template/calltag_spec.rb +30 -0
  50. data/spec/inversion/template/codetag_spec.rb +51 -0
  51. data/spec/inversion/template/commenttag_spec.rb +84 -0
  52. data/spec/inversion/template/configtag_spec.rb +105 -0
  53. data/spec/inversion/template/containertag_spec.rb +54 -0
  54. data/spec/inversion/template/elsetag_spec.rb +105 -0
  55. data/spec/inversion/template/elsiftag_spec.rb +87 -0
  56. data/spec/inversion/template/endtag_spec.rb +78 -0
  57. data/spec/inversion/template/escapetag_spec.rb +59 -0
  58. data/spec/inversion/template/fortag_spec.rb +98 -0
  59. data/spec/inversion/template/iftag_spec.rb +241 -0
  60. data/spec/inversion/template/importtag_spec.rb +106 -0
  61. data/spec/inversion/template/includetag_spec.rb +108 -0
  62. data/spec/inversion/template/node_spec.rb +81 -0
  63. data/spec/inversion/template/parser_spec.rb +170 -0
  64. data/spec/inversion/template/pptag_spec.rb +51 -0
  65. data/spec/inversion/template/publishtag_spec.rb +69 -0
  66. data/spec/inversion/template/subscribetag_spec.rb +60 -0
  67. data/spec/inversion/template/tag_spec.rb +97 -0
  68. data/spec/inversion/template/textnode_spec.rb +86 -0
  69. data/spec/inversion/template/unlesstag_spec.rb +84 -0
  70. data/spec/inversion/template/uriencodetag_spec.rb +49 -0
  71. data/spec/inversion/template/yieldtag_spec.rb +54 -0
  72. data/spec/inversion/template_spec.rb +269 -0
  73. data/spec/inversion/tilt_spec.rb +47 -0
  74. data/spec/inversion_spec.rb +95 -0
  75. data/spec/lib/constants.rb +9 -0
  76. data/spec/lib/helpers.rb +160 -0
  77. metadata +316 -0
  78. metadata.gz.sig +0 -0
@@ -0,0 +1,106 @@
1
+ #!/usr/bin/env rspec -cfd -b
2
+ # vim: set noet nosta sw=4 ts=4 :
3
+
4
+ BEGIN {
5
+ require 'pathname'
6
+ basedir = Pathname( __FILE__ ).dirname.parent.parent.parent
7
+ libdir = basedir + 'lib'
8
+
9
+ $LOAD_PATH.unshift( basedir.to_s ) unless $LOAD_PATH.include?( basedir.to_s )
10
+ $LOAD_PATH.unshift( libdir.to_s ) unless $LOAD_PATH.include?( libdir.to_s )
11
+ }
12
+
13
+ require 'rspec'
14
+ require 'spec/lib/helpers'
15
+ require 'inversion/template/importtag'
16
+
17
+ describe Inversion::Template::ImportTag do
18
+
19
+ before( :all ) do
20
+ setup_logging( :fatal )
21
+ end
22
+
23
+ after( :all ) do
24
+ reset_logging()
25
+ end
26
+
27
+ it "can import a single attribute" do
28
+ tag = Inversion::Template::ImportTag.new( 'txn ' )
29
+ tag.attributes.should == [ :txn ]
30
+ end
31
+
32
+ it "can import multiple attributes" do
33
+ tag = Inversion::Template::ImportTag.new( 'txn, applet' )
34
+ tag.attributes.should == [ :txn, :applet ]
35
+ end
36
+
37
+
38
+ context "with a single attribute name" do
39
+
40
+ before( :each ) do
41
+ @subtemplate = Inversion::Template.new( '<?import foo ?><?attr foo ?>' )
42
+ end
43
+
44
+ it "copies the named value from the enclosing template" do
45
+ outside = Inversion::Template.new( '<?attr foo ?><?attr subtemplate ?>' )
46
+ outside.subtemplate = @subtemplate
47
+ outside.foo = 'Froo'
48
+
49
+ outside.render.should == 'FrooFroo'
50
+ end
51
+
52
+ it "doesn't override values set explicitly on the subtemplate" do
53
+ outside = Inversion::Template.new( '<?attr foo ?><?attr subtemplate ?>' )
54
+ outside.subtemplate = @subtemplate
55
+ outside.foo = 'Froo'
56
+
57
+ @subtemplate.foo = 'Frar'
58
+
59
+ outside.render.should == 'FrooFrar'
60
+ end
61
+
62
+ end
63
+
64
+
65
+ context "with multiple attribute names" do
66
+
67
+ before( :each ) do
68
+ source = '<?import foo, bar ?><?attr foo.capitalize ?>: ' +
69
+ '<?attr bar.capitalize ?><?attr baz ?>'
70
+ @subtemplate = Inversion::Template.new( source )
71
+ end
72
+
73
+ it "copies the named value from the enclosing template" do
74
+ outside = Inversion::Template.new( '<?attr subtemplate ?>' )
75
+ outside.subtemplate = @subtemplate
76
+ outside.foo = 'mission'
77
+ outside.bar = 'accomplished'
78
+
79
+ outside.render.should == 'Mission: Accomplished'
80
+ end
81
+
82
+ it "doesn't override values set explicitly on the subtemplate" do
83
+ outside = Inversion::Template.new( '<?attr subtemplate ?>' )
84
+ outside.subtemplate = @subtemplate
85
+ outside.foo = 'mission'
86
+ outside.bar = 'accomplished'
87
+
88
+ @subtemplate.bar = 'abandoned'
89
+
90
+ outside.render.should == 'Mission: Abandoned'
91
+ end
92
+
93
+ it "only imports listed attributes" do
94
+ outside = Inversion::Template.new( '<?attr subtemplate ?>' )
95
+ outside.subtemplate = @subtemplate
96
+ outside.foo = 'Attributes'
97
+ outside.bar = 'Just This One'
98
+ outside.baz = 'And Not This One'
99
+
100
+ outside.render.should == 'Attributes: Just this one'
101
+ end
102
+ end
103
+
104
+ end
105
+
106
+
@@ -0,0 +1,108 @@
1
+ #!/usr/bin/env rspec -cfd -b
2
+ # vim: set noet nosta sw=4 ts=4 :
3
+
4
+ BEGIN {
5
+ require 'pathname'
6
+ basedir = Pathname( __FILE__ ).dirname.parent.parent.parent
7
+ libdir = basedir + 'lib'
8
+
9
+ $LOAD_PATH.unshift( basedir.to_s ) unless $LOAD_PATH.include?( basedir.to_s )
10
+ $LOAD_PATH.unshift( libdir.to_s ) unless $LOAD_PATH.include?( libdir.to_s )
11
+ }
12
+
13
+ require 'timeout'
14
+ require 'rspec'
15
+ require 'spec/lib/helpers'
16
+ require 'inversion/template'
17
+ require 'inversion/template/includetag'
18
+
19
+ describe Inversion::Template::IncludeTag do
20
+
21
+ before( :all ) do
22
+ setup_logging( :fatal )
23
+ end
24
+
25
+ after( :all ) do
26
+ reset_logging()
27
+ end
28
+
29
+
30
+ it "renders the IncludeTag as an empty string" do
31
+ included_path = Pathname.pwd + 'included.tmpl'
32
+ FileTest.stub( :exist? ).with( included_path.to_s ).and_return true
33
+ IO.stub( :read ).with( included_path.to_s ).and_return( 'there,' )
34
+
35
+ tmpl = Inversion::Template.new( "hi <?include included.tmpl ?> handsome!" )
36
+ tmpl.render.should == "hi there, handsome!"
37
+ end
38
+
39
+
40
+ it "renders debugging comments with the included template path" do
41
+ included_path = Pathname.pwd + 'included.tmpl'
42
+ FileTest.stub( :exist? ).with( included_path.to_s ).and_return true
43
+ IO.stub( :read ).with( included_path.to_s ).and_return( 'there,' )
44
+
45
+ tmpl = Inversion::Template.
46
+ new( "hi <?include included.tmpl ?> handsome!", :debugging_comments => true )
47
+ tmpl.render.should =~ /Include "included\.tmpl"/
48
+ end
49
+
50
+
51
+ it "appends the nodes from a separate template onto the including template" do
52
+ included_path = Pathname.pwd + 'included.tmpl'
53
+ FileTest.stub( :exist? ).with( included_path.to_s ).and_return true
54
+ IO.stub( :read ).with( included_path.to_s ).and_return( 'there,' )
55
+
56
+ tmpl = Inversion::Template.new( "hi <?include included.tmpl ?> handsome!" )
57
+ tmpl.node_tree.should have(4).members
58
+ tmpl.node_tree[0].should be_a( Inversion::Template::TextNode )
59
+ tmpl.node_tree[1].should be_a( Inversion::Template::IncludeTag )
60
+ tmpl.node_tree[2].should be_a( Inversion::Template::TextNode )
61
+ tmpl.node_tree[3].should be_a( Inversion::Template::TextNode )
62
+ end
63
+
64
+
65
+ it "allows the same template to be included multiple times" do
66
+ included_path = Pathname.pwd + 'included.tmpl'
67
+ FileTest.stub( :exist? ).with( included_path.to_s ).and_return true
68
+ IO.stub( :read ).with( included_path.to_s ).and_return( ' hi' )
69
+
70
+ tmpl = Inversion::Template.
71
+ new( "hi<?include included.tmpl ?><?include included.tmpl ?> handsome!" )
72
+ tmpl.render.should == "hi hi hi handsome!"
73
+ end
74
+
75
+
76
+ it "raises exception on include loops" do
77
+ included_path = Pathname.pwd + 'included.tmpl'
78
+ FileTest.stub( :exist? ).with( included_path.to_s ).and_return true
79
+ IO.stub( :read ).with( included_path.to_s ).and_return( "<?include included.tmpl ?>" )
80
+
81
+ expect {
82
+ Inversion::Template.new( "hi <?include included.tmpl ?> handsome!" )
83
+ }.to raise_error( Inversion::StackError, /Recursive load .+"included.tmpl"/ )
84
+ end
85
+
86
+
87
+ it "raises exception on complex include loops" do
88
+ top_path = Pathname.pwd + 'top.tmpl'
89
+ middle_path = Pathname.pwd + 'middle.tmpl'
90
+ bottom_path = Pathname.pwd + 'bottom.tmpl'
91
+
92
+ FileTest.stub( :exist? ).with( top_path.to_s ).and_return true
93
+ IO.stub( :read ).with( top_path.to_s ).and_return( "<?include middle.tmpl ?>" )
94
+
95
+ FileTest.stub( :exist? ).with( middle_path.to_s ).and_return true
96
+ IO.stub( :read ).with( middle_path.to_s ).and_return( "<?include bottom.tmpl ?>" )
97
+
98
+ FileTest.stub( :exist? ).with( bottom_path.to_s ).and_return true
99
+ IO.stub( :read ).with( bottom_path.to_s ).and_return( "<?include top.tmpl ?>" )
100
+
101
+ expect {
102
+ Inversion::Template.new( "hi <?include top.tmpl ?> handsome!" )
103
+ }.to raise_error( Inversion::StackError, /Recursive load .+"top.tmpl"/ )
104
+ end
105
+ end
106
+
107
+
108
+
@@ -0,0 +1,81 @@
1
+ #!/usr/bin/env rspec -cfd -b
2
+ # vim: set noet nosta sw=4 ts=4 :
3
+
4
+ BEGIN {
5
+ require 'pathname'
6
+ basedir = Pathname( __FILE__ ).dirname.parent.parent.parent
7
+ libdir = basedir + 'lib'
8
+
9
+ $LOAD_PATH.unshift( basedir.to_s ) unless $LOAD_PATH.include?( basedir.to_s )
10
+ $LOAD_PATH.unshift( libdir.to_s ) unless $LOAD_PATH.include?( libdir.to_s )
11
+ }
12
+
13
+ require 'rspec'
14
+ require 'spec/lib/helpers'
15
+ require 'inversion/template/node'
16
+
17
+ describe Inversion::Template::Node do
18
+
19
+ let( :concrete_subclass ) { Class.new(Inversion::Template::Node) }
20
+
21
+ it "is an abstract class" do
22
+ expect {
23
+ Inversion::Template::Node.new( '' )
24
+ }.to raise_exception( NoMethodError, /private method `new'/i )
25
+ end
26
+
27
+ it "defaults to using inspect to render itself as a comment" do
28
+ node = concrete_subclass.new( '' )
29
+ node.as_comment_body.should == node.inspect
30
+ end
31
+
32
+ it "isn't a container" do
33
+ node = concrete_subclass.new( '' )
34
+ node.should_not be_a_container()
35
+ end
36
+
37
+ it "knows where it was parsed from if constructed with a position" do
38
+ node = concrete_subclass.new( '', 4, 12 )
39
+ node.location.should =~ /line 4, column 12/
40
+ end
41
+
42
+ it "knows that it was created from an unknown location if created without a position" do
43
+ node = concrete_subclass.new( '' )
44
+ node.location.should == 'line ??, column ??'
45
+ end
46
+
47
+ it "doesn't raise an exception when the before_appending event callback is called" do
48
+ state = double( "parser state" )
49
+ node = concrete_subclass.new( '' )
50
+ expect {
51
+ node.before_appending( state )
52
+ }.to_not raise_error()
53
+ end
54
+
55
+ it "doesn't raise an exception when the after_appending event callback is called" do
56
+ state = double( "parser state" )
57
+ node = concrete_subclass.new( '' )
58
+ expect {
59
+ node.after_appending( state )
60
+ }.to_not raise_error()
61
+ end
62
+
63
+ it "doesn't raise an exception when the before_rendering event callback is called" do
64
+ state = double( "render state" )
65
+ node = concrete_subclass.new( '' )
66
+ expect {
67
+ node.before_rendering( state )
68
+ }.to_not raise_error()
69
+ end
70
+
71
+ it "doesn't raise an exception when the after_rendering event callback is called" do
72
+ state = double( "render state" )
73
+ node = concrete_subclass.new( '' )
74
+ expect {
75
+ node.after_rendering( state )
76
+ }.to_not raise_error()
77
+ end
78
+ end
79
+
80
+
81
+
@@ -0,0 +1,170 @@
1
+ #!/usr/bin/env rspec -cfd -b
2
+ # vim: set noet nosta sw=4 ts=4 :
3
+
4
+ BEGIN {
5
+ require 'pathname'
6
+ basedir = Pathname( __FILE__ ).dirname.parent.parent.parent
7
+ libdir = basedir + 'lib'
8
+
9
+ $LOAD_PATH.unshift( basedir.to_s ) unless $LOAD_PATH.include?( basedir.to_s )
10
+ $LOAD_PATH.unshift( libdir.to_s ) unless $LOAD_PATH.include?( libdir.to_s )
11
+ }
12
+
13
+ require 'rspec'
14
+ require 'spec/lib/helpers'
15
+ require 'inversion/template/parser'
16
+
17
+ describe Inversion::Template::Parser do
18
+
19
+ before( :all ) do
20
+ setup_logging( :fatal )
21
+ Inversion::Template::Tag.load_all
22
+ end
23
+
24
+ before( :each ) do
25
+ @template = double( "An Inversion::Template" )
26
+ end
27
+
28
+ it "parses a string with no PIs as a single text node" do
29
+ result = Inversion::Template::Parser.new( @template ).parse( "render unto Caesar" )
30
+
31
+ result.should have( 1 ).member
32
+ result.first.should be_a( Inversion::Template::TextNode )
33
+ result.first.body.should == 'render unto Caesar'
34
+ end
35
+
36
+ it "parses an empty string as a empty tree" do
37
+ result = Inversion::Template::Parser.new( @template ).parse( "" )
38
+ result.should be_empty
39
+ end
40
+
41
+ it "raises a ParseError on mismatched tag brackets" do
42
+ expect {
43
+ Inversion::Template::Parser.new( @template ).parse( '[?foo bar ?>' )
44
+ }.to raise_error( Inversion::ParseError, /mismatched start and end brackets/i )
45
+ end
46
+
47
+ it "parses a string with a single 'attr' tag as a single AttrTag node" do
48
+ result = Inversion::Template::Parser.new( @template ).parse( "<?attr foo ?>" )
49
+
50
+ result.should have( 1 ).member
51
+ result.first.should be_a( Inversion::Template::AttrTag )
52
+ result.first.body.should == 'foo'
53
+ end
54
+
55
+ it "parses a single 'attr' tag surrounded by plain text" do
56
+ result = Inversion::Template::Parser.new( @template ).parse( "beginning<?attr foo ?>end" )
57
+
58
+ result.should have( 3 ).members
59
+ result[0].should be_a( Inversion::Template::TextNode )
60
+ result[1].should be_a( Inversion::Template::AttrTag )
61
+ result[1].body.should == 'foo'
62
+ result[2].should be_a( Inversion::Template::TextNode )
63
+ end
64
+
65
+ it "ignores unknown tags by default" do
66
+ result = Inversion::Template::Parser.new( @template ).parse( "Text <?hoooowhat ?>" )
67
+
68
+ result.should have( 2 ).members
69
+ result[0].should be_a( Inversion::Template::TextNode )
70
+ result[1].should be_a( Inversion::Template::TextNode )
71
+ result[1].body.should == '<?hoooowhat ?>'
72
+ end
73
+
74
+ it "can raise exceptions on unknown tags" do
75
+ expect {
76
+ Inversion::Template::Parser.new( @template, :ignore_unknown_tags => false ).
77
+ parse( "Text <?hoooowhat ?>" )
78
+ }.to raise_exception( Inversion::ParseError, /unknown tag/i )
79
+ end
80
+
81
+ it "can raise exceptions on unclosed (nested) tags" do
82
+ expect {
83
+ Inversion::Template::Parser.new( @template ).parse( "Text <?attr something <?attr something_else ?>" )
84
+ }.to raise_exception( Inversion::ParseError, /unclosed or nested tag/i )
85
+ end
86
+
87
+ it "can raise exceptions on unclosed (eof) tags" do
88
+ expect {
89
+ Inversion::Template::Parser.new( @template ).parse( "Text <?hoooowhat" )
90
+ }.to raise_exception( Inversion::ParseError, /unclosed tag/i )
91
+ end
92
+
93
+
94
+ describe Inversion::Template::Parser::State do
95
+
96
+ before( :each ) do
97
+ @state = Inversion::Template::Parser::State.new( @template )
98
+ end
99
+
100
+ it "returns the node tree if it's well-formed" do
101
+ open_tag = Inversion::Template::ForTag.new( 'foo in bar' )
102
+ end_tag = Inversion::Template::EndTag.new( 'for' )
103
+
104
+ @state << open_tag << end_tag
105
+
106
+ @state.tree.should == [ open_tag, end_tag ]
107
+ end
108
+
109
+ it "knows it is well-formed if there are no open tags" do
110
+ @state << Inversion::Template::ForTag.new( 'foo in bar' )
111
+ @state.should_not be_well_formed
112
+
113
+ @state << Inversion::Template::ForTag.new( 'foo in bar' )
114
+ @state.should_not be_well_formed
115
+
116
+ @state << Inversion::Template::EndTag.new( 'for' )
117
+ @state.should_not be_well_formed
118
+
119
+ @state << Inversion::Template::EndTag.new( 'for' )
120
+ @state.should be_well_formed
121
+ end
122
+
123
+ it "can pop a container tag off of the current context" do
124
+ container = Inversion::Template::ForTag.new( 'foo in bar' )
125
+ @state << container
126
+ @state.pop.should == container
127
+ end
128
+
129
+ it "calls the #after_appending hook of container nodes when they're popped" do
130
+ container = mock( "container tag", :before_appending => false, :is_container? => true )
131
+ @state << container
132
+
133
+ container.should_receive( :after_appending ).with( @state )
134
+ @state.pop
135
+ end
136
+
137
+ it "raises an error when popping if there is no container tag" do
138
+ expect {
139
+ @state.pop
140
+ }.to raise_exception( Inversion::ParseError, /unbalanced end: no open tag/i )
141
+ end
142
+
143
+ it "raises an error when the tree is fetched if it isn't well-formed" do
144
+ open_tag = Inversion::Template::ForTag.new( 'foo in bar' )
145
+ @state << open_tag
146
+
147
+ expect {
148
+ @state.tree
149
+ }.to raise_exception( Inversion::ParseError, /unclosed/i )
150
+ end
151
+
152
+ it "calls the #before_appending callback on nodes that are appended to it" do
153
+ node = mock( "node", :is_container? => false, :after_appending => nil )
154
+ node.should_receive( :before_appending ).with( @state )
155
+
156
+ @state << node
157
+ end
158
+
159
+ it "calls the #after_appending callback on nodes that are appended to it" do
160
+ node = mock( "node", :is_container? => false, :before_appending => nil )
161
+ node.should_receive( :after_appending ).with( @state )
162
+
163
+ @state << node
164
+ end
165
+
166
+
167
+ end
168
+
169
+ end
170
+