undies 1.2.0 → 2.0.0

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.
data/lib/undies/source.rb CHANGED
@@ -1,26 +1,72 @@
1
+ require 'undies/named_source'
2
+
1
3
  module Undies
2
4
  class Source
3
5
 
4
- attr_reader :source, :data
6
+ attr_reader :source, :data, :layout
7
+
8
+ def initialize(*args, &block)
9
+ named = args.first.kind_of?(NamedSource) ? args.first : nil
10
+ args << block if block
11
+ self.args = named ? named.args.compact : args
12
+ end
5
13
 
6
- def initialize(source=nil)
7
- raise ArgumentError, "file or block required" if source.nil?
14
+ def file?
15
+ !@source.kind_of?(::Proc)
16
+ end
8
17
 
9
- @source = source
10
- if self.file? && !File.exists?(@source.to_s)
11
- raise ArgumentError, "no template file '#{@source}'"
18
+ def layouts
19
+ if layout
20
+ [self.layout, self.layout.layouts].flatten.compact
21
+ else
22
+ []
12
23
  end
24
+ end
25
+
26
+ def layout_sources
27
+ self.layouts.collect{|l| l.source}
28
+ end
13
29
 
14
- # load source data and prepare (uses binread to avoid encoding issues)
15
- @data = if self.file?
16
- File.send(File.respond_to?(:binread) ? :binread : :read, @source.to_s)
30
+ def ==(other_source)
31
+ self.source == other_source.source &&
32
+ self.layout_sources == other_source.layout_sources
33
+ end
34
+
35
+ def args=(values)
36
+ proc, opts, file = [
37
+ values.last.kind_of?(::Proc) ? values.pop : nil,
38
+ values.last.kind_of?(::Hash) ? values.pop : {},
39
+ values.last.kind_of?(::String) ? values.pop : nil
40
+ ]
41
+
42
+ self.source = file || proc
43
+ self.layout = opts[:layout]
44
+ end
45
+
46
+ def source=(value)
47
+ if value.nil?
48
+ raise ArgumentError, "source name, file, or block required"
49
+ end
50
+ @data = if value.kind_of?(::Proc)
51
+ value
17
52
  else
18
- @source
53
+ raise ArgumentError, "no source file '#{value}'" if !File.exists?(value.to_s)
54
+ File.send(File.respond_to?(:binread) ? :binread : :read, value.to_s)
19
55
  end
56
+ @source = value
20
57
  end
21
58
 
22
- def file?
23
- !@source.kind_of?(::Proc)
59
+ def layout=(value)
60
+ @layout = case value
61
+ when Source, NilClass
62
+ value
63
+ when ::Proc
64
+ Source.new(&value)
65
+ when ::String, NamedSource
66
+ Source.new(value)
67
+ else
68
+ raise ArgumentError, "invalid layout"
69
+ end
24
70
  end
25
71
 
26
72
  end
@@ -0,0 +1,22 @@
1
+ require "undies/source"
2
+
3
+ module Undies
4
+ class SourceStack < ::Array
5
+
6
+ # a source stack is used to manage which sources and any deeply nested
7
+ # layouts they are in. initialize this object with a content source obj
8
+ # and get a stack where the the top source is the outer most layout and
9
+ # the bottom source is the source used to initialize the stack (the content
10
+ # source). naturally any sources in between are the intermediate layouts
11
+ # for the content source
12
+
13
+ def initialize(source)
14
+ super([source, source.layouts].flatten.compact)
15
+ end
16
+
17
+ def pop
18
+ super
19
+ end
20
+
21
+ end
22
+ end
@@ -1,47 +1,69 @@
1
- require 'undies/source'
2
- require 'undies/node'
3
- require 'undies/element'
1
+ require 'undies/source_stack'
2
+ require 'undies/output'
4
3
 
5
4
  module Undies
6
5
  class Template
7
6
 
8
- # prefixing with a triple underscore to not pollut metaclass locals scope
7
+ # have as many methods to the class level as possilbe to keep from
8
+ # polluting the public instance methods, the instance scope, and to
9
+ # maximize the effectiveness of the Template#method_missing logic
9
10
 
