trenni 1.0.1 → 1.0.2
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/trenni/builder.rb +93 -41
- data/lib/trenni/strings.rb +32 -0
- data/lib/trenni/version.rb +1 -1
- data/test/test_builder.rb +55 -30
- data/test/test_strings.rb +70 -0
- data/test/test_template.rb +1 -1
- metadata +6 -3
data/lib/trenni/builder.rb
CHANGED
@@ -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
|
-
@
|
55
|
+
@escape = options[:escape]
|
33
56
|
|
34
57
|
@level = [0]
|
35
58
|
@children = [0]
|
36
59
|
end
|
37
60
|
|
38
|
-
|
39
|
-
@options[:indent] != nil
|
40
|
-
end
|
61
|
+
attr :output
|
41
62
|
|
42
|
-
def
|
43
|
-
if indent
|
44
|
-
@
|
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
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
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
|
-
|
94
|
-
|
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
|
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
|
132
|
+
def tag_attributes(attributes)
|
118
133
|
buffer = []
|
119
134
|
|
120
135
|
attributes.each do |key, value|
|
121
136
|
if value == true
|
122
|
-
buffer << (
|
137
|
+
buffer << Strings::to_simple_attribute(key, @strict)
|
123
138
|
elsif value
|
124
|
-
buffer <<
|
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 = {"&" => "&", "<" => "<", ">" => ">", "\"" => """}
|
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
|
data/lib/trenni/version.rb
CHANGED
data/test/test_builder.rb
CHANGED
@@ -27,54 +27,79 @@ require 'digest/md5'
|
|
27
27
|
|
28
28
|
require 'trenni'
|
29
29
|
|
30
|
-
class
|
30
|
+
class TestBuilder < Test::Unit::TestCase
|
31
31
|
def test_tags
|
32
|
-
|
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
|
45
|
-
|
42
|
+
def test_full_html
|
43
|
+
builder = Trenni::Builder.new(:indent => true)
|
46
44
|
|
47
|
-
builder
|
48
|
-
builder.
|
49
|
-
builder.
|
50
|
-
|
51
|
-
|
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
|
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
|
65
|
-
|
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
|
-
|
70
|
-
|
71
|
-
|
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
|
-
|
74
|
-
|
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
|
-
|
77
|
-
|
78
|
-
|
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=""Hello World"">if x < 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 "<foobar>", text
|
33
|
+
|
34
|
+
text = Trenni::Strings.to_html(%q{"I'd like to do this & that :p", she said.})
|
35
|
+
assert_equal %q{"I'd like to do this & that :p", 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
|
data/test/test_template.rb
CHANGED
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.
|
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: -
|
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: -
|
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
|