arbre 0.0.1 → 1.0.0.rc1

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