10
- attr_accessor :___nodes
11
+ def self.output(template)
12
+ template.instance_variable_get("@_undies_output")
13
+ end
14
+
15
+ def initialize(source, data, output)
16
+ # setup the source stack and output objects
17
+ raise ArgumentError, "please provide a Source object" if !source.kind_of?(Source)
18
+ @_undies_source_stack = SourceStack.new(source)
19
+ raise ArgumentError, "please provide an Output object" if !output.kind_of?(Output)
20
+ @_undies_output = output
21
+
22
+ # apply data to template scope
23
+ raise ArgumentError if !data.kind_of?(::Hash)
24
+ if (data.keys.map(&:to_s) & self.public_methods.map(&:to_s)).size > 0
25
+ raise ArgumentError, "data conflicts with template public methods."
26
+ end
27
+ metaclass = class << self; self; end
28
+ data.each {|key, value| metaclass.class_eval { define_method(key){value} }}
11
29
 
12
- def initialize(*args, &block)
13
- self.___nodes = NodeList.new
14
- targs = self.___template_args(args.compact, block)
15
- self.___locals, self.___io, self.___layout, self.___markup = targs
16
- self.___stack = ElementStack.new(self, self.___io)
17
- self.___compile { self.___render(self.___markup) if self.___layout }
30
+ # yield to recursivley render the source stack
31
+ self.__yield
32
+
33
+ # flush any remaining output to the stream
34
+ @_undies_output.flush
18
35
  end
19
36
 
20
- def to_s(pp_indent=nil)
21
- self.___nodes.to_s(0, pp_indent)
37
+ # call this to render template source
38
+ # use this method in layouts to insert a layout's content source
39
+ def __yield
40
+ return if @_undies_source_stack.nil? || (source = @_undies_source_stack.pop).nil?
41
+ if source.file?
42
+ instance_eval(source.data, source.source, 1)
43
+ else
44
+ instance_eval(&source.data)
45
+ end
22
46
  end
23
47
 
24
- # Add a text node (data escaped) to the nodes of the current node
25
- def _(data="")
26
- self.__ self.escape_html(data.to_s)
48
+ # call this to render partial source embedded in a template
49
+ # partial source is rendered with its own scope/data but shares
50
+ # its parent template's output object
51
+ def __partial(source, data)
52
+ Undies::Template.new(source, data, @_undies_output)
27
53
  end
28
54
 
55
+ # Add a text node (data escaped) to the nodes of the current node
56
+ def _(data=""); self.__ self.escape_html(data.to_s); end
57
+
29
58
  # Add a text node with the data un-escaped
30
- def __(data="")
31
- node = Node.new(data.to_s)
32
- self.___io << node.to_s if self.___io
33
- self.___add(node)
34
- end
59
+ def __(data=""); @_undies_output.node(data.to_s); end
35
60
 
36
61
  # Add an element to the nodes of the current node
37
- def element(name, attrs={}, &block)
38
- self.___add(Element.new(self.___stack, name, attrs, &block))
39
- end
62
+ def element(*args, &block); @_undies_output.element(*args, &block); end
40
63
  alias_method :tag, :element
41
64
 
42
65
  # Element proxy methods ('_<element>'') ========================
43
66
  ELEM_METH_REGEX = /^_(.+)$/
44
-
45
67
  def method_missing(meth, *args, &block)
46
68
  if meth.to_s =~ ELEM_METH_REGEX
47
69
  element($1, *args, &block)
@@ -49,7 +71,6 @@ module Undies
49
71
  super
50
72
  end
51
73
  end
52
-
53
74
  def respond_to?(*args)
54
75
  if args.first.to_s =~ ELEM_METH_REGEX
55
76
  true
@@ -70,119 +91,11 @@ module Undies
70
91
  "/" => "&#x2F;"
71
92
  }
72
93
  ESCAPE_HTML_PATTERN = Regexp.union(*ESCAPE_HTML.keys)
73
-
74
94
  # Escape ampersands, brackets and quotes to their HTML/XML entities.
