trenni 1.0.1 → 1.0.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -18,6 +18,8 @@
18
18
  # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19
19
  # THE SOFTWARE.
20
20
 
21
+ require 'trenni/strings'
22
+
21
23
  module Trenni
22
24
 
23
25
  INSTRUCT_ATTRIBUTES = [
@@ -26,22 +28,41 @@ module Trenni
26
28
  ].freeze
27
29
 
28
30
  class Builder
31
+ INDENT = "\t"
32
+
33
+ # A helper to generate fragments of markup.
34
+ def self.fragment(builder = nil, &block)
35
+ if builder
36
+ yield builder
37
+
38
+ return nil
39
+ else
40
+ builder = Builder.new
41
+
42
+ yield builder
43
+
44
+ return builder.output.string
45
+ end
46
+ end
47
+
29
48
  def initialize(options = {})
49
+ @strict = options[:strict]
50
+
30
51
  @output = options[:output] || StringIO.new
52
+ @indentation = options[:indentation] || INDENT
53
+ @indent = options.fetch(:indent, true)
31
54
 
32
- @options = options
55
+ @escape = options[:escape]
33
56
 
34
57
  @level = [0]
35
58
  @children = [0]
36
59
  end
37
60
 
38
- def indent?
39
- @options[:indent] != nil
40
- end
61
+ attr :output
41
62
 
42
- def indent
43
- if indent?
44
- @options[:indent] * (@level.size - 1)
63
+ def indentation
64
+ if @indent
65
+ @indentation * (@level.size - 1)
45
66
  else
46
67
  ''
47
68
  end
@@ -70,58 +91,52 @@ module Trenni
70
91
  @output.puts "<!DOCTYPE#{text}>"
71
92
  end
72
93
 
94
+ # Begin a block tag.
73
95
  def tag(name, attributes = {}, &block)
74
- if block_given?
75
- @output.puts if indent? and @level.last > 0
76
- @output.write indent + "<#{name}#{tag_attributes(attributes)}>"
77
- @output.puts if indent?
78
-
79
- @level[@level.size-1] += 1
80
-
81
- @level << 0
82
- yield self
83
- @level.pop
84
-
85
- @output.puts if indent?
86
- @output.write indent + "</#{name}>"
87
- else
88
- @output.write indent + "<#{name}#{tag_attributes(attributes)}/>"
96
+ full_tag(name, attributes, @indent, @indent, &block)
97
+ end
98
+
99
+ # Begin an inline tag.
100
+ def inline(name, attributes = {}, &block)
101
+ indent = @indent
102
+
103
+ full_tag(name, attributes, @indent, false) do
104
+ @indent = false
105
+ yield
106
+ @indent = indent
89
107
  end
90
108
  end
91
109
 
92
110
  def text(data)
93
- if indent?
94
- data.split(/\n/).each_with_index do |line, i|
111
+ append to_html(data)
112
+ end
113
+
114
+ # Append pre-existing markup:
115
+ def append(data)
116
+ # The parent has one more child:
117
+ @level[-1] += 1
118
+
119
+ if @indent
120
+ lines = data.strip.split(/\n/)
121
+
122
+ lines.each_with_index do |line, i|
95
123
  @output.puts if i > 0
96
- @output.write indent + line
124
+ @output.write indentation + line
97
125
  end
98
126
  else
99
127
  @output.write data
100
128
  end
101
129
  end
102
130
 
103
- def options(options)
104
- saved_options = @options
105
- @options = options
106
-
107
- yield
108
-
109
- @options = saved_options
110
- end
111
-
112
- def tag_attributes(attributes)
113
- self.class.tag_attributes(attributes, @options[:strict])
114
- end
115
-
116
131
  # Convert a set of attributes into a string suitable for use within a <tag>.
117
- def self.tag_attributes(attributes, strict = false)
132
+ def tag_attributes(attributes)
118
133
  buffer = []
119
134
 
120
135
  attributes.each do |key, value|
121
136
  if value == true
122
- buffer << (strict ? "#{key}=\"#{key}\"" : key)
137
+ buffer << Strings::to_simple_attribute(key, @strict)
123
138
  elsif value
