arbo 1.2.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/.github/workflows/ci.yaml +21 -0
- data/.github/workflows/daily.yaml +23 -0
- data/.gitignore +11 -0
- data/CHANGELOG.md +95 -0
- data/CONTRIBUTING.md +3 -0
- data/Gemfile +17 -0
- data/LICENSE +20 -0
- data/README.md +29 -0
- data/Rakefile +18 -0
- data/arbo.gemspec +25 -0
- data/docs/Gemfile +2 -0
- data/docs/_config.yml +7 -0
- data/docs/_includes/footer.html +8 -0
- data/docs/_includes/google-analytics.html +16 -0
- data/docs/_includes/head.html +7 -0
- data/docs/_includes/toc.html +12 -0
- data/docs/_includes/top-menu.html +8 -0
- data/docs/_layouts/default.html +21 -0
- data/docs/index.md +106 -0
- data/docs/stylesheets/main.css +1152 -0
- data/lib/arbo/component.rb +22 -0
- data/lib/arbo/context.rb +118 -0
- data/lib/arbo/element/builder_methods.rb +83 -0
- data/lib/arbo/element/proxy.rb +28 -0
- data/lib/arbo/element.rb +225 -0
- data/lib/arbo/element_collection.rb +31 -0
- data/lib/arbo/html/attributes.rb +41 -0
- data/lib/arbo/html/class_list.rb +28 -0
- data/lib/arbo/html/document.rb +31 -0
- data/lib/arbo/html/html5_elements.rb +47 -0
- data/lib/arbo/html/tag.rb +220 -0
- data/lib/arbo/html/text_node.rb +43 -0
- data/lib/arbo/rails/forms.rb +101 -0
- data/lib/arbo/rails/rendering.rb +17 -0
- data/lib/arbo/rails/template_handler.rb +35 -0
- data/lib/arbo/rails.rb +5 -0
- data/lib/arbo/version.rb +3 -0
- data/lib/arbo.rb +21 -0
- data/spec/arbo/integration/html_spec.rb +307 -0
- data/spec/arbo/unit/component_spec.rb +54 -0
- data/spec/arbo/unit/context_spec.rb +35 -0
- data/spec/arbo/unit/element_finder_methods_spec.rb +116 -0
- data/spec/arbo/unit/element_spec.rb +272 -0
- data/spec/arbo/unit/html/class_list_spec.rb +16 -0
- data/spec/arbo/unit/html/tag_attributes_spec.rb +104 -0
- data/spec/arbo/unit/html/tag_spec.rb +124 -0
- data/spec/arbo/unit/html/text_node_spec.rb +5 -0
- data/spec/rails/integration/forms_spec.rb +117 -0
- data/spec/rails/integration/rendering_spec.rb +98 -0
- data/spec/rails/rails_spec_helper.rb +46 -0
- data/spec/rails/stub_app/config/database.yml +3 -0
- data/spec/rails/stub_app/config/routes.rb +3 -0
- data/spec/rails/stub_app/db/schema.rb +3 -0
- data/spec/rails/stub_app/log/.gitignore +1 -0
- data/spec/rails/stub_app/public/favicon.ico +0 -0
- data/spec/rails/support/mock_person.rb +15 -0
- data/spec/rails/templates/arbo/_partial.arb +1 -0
- data/spec/rails/templates/arbo/_partial_with_assignment.arb +1 -0
- data/spec/rails/templates/arbo/empty.arb +0 -0
- data/spec/rails/templates/arbo/page_with_arb_partial_and_assignment.arb +3 -0
- data/spec/rails/templates/arbo/page_with_assignment.arb +1 -0
- data/spec/rails/templates/arbo/page_with_erb_partial.arb +3 -0
- data/spec/rails/templates/arbo/page_with_helpers.arb +7 -0
- data/spec/rails/templates/arbo/page_with_partial.arb +3 -0
- data/spec/rails/templates/arbo/simple_page.arb +8 -0
- data/spec/rails/templates/erb/_partial.erb +1 -0
- data/spec/spec_helper.rb +7 -0
- data/spec/support/bundle.rb +4 -0
- metadata +169 -0
@@ -0,0 +1,22 @@
|
|
1
|
+
module Arbo
|
2
|
+
class Component < Arbo::HTML::Div
|
3
|
+
|
4
|
+
# By default components render a div
|
5
|
+
def tag_name
|
6
|
+
'div'
|
7
|
+
end
|
8
|
+
|
9
|
+
def initialize(*)
|
10
|
+
super
|
11
|
+
add_class default_class_name
|
12
|
+
end
|
13
|
+
|
14
|
+
protected
|
15
|
+
|
16
|
+
# By default, add a css class named after the ruby class
|
17
|
+
def default_class_name
|
18
|
+
self.class.name.demodulize.underscore
|
19
|
+
end
|
20
|
+
|
21
|
+
end
|
22
|
+
end
|
data/lib/arbo/context.rb
ADDED
@@ -0,0 +1,118 @@
|
|
1
|
+
require 'arbo/element'
|
2
|
+
require 'ruby2_keywords'
|
3
|
+
|
4
|
+
module Arbo
|
5
|
+
|
6
|
+
# The Arbo::Context class is the frontend for using Arbo.
|
7
|
+
#
|
8
|
+
# The simplest example possible:
|
9
|
+
#
|
10
|
+
# html = Arbo::Context.new do
|
11
|
+
# h1 "Hello World"
|
12
|
+
# end
|
13
|
+
#
|
14
|
+
# html.to_s #=> "<h1>Hello World</h1>"
|
15
|
+
#
|
16
|
+
# The contents of the block are instance eval'd within the Context
|
17
|
+
# object. This means that you lose context to the outside world from
|
18
|
+
# within the block. To pass local variables into the Context, use the
|
19
|
+
# assigns param.
|
20
|
+
#
|
21
|
+
# html = Arbo::Context.new({one: 1}) do
|
22
|
+
# h1 "Your number #{one}"
|
23
|
+
# end
|
24
|
+
#
|
25
|
+
# html.to_s #=> "Your number 1"
|
26
|
+
#
|
27
|
+
class Context < Element
|
28
|
+
|
29
|
+
# Initialize a new Arbo::Context
|
30
|
+
#
|
31
|
+
# @param [Hash] assigns A hash of objecs that you would like to be
|
32
|
+
# availble as local variables within the Context
|
33
|
+
#
|
34
|
+
# @param [Object] helpers An object that has methods on it which will become
|
35
|
+
# instance methods within the context.
|
36
|
+
#
|
37
|
+
# @yield [] The block that will get instance eval'd in the context
|
38
|
+
def initialize(assigns = {}, helpers = nil, &block)
|
39
|
+
assigns = assigns || {}
|
40
|
+
@_assigns = assigns.symbolize_keys
|
41
|
+
|
42
|
+
@_helpers = helpers
|
43
|
+
@_current_arbo_element_buffer = [self]
|
44
|
+
|
45
|
+
super(self)
|
46
|
+
instance_eval(&block) if block_given?
|
47
|
+
end
|
48
|
+
|
49
|
+
def arbo_context
|
50
|
+
self
|
51
|
+
end
|
52
|
+
|
53
|
+
def assigns
|
54
|
+
@_assigns
|
55
|
+
end
|
56
|
+
|
57
|
+
def helpers
|
58
|
+
@_helpers
|
59
|
+
end
|
60
|
+
|
61
|
+
def indent_level
|
62
|
+
# A context does not increment the indent_level
|
63
|
+
super - 1
|
64
|
+
end
|
65
|
+
|
66
|
+
def bytesize
|
67
|
+
cached_html.bytesize
|
68
|
+
end
|
69
|
+
alias :length :bytesize
|
70
|
+
|
71
|
+
def respond_to_missing?(method, include_all)
|
72
|
+
super || cached_html.respond_to?(method, include_all)
|
73
|
+
end
|
74
|
+
|
75
|
+
# Webservers treat Arbo::Context as a string. We override
|
76
|
+
# method_missing to delegate to the string representation
|
77
|
+
# of the html.
|
78
|
+
ruby2_keywords def method_missing(method, *args, &block)
|
79
|
+
if cached_html.respond_to? method
|
80
|
+
cached_html.send method, *args, &block
|
81
|
+
else
|
82
|
+
super
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
def current_arbo_element
|
87
|
+
@_current_arbo_element_buffer.last
|
88
|
+
end
|
89
|
+
|
90
|
+
def with_current_arbo_element(tag)
|
91
|
+
raise ArgumentError, "Can't be in the context of nil. #{@_current_arbo_element_buffer.inspect}" unless tag
|
92
|
+
@_current_arbo_element_buffer.push tag
|
93
|
+
yield
|
94
|
+
@_current_arbo_element_buffer.pop
|
95
|
+
end
|
96
|
+
alias_method :within, :with_current_arbo_element
|
97
|
+
|
98
|
+
def output_buffer
|
99
|
+
@output_buffer ||= ActiveSupport::SafeBuffer.new
|
100
|
+
end
|
101
|
+
|
102
|
+
private
|
103
|
+
|
104
|
+
|
105
|
+
# Caches the rendered HTML so that we don't re-render just to
|
106
|
+
# get the content lenght or to delegate a method to the HTML
|
107
|
+
def cached_html
|
108
|
+
if defined?(@cached_html)
|
109
|
+
@cached_html
|
110
|
+
else
|
111
|
+
html = render_in(self)
|
112
|
+
@cached_html = html if html.length > 0
|
113
|
+
html
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
end
|
118
|
+
end
|
@@ -0,0 +1,83 @@
|
|
1
|
+
module Arbo
|
2
|
+
class Element
|
3
|
+
|
4
|
+
module BuilderMethods
|
5
|
+
|
6
|
+
def self.included(klass)
|
7
|
+
klass.extend ClassMethods
|
8
|
+
end
|
9
|
+
|
10
|
+
module ClassMethods
|
11
|
+
|
12
|
+
def builder_method(method_name)
|
13
|
+
BuilderMethods.class_eval <<-EOF, __FILE__, __LINE__
|
14
|
+
def #{method_name}(*args, &block)
|
15
|
+
insert_tag ::#{self.name}, *args, &block
|
16
|
+
end
|
17
|
+
EOF
|
18
|
+
end
|
19
|
+
|
20
|
+
end
|
21
|
+
|
22
|
+
def build_tag(klass, *args, &block)
|
23
|
+
tag = klass.new(arbo_context)
|
24
|
+
tag.parent = current_arbo_element
|
25
|
+
|
26
|
+
with_current_arbo_element tag do
|
27
|
+
if block_given? && block.arity > 0
|
28
|
+
tag.build(*args, &block)
|
29
|
+
else
|
30
|
+
tag.build(*args)
|
31
|
+
append_return_block(yield) if block_given?
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
tag
|
36
|
+
end
|
37
|
+
|
38
|
+
def insert_tag(klass, *args, &block)
|
39
|
+
tag = build_tag(klass, *args, &block)
|
40
|
+
current_arbo_element.add_child(tag)
|
41
|
+
tag
|
42
|
+
end
|
43
|
+
|
44
|
+
def current_arbo_element
|
45
|
+
arbo_context.current_arbo_element
|
46
|
+
end
|
47
|
+
|
48
|
+
def with_current_arbo_element(tag, &block)
|
49
|
+
arbo_context.with_current_arbo_element(tag, &block)
|
50
|
+
end
|
51
|
+
alias_method :within, :with_current_arbo_element
|
52
|
+
|
53
|
+
private
|
54
|
+
|
55
|
+
# Appends the value to the current DOM element if there are no
|
56
|
+
# existing DOM Children and it responds to #to_s
|
57
|
+
def append_return_block(tag)
|
58
|
+
return nil if current_arbo_element.children?
|
59
|
+
|
60
|
+
if appendable_tag?(tag)
|
61
|
+
current_arbo_element << Arbo::HTML::TextNode.from_string(tag.to_s)
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
# Returns true if the object should be converted into a text node
|
66
|
+
# and appended into the DOM.
|
67
|
+
def appendable_tag?(tag)
|
68
|
+
is_appendable = !tag.is_a?(Arbo::Element) && tag.respond_to?(:to_s)
|
69
|
+
|
70
|
+
# In ruby 1.9, Arraay.new.to_s prints out an empty array ("[]"). In
|
71
|
+
# Arbo, we append the return value of blocks to the output, which
|
72
|
+
# can cause empty arrays to show up within the output. To get
|
73
|
+
# around this, we check if the object responds to #empty?
|
74
|
+
if tag.respond_to?(:empty?) && tag.empty?
|
75
|
+
is_appendable = false
|
76
|
+
end
|
77
|
+
|
78
|
+
is_appendable
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
end
|
83
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
module Arbo
|
2
|
+
class Element
|
3
|
+
class Proxy < BasicObject
|
4
|
+
undef_method :==
|
5
|
+
undef_method :equal?
|
6
|
+
|
7
|
+
def initialize(element)
|
8
|
+
@element = element
|
9
|
+
end
|
10
|
+
|
11
|
+
def respond_to?(method, include_all = false)
|
12
|
+
if method.to_s == 'to_ary'
|
13
|
+
false
|
14
|
+
else
|
15
|
+
super || @element.respond_to?(method, include_all)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def method_missing(method, *args, &block)
|
20
|
+
if method.to_s == 'to_ary'
|
21
|
+
super
|
22
|
+
else
|
23
|
+
@element.__send__ method, *args, &block
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
data/lib/arbo/element.rb
ADDED
@@ -0,0 +1,225 @@
|
|
1
|
+
require 'arbo/element/builder_methods'
|
2
|
+
require 'arbo/element/proxy'
|
3
|
+
require 'arbo/element_collection'
|
4
|
+
require 'ruby2_keywords'
|
5
|
+
|
6
|
+
module Arbo
|
7
|
+
|
8
|
+
class Element
|
9
|
+
include BuilderMethods
|
10
|
+
|
11
|
+
attr_reader :parent
|
12
|
+
attr_reader :children, :arbo_context
|
13
|
+
|
14
|
+
def initialize(arbo_context = Arbo::Context.new)
|
15
|
+
@arbo_context = arbo_context
|
16
|
+
@children = ElementCollection.new
|
17
|
+
@parent = nil
|
18
|
+
end
|
19
|
+
|
20
|
+
def assigns
|
21
|
+
arbo_context.assigns
|
22
|
+
end
|
23
|
+
|
24
|
+
def helpers
|
25
|
+
arbo_context.helpers
|
26
|
+
end
|
27
|
+
|
28
|
+
def tag_name
|
29
|
+
@tag_name ||= self.class.name.demodulize.downcase
|
30
|
+
end
|
31
|
+
|
32
|
+
def build(*args, &block)
|
33
|
+
# Render the block passing ourselves in
|
34
|
+
append_return_block(block.call(self)) if block
|
35
|
+
end
|
36
|
+
|
37
|
+
def add_child(child)
|
38
|
+
return unless child
|
39
|
+
|
40
|
+
if child.is_a?(Array)
|
41
|
+
child.each{|item| add_child(item) }
|
42
|
+
return @children
|
43
|
+
end
|
44
|
+
|
45
|
+
# If its not an element, wrap it in a TextNode
|
46
|
+
unless child.is_a?(Element)
|
47
|
+
child = Arbo::HTML::TextNode.from_string(child)
|
48
|
+
end
|
49
|
+
|
50
|
+
if child.respond_to?(:parent)
|
51
|
+
# Remove the child
|
52
|
+
child.parent.remove_child(child) if child.parent && child.parent != self
|
53
|
+
# Set ourselves as the parent
|
54
|
+
child.parent = self
|
55
|
+
end
|
56
|
+
|
57
|
+
@children << child
|
58
|
+
end
|
59
|
+
|
60
|
+
def remove_child(child)
|
61
|
+
child.parent = nil if child.respond_to?(:parent=)
|
62
|
+
@children.delete(child)
|
63
|
+
end
|
64
|
+
|
65
|
+
def <<(child)
|
66
|
+
add_child(child)
|
67
|
+
end
|
68
|
+
|
69
|
+
def children?
|
70
|
+
@children.any?
|
71
|
+
end
|
72
|
+
|
73
|
+
def parent=(parent)
|
74
|
+
@parent = parent
|
75
|
+
end
|
76
|
+
|
77
|
+
def parent?
|
78
|
+
!@parent.nil?
|
79
|
+
end
|
80
|
+
|
81
|
+
def ancestors
|
82
|
+
if parent?
|
83
|
+
[parent] + parent.ancestors
|
84
|
+
else
|
85
|
+
[]
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
# TODO: Shouldn't grab whole tree
|
90
|
+
def find_first_ancestor(type)
|
91
|
+
ancestors.find{|a| a.is_a?(type) }
|
92
|
+
end
|
93
|
+
|
94
|
+
def content=(contents)
|
95
|
+
clear_children!
|
96
|
+
add_child(contents)
|
97
|
+
end
|
98
|
+
|
99
|
+
def get_elements_by_tag_name(tag_name)
|
100
|
+
elements = ElementCollection.new
|
101
|
+
children.each do |child|
|
102
|
+
elements << child if child.tag_name == tag_name
|
103
|
+
elements.concat(child.get_elements_by_tag_name(tag_name))
|
104
|
+
end
|
105
|
+
elements
|
106
|
+
end
|
107
|
+
alias_method :find_by_tag, :get_elements_by_tag_name
|
108
|
+
|
109
|
+
def get_elements_by_class_name(class_name)
|
110
|
+
elements = ElementCollection.new
|
111
|
+
children.each do |child|
|
112
|
+
elements << child if child.class_list.include?(class_name)
|
113
|
+
elements.concat(child.get_elements_by_class_name(class_name))
|
114
|
+
end
|
115
|
+
elements
|
116
|
+
end
|
117
|
+
alias_method :find_by_class, :get_elements_by_class_name
|
118
|
+
|
119
|
+
def content
|
120
|
+
children.to_s
|
121
|
+
end
|
122
|
+
|
123
|
+
def html_safe
|
124
|
+
to_s
|
125
|
+
end
|
126
|
+
|
127
|
+
def indent_level
|
128
|
+
parent? ? parent.indent_level + 1 : 0
|
129
|
+
end
|
130
|
+
|
131
|
+
def each(&block)
|
132
|
+
[to_s].each(&block)
|
133
|
+
end
|
134
|
+
|
135
|
+
def inspect
|
136
|
+
content
|
137
|
+
end
|
138
|
+
|
139
|
+
def to_str
|
140
|
+
ActiveSupport::Deprecation.warn("don't rely on implicit conversion of Element to String")
|
141
|
+
content
|
142
|
+
end
|
143
|
+
|
144
|
+
def to_s
|
145
|
+
ActiveSupport::Deprecation.warn("#render_in should be defined for rendering #{method_owner(:to_s)} instead of #to_s")
|
146
|
+
content
|
147
|
+
end
|
148
|
+
|
149
|
+
# Rendering strategy that visits all elements and appends output to a buffer.
|
150
|
+
def render_in(context = arbo_context)
|
151
|
+
children.collect do |element|
|
152
|
+
element.render_in_or_to_s(context)
|
153
|
+
end.join('')
|
154
|
+
end
|
155
|
+
|
156
|
+
# Use render_in to render element unless closer ancestor overrides :to_s only.
|
157
|
+
def render_in_or_to_s(context)
|
158
|
+
if method_distance(:render_in) <= method_distance(:to_s)
|
159
|
+
render_in(context)
|
160
|
+
else
|
161
|
+
ActiveSupport::Deprecation.warn("#render_in should be defined for rendering #{method_owner(:to_s)} instead of #to_s")
|
162
|
+
to_s.tap { |s| context.output_buffer << s }
|
163
|
+
end
|
164
|
+
end
|
165
|
+
|
166
|
+
def +(element)
|
167
|
+
case element
|
168
|
+
when Element, ElementCollection
|
169
|
+
else
|
170
|
+
element = Arbo::HTML::TextNode.from_string(element)
|
171
|
+
end
|
172
|
+
to_ary + element
|
173
|
+
end
|
174
|
+
|
175
|
+
def to_ary
|
176
|
+
ElementCollection.new [Proxy.new(self)]
|
177
|
+
end
|
178
|
+
alias_method :to_a, :to_ary
|
179
|
+
|
180
|
+
private
|
181
|
+
|
182
|
+
# Resets the Elements children
|
183
|
+
def clear_children!
|
184
|
+
@children.clear
|
185
|
+
end
|
186
|
+
|
187
|
+
# Implements the method lookup chain. When you call a method that
|
188
|
+
# doesn't exist, we:
|
189
|
+
#
|
190
|
+
# 1. Try to call the method on the current DOM context
|
191
|
+
# 2. Return an assigned variable of the same name
|
192
|
+
# 3. Call the method on the helper object
|
193
|
+
# 4. Call super
|
194
|
+
#
|
195
|
+
ruby2_keywords def method_missing(name, *args, &block)
|
196
|
+
if current_arbo_element.respond_to?(name)
|
197
|
+
current_arbo_element.send name, *args, &block
|
198
|
+
elsif assigns && assigns.has_key?(name)
|
199
|
+
assigns[name]
|
200
|
+
elsif helpers.respond_to?(name)
|
201
|
+
helper_capture(name, *args, &block)
|
202
|
+
else
|
203
|
+
super
|
204
|
+
end
|
205
|
+
end
|
206
|
+
|
207
|
+
# The helper might have a block that builds Arbo elements
|
208
|
+
# which will be rendered (#to_s) inside ActionView::Base#capture.
|
209
|
+
# We do not want such elements added to self, so we push a dummy
|
210
|
+
# current_arbo_element.
|
211
|
+
ruby2_keywords def helper_capture(name, *args, &block)
|
212
|
+
s = ""
|
213
|
+
within(Element.new) { s = helpers.send(name, *args, &block) }
|
214
|
+
s.is_a?(Element) ? s.to_s : s
|
215
|
+
end
|
216
|
+
|
217
|
+
def method_distance(name)
|
218
|
+
self.class.ancestors.index method_owner(name)
|
219
|
+
end
|
220
|
+
|
221
|
+
def method_owner(name)
|
222
|
+
self.class.instance_method(name).owner
|
223
|
+
end
|
224
|
+
end
|
225
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
module Arbo
|
2
|
+
|
3
|
+
# Stores a collection of Element objects
|
4
|
+
class ElementCollection < Array
|
5
|
+
|
6
|
+
def +(other)
|
7
|
+
self.class.new(super)
|
8
|
+
end
|
9
|
+
|
10
|
+
def -(other)
|
11
|
+
self.class.new(super)
|
12
|
+
end
|
13
|
+
|
14
|
+
def &(other)
|
15
|
+
self.class.new(super)
|
16
|
+
end
|
17
|
+
|
18
|
+
def to_s
|
19
|
+
self.collect do |element|
|
20
|
+
element.to_s
|
21
|
+
end.join('').html_safe
|
22
|
+
end
|
23
|
+
|
24
|
+
def render_in(context)
|
25
|
+
self.collect do |element|
|
26
|
+
element.render_in(context)
|
27
|
+
end.join('').html_safe
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
module Arbo
|
2
|
+
module HTML
|
3
|
+
|
4
|
+
class Attributes < Hash
|
5
|
+
|
6
|
+
def to_s
|
7
|
+
flatten_hash.map do |name, value|
|
8
|
+
next if value_empty?(value)
|
9
|
+
"#{html_escape(name)}=\"#{html_escape(value)}\""
|
10
|
+
end.compact.join ' '
|
11
|
+
end
|
12
|
+
|
13
|
+
def any?
|
14
|
+
super{ |k,v| !value_empty?(v) }
|
15
|
+
end
|
16
|
+
|
17
|
+
protected
|
18
|
+
|
19
|
+
def flatten_hash(hash = self, old_path = [], accumulator = {})
|
20
|
+
hash.each do |key, value|
|
21
|
+
path = old_path + [key]
|
22
|
+
if value.is_a? Hash
|
23
|
+
flatten_hash(value, path, accumulator)
|
24
|
+
else
|
25
|
+
accumulator[path.join('-')] = value
|
26
|
+
end
|
27
|
+
end
|
28
|
+
accumulator
|
29
|
+
end
|
30
|
+
|
31
|
+
def value_empty?(value)
|
32
|
+
value.respond_to?(:empty?) ? value.empty? : !value
|
33
|
+
end
|
34
|
+
|
35
|
+
def html_escape(s)
|
36
|
+
ERB::Util.html_escape(s)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
require 'set'
|
2
|
+
|
3
|
+
module Arbo
|
4
|
+
module HTML
|
5
|
+
|
6
|
+
# Holds a set of classes
|
7
|
+
class ClassList < Set
|
8
|
+
|
9
|
+
def self.build_from_string(class_names)
|
10
|
+
new.add(class_names)
|
11
|
+
end
|
12
|
+
|
13
|
+
def add(class_names)
|
14
|
+
class_names.to_s.split(" ").each do |class_name|
|
15
|
+
super(class_name)
|
16
|
+
end
|
17
|
+
self
|
18
|
+
end
|
19
|
+
alias :<< :add
|
20
|
+
|
21
|
+
def to_s
|
22
|
+
to_a.join(" ")
|
23
|
+
end
|
24
|
+
|
25
|
+
end
|
26
|
+
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
module Arbo
|
2
|
+
module HTML
|
3
|
+
|
4
|
+
class Document < Tag
|
5
|
+
|
6
|
+
def document
|
7
|
+
self
|
8
|
+
end
|
9
|
+
|
10
|
+
def tag_name
|
11
|
+
'html'
|
12
|
+
end
|
13
|
+
|
14
|
+
def doctype
|
15
|
+
'<!DOCTYPE html>'.html_safe
|
16
|
+
end
|
17
|
+
|
18
|
+
def to_s
|
19
|
+
doctype + super
|
20
|
+
end
|
21
|
+
|
22
|
+
def render_in(context = arbo_context)
|
23
|
+
context.output_buffer << doctype
|
24
|
+
super
|
25
|
+
context.output_buffer
|
26
|
+
end
|
27
|
+
|
28
|
+
end
|
29
|
+
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
module Arbo
|
2
|
+
module HTML
|
3
|
+
|
4
|
+
AUTO_BUILD_ELEMENTS = [ :a, :abbr, :address, :area, :article, :aside, :audio, :b, :base,
|
5
|
+
:bdo, :blockquote, :body, :br, :button, :canvas, :caption, :cite,
|
6
|
+
:code, :col, :colgroup, :command, :datalist, :dd, :del, :details,
|
7
|
+
:dfn, :div, :dl, :dt, :em, :embed, :fieldset, :figcaption, :figure,
|
8
|
+
:footer, :form, :h1, :h2, :h3, :h4, :h5, :h6, :head, :header, :hgroup,
|
9
|
+
:hr, :html, :i, :iframe, :img, :input, :ins, :keygen, :kbd, :label,
|
10
|
+
:legend, :li, :link, :map, :mark, :menu, :menuitem, :meta, :meter, :nav, :noscript,
|
11
|
+
:object, :ol, :optgroup, :option, :output, :param, :pre, :progress, :q,
|
12
|
+
:s, :samp, :script, :section, :select, :small, :source, :span,
|
13
|
+
:strong, :style, :sub, :summary, :sup, :svg, :table, :tbody, :td,
|
14
|
+
:textarea, :tfoot, :th, :thead, :time, :title, :tr, :track, :ul, :var, :video, :wbr ]
|
15
|
+
|
16
|
+
HTML5_ELEMENTS = [ :p ] + AUTO_BUILD_ELEMENTS
|
17
|
+
|
18
|
+
AUTO_BUILD_ELEMENTS.each do |name|
|
19
|
+
class_eval <<-EOF
|
20
|
+
class #{name.to_s.capitalize} < Tag
|
21
|
+
builder_method :#{name}
|
22
|
+
end
|
23
|
+
EOF
|
24
|
+
end
|
25
|
+
|
26
|
+
class P < Tag
|
27
|
+
builder_method :para
|
28
|
+
end
|
29
|
+
|
30
|
+
class Table < Tag
|
31
|
+
def initialize(*)
|
32
|
+
super
|
33
|
+
set_table_tag_defaults
|
34
|
+
end
|
35
|
+
|
36
|
+
protected
|
37
|
+
|
38
|
+
# Set some good defaults for tables
|
39
|
+
def set_table_tag_defaults
|
40
|
+
set_attribute :border, 0
|
41
|
+
set_attribute :cellspacing, 0
|
42
|
+
set_attribute :cellpadding, 0
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
end
|
47
|
+
end
|