75
95
  def escape_html(string)
76
96
  string.to_s.gsub(ESCAPE_HTML_PATTERN){|c| ESCAPE_HTML[c] }
77
97
  end
78
98
  # end Rip from Rack v1.3.0 =====================================
79
99
 
80
- protected
81
-
82
- # prefixing non-public methods with a triple underscore to not pollute
83
- # metaclass locals scope
84
-
85
- def ___compile
86
- self.___render(self.___layout || self.___markup)
87
- end
88
-
89
- def ___render(source)
90
- if source.file?
91
- instance_eval(source.data, source.source, 1)
92
- else
93
- instance_eval(&source.data)
94
- end
95
- end
96
-
97
- def ___locals=(data)
98
- if !data.kind_of?(::Hash)
99
- raise ArgumentError
100
- end
101
- if invalid_locals?(data.keys)
102
- raise ArgumentError, "locals conflict with template's public methods."
103
- end
104
- data.each do |key, value|
105
- self.___metaclass do
106
- define_method(key) { value }
107
- end
108
- end
109
- end
110
-
111
- def ___add(node)
112
- self.___stack.last.___nodes.append(node)
113
- end
114
-
115
- def ___stack
116
- @stack
117
- end
118
-
119
- def ___stack=(value)
120
- raise ArgumentError if !value.respond_to?(:push) || !value.respond_to?(:pop)
121
- @stack = value
122
- end
123
-
124
- def ___io
125
- @io
126
- end
127
-
128
- def ___io=(value)
129
- raise ArgumentError if value && !self.___is_a_stream?(value)
130
- @io = value
131
- end
132
-
133
- def ___layout
134
- @layout
135
- end
136
-
137
- def ___layout=(value)
138
- if value && !(value.kind_of?(Source) && value.file?)
139
- raise ArgumentError, "layout must be a file source"
140
- end
141
- @layout = value
142
- end
143
-
144
- def ___markup
145
- @markup
146
- end
147
-
148
- def ___markup=(value)
149
- raise ArgumentError if value && !value.kind_of?(Source)
150
- @markup = value
151
- end
152
-
153
- def ___template_args(args, block)
154
- [ args.last.kind_of?(::Hash) ? args.pop : {},
155
- self.___is_a_stream?(args.last) ? args.pop : nil,
156
- self.___layout_arg?(args, block) ? Source.new(args.pop) : nil,
157
- Source.new(args.first || block)
158
- ]
159
- end
160
-
161
- def ___metaclass(&block)
162
- metaclass = class << self; self; end
163
- metaclass.class_eval(&block)
164
- end
165
-
166
- def ___is_a_stream?(thing)
167
- !thing.kind_of?(::String) && thing.respond_to?(:<<)
168
- end
169
-
170
- def ___layout_arg?(args, block)
171
- if args.size >= 2
172
- true
173
- elsif args.size <= 0
174
- false
175
- else # args.size == 1
176
- !block.nil?
177
- end
178
- end
179
-
180
- private
181
-
182
- # you can't define locals that conflict with the template's public methods
183
- def invalid_locals?(keys)
184
- (keys.collect(&:to_s) & self.public_methods.collect(&:to_s)).size > 0
185
- end
186
-
187
100
  end
188
101
  end
@@ -1,3 +1,3 @@
1
1
  module Undies
2
- VERSION = "1.2.0"
2
+ VERSION = "2.0.0"
3
3
  end
data/lib/undies.rb CHANGED
@@ -1,13 +1,3 @@
1
1
  require 'undies/template'
2
- require 'undies/partial'
3
2
 
4
- module Undies
5
-
6
- # TODO: maybe implement this pattern??
7
- # class << self
8
- # def new(&block)
9
- # Template.new(&block)
10
- # end
11
- # end
12
-
13
- end
3
+ module Undies; end
data/test/element_test.rb CHANGED
@@ -1,116 +1,61 @@
1
- require "test_belt"
1
+ require "assert"
2
2
 
3
3
  require "undies/element"
4
- require "undies/element_stack"
5
4
  require "undies/template"
