undies 2.2.0 → 2.2.1

Sign up to get free protection for your applications and to get access to all the features.
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- undies (2.2.0)
4
+ undies (2.2.1)
5
5
 
6
6
  GEM
7
7
  remote: http://rubygems.org/
@@ -15,6 +15,8 @@ GEM
15
15
  erubis (2.7.0)
16
16
  rake (0.9.2)
17
17
  ruby-prof (0.10.8)
18
+ whysoslow (0.0.2)
19
+ ansi (~> 1.4)
18
20
 
19
21
  PLATFORMS
20
22
  ruby
@@ -27,3 +29,4 @@ DEPENDENCIES
27
29
  rake (~> 0.9.2)
28
30
  ruby-prof
29
31
  undies!
32
+ whysoslow (~> 0.0)
data/README.rdoc CHANGED
@@ -153,9 +153,30 @@ Now just interact with the Undies API directly.
153
153
  *Note:* there is one extra caveat to be aware of using this approach. You need to be sure and flush the template when content processing is complete. Just pass the template to the Undies::Template#flush method:
154
154
 
155
155
  # ensures all content is streamed to the template's output stream
156
- # this is only necessary using the "builder approach"
156
+ # this is necessary when not using the source approach above
157
157
  Undies::Template.flush(template)
158
158
 
159
+ === Manual approach
160
+
161
+ There is another method you can use to render output: the Manual approach. Like the Builder approach, this method is ideal when you don't know the source until render time. The key difference is that blocks are not used to imply nesting relationships. Using this approach, you manually 'push' and 'pop' to move up and down nesting relationship contexts. So a push on an element would move the template context to the element pushed. A pop would move back to the current context's parent element. As you would expect, pop'ing on the root of a template has no effect on the context and pushing a non-element node has no effect on the context.
162
+
163
+ To render using this approach, create a Template as you would with the Builder approach. Interact with the Undies API directly. Use the Template#push and Template#pop class methods to change the template scope.
164
+
165
+ # this is the equivalent to the Builder approach example above
166
+
167
+ template = Undies::Template.new(Undies::Output.new(@io))
168
+ something = "Some Thing!"
169
+ current = template._div.something!
170
+
171
+ template.__push(current)
172
+ template._ something.to_s
173
+ template.__pop
174
+
175
+ # alternate method for flushing a template
176
+ template.__flush
177
+
178
+ *Note:* as with the Builder approach, you must flush the template when content processing is complete.
179
+
159
180
  == License
160
181
 
161
182
  Copyright (c) 2011-Present Kelly Redding
@@ -1,118 +1,99 @@
1
- require 'benchmark'
1
+ require 'whysoslow'
2
2
  require 'stringio'
3
- require 'ansi'
4
3
 
5
- require 'undies'
6
4
  require 'erb'
7
5
  require 'erubis'
6
+ require 'undies'
8
7
 
9
- class BenchResults
8
+ class UndiesBenchResults
10
9
 
11
- attr_reader :user, :system, :total, :real
12
- attr_accessor :name, :ext, :size, :out, :outstream
10
+ attr_accessor :name, :ext, :size
13
11
 
14
- def initialize(name, ext, size)
12
+ def initialize(name, ext, size, build)
15
13
  @name = name.to_s
16
14
  @ext = ext.to_s
17
15
  @size = size.to_s
18
- @user, @system, @total, @real = 0
19
- @outstream = StringIO.new(@out = "")
16
+ @build = build
17
+
18
+ @printer = Whysoslow::DefaultPrinter.new({
19
+ :title => "#{@name}, #{@size}",
20
+ :verbose => true
21
+ })
22
+ @runner = Whysoslow::Runner.new(@printer)
20
23
  end
21
24
 
22
25
  def file
23
26
  File.expand_path("bench/#{@size}.html#{@ext}")
24
27
  end
25
28
 
