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 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
@@ -0,0 +1,8 @@
1
+ require 'rake/testtask'
2
+
3
+ task :default => :spec
4
+
5
+ Rake::TestTask.new(:spec) do |t|
6
+ t.test_files = FileList['spec/*_spec.rb']
7
+ t.warning = true
8
+ end
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.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 programmatically'
8
+ s.description = 'A straightforward API for generating HTML'
9
9
  s.summary = 'See description'
10
- s.files = Dir.glob('{lib,test}/**/*') + %w(README.txt hom.gemspec)
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 html
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
- def initialize(tag_name, attributes = nil, content = nil)
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 html
30
- @content.nil? ? start_tag : "#{start_tag}#{encode(@content)}</#{@tag_name}>"
37
+ def content?
38
+ @content != Undefined
31
39
  end
32
40
 
33
41
  def to_s
34
- html_safe(html)
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
- private
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
- def attribute_html(name, value)
93
- value == Undefined ? " #{name}" : %( #{name}="#{HOM.escape value}")
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
- def self.escape(object)
98
- CGI.escapeHTML(object.to_s)
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 &amp;&amp; b, x &gt; 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>&lt;br&gt;</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
@@ -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('&nbsp;')
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('&#160;')
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.3.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-02-23 00:00:00.000000000Z
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: &10113880 !ruby/object:Gem::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: *10113880
25
- description: A straightforward API for generating HTML programmatically
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
- - README.txt
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.10
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.