arbre2 2.1.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.
- checksums.yaml +7 -0
- data/.gitignore +30 -0
- data/.ruby-gemset +1 -0
- data/.ruby-version +1 -0
- data/.travis.yml +6 -0
- data/CHANGELOG.md +75 -0
- data/Gemfile +2 -0
- data/Gemfile.lock +93 -0
- data/LICENSE +20 -0
- data/README.md +92 -0
- data/Rakefile +7 -0
- data/arbre.gemspec +28 -0
- data/lib/arbre/child_element_collection.rb +86 -0
- data/lib/arbre/container.rb +20 -0
- data/lib/arbre/context.rb +83 -0
- data/lib/arbre/element/building.rb +151 -0
- data/lib/arbre/element.rb +194 -0
- data/lib/arbre/element_collection.rb +93 -0
- data/lib/arbre/html/attributes.rb +91 -0
- data/lib/arbre/html/class_list.rb +53 -0
- data/lib/arbre/html/comment.rb +47 -0
- data/lib/arbre/html/document.rb +93 -0
- data/lib/arbre/html/html_tags.rb +67 -0
- data/lib/arbre/html/querying.rb +256 -0
- data/lib/arbre/html/tag.rb +317 -0
- data/lib/arbre/rails/layouts.rb +126 -0
- data/lib/arbre/rails/legacy_document.rb +29 -0
- data/lib/arbre/rails/rendering.rb +76 -0
- data/lib/arbre/rails/rspec/arbre_support.rb +61 -0
- data/lib/arbre/rails/rspec.rb +2 -0
- data/lib/arbre/rails/template_handler.rb +32 -0
- data/lib/arbre/rails.rb +35 -0
- data/lib/arbre/rspec/be_rendered_as_matcher.rb +103 -0
- data/lib/arbre/rspec/be_scripted_as_matcher.rb +68 -0
- data/lib/arbre/rspec/contain_script_matcher.rb +64 -0
- data/lib/arbre/rspec.rb +3 -0
- data/lib/arbre/text_node.rb +35 -0
- data/lib/arbre/version.rb +3 -0
- data/lib/arbre.rb +27 -0
- data/spec/arbre/integration/html_document_spec.rb +90 -0
- data/spec/arbre/integration/html_spec.rb +283 -0
- data/spec/arbre/integration/querying_spec.rb +187 -0
- data/spec/arbre/integration/rails_spec.rb +183 -0
- data/spec/arbre/rails/rspec/arbre_support_spec.rb +75 -0
- data/spec/arbre/rspec/be_rendered_as_matcher_spec.rb +80 -0
- data/spec/arbre/rspec/be_scripted_as_matcher_spec.rb +61 -0
- data/spec/arbre/rspec/contain_script_matcher_spec.rb +40 -0
- data/spec/arbre/support/arbre_example_group.rb +0 -0
- data/spec/arbre/unit/child_element_collection_spec.rb +146 -0
- data/spec/arbre/unit/container_spec.rb +23 -0
- data/spec/arbre/unit/context_spec.rb +95 -0
- data/spec/arbre/unit/element/building_spec.rb +300 -0
- data/spec/arbre/unit/element_collection_spec.rb +169 -0
- data/spec/arbre/unit/element_spec.rb +297 -0
- data/spec/arbre/unit/html/attributes_spec.rb +219 -0
- data/spec/arbre/unit/html/class_list_spec.rb +109 -0
- data/spec/arbre/unit/html/comment_spec.rb +42 -0
- data/spec/arbre/unit/html/querying_spec.rb +32 -0
- data/spec/arbre/unit/html/tag_spec.rb +300 -0
- data/spec/arbre/unit/rails/layouts_spec.rb +127 -0
- data/spec/arbre/unit/text_node_spec.rb +40 -0
- data/spec/rails/app/controllers/example_controller.rb +18 -0
- data/spec/rails/app/views/example/_arbre_partial.html.arb +7 -0
- data/spec/rails/app/views/example/_erb_partial.html.erb +1 -0
- data/spec/rails/app/views/example/arbre.html.arb +1 -0
- data/spec/rails/app/views/example/arbre_partial_result.html.arb +3 -0
- data/spec/rails/app/views/example/erb.html.erb +5 -0
- data/spec/rails/app/views/example/erb_partial_result.html.arb +3 -0
- data/spec/rails/app/views/example/partials.html.arb +11 -0
- data/spec/rails/app/views/layouts/empty.html.arb +1 -0
- data/spec/rails/app/views/layouts/with_title.html.arb +5 -0
- data/spec/rails/config/routes.rb +4 -0
- data/spec/rails_spec_helper.rb +13 -0
- data/spec/spec_helper.rb +20 -0
- data/spec/support/arbre_example_group.rb +19 -0
- metadata +254 -0
@@ -0,0 +1,151 @@
|
|
1
|
+
module Arbre
|
2
|
+
class Element
|
3
|
+
|
4
|
+
# Dynamic module onto which builder methods can be defined.
|
5
|
+
module BuilderMethods
|
6
|
+
end
|
7
|
+
|
8
|
+
# Element building concern. Contains methods pertaining to building and inserting child
|
9
|
+
# elements.
|
10
|
+
module Building
|
11
|
+
|
12
|
+
######
|
13
|
+
# Builder methods
|
14
|
+
|
15
|
+
def self.included(klass)
|
16
|
+
klass.send :include, BuilderMethods
|
17
|
+
klass.send :extend, BuilderMethod
|
18
|
+
end
|
19
|
+
|
20
|
+
######
|
21
|
+
# Builder method DSL
|
22
|
+
|
23
|
+
module BuilderMethod
|
24
|
+
|
25
|
+
def builder_method(method_name)
|
26
|
+
BuilderMethods.class_eval <<-EOF, __FILE__, __LINE__
|
27
|
+
def #{method_name}(*args, &block)
|
28
|
+
insert ::#{self.name}, *args, &block
|
29
|
+
end
|
30
|
+
EOF
|
31
|
+
end
|
32
|
+
|
33
|
+
end
|
34
|
+
|
35
|
+
######
|
36
|
+
# Building & inserting elements
|
37
|
+
|
38
|
+
# Builds an element of the given class using the given arguments and block, in the
|
39
|
+
# same arbre context as this element.
|
40
|
+
def build(klass, *args, &block)
|
41
|
+
element = klass.new(arbre_context)
|
42
|
+
within(element) { element.build! *args, &block }
|
43
|
+
element
|
44
|
+
end
|
45
|
+
|
46
|
+
# Builds an element of the given class using the given arguments and block, in the
|
47
|
+
# same arbre context as this element, and adds it to the current arbre element's
|
48
|
+
# children.
|
49
|
+
def insert(klass, *args, &block)
|
50
|
+
element = klass.new(arbre_context)
|
51
|
+
current_element.insert_child element
|
52
|
+
within(element) { element.build! *args, &block }
|
53
|
+
element
|
54
|
+
end
|
55
|
+
|
56
|
+
######
|
57
|
+
# Flow
|
58
|
+
|
59
|
+
# Executes a block within the context of the given element, or DOM query.
|
60
|
+
def append_within(element, &block)
|
61
|
+
element = find(element).first if element.is_a?(String)
|
62
|
+
arbre_context.with_current element: element, flow: :append, &block
|
63
|
+
end
|
64
|
+
alias_method :within, :append_within
|
65
|
+
|
66
|
+
# Executes a block within the context of the given element, or DOM query. All elements
|
67
|
+
# are prepended.
|
68
|
+
def prepend_within(element, &block)
|
69
|
+
element = find(element).first if element.is_a?(String)
|
70
|
+
arbre_context.with_current element: element, flow: :prepend, &block
|
71
|
+
end
|
72
|
+
|
73
|
+
%w(append prepend).each do |flow|
|
74
|
+
class_eval <<-RUBY, __FILE__, __LINE__+1
|
75
|
+
def #{flow}(klass = nil, *args, &block)
|
76
|
+
arbre_context.with_current element: current_element, flow: :#{flow} do
|
77
|
+
insert_or_call_block klass, *args, &block
|
78
|
+
end
|
79
|
+
end
|
80
|
+
RUBY
|
81
|
+
end
|
82
|
+
|
83
|
+
%w(after before).each do |flow|
|
84
|
+
class_eval <<-RUBY, __FILE__, __LINE__+1
|
85
|
+
def #{flow}(element, klass = nil, *args, &block)
|
86
|
+
element = find(element).first if element.is_a?(String)
|
87
|
+
|
88
|
+
arbre_context.with_current element: element.parent, flow: [ :#{flow}, element ] do
|
89
|
+
insert_or_call_block klass, *args, &block
|
90
|
+
end
|
91
|
+
end
|
92
|
+
RUBY
|
93
|
+
end
|
94
|
+
|
95
|
+
def insert_or_call_block(klass, *args, &block)
|
96
|
+
if klass
|
97
|
+
insert klass, *args, &block
|
98
|
+
else
|
99
|
+
yield
|
100
|
+
end
|
101
|
+
end
|
102
|
+
private :insert_or_call_block
|
103
|
+
|
104
|
+
# Inserts a child element at the right place in the child array, taking the current
|
105
|
+
# flow into account.
|
106
|
+
def insert_child(child)
|
107
|
+
case current_flow
|
108
|
+
when :append
|
109
|
+
children << child
|
110
|
+
|
111
|
+
when :prepend
|
112
|
+
children.insert_at 0, child
|
113
|
+
|
114
|
+
# Update the flow - the next element should be added after this one, not be
|
115
|
+
# prepended.
|
116
|
+
arbre_context.replace_current_flow [:after, child]
|
117
|
+
|
118
|
+
else
|
119
|
+
# flow: [ :before, element ] or [ :after, element ]
|
120
|
+
operation, element = current_flow
|
121
|
+
children.send :"insert_#{operation}", element, child
|
122
|
+
|
123
|
+
if operation == :after
|
124
|
+
# Now that we've inserted something after the element, we need to
|
125
|
+
# make sure that the next element we insert will be after this one.
|
126
|
+
arbre_context.replace_current_flow [:after, child]
|
127
|
+
end
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
131
|
+
######
|
132
|
+
# Support methods
|
133
|
+
|
134
|
+
# Builds a temporary container using the given block, but doesn't add it to the tree.
|
135
|
+
# The block is executed within the current context.
|
136
|
+
def temporary(&block)
|
137
|
+
build Element, &block
|
138
|
+
end
|
139
|
+
|
140
|
+
def current_element
|
141
|
+
arbre_context.current_element
|
142
|
+
end
|
143
|
+
|
144
|
+
def current_flow
|
145
|
+
arbre_context.current_flow
|
146
|
+
end
|
147
|
+
|
148
|
+
end
|
149
|
+
|
150
|
+
end
|
151
|
+
end
|
@@ -0,0 +1,194 @@
|
|
1
|
+
require 'arbre/element/building'
|
2
|
+
require 'arbre/html/querying'
|
3
|
+
|
4
|
+
module Arbre
|
5
|
+
|
6
|
+
# Base class for all Arbre elements. Rendering is not implemented, and should be implemented
|
7
|
+
# by subclasses.
|
8
|
+
class Element
|
9
|
+
include Building
|
10
|
+
include Html::Querying
|
11
|
+
|
12
|
+
######
|
13
|
+
# Initialization
|
14
|
+
|
15
|
+
# Initializes a new Arbre element. Pass an existing Arbre context to re-use it.
|
16
|
+
def initialize(arbre_context = Arbre::Context.new)
|
17
|
+
@_arbre_context = arbre_context
|
18
|
+
@_children = ChildElementCollection.new(self)
|
19
|
+
|
20
|
+
expose_assigns
|
21
|
+
end
|
22
|
+
|
23
|
+
######
|
24
|
+
# Context
|
25
|
+
|
26
|
+
def arbre_context
|
27
|
+
@_arbre_context
|
28
|
+
end
|
29
|
+
|
30
|
+
def assigns
|
31
|
+
arbre_context.assigns
|
32
|
+
end
|
33
|
+
|
34
|
+
def helpers
|
35
|
+
arbre_context.helpers
|
36
|
+
end
|
37
|
+
|
38
|
+
######
|
39
|
+
# Hierarchy
|
40
|
+
|
41
|
+
def parent
|
42
|
+
@_parent
|
43
|
+
end
|
44
|
+
def parent=(parent)
|
45
|
+
@_parent = parent
|
46
|
+
end
|
47
|
+
|
48
|
+
def children
|
49
|
+
@_children
|
50
|
+
end
|
51
|
+
|
52
|
+
# Removes this element from its parent.
|
53
|
+
def remove!
|
54
|
+
parent.children.remove self if parent
|
55
|
+
end
|
56
|
+
|
57
|
+
def <<(child)
|
58
|
+
children << child
|
59
|
+
end
|
60
|
+
|
61
|
+
def has_children?
|
62
|
+
children.any?
|
63
|
+
end
|
64
|
+
|
65
|
+
def empty?
|
66
|
+
!has_children?
|
67
|
+
end
|
68
|
+
|
69
|
+
def orphan?
|
70
|
+
!parent
|
71
|
+
end
|
72
|
+
|
73
|
+
# Retrieves all ancestors (ordered from near to far) for this element.
|
74
|
+
# @return [ElementCollection]
|
75
|
+
def ancestors
|
76
|
+
ancestors = ElementCollection.new
|
77
|
+
|
78
|
+
unless orphan?
|
79
|
+
ancestors << parent
|
80
|
+
ancestors.concat parent.ancestors
|
81
|
+
end
|
82
|
+
|
83
|
+
ancestors
|
84
|
+
end
|
85
|
+
|
86
|
+
# Retrieves all descendants (in prefix form) for this element.
|
87
|
+
# @return [ElementCollection]
|
88
|
+
def descendants
|
89
|
+
descendants = ElementCollection.new
|
90
|
+
children.each do |child|
|
91
|
+
descendants << child
|
92
|
+
descendants.concat child.descendants
|
93
|
+
end
|
94
|
+
|
95
|
+
descendants
|
96
|
+
end
|
97
|
+
|
98
|
+
######
|
99
|
+
# Content
|
100
|
+
|
101
|
+
def content=(content)
|
102
|
+
children.clear
|
103
|
+
case content
|
104
|
+
when Element
|
105
|
+
children << content
|
106
|
+
when ElementCollection
|
107
|
+
children.concat content
|
108
|
+
else
|
109
|
+
children << TextNode.from_string(content.to_s)
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
def content
|
114
|
+
children.to_s
|
115
|
+
end
|
116
|
+
|
117
|
+
######
|
118
|
+
# Set operations
|
119
|
+
|
120
|
+
def +(element)
|
121
|
+
if element.is_a?(Enumerable)
|
122
|
+
ElementCollection.new([self] + element)
|
123
|
+
else
|
124
|
+
ElementCollection.new([ self, element])
|
125
|
+
end
|
126
|
+
end
|
127
|
+
|
128
|
+
######
|
129
|
+
# Building & rendering
|
130
|
+
|
131
|
+
# Override this method to build your element.
|
132
|
+
def build!
|
133
|
+
yield self if block_given?
|
134
|
+
|
135
|
+
self
|
136
|
+
end
|
137
|
+
|
138
|
+
def indent_level
|
139
|
+
if parent
|
140
|
+
parent.indent_level + 1
|
141
|
+
else
|
142
|
+
0
|
143
|
+
end
|
144
|
+
end
|
145
|
+
|
146
|
+
def to_s
|
147
|
+
content
|
148
|
+
end
|
149
|
+
|
150
|
+
def to_html
|
151
|
+
to_s
|
152
|
+
end
|
153
|
+
|
154
|
+
# Provide a clean element description when inspect is used.
|
155
|
+
def inspect
|
156
|
+
"#<#{self.class.name}:0x#{object_id.to_s(16)}>"
|
157
|
+
end
|
158
|
+
|
159
|
+
######
|
160
|
+
# Helpers & assigns accessing
|
161
|
+
|
162
|
+
def respond_to?(method, include_private = false)
|
163
|
+
super || (helpers && helpers.respond_to?(method))
|
164
|
+
end
|
165
|
+
|
166
|
+
private
|
167
|
+
|
168
|
+
# Exposes the assigns from the context as instance variables to the given target.
|
169
|
+
def expose_assigns
|
170
|
+
assigns.each do |key, value|
|
171
|
+
instance_variable_set "@#{key}", value
|
172
|
+
end
|
173
|
+
end
|
174
|
+
|
175
|
+
# Access helper methods from any Arbre element through its context.
|
176
|
+
def method_missing(name, *args, &block)
|
177
|
+
if helpers && helpers.respond_to?(name)
|
178
|
+
define_helper_method name
|
179
|
+
send name, *args, &block
|
180
|
+
else
|
181
|
+
super
|
182
|
+
end
|
183
|
+
end
|
184
|
+
|
185
|
+
def define_helper_method(name)
|
186
|
+
self.class.class_eval <<-RUBY, __FILE__, __LINE__+1
|
187
|
+
def #{name}(*args, &block)
|
188
|
+
helpers.send :#{name}, *args, &block
|
189
|
+
end
|
190
|
+
RUBY
|
191
|
+
end
|
192
|
+
|
193
|
+
end
|
194
|
+
end
|
@@ -0,0 +1,93 @@
|
|
1
|
+
module Arbre
|
2
|
+
|
3
|
+
class ElementCollection
|
4
|
+
|
5
|
+
######
|
6
|
+
# Initialization
|
7
|
+
|
8
|
+
def initialize(elements = [])
|
9
|
+
@elements = elements.to_a
|
10
|
+
end
|
11
|
+
|
12
|
+
######
|
13
|
+
# Array proxy
|
14
|
+
|
15
|
+
include Enumerable
|
16
|
+
delegate :each, :empty?, :length, :size, :count, :last, :to => :@elements
|
17
|
+
delegate :clear, :to => :@elements
|
18
|
+
|
19
|
+
def [](*args)
|
20
|
+
result = @elements[*args]
|
21
|
+
result = self.class.new(result) if result.is_a?(Enumerable)
|
22
|
+
result
|
23
|
+
end
|
24
|
+
|
25
|
+
def add(element)
|
26
|
+
@elements << element unless include?(element)
|
27
|
+
self
|
28
|
+
end
|
29
|
+
def <<(element)
|
30
|
+
add element
|
31
|
+
end
|
32
|
+
|
33
|
+
def concat(elements)
|
34
|
+
elements.each do |element|
|
35
|
+
self << element
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def remove(element)
|
40
|
+
@elements.delete element
|
41
|
+
end
|
42
|
+
|
43
|
+
def to_ary
|
44
|
+
@elements
|
45
|
+
end
|
46
|
+
def to_a
|
47
|
+
@elements.dup
|
48
|
+
end
|
49
|
+
|
50
|
+
def ==(other)
|
51
|
+
to_a == other.to_a
|
52
|
+
end
|
53
|
+
|
54
|
+
def eql?(other)
|
55
|
+
other.is_a?(ElementCollection) && self == other
|
56
|
+
end
|
57
|
+
|
58
|
+
######
|
59
|
+
# Set operations
|
60
|
+
|
61
|
+
def +(other)
|
62
|
+
self.class.new((@elements + other).uniq)
|
63
|
+
end
|
64
|
+
|
65
|
+
def -(other)
|
66
|
+
self.class.new(@elements - other)
|
67
|
+
end
|
68
|
+
|
69
|
+
def &(other)
|
70
|
+
self.class.new(@elements & other)
|
71
|
+
end
|
72
|
+
|
73
|
+
######
|
74
|
+
# String conversion
|
75
|
+
|
76
|
+
def to_s
|
77
|
+
html_safe_join(map(&:to_s), "\n")
|
78
|
+
end
|
79
|
+
|
80
|
+
private
|
81
|
+
|
82
|
+
def html_safe_join(array, delimiter = '')
|
83
|
+
ActiveSupport::SafeBuffer.new.tap do |str|
|
84
|
+
array.each_with_index do |element, i|
|
85
|
+
str << delimiter if i > 0
|
86
|
+
str << element
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
end
|
92
|
+
|
93
|
+
end
|
@@ -0,0 +1,91 @@
|
|
1
|
+
module Arbre
|
2
|
+
module Html
|
3
|
+
|
4
|
+
# HTML attributes hash. Behaves like a hash with some minor differences:
|
5
|
+
#
|
6
|
+
# - Indifferent access: everything is stored as strings, but also values.
|
7
|
+
# - Setting an attribute to +true+ sets it to the name of the attribute, as per the HTML
|
8
|
+
# standard.
|
9
|
+
# - Setting an attribute to +false+ or +nil+ will remove it.
|
10
|
+
class Attributes
|
11
|
+
|
12
|
+
def initialize(attributes = {})
|
13
|
+
@attributes = {}
|
14
|
+
update attributes
|
15
|
+
end
|
16
|
+
|
17
|
+
def self.[](*args)
|
18
|
+
Attributes.new(Hash[*args])
|
19
|
+
end
|
20
|
+
|
21
|
+
def [](attribute)
|
22
|
+
if attribute.to_s == 'class'
|
23
|
+
@attributes['class'] ||= ClassList.new
|
24
|
+
else
|
25
|
+
@attributes[attribute.to_s]
|
26
|
+
end
|
27
|
+
end
|
28
|
+
def []=(attribute, value)
|
29
|
+
if attribute.to_s == 'class'
|
30
|
+
if value.present?
|
31
|
+
@attributes['class'] = ClassList.new(value)
|
32
|
+
else
|
33
|
+
remove 'class'
|
34
|
+
end
|
35
|
+
elsif value == true
|
36
|
+
@attributes[attribute.to_s] = attribute.to_s
|
37
|
+
elsif value
|
38
|
+
@attributes[attribute.to_s] = value.to_s
|
39
|
+
else
|
40
|
+
remove attribute
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def remove(attribute)
|
45
|
+
@attributes.delete attribute.to_s
|
46
|
+
end
|
47
|
+
|
48
|
+
def update(attributes)
|
49
|
+
attributes.each { |k, v| self[k] = v }
|
50
|
+
end
|
51
|
+
|
52
|
+
def ==(other)
|
53
|
+
to_hash == other.to_hash
|
54
|
+
end
|
55
|
+
|
56
|
+
def eql?(other)
|
57
|
+
other.is_a?(Attributes) && self == other
|
58
|
+
end
|
59
|
+
|
60
|
+
def has_key?(key)
|
61
|
+
@attributes.has_key?(key.to_s)
|
62
|
+
end
|
63
|
+
|
64
|
+
include Enumerable
|
65
|
+
delegate :each, :empty?, :length, :size, :count, :to => :@attributes
|
66
|
+
|
67
|
+
def pairs
|
68
|
+
map do |name, value|
|
69
|
+
next if name == 'class' && value.blank?
|
70
|
+
"#{html_escape(name)}=\"#{html_escape(value)}\""
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
def to_hash
|
75
|
+
@attributes
|
76
|
+
end
|
77
|
+
|
78
|
+
def to_s
|
79
|
+
pairs.join(' ').html_safe
|
80
|
+
end
|
81
|
+
|
82
|
+
protected
|
83
|
+
|
84
|
+
def html_escape(s)
|
85
|
+
ERB::Util.html_escape(s)
|
86
|
+
end
|
87
|
+
|
88
|
+
end
|
89
|
+
|
90
|
+
end
|
91
|
+
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
require 'set'
|
2
|
+
|
3
|
+
module Arbre
|
4
|
+
module Html
|
5
|
+
|
6
|
+
# Holds a set of classes
|
7
|
+
class ClassList < Set
|
8
|
+
|
9
|
+
def initialize(classes = [])
|
10
|
+
super()
|
11
|
+
[*classes].each do |cls|
|
12
|
+
add cls
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
# Alias to the list itself.
|
17
|
+
def classes
|
18
|
+
self
|
19
|
+
end
|
20
|
+
|
21
|
+
# Adds one ore more classes to the list. You can pass in a string which is split by space, or
|
22
|
+
# an array of some kind.
|
23
|
+
def add(classes)
|
24
|
+
classes = classes.to_s.split(' ')
|
25
|
+
classes.each { |cls| super cls }
|
26
|
+
self
|
27
|
+
end
|
28
|
+
alias << add
|
29
|
+
|
30
|
+
def remove(classes)
|
31
|
+
classes = classes.split(' ')
|
32
|
+
classes.each { |cls| delete cls }
|
33
|
+
self
|
34
|
+
end
|
35
|
+
private :delete
|
36
|
+
|
37
|
+
def concat(classes)
|
38
|
+
classes.each { |cls| add cls }
|
39
|
+
self
|
40
|
+
end
|
41
|
+
|
42
|
+
def ==(other)
|
43
|
+
to_a.sort == other.to_a.sort
|
44
|
+
end
|
45
|
+
|
46
|
+
def to_s
|
47
|
+
to_a.join(' ')
|
48
|
+
end
|
49
|
+
|
50
|
+
end
|
51
|
+
|
52
|
+
end
|
53
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
module Arbre
|
2
|
+
module Html
|
3
|
+
|
4
|
+
class Comment < Element
|
5
|
+
builder_method :comment
|
6
|
+
|
7
|
+
attr_accessor :comment
|
8
|
+
|
9
|
+
def build!(comment = nil)
|
10
|
+
@comment = comment
|
11
|
+
end
|
12
|
+
|
13
|
+
def to_s
|
14
|
+
spaces = (' ' * indent_level * Tag::INDENT_SIZE)
|
15
|
+
|
16
|
+
out = ActiveSupport::SafeBuffer.new
|
17
|
+
|
18
|
+
if !comment
|
19
|
+
out << '<!-- -->'.html_safe
|
20
|
+
elsif comment.include?("\n")
|
21
|
+
out << spaces << '<!--'.html_safe
|
22
|
+
out << "\n"
|
23
|
+
out << indent_comment
|
24
|
+
out << "\n"
|
25
|
+
out << spaces << '-->'.html_safe
|
26
|
+
else
|
27
|
+
out << '<!-- '.html_safe << comment << ' -->'.html_safe
|
28
|
+
end
|
29
|
+
|
30
|
+
out
|
31
|
+
end
|
32
|
+
|
33
|
+
private
|
34
|
+
|
35
|
+
def indent_comment
|
36
|
+
ActiveSupport::SafeBuffer.new.tap do |out|
|
37
|
+
comment.split("\n").each_with_index.map do |line, index|
|
38
|
+
out << "\n" unless index == 0
|
39
|
+
out << (' ' * (indent_level+1) * Tag::INDENT_SIZE) << line
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
end
|
45
|
+
|
46
|
+
end
|
47
|
+
end
|
@@ -0,0 +1,93 @@
|
|
1
|
+
module Arbre
|
2
|
+
module Html
|
3
|
+
|
4
|
+
# Root tag for any Html document.
|
5
|
+
#
|
6
|
+
# Represents the combination of a doctype, a +head+ tag and a +body+ tag.
|
7
|
+
class Document < Tag
|
8
|
+
|
9
|
+
######
|
10
|
+
# Initialization
|
11
|
+
|
12
|
+
def initialize(*)
|
13
|
+
super
|
14
|
+
end
|
15
|
+
|
16
|
+
######
|
17
|
+
# Building
|
18
|
+
|
19
|
+
def build!
|
20
|
+
append_head
|
21
|
+
append_body
|
22
|
+
|
23
|
+
within body do
|
24
|
+
yield self if block_given?
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
private
|
29
|
+
|
30
|
+
# Builds up a default head tag.
|
31
|
+
def append_head
|
32
|
+
@_head = append(Head) do
|
33
|
+
meta :"http-equiv" => "Content-Type", :content => "text/html; charset=utf-8"
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
# Builds up a default body tag.
|
38
|
+
def append_body
|
39
|
+
@_body = append(Body)
|
40
|
+
end
|
41
|
+
|
42
|
+
public
|
43
|
+
|
44
|
+
######
|
45
|
+
# Head & body accessors
|
46
|
+
|
47
|
+
def title
|
48
|
+
@title ||= title_tag.content
|
49
|
+
end
|
50
|
+
|
51
|
+
def title=(title)
|
52
|
+
@title = title_tag.content = title
|
53
|
+
end
|
54
|
+
|
55
|
+
def title_tag
|
56
|
+
head.find_first('> title') || within(head) { prepend Title }
|
57
|
+
end
|
58
|
+
private :title_tag
|
59
|
+
|
60
|
+
# Adds content to the head tag and/or returns it.
|
61
|
+
def head(&block)
|
62
|
+
within @_head, &block if block_given?
|
63
|
+
@_head
|
64
|
+
end
|
65
|
+
|
66
|
+
# Adds content to the body tag and/or returns it.
|
67
|
+
def body(&block)
|
68
|
+
within @_body, &block if block_given?
|
69
|
+
@_body
|
70
|
+
end
|
71
|
+
|
72
|
+
######
|
73
|
+
# Rendering
|
74
|
+
|
75
|
+
def tag_name
|
76
|
+
'html'
|
77
|
+
end
|
78
|
+
|
79
|
+
def doctype
|
80
|
+
'<!DOCTYPE html>'.html_safe
|
81
|
+
end
|
82
|
+
|
83
|
+
def to_s
|
84
|
+
out = ActiveSupport::SafeBuffer.new
|
85
|
+
out << doctype
|
86
|
+
out << "\n\n"
|
87
|
+
out << super
|
88
|
+
end
|
89
|
+
|
90
|
+
end
|
91
|
+
|
92
|
+
end
|
93
|
+
end
|