hom 0.3.0 → 0.4.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/README.md +62 -0
- data/Rakefile +8 -0
- data/hom.gemspec +3 -3
- data/lib/hom.rb +59 -39
- data/spec/element_spec.rb +71 -0
- data/spec/entity_spec.rb +75 -0
- metadata +16 -7
- data/README.txt +0 -50
data/README.md
ADDED
@@ -0,0 +1,62 @@
|
|
1
|
+
HOM: A straightforward API for generating HTML
|
2
|
+
==============================================
|
3
|
+
|
4
|
+
Motivation
|
5
|
+
----------
|
6
|
+
|
7
|
+
HOM helps you implement HTML presentation logic in your code. Things like
|
8
|
+
navigation links, select boxes, sets of checkboxes; anything with behaviour
|
9
|
+
that is too complex for your templates.
|
10
|
+
|
11
|
+
Usage
|
12
|
+
-----
|
13
|
+
|
14
|
+
Build up an object tree using `HOM::Element` objects. The first constuctor
|
15
|
+
argument is a symbol representing the tag name. For example, here's how you'd
|
16
|
+
represent a line break element:
|
17
|
+
|
18
|
+
```ruby
|
19
|
+
HOM::Element.new(:br)
|
20
|
+
```
|
21
|
+
|
22
|
+
The second constructor argument represents the element attributes, and can be
|
23
|
+
nil, a single symbol/string, a hash, or an array of hashes and symbols/strings.
|
24
|
+
For example, here's how you'd represent some input elements:
|
25
|
+
|
26
|
+
```ruby
|
27
|
+
HOM::Element.new(:input, :disabled)
|
28
|
+
|
29
|
+
HOM::Element.new(:input, {type: :text, size: 30})
|
30
|
+
|
31
|
+
HOM::Element.new(:input, [{type: :text, size: 30}, :disabled])
|
32
|
+
```
|
33
|
+
|
34
|
+
The third constructor argument is the inner content, which can be a string,
|
35
|
+
another element object, or an array of child items. For example, here's how
|
36
|
+
you can represent elements with attributes:
|
37
|
+
|
38
|
+
```ruby
|
39
|
+
span = HOM::Element.new(:span, nil, '')
|
40
|
+
|
41
|
+
h1 = HOM::Element.new(:h1, {}, 'hello world')
|
42
|
+
|
43
|
+
image = HOM::Element.new(:img, {src: 'image.png', width: 100, height: 30})
|
44
|
+
|
45
|
+
link = HOM::Element.new(:a, {target: :_blank, href: '/'}, image)
|
46
|
+
|
47
|
+
list = HOM::Element.new(:ul, nil, (1..3).map { |n| HOM::Element.new(:li, nil, n) })
|
48
|
+
```
|
49
|
+
|
50
|
+
There's also a `HOM::Entity` class which you can use to represent HTML entities;
|
51
|
+
integer values for numeric entities and symbol/string values for named entities,
|
52
|
+
for example:
|
53
|
+
|
54
|
+
```ruby
|
55
|
+
HOM::Element.new(:span, nil, HOM::Entity.new(160))
|
56
|
+
|
57
|
+
HOM::Element.new(:span, nil, HOM::Entity.new(:nbsp))
|
58
|
+
```
|
59
|
+
|
60
|
+
Calling `#to_s` on a `HOM::Element` object will return a string containing
|
61
|
+
the generated markup. `HOM::Element` objects are safe to use directly in
|
62
|
+
Rails templates, all escaping is handled automatically.
|
data/Rakefile
ADDED
data/hom.gemspec
CHANGED
@@ -1,13 +1,13 @@
|
|
1
1
|
Gem::Specification.new do |s|
|
2
2
|
s.name = 'hom'
|
3
|
-
s.version = '0.
|
3
|
+
s.version = '0.4.0'
|
4
4
|
s.platform = Gem::Platform::RUBY
|
5
5
|
s.authors = ['Tim Craft']
|
6
6
|
s.email = ['mail@timcraft.com']
|
7
7
|
s.homepage = 'http://github.com/timcraft/hom'
|
8
|
-
s.description = 'A straightforward API for generating HTML
|
8
|
+
s.description = 'A straightforward API for generating HTML'
|
9
9
|
s.summary = 'See description'
|
10
|
-
s.files = Dir.glob('{lib,
|
10
|
+
s.files = Dir.glob('{lib,spec}/**/*') + %w(Rakefile README.md hom.gemspec)
|
11
11
|
s.add_development_dependency('activesupport', ['>= 3.0.3'])
|
12
12
|
s.require_path = 'lib'
|
13
13
|
end
|
data/lib/hom.rb
CHANGED
@@ -1,6 +1,8 @@
|
|
1
1
|
require 'cgi'
|
2
2
|
|
3
3
|
module HOM
|
4
|
+
Undefined = Class.new
|
5
|
+
|
4
6
|
class Entity
|
5
7
|
attr_reader :value
|
6
8
|
|
@@ -8,7 +10,7 @@ module HOM
|
|
8
10
|
@value = value
|
9
11
|
end
|
10
12
|
|
11
|
-
def
|
13
|
+
def to_s
|
12
14
|
numeric? ? "&\##{value};" : "&#{value};"
|
13
15
|
end
|
14
16
|
|
@@ -19,41 +21,25 @@ module HOM
|
|
19
21
|
def named?
|
20
22
|
!numeric?
|
21
23
|
end
|
24
|
+
|
25
|
+
def html_safe?
|
26
|
+
true
|
27
|
+
end
|
22
28
|
end
|
23
29
|
|
24
30
|
class Element
|
25
|
-
|
31
|
+
attr_reader :tag_name, :attributes, :content
|
32
|
+
|
33
|
+
def initialize(tag_name, attributes = nil, content = Undefined)
|
26
34
|
@tag_name, @attributes, @content = tag_name, AttributeList.new.update(attributes), content
|
27
35
|
end
|
28
36
|
|
29
|
-
def
|
30
|
-
@content
|
37
|
+
def content?
|
38
|
+
@content != Undefined
|
31
39
|
end
|
32
40
|
|
33
41
|
def to_s
|
34
|
-
|
35
|
-
end
|
36
|
-
|
37
|
-
private
|
38
|
-
|
39
|
-
def html_safe(string)
|
40
|
-
string.respond_to?(:html_safe) ? string.html_safe : string
|
41
|
-
end
|
42
|
-
|
43
|
-
def encode(object)
|
44
|
-
if object.is_a?(Array)
|
45
|
-
object.map { |item| encode(item) }.join
|
46
|
-
elsif object.respond_to?(:html)
|
47
|
-
object.html
|
48
|
-
elsif object.respond_to?(:html_safe?) && object.html_safe?
|
49
|
-
object.to_s
|
50
|
-
else
|
51
|
-
HOM.escape(object)
|
52
|
-
end
|
53
|
-
end
|
54
|
-
|
55
|
-
def start_tag
|
56
|
-
"<#{@tag_name}#{@attributes.html}>"
|
42
|
+
Encoding.safe_encode(self)
|
57
43
|
end
|
58
44
|
end
|
59
45
|
|
@@ -62,16 +48,14 @@ module HOM
|
|
62
48
|
@index = {}
|
63
49
|
end
|
64
50
|
|
65
|
-
def html
|
66
|
-
@index.map { |name, value| attribute_html(name, value) }.join
|
67
|
-
end
|
68
|
-
|
69
|
-
Undefined = Class.new
|
70
|
-
|
71
51
|
def set(name, value = Undefined)
|
72
52
|
@index[name.to_s] = value
|
73
53
|
end
|
74
54
|
|
55
|
+
def to_hash
|
56
|
+
@index
|
57
|
+
end
|
58
|
+
|
75
59
|
def update(object)
|
76
60
|
case object
|
77
61
|
when NilClass
|
@@ -86,15 +70,51 @@ module HOM
|
|
86
70
|
|
87
71
|
return self
|
88
72
|
end
|
73
|
+
end
|
74
|
+
|
75
|
+
module Encoding
|
76
|
+
def self.safe_encode(object)
|
77
|
+
safe(encode(object))
|
78
|
+
end
|
89
79
|
|
90
|
-
|
80
|
+
def self.encode(object)
|
81
|
+
if object.is_a?(Array)
|
82
|
+
object.map { |item| encode(item) }.join
|
83
|
+
elsif object.is_a?(Element)
|
84
|
+
encode_element(object)
|
85
|
+
elsif object.respond_to?(:html) # TODO: REMOVE ME
|
86
|
+
Kernel.warn '[hom] defining #html on custom objects is deprecated, map the objects to elements instead'
|
91
87
|
|
92
|
-
|
93
|
-
|
88
|
+
object.html
|
89
|
+
elsif object.respond_to?(:html_safe?) && object.html_safe?
|
90
|
+
object.to_s
|
91
|
+
else
|
92
|
+
escape(object)
|
93
|
+
end
|
94
94
|
end
|
95
|
-
end
|
96
95
|
|
97
|
-
|
98
|
-
|
96
|
+
def self.encode_element(object)
|
97
|
+
object.content? ? "#{start_tag(object)}#{encode(object.content)}</#{object.tag_name}>" : start_tag(object)
|
98
|
+
end
|
99
|
+
|
100
|
+
def self.start_tag(element)
|
101
|
+
"<#{element.tag_name}#{encode_attributes(element)}>"
|
102
|
+
end
|
103
|
+
|
104
|
+
def self.encode_attributes(element)
|
105
|
+
element.attributes.to_hash.map { |name, value| encode_attribute(name, value) }.join
|
106
|
+
end
|
107
|
+
|
108
|
+
def self.encode_attribute(name, value)
|
109
|
+
value == Undefined ? " #{name}" : %( #{name}="#{escape value}")
|
110
|
+
end
|
111
|
+
|
112
|
+
def self.safe(string)
|
113
|
+
string.respond_to?(:html_safe) ? string.html_safe : string
|
114
|
+
end
|
115
|
+
|
116
|
+
def self.escape(object)
|
117
|
+
CGI.escapeHTML(object.to_s)
|
118
|
+
end
|
99
119
|
end
|
100
120
|
end
|
@@ -0,0 +1,71 @@
|
|
1
|
+
require 'minitest/autorun'
|
2
|
+
require 'active_support/core_ext/string/output_safety'
|
3
|
+
|
4
|
+
require_relative '../lib/hom'
|
5
|
+
|
6
|
+
describe 'Element' do
|
7
|
+
before do
|
8
|
+
@br = HOM::Element.new(:br)
|
9
|
+
@i1 = HOM::Element.new(:input, :disabled)
|
10
|
+
@i2 = HOM::Element.new(:input, {type: :text, size: 30, value: nil})
|
11
|
+
@i3 = HOM::Element.new(:input, [{type: :text, size: 30}, :disabled])
|
12
|
+
@h1 = HOM::Element.new(:h1, nil, '')
|
13
|
+
@h2 = HOM::Element.new(:h2, nil, 'hello world')
|
14
|
+
@h3 = HOM::Element.new(:h3, nil, 'a && b, x > y')
|
15
|
+
@h4 = HOM::Element.new(:h4, nil, 1234567890)
|
16
|
+
@h5 = HOM::Element.new(:h5, nil, HOM::Element.new(:b, nil, 'how bold'))
|
17
|
+
@h6 = HOM::Element.new(:h6, nil, nil)
|
18
|
+
@ul = HOM::Element.new(:ul, nil, (1..3).map { |n| HOM::Element.new(:li, nil, n) })
|
19
|
+
end
|
20
|
+
|
21
|
+
describe 'content query method' do
|
22
|
+
it 'should return false if the content is undefined' do
|
23
|
+
@br.content?.must_equal(false)
|
24
|
+
end
|
25
|
+
|
26
|
+
it 'should return true otherwise' do
|
27
|
+
@h1.content?.must_equal(true)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
describe 'to_s method' do
|
32
|
+
it 'should return a string containing the correct markup' do
|
33
|
+
@br.to_s.must_equal('<br>')
|
34
|
+
@i1.to_s.must_equal('<input disabled>')
|
35
|
+
@i2.to_s.must_equal('<input type="text" size="30" value="">')
|
36
|
+
@i3.to_s.must_equal('<input type="text" size="30" disabled>')
|
37
|
+
@h1.to_s.must_equal('<h1></h1>')
|
38
|
+
@h2.to_s.must_equal('<h2>hello world</h2>')
|
39
|
+
@h3.to_s.must_equal('<h3>a && b, x > y</h3>')
|
40
|
+
@h4.to_s.must_equal('<h4>1234567890</h4>')
|
41
|
+
@h5.to_s.must_equal('<h5><b>how bold</b></h5>')
|
42
|
+
@h6.to_s.must_equal('<h6></h6>')
|
43
|
+
@ul.to_s.must_equal('<ul><li>1</li><li>2</li><li>3</li></ul>')
|
44
|
+
end
|
45
|
+
|
46
|
+
it 'should not encode content that has been marked as html safe' do
|
47
|
+
HOM::Element.new(:span, nil, '<br>').to_s.must_equal('<span><br></span>')
|
48
|
+
HOM::Element.new(:span, nil, '<br>'.html_safe).to_s.must_equal('<span><br></span>')
|
49
|
+
end
|
50
|
+
|
51
|
+
it 'should return content that is marked as html safe' do
|
52
|
+
@br.to_s.html_safe?.must_equal(true)
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
describe 'encoding objects with an html method' do # TODO: REMOVE ME
|
57
|
+
it 'should emit a deprecation warning' do
|
58
|
+
require 'mocha'
|
59
|
+
|
60
|
+
object = Object.new
|
61
|
+
|
62
|
+
def object.html; HOM::Element.new(:span, nil, 'html') end
|
63
|
+
|
64
|
+
div = HOM::Element.new(:div, nil, object)
|
65
|
+
|
66
|
+
Kernel.expects(:warn).with(regexp_matches(/defining #html on custom objects is deprecated/))
|
67
|
+
|
68
|
+
div.to_s.must_equal('<div><span>html</span></div>')
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
data/spec/entity_spec.rb
ADDED
@@ -0,0 +1,75 @@
|
|
1
|
+
require 'minitest/autorun'
|
2
|
+
|
3
|
+
require_relative '../lib/hom'
|
4
|
+
|
5
|
+
describe 'Named entity' do
|
6
|
+
before do
|
7
|
+
@entity = HOM::Entity.new(:nbsp)
|
8
|
+
end
|
9
|
+
|
10
|
+
describe 'value method' do
|
11
|
+
it 'should return the symbol value' do
|
12
|
+
@entity.value.must_equal(:nbsp)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
describe 'numeric query method' do
|
17
|
+
it 'should return false' do
|
18
|
+
@entity.numeric?.must_equal(false)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
describe 'named query method' do
|
23
|
+
it 'should return true' do
|
24
|
+
@entity.named?.must_equal(true)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
describe 'to_s method' do
|
29
|
+
it 'should return the encoded html entity' do
|
30
|
+
@entity.to_s.must_equal(' ')
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
describe 'html_safe query method' do
|
35
|
+
it 'should return true' do
|
36
|
+
@entity.html_safe?.must_equal(true)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
describe 'Numeric entity' do
|
42
|
+
before do
|
43
|
+
@entity = HOM::Entity.new(160)
|
44
|
+
end
|
45
|
+
|
46
|
+
describe 'value method' do
|
47
|
+
it 'should return the integer value' do
|
48
|
+
@entity.value.must_equal(160)
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
describe 'numeric query method' do
|
53
|
+
it 'should return true' do
|
54
|
+
@entity.numeric?.must_equal(true)
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
describe 'named query method' do
|
59
|
+
it 'should return false' do
|
60
|
+
@entity.named?.must_equal(false)
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
describe 'to_s method' do
|
65
|
+
it 'should return the encoded html entity' do
|
66
|
+
@entity.to_s.must_equal(' ')
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
describe 'html_safe query method' do
|
71
|
+
it 'should return true' do
|
72
|
+
@entity.html_safe?.must_equal(true)
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: hom
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.4.0
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,11 +9,11 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2012-
|
12
|
+
date: 2012-09-26 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: activesupport
|
16
|
-
requirement:
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
17
|
none: false
|
18
18
|
requirements:
|
19
19
|
- - ! '>='
|
@@ -21,8 +21,13 @@ dependencies:
|
|
21
21
|
version: 3.0.3
|
22
22
|
type: :development
|
23
23
|
prerelease: false
|
24
|
-
version_requirements:
|
25
|
-
|
24
|
+
version_requirements: !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ! '>='
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: 3.0.3
|
30
|
+
description: A straightforward API for generating HTML
|
26
31
|
email:
|
27
32
|
- mail@timcraft.com
|
28
33
|
executables: []
|
@@ -30,7 +35,10 @@ extensions: []
|
|
30
35
|
extra_rdoc_files: []
|
31
36
|
files:
|
32
37
|
- lib/hom.rb
|
33
|
-
-
|
38
|
+
- spec/element_spec.rb
|
39
|
+
- spec/entity_spec.rb
|
40
|
+
- Rakefile
|
41
|
+
- README.md
|
34
42
|
- hom.gemspec
|
35
43
|
homepage: http://github.com/timcraft/hom
|
36
44
|
licenses: []
|
@@ -52,8 +60,9 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
52
60
|
version: '0'
|
53
61
|
requirements: []
|
54
62
|
rubyforge_project:
|
55
|
-
rubygems_version: 1.8.
|
63
|
+
rubygems_version: 1.8.24
|
56
64
|
signing_key:
|
57
65
|
specification_version: 3
|
58
66
|
summary: See description
|
59
67
|
test_files: []
|
68
|
+
has_rdoc:
|
data/README.txt
DELETED
@@ -1,50 +0,0 @@
|
|
1
|
-
A straightforward API for generating HTML programmatically.
|
2
|
-
|
3
|
-
Install via rubygems (no dependencies):
|
4
|
-
|
5
|
-
gem install hom
|
6
|
-
|
7
|
-
Either require it, or add it to your Rails Gemfile:
|
8
|
-
|
9
|
-
require 'hom'
|
10
|
-
|
11
|
-
gem 'hom'
|
12
|
-
|
13
|
-
Generate HTML using HOM::Element objects. The first constuctor argument is
|
14
|
-
a symbol representing the tag name, for example:
|
15
|
-
|
16
|
-
HOM::Element.new(:br)
|
17
|
-
|
18
|
-
The second constructor argument represents the element attributes, and can be
|
19
|
-
nil, a single symbol/string, a hash, or an array of hashes and symbols/strings.
|
20
|
-
For example:
|
21
|
-
|
22
|
-
HOM::Element.new(:input, :disabled)
|
23
|
-
|
24
|
-
HOM::Element.new(:input, {type: :text, size: 30})
|
25
|
-
|
26
|
-
HOM::Element.new(:input, [{type: :text, size: 30}, :disabled])
|
27
|
-
|
28
|
-
The third constructor argument is the inner content, which can be a string,
|
29
|
-
another element object, or an array of child items. For example:
|
30
|
-
|
31
|
-
span = HOM::Element.new(:span, nil, '')
|
32
|
-
|
33
|
-
h1 = HOM::Element.new(:h1, {}, 'hello world')
|
34
|
-
|
35
|
-
image = HOM::Element.new(:img, {src: 'image.png', width: 100, height: 30})
|
36
|
-
|
37
|
-
link = HOM::Element.new(:a, {target: :_blank, href: '/'}, image)
|
38
|
-
|
39
|
-
list = HOM::Element.new(:ul, nil, (1..3).map { |n| HOM::Element.new(:li, nil, n) })
|
40
|
-
|
41
|
-
Use HOM::Entity objects for HTML entities; integer values for numeric entities
|
42
|
-
and symbol/string values for named entities, for example:
|
43
|
-
|
44
|
-
HOM::Element.new(:span, nil, HOM::Entity.new(160))
|
45
|
-
|
46
|
-
HOM::Element.new(:span, nil, HOM::Entity.new(:nbsp))
|
47
|
-
|
48
|
-
Call #to_s or #html on a HOM::Element object to return a string containing
|
49
|
-
the generated HTML. HOM::Element objects are safe to use directly in Rails
|
50
|
-
templates, all escaping is handled automatically.
|