6
5
 
7
6
  class Undies::Element
8
7
 
9
-
10
-
11
- class BasicTest < Test::Unit::TestCase
12
- include TestBelt
13
-
14
- context 'an element'
15
- before { @e = Undies::Element.new(Undies::ElementStack.new, :div) }
8
+ class BasicTests < Assert::Context
9
+ desc 'an element'
10
+ before do
11
+ @e = Undies::Element.new(:div)
12
+ end
16
13
  subject { @e }
17
- should have_readers :___name, :___attrs
18
- should have_accessor :___nodes
14
+
15
+ should have_class_methods :html_attrs, :content, :flush
16
+ should have_instance_method :to_str
19
17
 
20
18
  should "be a Node" do
21
19
  assert_kind_of Undies::Node, subject
22
20
  end
23
21
 
24
22
  should "store it's name as a string" do
25
- assert_equal "div", subject.___name
23
+ assert_equal "div", subject.instance_variable_get("@name")
26
24
  end
27
25
 
28
- should "have a NodeList as its nodes" do
29
- assert_kind_of Undies::NodeList, subject.___nodes
30
- end
31
-
32
- should "have its nodes be its content" do
33
- assert_equal subject.___nodes.object_id, subject.___content.object_id
34
- end
35
-
36
- should "have an element stack as its stack" do
37
- assert_kind_of Undies::ElementStack, subject.send(:instance_variable_get, "@stack")
38
- end
39
-
40
- should "complain is not created with an ElementStack" do
41
- assert_raises ArgumentError do
42
- Undies::Element.new([], :div)
43
- end
44
- end
45
-
46
- end
47
-
48
-
49
-
50
- class EmptyTest < Test::Unit::TestCase
51
- include TestBelt
52
- context 'an empty element'
53
- before { @e = Undies::Element.new(Undies::ElementStack.new, :br) }
54
- subject { @e }
55
-
56
- should "have no nodes" do
57
- assert_equal([], subject.___nodes)
26
+ should "have no content itself" do
27
+ assert_nil subject.class.content(subject)
58
28
  end
59
29
 
60
30
  end
61
31
 
62
-
63
-
64
- class HtmlAttrsTest < BasicTest
65
- context "html_attrs util"
32
+ class HtmlAttrsTest < BasicTests
33
+ desc "the element class html_attrs util"
66
34
 
67
35
  should "convert an empty hash to html attrs" do
68
- assert_equal('', subject.send(:html_attrs, {}))
36
+ assert_equal '', Undies::Element.html_attrs({})
69
37
  end
70
38
 
71
39
  should "convert a basic hash to html attrs" do
72
- attrs = subject.send(:html_attrs, :class => "test", :id => "test_1")
40
+ attrs = Undies::Element.html_attrs(:class => "test", :id => "test_1")
73
41
  assert_match /^\s{1}/, attrs
74
42
  assert attrs.include?('class="test"')
75
43
  assert attrs.include?('id="test_1"')
76
44
  end
77
45
 
78
46
  should "convert a nested hash to html attrs" do
