arbre 0.0.1 → 1.0.0.rc1

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.
Files changed (49) hide show
  1. data/.DS_Store +0 -0
  2. data/.gitignore +3 -0
  3. data/Gemfile +10 -1
  4. data/README.rdoc +68 -2
  5. data/arbre.gemspec +2 -0
  6. data/lib/.DS_Store +0 -0
  7. data/lib/arbre.rb +18 -0
  8. data/lib/arbre/component.rb +22 -0
  9. data/lib/arbre/context.rb +81 -0
  10. data/lib/arbre/element.rb +182 -0
  11. data/lib/arbre/element/builder_methods.rb +92 -0
  12. data/lib/arbre/element_collection.rb +25 -0
  13. data/lib/arbre/html/attributes.rb +20 -0
  14. data/lib/arbre/html/class_list.rb +24 -0
  15. data/lib/arbre/html/document.rb +42 -0
  16. data/lib/arbre/html/html5_elements.rb +47 -0
  17. data/lib/arbre/html/tag.rb +175 -0
  18. data/lib/arbre/html/text_node.rb +35 -0
  19. data/lib/arbre/rails.rb +5 -0
  20. data/lib/arbre/rails/forms.rb +92 -0
  21. data/lib/arbre/rails/rendering.rb +17 -0
  22. data/lib/arbre/rails/template_handler.rb +15 -0
  23. data/lib/arbre/version.rb +1 -1
  24. data/spec/arbre/integration/html_spec.rb +241 -0
  25. data/spec/arbre/unit/component.rb +23 -0
  26. data/spec/arbre/unit/context_spec.rb +35 -0
  27. data/spec/arbre/unit/element_finder_methods_spec.rb +101 -0
  28. data/spec/arbre/unit/element_spec.rb +252 -0
  29. data/spec/arbre/unit/html/tag_attributes_spec.rb +60 -0
  30. data/spec/arbre/unit/html/tag_spec.rb +63 -0
  31. data/spec/arbre/unit/html/text_node_spec.rb +5 -0
  32. data/spec/rails/integration/forms_spec.rb +121 -0
  33. data/spec/rails/integration/rendering_spec.rb +73 -0
  34. data/spec/rails/rails_spec_helper.rb +45 -0
  35. data/spec/rails/stub_app/config/database.yml +3 -0
  36. data/spec/rails/stub_app/config/routes.rb +3 -0
  37. data/spec/rails/stub_app/db/schema.rb +3 -0
  38. data/spec/rails/stub_app/log/.gitignore +1 -0
  39. data/spec/rails/stub_app/public/favicon.ico +0 -0
  40. data/spec/rails/templates/arbre/_partial.arb +1 -0
  41. data/spec/rails/templates/arbre/empty.arb +0 -0
  42. data/spec/rails/templates/arbre/page_with_assignment.arb +1 -0
  43. data/spec/rails/templates/arbre/page_with_erb_partial.arb +3 -0
  44. data/spec/rails/templates/arbre/page_with_partial.arb +3 -0
  45. data/spec/rails/templates/arbre/simple_page.arb +8 -0
  46. data/spec/rails/templates/erb/_partial.erb +1 -0
  47. data/spec/spec_helper.rb +7 -0
  48. data/spec/support/bundle.rb +4 -0
  49. metadata +101 -45
