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,51 @@
1
+ #!/usr/bin/env ruby
2
+ # vim: set noet nosta sw=4 ts=4 :
3
+
4
+ require 'pathname'
5
+ require 'inversion/mixins'
6
+ require 'inversion/template/tag'
7
+
8
+
9
+ # Inversion 'yield' tag.
10
+ #
11
+ # A tag that yields to the block passed to Template#render (if there was one), and
12
+ # then inserts the resulting objects.
13
+ #
14
+ # == Example
15
+ #
16
+ # <?yield ?>
17
+ #
18
+ class Inversion::Template::YieldTag < Inversion::Template::Tag
19
+ include Inversion::Loggable
20
+
21
+
22
+ ### Set up a YieldTag's instance variables.
23
+ def initialize( * )
24
+ @block_value = nil
25
+ end
26
+
27
+
28
+ ######
29
+ public
30
+ ######
31
+
32
+ ### Rendering callback -- call the block before the template this tag
33
+ ### belongs to is rendered.
34
+ def before_rendering( renderstate )
35
+ if renderstate.block
36
+ self.log.debug "Yielding to %p before rendering." % [ renderstate.block ]
37
+ @block_value = renderstate.block.call( renderstate )
38
+ self.log.debug " render block returned: %p" % [ @block_value ]
39
+ end
40
+ end
41
+
42
+
43
+ ### Render the YieldTag by returning what the #render block returned during
44
+ ### #before_rendering (if there was a block).
45
+ def render( renderstate )
46
+ self.log.debug "Rendering as block return value: %p" % [ @block_value ]
47
+ return @block_value
48
+ end
49
+
50
+ end # class Inversion::Template::YieldTag
51
+
@@ -0,0 +1,82 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'tilt'
4
+
5
+ # Only add support if Tilt's already been loaded.
6
+ if defined?( ::Tilt )
7
+
8
+ # An adapter class for Tilt (https://github.com/rtomayko/tilt)
9
+ # :TODO: Add an example or two.
10
+ class Inversion::TiltWrapper < Tilt::Template
11
+
12
+ ### Tilt::Template API: returns true if Inversion is loaded.
13
+ def self::engine_initialized?
14
+ return defined?( Inversion::Template )
15
+ end
16
+
17
+
18
+ ### Tilt::Template API: lazy-load Inversion
19
+ def initialize_engine
20
+ require_template_library 'inversion'
21
+ end
22
+
23
+
24
+ ### Tilt::Template API: load a template
25
+ def prepare
26
+ # Load the instance and set the path to the source
27
+ @template = Inversion::Template.new( self.data, self.options )
28
+ @template.source_file = self.file
29
+ end
30
+
31
+
32
+ ### Hook the template's render phase.
33
+ def render( *args )
34
+ self.evaluate( *args )
35
+ end
36
+
37
+ ### Tilt::Template API: render the template with the given +scope+, +locals+, and +block+.
38
+ def evaluate( scope, locals, &block )
39
+ @template.attributes.merge!( scope.to_h ) if scope.respond_to?( :to_h )
40
+ @template.attributes.merge!( locals )
41
+
42
+ return @template.render( &block )
43
+ end
44
+
45
+ end # class Inversion::TiltWrapper
46
+
47
+ # Also add #each to Inversion::Template so they can be returned from actions directly, too.
48
+ module Inversion::TemplateTiltAdditions
49
+
50
+ # TODO: Factor the common parts of this out in Inversion::Template so there's no
51
+ # duplication.
52
+ def each
53
+ self.log.info "rendering template 0x%08x (Sinatra-style)" % [ self.object_id/2 ]
54
+ state = Inversion::RenderState.new( nil, self.attributes, self.options )
55
+
56
+ # Pre-render hook
57
+ self.walk_tree {|node| node.before_rendering(state) }
58
+
59
+ self.log.debug " rendering node tree: %p" % [ @node_tree ]
60
+ self.walk_tree {|node| state << node }
61
+
62
+ # Post-render hook
63
+ self.walk_tree {|node| node.after_rendering(state) }
64
+
65
+ self.log.info " done rendering template 0x%08x" % [ self.object_id/2 ]
66
+ return state.destination.each do |node|
67
+ yield( node.to_s )
68
+ end
69
+ end
70
+
71
+ end # module Inversion::TemplateTiltAdditions
72
+
73
+ # Add the mixin to Template
74
+ class Inversion::Template
75
+ include Inversion::TemplateTiltAdditions
76
+ end
77
+
78
+
79
+ Tilt.register( Inversion::TiltWrapper, 'tmpl', 'inversion' )
80
+
81
+ end
82
+
@@ -0,0 +1,235 @@
1
+ #!/usr/bin/env ruby
2
+ # vim: set nosta noet ts=4 sw=4:
3
+
4
+ require 'logger'
5
+ require 'erb'
6
+
7
+ require 'inversion' unless defined?( Inversion )
8
+ require 'inversion/mixins'
9
+
10
+
11
+ module Inversion
12
+
13
+ # :stopdoc:
14
+
15
+ # A alternate formatter for Logger instances.
16
+ class LogFormatter < Logger::Formatter
17
+
18
+ # The format to output unless debugging is turned on
19
+ DEFAULT_FORMAT = "[%1$s.%2$06d %3$d/%4$s] %5$5s -- %7$s\n"
20
+
21
+ # The format to output if debugging is turned on
22
+ DEFAULT_DEBUG_FORMAT = "[%1$s.%2$06d %3$d/%4$s] %5$5s {%6$s} -- %7$s\n"
23
+
24
+
25
+ ### Initialize the formatter with a reference to the logger so it can check for log level.
26
+ def initialize( logger, format=DEFAULT_FORMAT, debug=DEFAULT_DEBUG_FORMAT ) # :notnew:
27
+ @logger = logger
28
+ @format = format
29
+ @debug_format = debug
30
+
31
+ super()
32
+ end
33
+
34
+ ######
35
+ public
36
+ ######
37
+
38
+ # The Logger object associated with the formatter
39
+ attr_accessor :logger
40
+
41
+ # The logging format string
42
+ attr_accessor :format
43
+
44
+ # The logging format string that's used when outputting in debug mode
45
+ attr_accessor :debug_format
46
+
47
+
48
+ ### Log using either the DEBUG_FORMAT if the associated logger is at ::DEBUG level or
49
+ ### using FORMAT if it's anything less verbose.
50
+ def call( severity, time, progname, msg )
51
+ args = [
52
+ time.strftime( '%Y-%m-%d %H:%M:%S' ), # %1$s
53
+ time.usec, # %2$d
54
+ Process.pid, # %3$d
55
+ Thread.current == Thread.main ? 'main' : Thread.object_id, # %4$s
56
+ severity, # %5$s
57
+ progname, # %6$s
58
+ msg # %7$s
59
+ ]
60
+
61
+ if @logger.level == Logger::DEBUG
62
+ return self.debug_format % args
63
+ else
64
+ return self.format % args
65
+ end
66
+ end
67
+ end # class LogFormatter
68
+
69
+
70
+ # A ANSI-colorized formatter for Logger instances.
71
+ class ColorLogFormatter < Logger::Formatter
72
+
73
+ # Set some ANSI escape code constants (Shamelessly stolen from Perl's
74
+ # Term::ANSIColor by Russ Allbery <rra@stanford.edu> and Zenin <zenin@best.com>
75
+ ANSI_ATTRIBUTES = {
76
+ 'clear' => 0,
77
+ 'reset' => 0,
78
+ 'bold' => 1,
79
+ 'dark' => 2,
80
+ 'underline' => 4,
81
+ 'underscore' => 4,
82
+ 'blink' => 5,
83
+ 'reverse' => 7,
84
+ 'concealed' => 8,
85
+
86
+ 'black' => 30, 'on_black' => 40,
87
+ 'red' => 31, 'on_red' => 41,
88
+ 'green' => 32, 'on_green' => 42,
89
+ 'yellow' => 33, 'on_yellow' => 43,
90
+ 'blue' => 34, 'on_blue' => 44,
91
+ 'magenta' => 35, 'on_magenta' => 45,
92
+ 'cyan' => 36, 'on_cyan' => 46,
93
+ 'white' => 37, 'on_white' => 47
94
+ }
95
+
96
+
97
+ ### Create a string that contains the ANSI codes specified and return it
98
+ def self::ansi_code( *attributes )
99
+ attributes.flatten!
100
+ attributes.collect! {|at| at.to_s }
101
+ return '' unless /(?:vt10[03]|xterm(?:-color)?|linux|screen)/i =~ ENV['TERM']
102
+ attributes = ANSI_ATTRIBUTES.values_at( *attributes ).compact.join(';')
103
+
104
+ if attributes.empty?
105
+ return ''
106
+ else
107
+ return "\e[%sm" % attributes
108
+ end
109
+ end
110
+
111
+
112
+ ### Colorize the given +string+ with the specified +attributes+ and
113
+ ### return it, handling line-endings, color reset, etc.
114
+ def self::colorize( *args )
115
+ string = ''
116
+
117
+ if block_given?
118
+ string = yield
119
+ else
120
+ string = args.shift
121
+ end
122
+
123
+ ending = string[/(\s)$/] || ''
124
+ string = string.rstrip
125
+
126
+ return self.ansi_code( args.flatten ) + string + self.ansi_code( 'reset' ) + ending
127
+ end
128
+
129
+
130
+ # Color settings
131
+ LEVEL_FORMATS = {
132
+ :debug => colorize( :bold, :black ) {"[%1$s.%2$06d %3$d/%4$s] %5$5s {%6$s} -- %7$s\n"},
133
+ :info => colorize( :normal ) {"[%1$s.%2$06d %3$d/%4$s] %5$5s -- %7$s\n"},
134
+ :warn => colorize( :bold, :yellow ) {"[%1$s.%2$06d %3$d/%4$s] %5$5s -- %7$s\n"},
135
+ :error => colorize( :red ) {"[%1$s.%2$06d %3$d/%4$s] %5$5s -- %7$s\n"},
136
+ :fatal => colorize( :bold, :red, :on_white ) {"[%1$s.%2$06d %3$d/%4$s] %5$5s -- %7$s\n"},
137
+ }
138
+
139
+
140
+ ### Initialize the formatter with a reference to the logger so it can check for log level.
141
+ def initialize( logger, settings={} ) # :notnew:
142
+ settings = LEVEL_FORMATS.merge( settings )
143
+
144
+ @logger = logger
145
+ @settings = settings
146
+
147
+ super()
148
+ end
149
+
150
+ ######
151
+ public
152
+ ######
153
+
154
+ # The Logger object associated with the formatter
155
+ attr_accessor :logger
156
+
157
+ # The formats, by level
158
+ attr_accessor :settings
159
+
160
+
161
+ ### Log using the format associated with the severity
162
+ def call( severity, time, progname, msg )
163
+ args = [
164
+ time.strftime( '%Y-%m-%d %H:%M:%S' ), # %1$s
165
+ time.usec, # %2$d
166
+ Process.pid, # %3$d
167
+ Thread.current == Thread.main ? 'main' : Thread.object_id, # %4$s
168
+ severity, # %5$s
169
+ progname, # %6$s
170
+ msg # %7$s
171
+ ]
172
+
173
+ return self.settings[ severity.downcase.to_sym ] % args
174
+ end
175
+
176
+ end # class LogFormatter
177
+
178
+
179
+ # An alternate formatter for Logger instances that outputs +div+ HTML
180
+ # fragments.
181
+ class HtmlLogFormatter < Logger::Formatter
182
+ include ERB::Util # for html_escape()
183
+
184
+ # The default HTML fragment that'll be used as the template for each log message.
185
+ HTML_LOG_FORMAT = %q{
186
+ <div class="log-message %5$s">
187
+ <span class="log-time">%1$s.%2$06d</span>
188
+ [
189
+ <span class="log-pid">%3$d</span>
190
+ /
191
+ <span class="log-tid">%4$s</span>
192
+ ]
193
+ <span class="log-level">%5$s</span>
194
+ :
195
+ <span class="log-name">%6$s</span>
196
+ <span class="log-message-text">%7$s</span>
197
+ </div>
198
+ }
199
+
200
+ ### Override the logging formats with ones that generate HTML fragments
201
+ def initialize( logger, format=HTML_LOG_FORMAT ) # :notnew:
202
+ @logger = logger
203
+ @format = format
204
+ super()
205
+ end
206
+
207
+
208
+ ######
209
+ public
210
+ ######
211
+
212
+ # The HTML fragment that will be used as a format() string for the log
213
+ attr_accessor :format
214
+
215
+
216
+ ### Return a log message composed out of the arguments formatted using the
217
+ ### formatter's format string
218
+ def call( severity, time, progname, msg )
219
+ args = [
220
+ time.strftime( '%Y-%m-%d %H:%M:%S' ), # %1$s
221
+ time.usec, # %2$d
222
+ Process.pid, # %3$d
223
+ Thread.current == Thread.main ? 'main' : Thread.object_id, # %4$s
224
+ severity.downcase, # %5$s
225
+ progname, # %6$s
226
+ html_escape( msg ).gsub(/\n/, '<br />') # %7$s
227
+ ]
228
+
229
+ return self.format % args
230
+ end
231
+
232
+ end # class HtmlLogFormatter
233
+
234
+ end # module Inversion
235
+
@@ -0,0 +1 @@
1
+ Hello, Sinatra!
@@ -0,0 +1,177 @@
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
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
+
16
+ require 'inversion/mixins'
17
+
18
+
19
+ describe Inversion, "mixins" do
20
+
21
+ describe Inversion::Loggable do
22
+ before(:each) do
23
+ @logfile = StringIO.new('')
24
+ Inversion.logger = Logger.new( @logfile )
25
+
26
+ @test_class = Class.new do
27
+ include Inversion::Loggable
28
+
29
+ def log_test_message( level, msg )
30
+ self.log.send( level, msg )
31
+ end
32
+
33
+ def logdebug_test_message( msg )
34
+ self.log_debug.debug( msg )
35
+ end
36
+ end
37
+ @obj = @test_class.new
38
+ end
39
+
40
+
41
+ it "is able to output to the log via its #log method" do
42
+ @obj.log_test_message( :debug, "debugging message" )
43
+ @logfile.rewind
44
+ @logfile.read.should =~ /debugging message/
45
+ end
46
+
47
+ it "is able to output to the log via its #log_debug method" do
48
+ @obj.logdebug_test_message( "sexydrownwatch" )
49
+ @logfile.rewind
50
+ @logfile.read.should =~ /sexydrownwatch/
51
+ end
52
+ end
53
+
54
+
55
+ describe Inversion::HashUtilities do
56
+ it "includes a function for stringifying Hash keys" do
57
+ testhash = {
58
+ :foo => 1,
59
+ :bar => {
60
+ :klang => 'klong',
61
+ :barang => { :kerklang => 'dumdumdum' },
62
+ }
63
+ }
64
+
65
+ result = Inversion::HashUtilities.stringify_keys( testhash )
66
+
67
+ result.should be_an_instance_of( Hash )
68
+ result.should_not be_equal( testhash )
69
+ result.should == {
70
+ 'foo' => 1,
71
+ 'bar' => {
72
+ 'klang' => 'klong',
73
+ 'barang' => { 'kerklang' => 'dumdumdum' },
74
+ }
75
+ }
76
+ end
77
+
78
+
79
+ it "includes a function for symbolifying Hash keys" do
80
+ testhash = {
81
+ 'foo' => 1,
82
+ 'bar' => {
83
+ 'klang' => 'klong',
84
+ 'barang' => { 'kerklang' => 'dumdumdum' },
85
+ }
86
+ }
87
+
88
+ result = Inversion::HashUtilities.symbolify_keys( testhash )
89
+
90
+ result.should be_an_instance_of( Hash )
91
+ result.should_not be_equal( testhash )
92
+ result.should == {
93
+ :foo => 1,
94
+ :bar => {
95
+ :klang => 'klong',
96
+ :barang => { :kerklang => 'dumdumdum' },
97
+ }
98
+ }
99
+ end
100
+
101
+ end
102
+
103
+
104
+ describe Inversion::AbstractClass do
105
+
106
+ context "mixed into a class" do
107
+ it "will cause the including class to hide its ::new method" do
108
+ testclass = Class.new { include Inversion::AbstractClass }
109
+
110
+ expect {
111
+ testclass.new
112
+ }.to raise_error( NoMethodError, /private/ )
113
+ end
114
+
115
+ end
116
+
117
+
118
+ context "mixed into a superclass" do
119
+
120
+ before(:each) do
121
+ testclass = Class.new {
122
+ include Inversion::AbstractClass
123
+ pure_virtual :test_method
124
+ }
125
+ subclass = Class.new( testclass )
126
+ @instance = subclass.new
127
+ end
128
+
129
+
130
+ it "raises a NotImplementedError when unimplemented API methods are called" do
131
+ expect {
132
+ @instance.test_method
133
+ }.to raise_error( NotImplementedError, /does not provide an implementation of/ )
134
+ end
135
+
136
+ it "declares the virtual methods so that they can be used with arguments under Ruby 1.9" do
137
+ expect {
138
+ @instance.test_method( :some, :arguments )
139
+ }.to raise_error( NotImplementedError, /does not provide an implementation of/ )
140
+ end
141
+
142
+ end
143
+
144
+ end
145
+
146
+
147
+ describe Inversion::Escaping do
148
+
149
+ before( :each ) do
150
+ objclass = Class.new do
151
+ include Inversion::Escaping
152
+
153
+ def render( state )
154
+ return self.escape( "<something>", state )
155
+ end
156
+ end
157
+ @obj = objclass.new
158
+ end
159
+
160
+ it "adds configurable escaping to including classes" do
161
+ render_state = Inversion::RenderState.new( {}, :escape_format => :html )
162
+ @obj.render( render_state ).should == "&lt;something&gt;"
163
+ end
164
+
165
+ it "doesn't escape anything if escaping is disabled" do
166
+ render_state = Inversion::RenderState.new( {}, :escape_format => nil )
167
+ @obj.render( render_state ).should == "<something>"
168
+ end
169
+
170
+ it "doesn't escape anything if escaping is set to ':none'" do
171
+ render_state = Inversion::RenderState.new( {}, :escape_format => :none )
172
+ @obj.render( render_state ).should == "<something>"
173
+ end
174
+ end
175
+
176
+ end
177
+