79
- attrs = subject.send(:html_attrs, {
47
+ attrs = Undies::Element.html_attrs({
80
48
  :class => "testing", :id => "test_2",
81
49
  :nested => {:something => 'is_awesome'}
82
50
  })
83
51
  assert_match /^\s{1}/, attrs
84
- assert attrs.include?('class="testing"')
85
- assert attrs.include?('id="test_2"')
86
- assert attrs.include?('nested="somethingis_awesome"')
52
+ assert_included 'class="testing"', attrs
53
+ assert_included 'id="test_2"', attrs
54
+ assert_included 'nested_something="is_awesome"', attrs
87
55
  end
88
56
  end
89
57
 
90
-
91
-
92
- class SerializeTest < BasicTest
93
- should "serialize with no child elements" do
94
- element = Undies::Element.new(Undies::ElementStack.new, :br)
95
- assert_equal "<br />", element.to_s
96
- end
97
-
98
- should "serialize with attrs" do
99
- element = Undies::Element.new(Undies::ElementStack.new, :br, {:class => 'big'})
100
- assert_equal '<br class="big" />', element.to_s
101
- end
102
-
103
- should "serialize with attrs and content" do
104
- templ = Undies::Template.new do
105
- element(:strong, {:class => 'big'}) { __ "Loud Noises!" }
106
- end
107
- assert_equal '<strong class="big">Loud Noises!</strong>', templ.to_s
108
- end
109
- end
110
-
111
-
112
-
113
- class CSSProxyTest < BasicTest
58
+ class CSSProxyTests < BasicTests
114
59
 
115
60
  should "respond to any method ending in '!' as an id proxy" do
116
61
  assert subject.respond_to?(:asdgasdg!)
@@ -119,32 +64,25 @@ class Undies::Element
119
64
  should "proxy id attr with methods ending in '!'" do
120
65
  assert_equal({
121
66
  :id => 'thing1'
122
- }, subject.thing1!.___attrs)
123
- end
124
-
125
- should "nest elements from proxy id call" do
126
- templ = Undies::Template.new do
127
- element(:div).thing1! { _ "stuff" }
128
- end
129
- assert_equal "<div id=\"thing1\">stuff</div>", templ.to_s
67
+ }, subject.thing1!.instance_variable_get("@attrs"))
130
68
  end
131
69
 
132
70
  should "proxy id attr with last method call ending in '!'" do
133
71
  assert_equal({
134
72
  :id => 'thing2'
135
- }, subject.thing1!.thing2!.___attrs)
73
+ }, subject.thing1!.thing2!.instance_variable_get("@attrs"))
136
74
  end
137
75
 
138
76
  should "set id attr to explicit if called last " do
139
77
  assert_equal({
140
78
  :id => 'thing3'
141
- }, subject.thing1!.thing2!(:id => 'thing3').___attrs)
79
+ }, subject.thing1!.thing2!(:id => 'thing3').instance_variable_get("@attrs"))
142
80
  end
143
81
 
144
82
  should "set id attr to proxy if called last" do
145
83
  assert_equal({
146
84
  :id => 'thing1'
147
- }, subject.thing2!(:id => 'thing3').thing1!.___attrs)
85
+ }, subject.thing2!(:id => 'thing3').thing1!.instance_variable_get("@attrs"))
148
86
  end
149
87
 
150
88
  should "respond to any other method as a class proxy" do
@@ -154,32 +92,25 @@ class Undies::Element
154
92
  should "proxy single html class attr" do
155
93
  assert_equal({
156
94
  :class => 'thing'
157
- }, subject.thing.___attrs)
158
- end
159
-
160
- should "nest elements from proxy class call" do
161
- templ = Undies::Template.new do
162
- element(:div).thing { _ "stuff" }
163
- end
164
- assert_equal "<div class=\"thing\">stuff</div>", templ.to_s
95
+ }, subject.thing.instance_variable_get("@attrs"))
165
96
  end
166
97
 
167
98
  should "proxy multi html class attrs" do
168
99
  assert_equal({
169
100
  :class => 'list thing awesome'
170
- }, subject.list.thing.awesome.___attrs)
101
+ }, subject.list.thing.awesome.instance_variable_get("@attrs"))
171
102
  end
172
103
 
173
104
  should "set class attr with explicit if called last " do
174
105
  assert_equal({
175
106
  :class => 'list'
176
- }, subject.thing.awesome(:class => "list").___attrs)
107
+ }, subject.thing.awesome(:class => "list").instance_variable_get("@attrs"))
177
108
  end
178
109
 
179
110
  should "update class attr with proxy if called last" do
180
111
  assert_equal({
181
112
  :class => 'list is good'
182
- }, subject.thing.awesome(:class => "list is").good.___attrs)
113
+ }, subject.thing.awesome(:class => "list is").good.instance_variable_get("@attrs"))
183
114
  end
184
115
 
185
116
  should "proxy mixed class and id selector attrs" do