124
- buffer << "#{key}=\"#{value.to_s.gsub('"', '&quot;')}\""
139
+ buffer << Strings::to_attribute(key, to_html(value))
125
140
  end
126
141
  end
127
142
 
@@ -131,6 +146,43 @@ module Trenni
131
146
  return ''
132
147
  end
133
148
  end
149
+
150
+ protected
151
+
152
+ def to_html(data)
153
+ @escape ? Strings::to_html(data) : data
154
+ end
155
+
156
+ # A normal block level/container tag.
157
+ def full_tag(name, attributes, indent_outer, indent_inner, &block)
158
+ if block_given?
159
+ if indent_outer
160
+ @output.puts if @level.last > 0
161
+ @output.write indentation
162
+ end
163
+
164
+ @output.write "<#{name}#{tag_attributes(attributes)}>"
165
+ @output.puts if indent_inner
166
+
167
+ # The parent has one more child:
168
+ @level[-1] += 1
169
+
170
+ @level << 0
171
+
172
+ yield
173
+
174
+ children = @level.pop
175
+
176
+ if indent_inner
177
+ @output.puts if children > 0
178
+ @output.write indentation
179
+ end
180
+
181
+ @output.write "</#{name}>"
182
+ else
183
+ @output.write indentation + "<#{name}#{tag_attributes(attributes)}/>"
184
+ end
185
+ end
134
186
  end
135
187
 
136
188
  end