@@ -0,0 +1,25 @@
1
+ module Arbre
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
+ end
24
+
25
+ end
@@ -0,0 +1,20 @@
1
+ module Arbre
2
+ module HTML
3
+
4
+ class Attributes < Hash
5
+
6
+ def to_s
7
+ self.collect do |name, value|
8
+ "#{html_escape(name)}=\"#{html_escape(value)}\""
9
+ end.join(" ")
10
+ end
11
+
12
+ protected
13
+
14
+ def html_escape(s)
15
+ ERB::Util.html_escape(s)
16
+ end
17
+ end
18
+
19
+ end
20
+ end
@@ -0,0 +1,24 @@
1
+ require 'set'
2
+
3
+ module Arbre
4
+ module HTML
5
+
6
+ # Holds a set of classes
7
+ class ClassList < Set
8
+
9
+ def add(class_names)
10
+ class_names.to_s.split(" ").each do |class_name|
11
+ super(class_name)
12
+ end
13
+ self
14
+ end
15
+ alias :<< :add
16
+
17
+ def to_s
18
+ to_a.join(" ")
19
+ end
20
+
21
+ end
22
+
23
+ end
24
+ end
@@ -0,0 +1,42 @@
1
+ module Arbre
2
+ module HTML
3
+
4
+ class Document < Tag
5
+
6
+ def build(*args)
7
+ super
8
+ build_head
9
+ build_body
10
+ end
11
+
12
+ def document
13
+ self
14
+ end
15
+
16
+ def tag_name
17
+ 'html'
18
+ end
19
+
20
+ def doctype
21
+ '<!DOCTYPE html>'.html_safe
22
+ end
23
+
24
+ def to_s
25
+ doctype + super
26
+ end
27
+
28
+ protected
29
+
30
+ def build_head
31
+ @head = head do
32
+ meta :"http-equiv" => "Content-type", :content => "text/html; charset=utf-8"
33
+ end
34
+ end
35
+
36
+ def build_body
37
+ @body = body
38
+ end
39
+ end
40
+
41
+ end
42
+ end
@@ -0,0 +1,47 @@
1
+ module Arbre
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, :meta, :meter, :nav, :noscript,
11
+ :object, :ol, :optgroup, :option, :output, :pre, :progress, :q,
12
+ :s, :samp, :script, :section, :select, :small, :source, :span,
13
+ :strong, :style, :sub, :summary, :sup, :table, :tbody, :td,
14
+ :textarea, :tfoot, :th, :thead, :time, :title, :tr, :ul, :var, :video ]
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
@@ -0,0 +1,175 @@
1
+ require 'erb'
2
+
3
+ module Arbre
4
+ module HTML
5
+
6
+ class Tag < Element
7
+ attr_reader :attributes
8
+
9
+ def initialize(*)
10
+ super
11
+ @attributes = Attributes.new
12
+ end
13
+
14
+ def build(*args)
15
+ super
16
+ attributes = extract_arguments(args)
17
+ self.content = args.first if args.first
18
+
19
+ set_for_attribute(attributes.delete(:for))
20
+
21
+ attributes.each do |key, value|
22
+ set_attribute(key, value)
23
+ end
24
+ end
25
+
26
+ def extract_arguments(args)
27
+ if args.last.is_a?(Hash)
28
+ args.pop
29
+ else
30
+ {}
31
+ end
32
+ end
33
+
34
+ def set_attribute(name, value)
35
+ @attributes[name.to_sym] = value
36
+ end
37
+
38
+ def get_attribute(name)
39
+ @attributes[name.to_sym]
40
+ end
41
+ alias :attr :get_attribute
42
+
43
+ def has_attribute?(name)
44
+ @attributes.has_key?(name.to_sym)
45
+ end
46
+
47
+ def remove_attribute(name)
48
+ @attributes.delete(name.to_sym)
49
+ end
50
+
51
+ def id
52
+ get_attribute(:id)
53
+ end
54
+
55
+ # Generates and id for the object if it doesn't exist already
56
+ def id!
57
+ return id if id
58
+ self.id = object_id.to_s
59
+ id
60
+ end
61
+
62
+ def id=(id)
63
+ set_attribute(:id, id)
64
+ end
65
+
66
+ def add_class(class_names)
67
+ class_list.add class_names
68
+ end
69
+
70
+ def remove_class(class_names)
71
+ class_list.delete(class_names)
72
+ end
73
+
74
+ # Returns a string of classes
75
+ def class_names
76
+ class_list.to_s
77
+ end
78
+
79
+ def class_list
80
+ get_attribute(:class) || set_attribute(:class, ClassList.new)
81
+ end
82
+
83
+ def to_s
84
+ indent(opening_tag, content, closing_tag).html_safe
85
+ end
86
+
87
+ private
88
+
89
+ def opening_tag
90
+ "<#{tag_name}#{attributes_html}>"
91
+ end
92
+
93
+ def closing_tag
94
+ "</#{tag_name}>"
95
+ end
96
+
97
+ INDENT_SIZE = 2
98
+
99
+ def indent(open_tag, child_content, close_tag)
100
+ spaces = ' ' * indent_level * INDENT_SIZE
101
+
102
+ html = ""
103
+
104
+ if no_child? || child_is_text?
105
+ if self_closing_tag?
106
+ html << spaces << open_tag.sub( />$/, '/>' )
107
+ else
108
+ # one line
109
+ html << spaces << open_tag << child_content << close_tag
110
+ end
111
+ else
112
+ # multiple lines
113
+ html << spaces << open_tag << "\n"
114
+ html << child_content # the child takes care of its own spaces
115
+ html << spaces << close_tag
116
+ end
117
+
118
+ html << "\n"
119
+
120
+ html
121
+ end
122
+
123
+ def self_closing_tag?
124
+ %w|meta link|.include?(tag_name)
125
+ end
126
+
127
+ def no_child?
128
+ children.empty?
129
+ end
130
+
131
+ def child_is_text?
132
+ children.size == 1 && children.first.is_a?(TextNode)
133
+ end
134
+
135
+
136
+ def attributes_html
137
+ attributes.any? ? " " + attributes.to_s : nil
138
+ end
139
+
140
+ def set_for_attribute(record)
141
+ return unless record
142
+ # set_attribute :id, ActionController::RecordIdentifier.dom_id(record, default_id_for_prefix)
143
+ # add_class ActionController::RecordIdentifier.dom_class(record)
144
+ set_attribute :id, dom_id_for(record)
145
+ add_class dom_class_name_for(record)
146
+ end
147
+
148
+ def dom_class_name_for(record)
149
+ if record.class.respond_to?(:model_name)
150
+ record.class.model_name.singular
151
+ else
152
+ record.class.underscore.gsub("/", "_")
153
+ end
154
+ end
155
+
156
+ def dom_id_for(record)
157
+ id = if record.respond_to?(:to_key)
158
+ record.to_key
159
+ elsif record.respond_to?(:id)
160
+ record.id
161
+ else
162
+ record.object_id
163
+ end
164
+
165
+ [dom_class_name_for(record), id].join("_")
166
+ end
167
+
168
+ def default_id_for_prefix
169
+ nil
170
+ end
171
+
172
+ end
173
+
174
+ end
175
+ end
@@ -0,0 +1,35 @@
1
+ require 'erb'
2
+
3
+ module Arbre
4
+ module HTML
5
+
6
+ class TextNode < Element
7
+
8
+ builder_method :text_node
9
+
10
+ # Builds a text node from a string
11
+ def self.from_string(string)
12
+ node = new
13
+ node.build(string)
14
+ node
15
+ end
16
+
17
+ def add_child(*args)
18
+ raise "TextNodes do not have children"
19
+ end
20
+
21
+ def build(string)
22
+ @content = string
23
+ end
24
+
25
+ def tag_name
26
+ nil
27
+ end
28
+
29
+ def to_s
30
+ ERB::Util.html_escape(@content.to_s)
31
+ end
32
+ end
33
+
34
+ end
35
+ end
@@ -0,0 +1,5 @@
1
+ require 'arbre/rails/template_handler'
2
+ require 'arbre/rails/forms'
3
+ require 'arbre/rails/rendering'
4
+
5
+ Arbre::Element.send :include, Arbre::Rails::Rendering
@@ -0,0 +1,92 @@
1
+ module SplitOpenAndCloseOn
2
+
3
+ def split_open_and_close_on(string, match)
4
+ return "" unless string && match
5
+ @opening_tag = string.split(Regexp.new("#{match}\\z")).first
6
+ @closing_tag = match
7
+ end
8
+
9
+ def opening_tag
10
+ @opening_tag || ""
11
+ end
12
+
13
+ def closing_tag
14
+ @closing_tag || ""
15
+ end
16
+
17
+ end
18
+
19
+ class FormBuilderProxy < Arbre::Component
20
+ attr_reader :form_builder
21
+
22
+ # Since label and select are Arbre Elements already, we must
23
+ # override it here instead of letting method_missing
24
+ # deal with it
25
+ def label(*args)
26
+ proxy_call_to_form :label, *args
27
+ end
28
+
29
+ def select(*args)
30
+ proxy_call_to_form :select, *args
31
+ end
32
+
33
+ def respond_to?(name)
34
+ if form_builder && form_builder.respond_to?(name)
35
+ true
36
+ else
37
+ super
38
+ end
39
+ end
40
+
41
+ private
42
+
43
+ def proxy_call_to_form(method, *args, &block)
44
+ text_node form_builder.send(method, *args, &block)
45
+ end
46
+
47
+ def method_missing(method, *args, &block)
48
+ if form_builder && form_builder.respond_to?(method)
49
+ proxy_call_to_form(method, *args, &block)
50
+ else
51
+ super
52
+ end
53
+ end
54
+
55
+ end
56
+
57
+ class FormForProxy < FormBuilderProxy
58
+ builder_method :form_for
59
+ include SplitOpenAndCloseOn
60
+
61
+ def build(resource, form_options = {}, &block)
62
+ form_string = helpers.form_for(resource, form_options) do |f|
63
+ @form_builder = f
64
+ end
65
+
66
+ split_open_and_close_on form_string, "</form>"
67
+
68
+ super(&block)
69
+ end
70
+
71
+ def fields_for(*args, &block)
72
+ insert_tag FieldsForProxy, form_builder, *args, &block
73
+ end
74
+
75
+ end
76
+
77
+ class FieldsForProxy < FormBuilderProxy
78
+
79
+ def build(form_builder, *args, &block)
80
+ form_builder.fields_for(*args) do |f|
81
+ @form_builder = f
82
+ end
83
+
84
+ super(&block)
85
+ end
86
+
87
+ def to_s
88
+ children.to_s
89
+ end
90
+
91
+ end
92
+