@@ -189,11 +120,87 @@ class Undies::Element
189
120
  }, subject.thing1!.awesome({
190
121
  :class => "list is",
191
122
  :id => "thing2"
192
- }).good.thing3!.___attrs)
123
+ }).good.thing3!.instance_variable_get("@attrs"))
124
+ end
125
+
126
+ should "not proxy if private methods are called" do
127
+ assert_equal "<div />", subject.send(:start_tag)
128
+ assert_equal nil, subject.send(:end_tag)
193
129
  end
194
130
 
195
131
  end
196
132
 
133
+ class SerializeTests < BasicTests
134
+ before do
135
+ @output = Undies::Output.new(StringIO.new(@out = ""))
136
+ end
137
+
138
+ should "serialize with no child elements" do
139
+ src = Undies::Source.new do
140
+ element(:br)
141
+ end
142
+ templ = Undies::Template.new(src, {}, @output)
143
+ assert_equal "<br />", @out
144
+ end
145
+
146
+ should "serialize with attrs" do
147
+ src = Undies::Source.new do
148
+ element(:br, :class => 'big')
149
+ end
150
+ templ = Undies::Template.new(src, {}, @output)
151
+ assert_equal '<br class="big" />', @out
152
+ end
197
153
 
154
+ should "serialize with attrs and content" do
155
+ src = Undies::Source.new do
156
+ element(:strong, {:class => 'big'}) { __ "Loud Noises!" }
157
+ end
158
+ templ = Undies::Template.new(src, {}, @output)
159
+ assert_equal '<strong class="big">Loud Noises!</strong>', @out
160
+ end
161
+
162
+ should "serialize element proxy id call" do
163
+ src = Undies::Source.new do
164
+ element(:div).thing1! { _ "stuff" }
165
+ end
166
+ templ = Undies::Template.new(src, {}, @output)
167
+ assert_equal "<div id=\"thing1\">stuff</div>", @out
168
+ end
169
+
170
+ should "serialize element proxy class call" do
171
+ src = Undies::Source.new do
172
+ element(:div).thing { _ "stuff" }
173
+ end
174
+ templ = Undies::Template.new(src, {}, @output)
175
+ assert_equal "<div class=\"thing\">stuff</div>", @out
176
+ end
177
+
178
+ should "serialize content from separate content blocks" do
179
+ src = Undies::Source.new do
180
+ element(:div){ _ "stuff" }.thing1!{ _ " and more stuff" }
181
+ end
182
+ templ = Undies::Template.new(src, {}, @output)
183
+ assert_equal "<div id=\"thing1\">stuff and more stuff</div>", @out
184
+ end
185
+
186
+ should "serialize nested elements with pp" do
187
+ output = Undies::Output.new(StringIO.new(@out = ""), :pp => 4)
188
+ src = Undies::Source.new do
189
+ element(:div) {
190
+ element(:span) { _ "Content!" }
191
+ __ "Raw"
192
+ element(:span) { _ "More content" }
193
+ }
194
+ end
195
+ templ = Undies::Template.new(src, {}, output)
196
+ assert_equal "
197
+ <div>
198
+ <span>Content!</span>
199
+ Raw
200
+ <span>More content</span>
201
+ </div>", @out
202
+ end
203
+
204
+ end
198
205
 
199
206
  end
data/test/helper.rb CHANGED
@@ -1,2 +1,4 @@
1
- # this file is automatically required in when you require 'test_belt'
2
- # put test helpers here
1
+ # this file is automatically required in when you require 'assert' in your tests
2
+
3
+ # add root dir to the load path
4
+ $LOAD_PATH.unshift(File.expand_path("../..", __FILE__))
data/test/irb.rb CHANGED
@@ -1,10 +1,9 @@
1
- require 'test_belt'
1
+ require 'assert/setup'
2
2
 
3
3
  # this file is required in when the 'irb' rake test is run.
4
- # b/c 'test_belt' is required above, lib and test dirs will
5
- # be added to the LOAD_PATH and the test helper will be
4
+ # b/c 'assert' is required above, the test helper will be
6
5
  # required in.
7
6
 
8
7
  # put any IRB setup code here
9
8
 
10
- require 'undies'
9
+ require 'undies'