@@ -0,0 +1,32 @@
1
+
2
+ module Trenni
3
+ module Strings
4
+ HTML_ESCAPE = {"&" => "&amp;", "<" => "&lt;", ">" => "&gt;", "\"" => "&quot;"}
5
+ HTML_ESCAPE_PATTERN = Regexp.new("[" + Regexp.quote(HTML_ESCAPE.keys.join) + "]")
6
+
7
+ def self.to_html(string)
8
+ string.gsub(HTML_ESCAPE_PATTERN){|c| HTML_ESCAPE[c]}
9
+ end
10
+
11
+ def self.to_quoted_string(string)
12
+ '"' + string.gsub('"', '\\"').gsub(/\r/, "\\r").gsub(/\n/, "\\n") + '"'
13
+ end
14
+
15
+ # `value` must already be escaped.
16
+ def self.to_attribute(key, value)
17
+ %Q{#{key}="#{value}"}
18
+ end
19
+
20
+ def self.to_simple_attribute(key, strict)
21
+ strict ? %Q{#{key}="#{key}"} : key.to_s
22
+ end
23
+
24
+ def self.to_title(string)
25
+ string.gsub(/(^|[ \-_])(.)/){" " + $2.upcase}.strip
26
+ end
27
+
28
+ def self.to_snake(string)
29
+ string.gsub("::", "").gsub(/([A-Z]+)/){"_" + $1.downcase}.sub(/^_+/, "")
30
+ end
31
+ end
32
+ end
@@ -19,5 +19,5 @@
19
19
  # THE SOFTWARE.
20
20
 
21
21
  module Trenni
22
- VERSION = "1.0.1"
22
+ VERSION = "1.0.2"
23
23
  end
data/test/test_builder.rb CHANGED
@@ -27,54 +27,79 @@ require 'digest/md5'
27
27
 
28
28
  require 'trenni'
29
29
 
30
- class BuilderTest < Test::Unit::TestCase
30
+ class TestBuilder < Test::Unit::TestCase
31
31
  def test_tags
32
- output = StringIO.new
33
-
34
- builder = Trenni::Builder.new(:output => output)
32
+ builder = Trenni::Builder.new(:indent => false)
35
33
 
36
34
  builder.instruct
37
35
  builder.tag('foo', 'bar' => 'baz') do
38
36
  builder.text("apples and oranges")
39
37
  end
40
38
 
41
- assert_equal "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<foo bar=\"baz\">apples and oranges</foo>", output.string
39
+ assert_equal "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<foo bar=\"baz\">apples and oranges</foo>", builder.output.string
42
40
  end
43
41
 
44
- def test_html
45
- output = StringIO.new
42
+ def test_full_html
43
+ builder = Trenni::Builder.new(:indent => true)
46
44
 
47
- builder = Trenni::Builder.new(:output => output, :indent => "\t")
48
- builder.options(:indent => "\t") do
49
- builder.doctype
50
- builder.tag('html') do
51
- builder.tag('head') do
52
- builder.tag('title') do
53
- builder.text('Hello World')
54
- end
55
- end
56
- builder.tag('body') do
45
+ builder.doctype
46
+ builder.tag('html') do
47
+ builder.tag('head') do
48
+ builder.inline('title') do
49
+ builder.text('Hello World')
57
50
  end
58
51
  end
52
+ builder.tag('body') do
53
+ end
59
54
  end
60
55
 
61
- assert_equal "<!DOCTYPE html>\n<html>\n\t<head>\n\t\t<title>\n\t\t\tHello World\n\t\t</title>\n\t</head>\n\t<body>\n\n\t</body>\n</html>", output.string
56
+ assert_equal "<!DOCTYPE html>\n<html>\n\t<head>\n\t\t<title>Hello World</title>\n\t</head>\n\t<body>\n\t</body>\n</html>", builder.output.string
62
57
  end
63
58
 
64
- def test_attributes
65
- # Non-strict output (e.g. HTML)
66
- text = Trenni::Builder.tag_attributes({:required => true})
67
- assert_equal " required", text
59
+ def test_inline_html
60
+ builder = Trenni::Builder.new(:indent => true)
68
61
 
69
- # Strict output (e.g. XML)
70
- text = Trenni::Builder.tag_attributes({:required => true}, true)
71
- assert_equal " required=\"required\"", text
62
+ builder.inline("div") do
63
+ builder.tag("strong") do
64
+ builder.text("Hello")
65
+ end
66
+
67
+ builder.text "World!"
68
+ end
72
69
 
73
- text = Trenni::Builder.tag_attributes({:required => false})
74
- assert_equal "", text
70
+ assert_equal "<div><strong>Hello</strong>World!</div>", builder.output.string
71
+ end
72
+
73
+ def test_indentation
74
+ builder = Trenni::Builder.new(:indent => "\t")
75
75
 
76
- # Check the order is correct
77
- text = Trenni::Builder.tag_attributes([[:a, 10], [:b, 20]])
78
- assert_equal " a=\"10\" b=\"20\"", text
76
+ puts builder.output.string
77
+ end
78
+
79
+ def test_escaping
80
+ builder = Trenni::Builder.new(:escape => true)
81
+ builder.inline :foo, :bar => %Q{"Hello World"} do
82
+ builder.text %Q{if x < 10}
83
+ end
84
+
85
+ assert_equal %Q{<foo bar="&quot;Hello World&quot;">if x &lt; 10</foo>}, builder.output.string
86
+ end
87
+
88
+ def test_attributes_strict
89
+ builder = Trenni::Builder.new(:strict => true)
90
+ builder.tag :option, :required => true
91
+ assert_equal %Q{<option required="required"/>}, builder.output.string
92
+ end
93
+
94
+ def test_attributes_compact
95
+ builder = Trenni::Builder.new(:strict => false)
96
+ builder.tag :option, :required => true
97
+ assert_equal %Q{<option required/>}, builder.output.string
98
+ end
99
+
100
+ def test_attributes_order
101
+ builder = Trenni::Builder.new(:strict => true)
102
+ builder.tag :t, [[:a, 10], [:b, 20]]
103
+ assert_equal %Q{<t a="10" b="20"/>}, builder.output.string
79
104
  end
80
105
  end
@@ -0,0 +1,70 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ # Copyright (c) 2007, 2012 Samuel G. D. Williams. <http://www.oriontransfer.co.nz>
4
+ #
5
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ # of this software and associated documentation files (the "Software"), to deal
7
+ # in the Software without restriction, including without limitation the rights
8
+ # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ # copies of the Software, and to permit persons to whom the Software is
10
+ # furnished to do so, subject to the following conditions:
11
+ #
12
+ # The above copyright notice and this permission notice shall be included in
13
+ # all copies or substantial portions of the Software.
14
+ #
15
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ # THE SOFTWARE.
22
+
23
+ require 'pathname'
24
+ require 'test/unit'
25
+ require 'stringio'
26
+
27
+ require 'trenni/strings'
28
+
29
+ class TestStrings < Test::Unit::TestCase
30
+ def test_to_html
31
+ text = Trenni::Strings.to_html("<foobar>")
32
+ assert_equal "&lt;foobar&gt;", text
33
+
34
+ text = Trenni::Strings.to_html(%q{"I'd like to do this & that :p", she said.})
35
+ assert_equal %q{&quot;I'd like to do this &amp; that :p&quot;, she said.}, text
36
+ end
37
+
38
+ def test_to_quoted_string
39
+ text = Trenni::Strings.to_quoted_string(%Q{"Hello World"})
40
+ assert_equal %q{"\"Hello World\""}, text
41
+
42
+ text = Trenni::Strings.to_quoted_string(%Q{"Hello\r\nWorld"})
43
+ assert_equal %q{"\"Hello\r\nWorld\""}, text
44
+ end
45
+
46
+ def test_to_attribute
47
+ text = Trenni::Strings.to_attribute(:foo, 'bar')
48
+ assert_equal %Q{foo="bar"}, text
49
+
50
+ text = Trenni::Strings.to_simple_attribute(:foo, false)
51
+ assert_equal %Q{foo}, text
52
+
53
+ text = Trenni::Strings.to_simple_attribute(:foo, true)
54
+ assert_equal %Q{foo="foo"}, text
55
+ end
56
+
57
+ def self.to_simple_attribute
58
+ strict ? %Q{#{key}="#{key}"} : key.to_s
59
+ end
60
+
61
+ def test_to_title
62
+ text = Trenni::Strings.to_title("foo bar")
63
+ assert_equal "Foo Bar", text
64
+ end
65
+
66
+ def test_to_snake
67
+ text = Trenni::Strings.to_snake("Happy::Go::Lucky")
68
+ assert_equal "happy_go_lucky", text
69
+ end
70
+ end
@@ -26,7 +26,7 @@ require 'stringio'
26
26
 
27
27
  require 'trenni'
28
28
 
29
- class TemplateTest < Test::Unit::TestCase
29
+ class TestTemplate < Test::Unit::TestCase
30
30
  def test_template_each
31
31
  template = Trenni::Template.new('<?r items.each do |item| ?>#{item}<?r end ?>')
32
32
 
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: trenni
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.1
4
+ version: 1.0.2
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -28,9 +28,11 @@ files:
28
28
  - Rakefile
29
29
  - lib/trenni.rb
30
30
  - lib/trenni/builder.rb
31
+ - lib/trenni/strings.rb
31
32
  - lib/trenni/template.rb
32
33
  - lib/trenni/version.rb
33
34
  - test/test_builder.rb
35
+ - test/test_strings.rb
34
36
  - test/test_template.rb
35
37
  - trenni.gemspec
36
38
  homepage: https://github.com/ioquatix/trenni
@@ -47,7 +49,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
47
49
  version: '0'
48
50
  segments:
49
51
  - 0
50
- hash: -1799901624385534337
52
+ hash: -2578082460541813883
51
53
  required_rubygems_version: !ruby/object:Gem::Requirement
52
54
  none: false
53
55
  requirements:
@@ -56,7 +58,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
56
58
  version: '0'
57
59
  segments:
58
60
  - 0
59
- hash: -1799901624385534337
61
+ hash: -2578082460541813883
60
62
  requirements: []
61
63
  rubyforge_project:
62
64
  rubygems_version: 1.8.24
@@ -65,4 +67,5 @@ specification_version: 3
65
67
  summary: A fast native templating system that compiles directly to Ruby code.
66
68
  test_files:
67
69
  - test/test_builder.rb
70
+ - test/test_strings.rb
68
71
  - test/test_template.rb