arbo 1.2.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|