26
- def user=(value_in_secs); @user = value_in_secs.to_f * 1000; end
27
- def system=(value_in_secs); @system = value_in_secs.to_f * 1000; end
28
- def total=(value_in_secs); @total = value_in_secs.to_f * 1000; end
29
- def real=(value_in_secs); @real = value_in_secs.to_f * 1000; end
30
-
31
- def to_s(meas=:real, basis=nil)
32
- "#{name_s} (#{size_s}): #{time_s(meas)} ms #{"(#{basis_s(meas, basis)})" if basis}"
29
+ def run
30
+ @runner.run &@build
33
31
  end
34
32
 
35
- protected
36
-
37
- def measure(&block)
38
- Benchmark.measure(&block).to_s.strip.gsub(/[^\s|0-9|\.]/, '').split(/\s+/).tap do |values|
39
- self.user, self.system, self.total, self.real = values
40
- end
41
- end
33
+ end
42
34
 
43
- def name_s; @name.ljust(6); end
44
- def size_s; @size.to_s; end
45
- def time_s(meas); self.send(meas).to_s.rjust(7); end
46
-
47
- def basis_s(meas, time)
48
- diff = (time - self.send(meas))
49
- perc = ((diff / time) * 100).round
50
- if diff >= 0
51
- ANSI.green + "+#{diff} ms, +#{perc}%" + ANSI.reset
52
- else
53
- ANSI.red + "#{diff} ms, #{perc}%" + ANSI.reset
54
- end
55
- end
56
35
 
57
- end
58
36
 
59
- class UndiesResults < BenchResults
37
+ class UndiesResults < UndiesBenchResults
60
38
 
61
39
  def initialize(size='large')
62
- super(:undies, '.rb', size)
63
- measure do
40
+ @outstream = StringIO.new(@out = "")
41
+ super(:undies, '.rb', size, Proc.new do
64
42
  Undies::Template.new(
65
43
  Undies::Source.new(self.file),
66
44
  {},
67
- Undies::Output.new(self.outstream, :pp => 2)
45
+ Undies::Output.new(@outstream, :pp => 2)
68
46
  )
69
- end
47
+ end)
70
48
  end
71
49
 
72
50
  end
73
51
 
74
- class ErbResults < BenchResults
52
+ class ErbResults < UndiesBenchResults
75
53
 
76
54
  def initialize(size='large')
77
- super(:erb, '.erb', size)
78
- measure do
79
- self.out = ERB.new(File.read(self.file), 0, "%<>").result(binding)
80
- end
55
+ @out = ""
56
+ super(:erb, '.erb', size, Proc.new do
57
+ @out = ERB.new(File.read(self.file), 0, "%<>").result(binding)
58
+ end)
81
59
  end
82
60
 
83
61
  end
84
62
 
85
- class ErubisResults < BenchResults
63
+ class ErubisResults < UndiesBenchResults
86
64
 
87
65
  def initialize(size='large')
88
- super(:erubis, '.erb', size)
89
- measure do
90
- self.out = Erubis::Eruby.new(File.read(self.file)).result(binding)
91
- end
66
+ @out = ""
67
+ super(:erubis, '.erb', size, Proc.new do
68
+ @out = Erubis::Eruby.new(File.read(self.file)).result(binding)
69
+ end)
92
70
  end
93
71
 
94
72
  end
95
73
 
74
+
75
+
96
76
  class UndiesBenchRunner
97
77
 
98
78
  SIZES = {
99
- :small => "~20 nodes",
100
- :large => "~2000 nodes",
79
+ # :small => "~20 nodes",
80
+ # :large => "~2000 nodes",
101
81
  :verylarge => "~20000 nodes"
102
82
  }
103
83
 
84
+
104
85
  def initialize
105
86
  puts "Benchmark Results:"
106
87
  puts
107
- [:small, :large, :verylarge].each do |size|
108
- puts "#{size.to_s.upcase} (#{SIZES[size]})"
109
- puts '-'*(size.to_s.length+3+SIZES[size].length)
110
- basis = UndiesResults.new(size)
111
- puts basis.to_s(:real)
112
- puts ErbResults.new(size).to_s(:real, basis.real)
113
- puts ErubisResults.new(size).to_s(:real, basis.real)
88
+ SIZES.each do |size, desc|
89
+ ErbResults.new(size).run
90
+ puts
91
+ ErubisResults.new(size).run
92
+ puts
93
+ UndiesResults.new(size).run
114
94
  puts
115
95
  end
96
+ puts
116
97
  end
117
98
 
118
99
  end
@@ -29,35 +29,59 @@ module Undies
29
29
  gsub('"', '&quot;')
30
30
  end
31
31
 
32
- def self.content(element)
33
- nil
32
+ def self.set_children(element, value)
33
+ element.instance_variable_set("@children", value)
34
34
  end
35
35
 
36
- def self.flush(output, element)
37
- output.pp_use_indent = true
38
- output << element.instance_variable_get("@start_tag")
39
- if (cbs = element.instance_variable_get("@content_blocks")).size > 0
40
- output.pp_level += 1
41
- output.pp_use_indent = false
42
- cbs.each{ |content| content.call }
43
- output.flush
44
- output.pp_level -= 1
36
+ def self.children(element)
37
+ element.instance_variable_get("@children")
38
+ end
39
+
40
+ def self.prefix(element, meth, level, indent)
41
+ "".tap do |value|
42
+ if indent > 0
43
+ if meth == 'start_tag'
44
+ value << "#{level > 0 ? "\n": ''}#{' '*level*indent}"
45
+ elsif meth == 'end_tag'
46
+ value << "\n#{' '*level*indent}" if children(element)
47
+ end
48
+ end
45
49
  end
46
- output << element.instance_variable_get("@end_tag") if element.instance_variable_get("@end_tag")
47
- output.pp_use_indent = true
48
50
  end
49
51
 
50
- def initialize(name, attrs={}, &block)
52
+ def self.set_start_tag(element)
53
+ element.instance_variable_set(
54
+ "@start_tag",
55
+ "<#{node_name(element)}#{hash_attrs(attrs(element))}" + (builds(element).size > 0 ? ">" : " />")
56
+ )
57
+ end
58
+
59
+ def self.set_end_tag(element)
60
+ element.instance_variable_set(
61
+ "@end_tag",
62
+ builds(element).size > 0 ? "</#{node_name(element)}>" : nil
63
+ )
64
+ end
65
+
66
+ def initialize(name, attrs={}, &build)
67
+ super(nil)
68
+ @start_tag = ""
69
+ @end_tag = ""
70
+ @content = nil
71
+ @builds = []
72
+ @children = false
73
+
51
74
  if !attrs.kind_of?(::Hash)
52
75
  raise ArgumentError, "#{name.inspect} attrs must be provided as a Hash."
53
76
  end
54
77
 
55
78
  @name = name.to_s
56
79
  @attrs = attrs
57
- @content_blocks = []
58
- @content_blocks << block if block
59
- @start_tag = start_tag
60
- @end_tag = end_tag
80
+ @builds << build if build
81
+
82
+ # cache in an instance variable for fast access with flush and pop
83
+ self.class.set_start_tag(self)
84
+ self.class.set_end_tag(self)
61
85
  end
62
86
 
63
87
  # CSS proxy methods ============================================
@@ -89,35 +113,30 @@ module Undies
89
113
 
90
114
  def ==(other)
91
115
  other.instance_variable_get("@name") == @name &&
92
- other.instance_variable_get("@attrs") == @attrs &&
93
- other.instance_variable_get("@nodes") == @nodes
116
+ other.instance_variable_get("@attrs") == @attrs
94
117
  end
95
118
 
96
119
  # overriding this because the base Node class defines a 'to_s' method that
97
120
  # needs to be honored
98
121
  def to_str(*args)
99
122
  "Undies::Element:#{self.object_id} " +
100
- "@name=#{@name.inspect}, @attrs=#{@attrs.inspect}, @nodes=#{@nodes.inspect}"
123
+ "@name=#{@name.inspect}, @attrs=#{@attrs.inspect}"
101
124
  end
102
125
  alias_method :inspect, :to_str
103
126
 
104
127
  private
105
128
 
106
- def proxy(value, attrs, content_block)
129
+ def proxy(value, attrs, build)
107
130
  yield value if block_given?
108
131
  @attrs.merge!(attrs)
109
- @content_blocks << content_block if content_block
110
- @start_tag = start_tag
111
- @end_tag = end_tag
112
- self
113
- end
132
+ @builds << build if build
114
133
 
115
- def start_tag
116
- "<#{@name}#{self.class.hash_attrs(@attrs)}" + (@content_blocks.size > 0 ? ">" : " />")
117
- end
134
+ # cache in an instance variable for fast access with flush and pop
135
+ self.class.set_start_tag(self)
136
+ self.class.set_end_tag(self)
118
137
 
119
- def end_tag
120
- @content_blocks.size > 0 ? "</#{@name}>" : nil
138
+ # return self so you can chain proxy method calls
139
+ self
121
140
  end
122
141
 
123
142
  end
data/lib/undies/node.rb CHANGED
@@ -2,28 +2,79 @@ module Undies
2
2
 
3
3
  class Node
4
4
 
5
- # have as many methods to the class level as possilbe to keep from
5
+ # have as many methods to the class level as possible to keep from
6
6
  # polluting the public instance methods and to maximize the effectiveness
7
7
  # of the Element#method_missing logic
8
8
 
9
+ def self.start_tag(node)
10
+ node.instance_variable_get("@start_tag") || ""
11
+ end
12
+
13
+ def self.end_tag(node)
14
+ node.instance_variable_get("@end_tag") || ""
15
+ end
16
+
17
+ def self.set_start_tag(node); end
18
+ def self.set_end_tag(node); end
19
+
20
+ def self.builds(node)
21
+ node.instance_variable_get("@builds") || []
22
+ end
23
+
24
+ def self.add_build(node, build_block)
25
+ node.instance_variable_set("@builds", builds(node) + [build_block])
26
+ end
27
+
28
+ def self.children(node)
29
+ node.instance_variable_get("@children")
30
+ end
31
+
32
+ def self.set_children(node, value)
33
+ node.instance_variable_set("@children", value)
34
+ end
35
+
36
+ def self.attrs(element)
37
+ element.instance_variable_get("@attrs")
38
+ end
39
+
40
+ def self.merge_attrs(element, value={})
41
+ attrs(element).merge(value).tap do |a|
42
+ element.instance_variable_set("@attrs", a)
43
+ end
44
+ end
45
+
46
+ def self.node_name(node)
47
+ node.instance_variable_get("@name") || ""
48
+ end
49
+
9
50
  def self.content(node)
10
- node.instance_variable_get("@content")
51
+ node.instance_variable_get("@content") || ""
52
+ end
53
+
54
+ def self.mode(node)
55
+ node.instance_variable_get("@mode") || :inline
11
56
  end
12
57
 
13
- def self.flush(output, node)
14
- output.pp_use_indent = true if node.force_pp?
15
- output << self.content(node)
58
+ def self.prefix(node, meth, level, indent)
59
+ "".tap do |value|
60
+ if mode(node) != :inline && indent > 0
61
+ if meth == 'start_tag'
62
+ value << "#{level > 0 ? "\n": ''}#{' '*level*indent}"
63
+ elsif meth == 'end_tag'
64
+ value << "\n#{' '*(level > 0 ? level-1 : level)*indent}"
65
+ end
66
+ end
67
+ end
16
68
  end
17
69
 
18
- def initialize(content, opts={})
70
+ def initialize(content, mode=:inline)
19
71
  @start_tag = nil
20
72
  @end_tag = nil
21
- @force_pp = opts[:force_pp]
22
73
  @content = content
23
- end
24
-
25
- def force_pp?
26
- !!@force_pp
74
+ @builds = []
75
+ @attrs = {}
76
+ @children = false
77
+ @mode = mode
27
78
  end
28
79
 
29
80
  def ==(other_node)
@@ -0,0 +1,111 @@
1
+ module Undies
2
+ class NodeStack
3
+
4
+ # the node stack handles caching nodes for processing, running node
5
+ # builds, and buffering node output for writing. I want to treat this
6
+ # as a stack of nodes for the template API to reference. I need to push
7
+ # a node onto the stack, reference it using the 'current' method,
8
+ # and pop it off the stack when I'm done.
9
+
10
+ # the stack first caches new nodes before pushing them onto the stack
11
+ # and node output is buffered as new nodes are pushed
12
+
13
+ def self.create(output)
14
+ output.kind_of?(NodeStack) ? output : NodeStack.new(output)
15
+ end
16
+
17
+ attr_reader :stack, :output, :buffer
18
+ attr_accessor :cached_node
19
+
20
+ def initialize(output)
21
+ @stack = []
22
+ @cached_node = nil
23
+ @output = output
24
+ @written_level = 0
25
+ end
26
+
27
+ def empty?; @stack.empty?; end
28
+ def size; @stack.size; end
29
+ def first; @stack.first; end
30
+ def last; @stack.last; end
31
+
32
+ alias_method :current, :last
33
+ alias_method :level, :size
34
+
35
+ def push(node)
36
+ if current
37
+ current.class.set_children(current, node.kind_of?(Element))
38
+ end
39
+ if @written_level < level
40
+ open(current)
41
+ end
42
+ @stack.push(node)
43
+ end
44
+
45
+ def pop(*args)
46
+ if !empty?
47
+ node = if @written_level < level
48
+ @stack.pop.tap { |node| write(node) }
49
+ else
50
+ @stack.pop.tap { |node| close(node) }
51
+ end
52
+ end
53
+ end
54
+
55
+ def node(node)
56
+ clear_cached
57
+ self.cached_node = node
58
+ end
59
+
60
+ def flush
61
+ clear_cached
62
+ end
63
+
64
+ def clear_cached
65
+ node_to_push, self.cached_node = self.cached_node, nil
66
+ if node_to_push
67
+ push(node_to_push)
68
+ node_to_push.class.builds(node_to_push).each { |build| build.call }
69
+ clear_cached
70
+ pop
71
+ end
72
+ end
73
+
74
+ private
75
+
76
+ def open(node)
77
+ start_tag(node, @written_level)
78
+ @written_level += 1
79
+ content(node, @written_level)
80
+ end
81
+
82
+ def write(node)
83
+ start_tag(node, @written_level)
84
+ content(node, @written_level+1)
85
+ end_tag(node, @written_level)
86
+ end
87
+
88
+ def close(node)
89
+ @written_level -= 1
90
+ end_tag(node, @written_level)
91
+ end
92
+
93
+ # TODO: Fill up an output write buffer with arg arrays
94
+ # then empty it
95
+
96
+ def start_tag(node, level)
97
+ @output.write(node, 'start_tag', level)
98
+ end
99
+
100
+ def content(node, level)
101
+ @output.write(node, 'content', level)
102
+ end
103
+
104
+ def end_tag(node, level)
105
+ @output.write(node, 'end_tag', level)
106
+ end
107
+
108
+ end
109
+
110
+
111
+ end
data/lib/undies/output.rb CHANGED
@@ -1,18 +1,15 @@
1
- require 'undies/node_buffer'
2
-
3
1
  module Undies
4
2
  class Output
5
3
 
6
- attr_reader :io, :options, :pp, :node_buffer
7
- attr_accessor :pp_use_indent
8
-
9
4
  # the output class wraps an IO stream, gathers pretty printing options,
10
- # and handles buffering nodes and pretty printing to the stream
5
+ # and handles writing out buffered node stack items
6
+
7
+ attr_reader :io, :pp
8
+ attr_accessor :pp_level
11
9
 
12
10
  def initialize(io, opts={})
13
11
  @io = io
14
12
  self.options = opts
15
- @node_buffer = NodeBuffer.new
16
13
  end
17
14
 
18
15
  def options=(opts)
@@ -21,33 +18,13 @@ module Undies
21
18
  end
22
19
 
23
20
  # setup any pretty printing
24
- @pp = opts[:pp]
25
- self.pp_level = opts[:pp_level] || 0
26
- self.pp_use_indent = true
27
-
28
- @options = opts
29
- end
30
-
31
- def pp_level
32
- @pp_level
33
- end
34
-
35
- def pp_level=(value)
36
- @pp_indent = @pp ? "\n#{' '*value*@pp}" : ""
37
- @pp_level = value
38
- end
39
-
40
- def <<(data)
41
- @io << (@pp_use_indent ? "#{@pp_indent}#{data}" : data.to_s)
42
- end
43
-
44
- def node(obj)
45
- self.node_buffer.pull(self)
46
- self.node_buffer.push(obj)
21
+ @pp = opts[:pp] || 0
22
+ @pp_level = opts[:pp_level] || 0
47
23
  end
48
24
 
49
- def flush
50
- self.node_buffer.flush(self)
25
+ def write(node, meth, level)
26
+ @io << node.class.prefix(node, meth, level+@pp_level, @pp)
27
+ @io << node.class.send(meth, node)
51
28
  end
52
29
 
53
30
  end