inversion 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
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,28 @@
1
+ #!/usr/bin/env ruby
2
+ # vim: set noet nosta sw=4 ts=4 :
3
+
4
+ require 'pp'
5
+ require 'inversion/template/calltag'
6
+
7
+ # Inversion object inspection tag.
8
+ #
9
+ # This tag dumps the result of the attribute or method chain.
10
+ #
11
+ # == Syntax
12
+ #
13
+ # <?pp foo.bar ?>
14
+ #
15
+ class Inversion::Template::PpTag < Inversion::Template::CallTag
16
+ include Inversion::Escaping
17
+
18
+ ### Render the method chains against the attributes of the specified +render_state+
19
+ ### and return them.
20
+ def render( render_state )
21
+ raw = super
22
+ buf = ''
23
+ PP.pp( raw, buf )
24
+ return self.escape( buf.chomp, render_state )
25
+ end
26
+
27
+ end # class Inversion::Template::PpTag
28
+
@@ -0,0 +1,72 @@
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
+ # Inversion publish tag.
8
+ #
9
+ # The publish tag exports one or more subnodes to enclosing templates.
10
+ #
11
+ # == Syntax
12
+ #
13
+ # <!-- Outer template -->
14
+ # <html>
15
+ # <head>
16
+ # <?subscribe headers ?>
17
+ # </head>
18
+ # <body><?attr body ?></body>
19
+ # </html>
20
+ #
21
+ # <!-- In the body template, add a stylesheet link to the outer
22
+ # template's <head> -->
23
+ # <?publish headers ?>
24
+ # <link rel="stylesheet" ... />
25
+ # <?end ?>
26
+ # <div>(page content)</div>
27
+ #
28
+ class Inversion::Template::PublishTag < Inversion::Template::Tag
29
+ include Inversion::Template::ContainerTag
30
+
31
+
32
+ ### Create a new PublishTag with the given +body+.
33
+ def initialize( body, line=nil, column=nil )
34
+ super
35
+
36
+ key = self.body[ /^([a-z]\w+)$/ ] or
37
+ raise Inversion::ParseError,
38
+ "malformed key: expected simple identifier, got %p" % [ self.body ]
39
+ @key = key.to_sym
40
+ end
41
+
42
+
43
+ ######
44
+ public
45
+ ######
46
+
47
+ # The name of the key the nodes will be published under
48
+ attr_reader :key
49
+
50
+
51
+ ### Render the published subnodes in the context of the given +renderstate+, but
52
+ ### save them for publication after the render is done.
53
+ def render( renderstate )
54
+ self.log.debug "Publishing %d nodes as %s" % [ self.subnodes.length, self.key ]
55
+ rendered_nodes = []
56
+ renderstate.with_destination( rendered_nodes ) do
57
+ self.render_subnodes( renderstate )
58
+ end
59
+
60
+ renderstate.publish( self.key, *rendered_nodes ) unless rendered_nodes.empty?
61
+
62
+ return nil
63
+ end
64
+
65
+
66
+ ### Render the tag as the body of a comment, suitable for template debugging.
67
+ def as_comment_body
68
+ return "Published %d nodes as %s" % [ self.subnodes.length, self.key ]
69
+ end
70
+
71
+ end # class Inversion::Template::PublishTag
72
+
@@ -0,0 +1,88 @@
1
+ #!/usr/bin/env ruby
2
+ # vim: set noet nosta sw=4 ts=4 :
3
+
4
+ require 'inversion/template/tag'
5
+
6
+ # Inversion subscription tag.
7
+ #
8
+ # The subscribe tag places one or more published nodes from subtemplates.
9
+ #
10
+ # == Syntax
11
+ #
12
+ # <!-- Outer template -->
13
+ # <html>
14
+ # <head>
15
+ # <title><?subscribe title || Untitled ?></title>
16
+ # <?subscribe headers ?>
17
+ # </head>
18
+ # <body><?attr body ?></body>
19
+ # </html>
20
+ #
21
+ # <!-- In the body template, add a stylesheet link to the outer
22
+ # template's <head> -->
23
+ # <?publish headers ?>
24
+ # <link rel="stylesheet" ... />
25
+ # <?end ?>
26
+ # <div>(page content)</div>
27
+ #
28
+ class Inversion::Template::SubscribeTag < Inversion::Template::Tag
29
+
30
+ ### Create a new SubscribeTag with the given +body+.
31
+ def initialize( body, line=nil, column=nil )
32
+ super
33
+
34
+ unless self.body =~ /^([a-z]\w+)(?:\s*\|\|\s*(.+))?$/
35
+ raise Inversion::ParseError,
36
+ "malformed subscribe: %p" % [ self.body ]
37
+ end
38
+
39
+ key, default = $1, $2
40
+
41
+ @key = key.to_sym
42
+ @content = []
43
+ @default = default
44
+ end
45
+
46
+
47
+ ######
48
+ public
49
+ ######
50
+
51
+ # The name of the key the nodes will be published under
52
+ attr_reader :key
53
+
54
+
55
+ ### Tell the +renderstate+ that this tag is interested in nodes that are published with
56
+ ### its key.
57
+ def before_rendering( renderstate )
58
+ renderstate.subscribe( self.key, self )
59
+ end
60
+
61
+
62
+ ### Return the subscribe node itself to act as a placeholder for subscribed nodes.
63
+ def render( renderstate )
64
+ return self
65
+ end
66
+
67
+
68
+ ### Pub/sub callback. Called from the RenderState when a PublishTag publishes +nodes+
69
+ ### with the same key as the current tag.
70
+ def publish( *nodes )
71
+ @content.push( *nodes )
72
+ end
73
+
74
+
75
+ ### Stringify and join all of the published nodes for this subscription and return them
76
+ ### as a String.
77
+ def to_s
78
+ if @content.empty?
79
+ self.log.debug "Nothing published with the %p key, defaulting to %p" %
80
+ [ self.key, @default ]
81
+ return @default.to_s
82
+ else
83
+ return @content.map( &:to_s ).join( '' )
84
+ end
85
+ end
86
+
87
+ end # class Inversion::Template::SubscribeTag
88
+
@@ -0,0 +1,150 @@
1
+ #!/usr/bin/env ruby
2
+ # vim: set noet nosta sw=4 ts=4 :
3
+
4
+ require 'inversion' unless defined?( Inversion )
5
+ require 'inversion/template' unless defined?( Inversion::Template )
6
+
7
+ require 'inversion/template/node'
8
+ require 'inversion/mixins'
9
+
10
+ # Inversion template tag node base class. Represents a directive in a template
11
+ # that defines behavior and/or state.
12
+ #
13
+ # This class supports the RubyGems plugin API: to provide one or more Inversion tags
14
+ # in a gem of your own, put them into a directory named 'inversion/template' and
15
+ # name the files <tt><tagname>tag.rb</tt> and the classes <tagname.capitalize>Tag.
16
+ class Inversion::Template::Tag < Inversion::Template::Node
17
+ include Inversion::Loggable,
18
+ Inversion::AbstractClass
19
+
20
+ # The glob pattern for matching template tag plugins
21
+ TAG_PLUGIN_PATTERN = 'inversion/template/*tag.rb'
22
+
23
+
24
+ ########################################################################
25
+ ### C L A S S M E T H O D S
26
+ ########################################################################
27
+
28
+ # The hash of loaded tag types
29
+ @types = nil
30
+
31
+ # Derivatives of this class
32
+ @derivatives = []
33
+
34
+ class << self
35
+ attr_reader :types, :derivatives
36
+ end
37
+
38
+
39
+ ### Inheritance hook -- keep track of loaded derivatives.
40
+ def self::inherited( subclass )
41
+ # Inversion.log.debug "%p inherited from %p" % [ subclass, self ]
42
+ Inversion::Template::Tag.derivatives << subclass
43
+ super
44
+ end
45
+
46
+
47
+ ### Return a Hash of all loaded tag types, loading them if they haven't been loaded already.
48
+ def self::types
49
+ self.load_all unless @types
50
+ return @types
51
+ end
52
+
53
+
54
+ ### Load all available template tags and return them as a Hash keyed by their name.
55
+ def self::load_all
56
+ tags = {}
57
+
58
+ Gem.find_files( TAG_PLUGIN_PATTERN ).each do |tagfile|
59
+ tagname = tagfile[ %r{/(\w+)tag\.rb$}, 1 ].untaint
60
+
61
+ Inversion.log.debug "Loading tag type %p from %p" % [ tagname, tagfile ]
62
+ self.load( tagfile )
63
+
64
+ # Inversion.log.debug "Looking for class for %p tag" % [ tagname ]
65
+ tagclass = self.derivatives.find do |derivclass|
66
+ if derivclass.name.nil? || derivclass.name.empty?
67
+ # Inversion.log.debug " skipping anonymous class %p" % [ derivclass ]
68
+ nil
69
+ elsif !derivclass.respond_to?( :new )
70
+ # Inversion.log.debug " skipping abstract class %p" % [ derivclass ]
71
+ nil
72
+ else
73
+ derivclass.name.downcase =~ /\b#{tagname}tag$/
74
+ end
75
+ end
76
+
77
+ unless tagclass
78
+ Inversion.log.debug " no class found for %p tag" % [ tagname ]
79
+ next
80
+ end
81
+
82
+ Inversion.log.debug " found: %p" % [ tagclass ]
83
+ tags[ tagname.to_sym ] = tagclass
84
+ end
85
+
86
+ @types ||= {}
87
+ @types.merge!( tags )
88
+
89
+ return @types
90
+ end
91
+
92
+
93
+ ### Safely load the specified +tagfile+.
94
+ def self::load( tagfile )
95
+ require( tagfile )
96
+ rescue => err
97
+ Inversion.log.error "%s while loading tag plugin %p: %s" %
98
+ [ err.class.name, tagfile, err.message ]
99
+ Inversion.log.debug " " + err.backtrace.join( "\n " )
100
+ return false
101
+ end
102
+
103
+
104
+ ### Create a new Inversion::Template::Tag from the specified +tagname+ and +body+.
105
+ def self::create( tagname, body, linenum=nil, colnum=nil )
106
+ tagname =~ /^(\w+)$/i or raise ArgumentError, "invalid tag name %p" % [ tagname ]
107
+ tagtype = $1.downcase.untaint
108
+
109
+ unless tagclass = self.types[ tagtype.to_sym ]
110
+ Inversion.log.warn "Unknown tag type %p; registered: %p" %
111
+ [ tagtype, self.types.keys ]
112
+ return nil
113
+ end
114
+
115
+ return tagclass.new( body, linenum, colnum )
116
+ end
117
+
118
+
119
+ ########################################################################
120
+ ### I N S T A N C E M E T H O D S
121
+ ########################################################################
122
+
123
+ ### Create a new Inversion::Template::Tag with the specified +body+.
124
+ def initialize( body, linenum=nil, colnum=nil )
125
+ super
126
+ @body = body.to_s.strip
127
+ end
128
+
129
+
130
+ ######
131
+ public
132
+ ######
133
+
134
+ # the body of the tag
135
+ attr_reader :body
136
+
137
+
138
+ ### Render the tag as the body of a comment, suitable for template debugging.
139
+ def as_comment_body
140
+ return "%s %s at %s" % [ self.tagname, self.body.to_s.dump, self.location ]
141
+ end
142
+
143
+
144
+ ### Return the human-readable name of the tag class
145
+ def tagname
146
+ return self.class.name.sub(/Tag$/, '').sub( /^.*::/, '' )
147
+ end
148
+
149
+ end # class Inversion::Template::Tag
150
+
@@ -0,0 +1,43 @@
1
+ #!/usr/bin/env ruby
2
+ # vim: set noet nosta sw=4 ts=4 :
3
+
4
+ require 'inversion/template/node'
5
+
6
+ # Inversion text node class -- static content in templates between tags are contained
7
+ # in a text node.
8
+ class Inversion::Template::TextNode < Inversion::Template::Node
9
+ include Inversion::Loggable
10
+
11
+ ### Create a new TextNode with the specified +source+.
12
+ def initialize( body, linenum=nil, colnum=nil )
13
+ @body = body
14
+ super
15
+ end
16
+
17
+
18
+ ######
19
+ public
20
+ ######
21
+
22
+ # The node body
23
+ attr_reader :body
24
+
25
+
26
+ ### Render the node.
27
+ def render( renderstate )
28
+ body = self.body.dup
29
+ body.sub!( /\A\r?\n/, '' ) if renderstate && renderstate.options[:strip_tag_lines]
30
+
31
+ return body
32
+ end
33
+
34
+
35
+ ### Render the text node as the body of a comment.
36
+ def as_comment_body
37
+ comment_body = self.body[0,40].dump
38
+ comment_body[-1,0] = '...' unless comment_body == self.body.dump
39
+ return "Text (%d bytes): %s" % [ self.body.length, comment_body ]
40
+ end
41
+
42
+ end # class Inversion::Template::TextNode
43
+
@@ -0,0 +1,60 @@
1
+ #!/usr/bin/env ruby
2
+ # vim: set noet nosta sw=4 ts=4 :
3
+
4
+ require 'inversion/mixins'
5
+ require 'inversion/template/attrtag'
6
+ require 'inversion/template/containertag'
7
+ require 'inversion/template/conditionaltag'
8
+ require 'inversion/template/elsetag'
9
+
10
+
11
+ # Inversion 'unless' tag.
12
+ #
13
+ # This tag causes a section of the template to be rendered only if its methodchain or attribute
14
+ # is a *false* value.
15
+ #
16
+ # == Syntax
17
+ #
18
+ # <?unless attr ?>...<?end?>
19
+ # <?unless obj.method ?>...<?end?>
20
+ #
21
+ class Inversion::Template::UnlessTag < Inversion::Template::AttrTag
22
+ include Inversion::Loggable,
23
+ Inversion::Template::ContainerTag,
24
+ Inversion::Template::ConditionalTag
25
+
26
+ # Inherits AttrTag's tag patterns
27
+
28
+ ### Render the tag's contents if the condition is true, or any else or elsif sections
29
+ ### if the condition isn't true.
30
+ def render( state )
31
+ self.enable_rendering unless super
32
+ self.render_subnodes( state )
33
+
34
+ return state
35
+ end
36
+
37
+ ### Render the tag's subnodes according to the tag's logical state.
38
+ def render_subnodes( renderstate )
39
+ self.log.debug "Rendering subnodes. Rendering initially %s" %
40
+ [ self.rendering_enabled? ? "enabled" : "disabled" ]
41
+
42
+ # walk the subtree, modifying the logic flags for else and elsif tags,
43
+ # and rendering nodes if rendering is enabled
44
+ self.subnodes.each do |node|
45
+ if node.is_a?( Inversion::Template::ElseTag )
46
+ self.log.debug " logic switch: %p..." % [ node ]
47
+ if !self.rendering_was_enabled?
48
+ self.enable_rendering
49
+ else
50
+ self.disable_rendering
51
+ end
52
+
53
+ else
54
+ renderstate << node if self.rendering_enabled?
55
+ end
56
+ end
57
+ end
58
+
59
+ end # class Inversion::Template::UnlessTag
60
+
@@ -0,0 +1,30 @@
1
+ #!/usr/bin/env ruby
2
+ # vim: set noet nosta sw=4 ts=4 :
3
+
4
+ require 'uri'
5
+ require 'inversion/template/attrtag'
6
+
7
+ # Inversion URL encoding tag.
8
+ #
9
+ # This tag is a derivative of the 'attr' tag that encodes the results of its method call
10
+ # according to RFC 3986.
11
+ #
12
+ # == Syntax
13
+ #
14
+ # <?uriencode foo.bar ?>
15
+ #
16
+ class Inversion::Template::UriencodeTag < Inversion::Template::AttrTag
17
+
18
+ # Unreserved characters from section 2.3 of RFC 3986
19
+ # ALPHA / DIGIT / "-" / "." / "_" / "~"
20
+ DEFAULT_ENCODED_CHARACTERS = /[^\w\-\.~]/
21
+
22
+ ### Render the method chains against the attributes of the specified +render_state+
23
+ ### and return them.
24
+ def render( render_state )
25
+ raw = super
26
+ return URI.encode( raw.to_s, DEFAULT_ENCODED_CHARACTERS )
27
+ end
28
+
29
+ end # class Inversion::Template::UriencodeTag
30
+