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,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
+