jarrett-quarto 0.3.1 → 1.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.
- data/README +9 -3
- data/Rakefile +1 -0
- data/VERSION +1 -1
- data/lib/quarto/children.rb +216 -128
- data/lib/quarto/element_wrapper.rb +164 -136
- data/lib/quarto/inheritable_attributes.rb +0 -2
- data/lib/quarto/url_helper.rb +3 -0
- data/lib/quarto.rb +27 -1
- data/spec/children_spec.rb +178 -16
- data/spec/element_wrapper_spec.rb +45 -7
- data/spec/generator_spec.rb +2 -2
- data/spec/sample_models.rb +5 -0
- data/spec/sample_project/models/company.rb +7 -1
- data/spec/sample_project/models/employee.rb +1 -1
- data/spec/sample_project/models/location.rb +5 -0
- data/spec/sample_project/models/mascot.rb +5 -0
- data/spec/sample_project/models/product.rb +5 -0
- data/spec/spec_helper.rb +4 -0
- metadata +19 -7
- data/quarto.gemspec +0 -62
data/README
CHANGED
@@ -5,7 +5,7 @@ Quarto was built with HTML output in mind, but there's nothing to prevent you fr
|
|
5
5
|
to any other format. You could even output to an interpolated scripting language like PHP.
|
6
6
|
|
7
7
|
Why Quarto?
|
8
|
-
|
8
|
+
===========
|
9
9
|
|
10
10
|
Say you have a book in XML format, and you want to make a web site from it. You could transform it
|
11
11
|
to HTML using XSLT. But what if you need logic that can't be implemented in XSLT?
|
@@ -13,7 +13,13 @@ to HTML using XSLT. But what if you need logic that can't be implemented in XSLT
|
|
13
13
|
Enter Quarto. Instead of writing a series of XSLT sheets, you write ERB templates. You implement
|
14
14
|
whatever custom logic you need in classes that wrap the DOM elements, then you pass variables to the templates.
|
15
15
|
|
16
|
+
Installation
|
17
|
+
============
|
18
|
+
|
19
|
+
gem sources -a http://gems.github.com
|
20
|
+
sudo gem install jarrett-quarto
|
21
|
+
|
16
22
|
Using Quarto
|
17
|
-
|
23
|
+
============
|
18
24
|
|
19
|
-
Thorough documentation doesn't exist yet. For now, see spec/sample_project.
|
25
|
+
Thorough documentation doesn't exist yet. For now, see spec/sample_project and the RDoc.
|
data/Rakefile
CHANGED
@@ -10,6 +10,7 @@ begin
|
|
10
10
|
gemspec.authors = ['Jarrett Colby']
|
11
11
|
gemspec.files = FileList['[A-Z]*', '{bin,lib,test}/**/*']
|
12
12
|
gemspec.executables = 'quarto'
|
13
|
+
gemspec.add_dependency('activesupport', '>= 2.3.2')
|
13
14
|
end
|
14
15
|
rescue LoadError
|
15
16
|
puts "Jeweler not available. Install it with: sudo gem install technicalpickles-jeweler -s http://gems.github.com"
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
|
1
|
+
1.1.0
|
data/lib/quarto/children.rb
CHANGED
@@ -1,152 +1,240 @@
|
|
1
1
|
module Quarto
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
#
|
14
|
-
# # in generate.rb:
|
15
|
-
# company = Company.find :first
|
16
|
-
# company.employees.each do |employee|
|
17
|
-
# puts employee.name
|
18
|
-
# end
|
19
|
-
|
20
|
-
module ElementWrapperChildren
|
21
|
-
def self.included(base) # :nodoc:
|
22
|
-
base.extend(ClassMethods)
|
23
|
-
base.class_eval do
|
24
|
-
alias_method :method_missing_without_children, :method_missing
|
25
|
-
alias_method :method_missing, :method_missing_with_children
|
26
|
-
|
27
|
-
alias_method :respond_to_without_children?, :respond_to?
|
28
|
-
alias_method :respond_to?, :respond_to_with_children?
|
2
|
+
module ElementWrapper # :nodoc:
|
3
|
+
module Children # :nodoc:
|
4
|
+
def self.included(base)
|
5
|
+
base.extend(ClassMethods)
|
6
|
+
base.class_eval do
|
7
|
+
alias_method :method_missing_without_children, :method_missing
|
8
|
+
alias_method :method_missing, :method_missing_with_children
|
9
|
+
|
10
|
+
alias_method :respond_to_without_children?, :respond_to?
|
11
|
+
alias_method :respond_to?, :respond_to_with_children?
|
12
|
+
end
|
29
13
|
end
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
14
|
+
|
15
|
+
def method_missing_with_children(meth, *args)
|
16
|
+
if self.class.has_child_named?(meth)
|
17
|
+
child_obj(meth)
|
18
|
+
elsif self.class.has_children_named?(meth)
|
19
|
+
children_proxy(meth)
|
20
|
+
elsif self.class.has_parent_named?(meth)
|
21
|
+
wrapped_parent
|
22
|
+
else
|
23
|
+
method_missing_without_children(meth, *args)
|
24
|
+
end
|
39
25
|
end
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
26
|
+
|
27
|
+
def respond_to_with_children?(meth, include_private = false)
|
28
|
+
if self.class.has_child_named?(meth) or self.class.has_children_named?(meth) or self.class.has_parent_named?(meth)
|
29
|
+
true
|
30
|
+
else
|
31
|
+
respond_to_without_children?(meth, include_private)
|
32
|
+
end
|
47
33
|
end
|
48
|
-
end
|
49
34
|
|
50
|
-
|
51
|
-
|
52
|
-
def children_proxy(collection_el_name) # :nodoc:
|
53
|
-
@children_proxies ||= {}
|
54
|
-
@children_proxies[collection_el_name.to_s] ||= Children.new(self, collection_el_name.to_s.singularize)
|
55
|
-
end
|
35
|
+
protected
|
56
36
|
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
# Define children. +el_name+ must be the singular form. In the XML, all children must be
|
66
|
-
# wrapped in a collection element whose name is the plural form.
|
67
|
-
#
|
68
|
-
# Example:
|
69
|
-
#
|
70
|
-
# <company>
|
71
|
-
# <employees>
|
72
|
-
# <employee>
|
73
|
-
# </employee>
|
74
|
-
# </employees>
|
75
|
-
# </company>
|
76
|
-
#
|
77
|
-
# class Company < ElementWrapper
|
78
|
-
# children :employees
|
79
|
-
# end
|
80
|
-
def children(el_name)
|
81
|
-
write_inheritable_array(:children, [el_name.to_s])
|
37
|
+
def child_obj(meth)
|
38
|
+
options = self.class.read_inheritable_attribute(:singleton_children)[meth.to_sym]
|
39
|
+
el_name = (options[:element_name] || meth).to_s
|
40
|
+
child_class = Kernel.const_get(options[:wrapper_class] || el_name.classify)
|
41
|
+
child_element = @element.elements[el_name]
|
42
|
+
return nil if child_element.nil?
|
43
|
+
@singleton_children ||= {}
|
44
|
+
@singleton_children[meth] ||= child_class.new(child_element)
|
82
45
|
end
|
83
46
|
|
84
|
-
def
|
85
|
-
|
86
|
-
|
47
|
+
def children_proxy(meth)
|
48
|
+
options = self.class.read_inheritable_attribute(:children)[meth.to_sym]
|
49
|
+
@children_proxies ||= {}
|
50
|
+
@children_proxies[meth] ||= ChildrenProxy.new(self, options[:element_name], options)
|
87
51
|
end
|
88
52
|
|
89
|
-
def
|
90
|
-
read_inheritable_attribute(:parent)
|
53
|
+
def wrapped_parent
|
54
|
+
options = self.class.read_inheritable_attribute(:parent)
|
55
|
+
parent_class = Kernel.const_get(options[:wrapper_class] || options[:element_name].classify)
|
56
|
+
parent_el = @element
|
57
|
+
while parent_el.name != options[:element_name]
|
58
|
+
parent_el = parent_el.parent
|
59
|
+
end
|
60
|
+
parent_class.new(parent_el) # Go up two levels, since each child is expected to be inside a collection element
|
91
61
|
end
|
92
62
|
|
93
|
-
#
|
94
|
-
#
|
95
|
-
# Example:
|
63
|
+
# ElementWrapper::Base subclasses can define parent and child elements, resulting
|
64
|
+
# in handy accessor methods. For example:
|
96
65
|
#
|
97
|
-
# <
|
98
|
-
#
|
99
|
-
#
|
100
|
-
# </employee>
|
101
|
-
# </employees>
|
102
|
-
# </company>
|
66
|
+
# class Company < ElementWrapper::Base
|
67
|
+
# children :employees
|
68
|
+
# end
|
103
69
|
#
|
104
|
-
# class Employee < ElementWrapper
|
70
|
+
# class Employee < ElementWrapper::Base
|
105
71
|
# parent :company
|
72
|
+
# element_attr 'name'
|
73
|
+
# end
|
74
|
+
#
|
75
|
+
# and in generate.rb:
|
76
|
+
# company = Company.find :first
|
77
|
+
# company.employees.each do |employee|
|
78
|
+
# puts employee.name
|
106
79
|
# end
|
107
|
-
|
108
|
-
|
80
|
+
|
81
|
+
module ClassMethods
|
82
|
+
# Creates an attribute for a child element. +el_name+ must be the singular form.
|
83
|
+
#
|
84
|
+
# Options:
|
85
|
+
# * <tt>:element_name</tt> - The name of the child element. Defaults to +method_name+.
|
86
|
+
# * <tt>:wrapper_class</tt> - <tt>:wrapper_class</tt> - The subclass of ElementWrapper::Base to use.
|
87
|
+
# Defaults to the element name.
|
88
|
+
#
|
89
|
+
# Example:
|
90
|
+
#
|
91
|
+
# <company>
|
92
|
+
# <boss>
|
93
|
+
# <name>Joe Schmoe</name>
|
94
|
+
# </boss>
|
95
|
+
# </company>
|
96
|
+
#
|
97
|
+
# class Company
|
98
|
+
# child :boss
|
99
|
+
# end
|
100
|
+
def child(method_name, options = {})
|
101
|
+
write_inheritable_hash(:singleton_children, {method_name.to_sym => options})
|
102
|
+
end
|
103
|
+
|
104
|
+
# Creates an attribute for child elements.
|
105
|
+
#
|
106
|
+
# Options:
|
107
|
+
# * <tt>:element_name</tt> - The XML element of each individual child. Default
|
108
|
+
# to the singular form of +method_name+.
|
109
|
+
# * <tt>:collection_element</tt> - By default, ElementWrapper assums that all
|
110
|
+
# children are wrapped in a collection element whose name is +method_name+.
|
111
|
+
# You can override this with <tt>:collection_element</tt>. If
|
112
|
+
# the child elements are not wrapped in a collection element at all,
|
113
|
+
# use <tt>:collection_element => nil</tt>.
|
114
|
+
# * <tt>:wrapper_class</tt> - The subclass of ElementWrapper::Base to use.
|
115
|
+
# Defaults to the singular form of the element name.
|
116
|
+
#
|
117
|
+
# Example:
|
118
|
+
#
|
119
|
+
# <company>
|
120
|
+
# <employees>
|
121
|
+
# <employee>
|
122
|
+
# </employee>
|
123
|
+
# </employees>
|
124
|
+
# </company>
|
125
|
+
#
|
126
|
+
# class Company < ElementWrapper::Base
|
127
|
+
# children :employees
|
128
|
+
# end
|
129
|
+
def children(method_name, options = {})
|
130
|
+
write_inheritable_hash(:children, {method_name.to_sym => {
|
131
|
+
:element_name => method_name.to_s.singularize,
|
132
|
+
:collection_element => method_name.to_s
|
133
|
+
}.merge(options)})
|
134
|
+
end
|
135
|
+
|
136
|
+
def has_child_named?(method_name) # :nodoc:
|
137
|
+
return false if read_inheritable_attribute(:singleton_children).nil?
|
138
|
+
read_inheritable_attribute(:singleton_children).has_key?(method_name.to_sym)
|
139
|
+
end
|
140
|
+
|
141
|
+
def has_children_named?(method_name) # :nodoc:
|
142
|
+
return false if read_inheritable_attribute(:children).nil?
|
143
|
+
read_inheritable_attribute(:children).has_key?(method_name.to_sym)
|
144
|
+
end
|
145
|
+
|
146
|
+
def has_parent_named?(method_name) # :nodoc:
|
147
|
+
return false if read_inheritable_attribute(:parent).nil?
|
148
|
+
read_inheritable_attribute(:parent)[:method] == method_name.to_sym
|
149
|
+
end
|
150
|
+
|
151
|
+
# Defines the element's parent. Options:
|
152
|
+
#
|
153
|
+
# * <tt>:element_name</tt> - The name of the parent element. Defaults to +method_name+.
|
154
|
+
# * <tt>:wrapper_class</tt> - The subclass of ElementWrapper::Base to use.
|
155
|
+
# Defaults to the element name.
|
156
|
+
#
|
157
|
+
# Example:
|
158
|
+
#
|
159
|
+
# <company>
|
160
|
+
# <employees>
|
161
|
+
# <employee>
|
162
|
+
# </employee>
|
163
|
+
# </employees>
|
164
|
+
# </company>
|
165
|
+
#
|
166
|
+
# class Employee < ElementWrapper::Base
|
167
|
+
# parent :company
|
168
|
+
# end
|
169
|
+
def parent(method_name, options = {})
|
170
|
+
write_inheritable_attribute(:parent, {:method => method_name.to_sym, :element_name => method_name.to_s}.merge(options))
|
171
|
+
end
|
109
172
|
end
|
110
173
|
end
|
111
|
-
end
|
112
|
-
|
113
|
-
class Children
|
114
|
-
include Enumerable
|
115
|
-
|
116
|
-
# Returns the REXML::Element for the children collection.
|
117
|
-
attr_reader :collection_element
|
118
|
-
|
119
|
-
# Iterate over all children.
|
120
|
-
def each
|
121
|
-
to_a.each { |child| yield child }
|
122
|
-
end
|
123
|
-
|
124
|
-
# Returns true if there are no children.
|
125
|
-
def empty?
|
126
|
-
to_a.empty?
|
127
|
-
end
|
128
174
|
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
end
|
135
|
-
|
136
|
-
#
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
175
|
+
# Any call to a children accessor method returns an instance of ChildrenProxy. For example,
|
176
|
+
# consider this class:
|
177
|
+
#
|
178
|
+
# class Company < ElementWrapper::Base
|
179
|
+
# children :employees
|
180
|
+
# end
|
181
|
+
#
|
182
|
+
# If you call <tt>#employees</tt> on an instance of Company, you'll get a ChildrenProxy
|
183
|
+
# object.
|
184
|
+
|
185
|
+
class ChildrenProxy
|
186
|
+
include Enumerable
|
187
|
+
|
188
|
+
# Returns the REXML::Element for the children collection.
|
189
|
+
attr_reader :collection_element
|
190
|
+
|
191
|
+
# Iterates over all children.
|
192
|
+
def each
|
193
|
+
to_a.each { |child| yield child }
|
145
194
|
end
|
195
|
+
|
196
|
+
# Returns true if there are no children.
|
197
|
+
def empty?
|
198
|
+
to_a.empty?
|
199
|
+
end
|
200
|
+
|
201
|
+
# Returns the first child in the collection.
|
202
|
+
def first
|
203
|
+
to_a.first
|
204
|
+
end
|
205
|
+
|
206
|
+
def initialize(wrapped_parent, el_name, options = {}) # :nodoc:
|
207
|
+
@wrapped_parent = wrapped_parent
|
208
|
+
@el_name = el_name.to_s
|
209
|
+
if options[:collection_element].nil?
|
210
|
+
# The subclass says there is no collection element wrapping the children
|
211
|
+
@collection_element = nil
|
212
|
+
else
|
213
|
+
@collection_element = @wrapped_parent.element.elements[options[:collection_element]]
|
214
|
+
end
|
215
|
+
@wrapper_class = Kernel.const_get(options[:wrapper_class] || @el_name.classify)
|
216
|
+
end
|
217
|
+
|
218
|
+
# Returns the last child in the collection.
|
219
|
+
def last
|
220
|
+
to_a.last
|
221
|
+
end
|
222
|
+
|
223
|
+
# Returns the number of children.
|
224
|
+
def length
|
225
|
+
to_a.length
|
226
|
+
end
|
227
|
+
|
228
|
+
# Returns an array of all children. Each is an instance of ElementWrapper::Base. If +xpath+ is provided, the results will be filtered. +xpath+ is relative to the parent element
|
229
|
+
def to_a(xpath = nil)
|
230
|
+
@all ||= (@collection_element || @wrapped_parent.element).elements.to_a(xpath || @el_name).collect do |el|
|
231
|
+
@wrapper_class.new(el)
|
232
|
+
end
|
233
|
+
end
|
234
|
+
|
235
|
+
alias_method :size, :length
|
146
236
|
end
|
147
|
-
|
148
|
-
alias_method :size, :length
|
149
237
|
end
|
150
238
|
end
|
151
239
|
|
152
|
-
Quarto::ElementWrapper.send(:include, Quarto::
|
240
|
+
Quarto::ElementWrapper::Base.send(:include, Quarto::ElementWrapper::Children)
|
@@ -1,155 +1,183 @@
|
|
1
1
|
module Quarto
|
2
|
-
|
3
|
-
# directory within your project. All files in that directory will be automatically required.
|
4
|
-
#
|
5
|
-
# Each ElementWrapper subclass corresponds to exactly one XML element.
|
6
|
-
# You can specify the model's element name by calling element_name=, but
|
7
|
-
# generally, you just let ElementWrapper use the default, which is the subclass
|
8
|
-
# name in snake_case.
|
9
|
-
#
|
10
|
-
# Instance attributes corresponding to the XML attributes
|
11
|
-
#
|
12
|
-
# For example, suppose you have an XML document like this:
|
13
|
-
#
|
14
|
-
# <programmers>
|
15
|
-
# <programmer skill="genius">
|
16
|
-
# <name>Linus Torvalds</name>
|
17
|
-
# <programmer>
|
18
|
-
# </programmers>
|
19
|
-
#
|
20
|
-
# You could then subclass ElementWrapper like this:
|
21
|
-
#
|
22
|
-
# class Programmer < ElementWrapper
|
23
|
-
# element_name = 'programmer'
|
24
|
-
# element_attrs 'name'
|
25
|
-
# end
|
26
|
-
#
|
27
|
-
# You could then do something like this in your generate.rb file:
|
28
|
-
#
|
29
|
-
# programmer = Programmer.find :first
|
30
|
-
# puts programmer.name
|
31
|
-
# puts programmer.skill
|
32
|
-
#
|
33
|
-
# Also see the documentation for Quarto::Children
|
2
|
+
module ElementWrapper # :nodoc:
|
34
3
|
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
# Returns true if both instances come from the same node in the source XML document.
|
39
|
-
def ==(other_wrapped_element)
|
40
|
-
other_wrapped_element.is_a?(Quarto::ElementWrapper) and @element == other_wrapped_element.element
|
41
|
-
end
|
42
|
-
|
43
|
-
# Returns the currently-loaded REXML::Document.
|
44
|
-
def self.xml_doc
|
45
|
-
Quarto.xml_doc
|
46
|
-
end
|
47
|
-
|
48
|
-
# Returns the REXML::Element from which the instance was created.
|
49
|
-
attr_reader :element
|
50
|
-
|
51
|
-
# Creates read-only attributes from the given strings. When a model is instantiated from an XML node,
|
52
|
-
# ElementWrapper will try to populate these attributes using the node's child elements.
|
4
|
+
# Abstract base class for your models. Put your ElementWrapper::Base subclasses inside the
|
5
|
+
# "models" directory within your project. All files in that directory will be automatically
|
6
|
+
# required.
|
53
7
|
#
|
54
|
-
#
|
8
|
+
# Each ElementWrapper::Base subclass corresponds to exactly one XML element.
|
9
|
+
# You can specify the model's element name by calling element_name=, but
|
10
|
+
# generally, you just let ElementWrapper use the default, which is the subclass
|
11
|
+
# name in snake_case.
|
55
12
|
#
|
56
|
-
#
|
13
|
+
# Inside each ElementWrapper::Base is a REXML::Element. ElementWrapper::Base implements
|
14
|
+
# <tt>method_missing</tt>, allowing you to call that REXML::Element's methods
|
15
|
+
# on the ElementWrapper::Base instance.
|
57
16
|
#
|
58
|
-
#
|
17
|
+
# Instance attributes corresponding to the XML attributes will automatically
|
18
|
+
# be defined. Hwoever, if you want an attribute defined by the text of a child
|
19
|
+
# element, you have to specify it yourself.
|
59
20
|
#
|
60
|
-
#
|
61
|
-
#
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
#
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
#
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
# Searches the XML document and returns instances of the class. The first parameter must be either :first, :last, or :all.
|
77
|
-
# If it's :first or :last, the method returns a single instance or nil. If it's :all, the method returns an array (which may be empty).
|
21
|
+
# For example, suppose you have an XML document like this:
|
22
|
+
#
|
23
|
+
# <programmers>
|
24
|
+
# <programmer skill="genius">
|
25
|
+
# <name>Linus Torvalds</name>
|
26
|
+
# <programmer>
|
27
|
+
# </programmers>
|
28
|
+
#
|
29
|
+
# You could then subclass ElementWrapper like this:
|
30
|
+
#
|
31
|
+
# class Programmer < ElementWrapper::Base
|
32
|
+
# element_name = 'programmer'
|
33
|
+
# element_attrs 'name'
|
34
|
+
# end
|
35
|
+
#
|
36
|
+
# You could then do something like this in your generate.rb file:
|
78
37
|
#
|
79
|
-
#
|
38
|
+
# programmer = Programmer.find :first
|
39
|
+
# puts programmer.name
|
40
|
+
# puts programmer.skill
|
80
41
|
#
|
81
|
-
#
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
# TODO: add support for :root and :conditions (XPath predicates)
|
42
|
+
# Also see the documentation for ElementWrapper::Children
|
43
|
+
|
44
|
+
class Base
|
45
|
+
include InheritableAttributes
|
46
|
+
|
47
|
+
# Returns true if both instances come from the same node in the source XML document.
|
48
|
+
def ==(other_wrapped_element)
|
49
|
+
other_wrapped_element.is_a?(Quarto::ElementWrapper::Base) and @element == other_wrapped_element.element
|
90
50
|
end
|
91
|
-
|
92
|
-
|
51
|
+
|
52
|
+
# Returns the currently-loaded REXML::Document.
|
53
|
+
def self.xml_doc
|
54
|
+
Quarto.xml_doc
|
93
55
|
end
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
56
|
+
|
57
|
+
# Returns the REXML::Element from which the instance was created.
|
58
|
+
attr_reader :element
|
59
|
+
|
60
|
+
# Creates read-only attributes from the given strings. When a model is instantiated from an XML node,
|
61
|
+
# ElementWrapper will try to populate these attributes using the node's child elements.
|
62
|
+
#
|
63
|
+
# For example, if your "employee" element has a child element called "name," you can use:
|
64
|
+
#
|
65
|
+
# element_attrs 'name'
|
66
|
+
#
|
67
|
+
# ...which will then expose a #name method for every instance of your class. Also see the usage example in the class description.
|
68
|
+
#
|
69
|
+
# Remember, XML attributes will automatically have corresponding ElementWrapper attributes. You only need to tell
|
70
|
+
# ElementWrapper which child elements to use.
|
71
|
+
def self.element_attrs(*element_names)
|
72
|
+
write_inheritable_array :element_attrs, element_names.collect { |en| en.to_sym}
|
101
73
|
end
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
end
|
107
|
-
|
108
|
-
def initialize(el) # :nodoc:
|
109
|
-
unless el.is_a?(REXML::Element)
|
110
|
-
raise ArgumentError, "Quarto::ElementWrapper.new must be passed a REXML::Element, but got #{el.inspect}"
|
74
|
+
|
75
|
+
# Returns the XML element name.
|
76
|
+
def self.element_name
|
77
|
+
@element_name
|
111
78
|
end
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
@
|
79
|
+
|
80
|
+
# Overrides the XML element name. The default is the class name in snake_case.
|
81
|
+
def self.element_name=(el_name)
|
82
|
+
@element_name = el_name
|
116
83
|
end
|
117
|
-
|
118
|
-
|
119
|
-
|
84
|
+
|
85
|
+
# Searches the XML document and returns instances of the class. The first parameter must be either :first, :last, or :all.
|
86
|
+
# If it's :first or :last, the method returns a single instance or nil. If it's :all, the method returns an array (which may be empty).
|
87
|
+
#
|
88
|
+
# Options:
|
89
|
+
#
|
90
|
+
# * <tt>:xpath</tt> - An XPath expression to limit the search. If this option is not given, the default XPath is "//element_name"
|
91
|
+
def self.find(quantifier, options = {})
|
92
|
+
raise ArgumentError, "Quantifier must be :all, :first, or :last, but got #{quantifier.inspect}" unless [:all, :first, :last].include?(quantifier)
|
93
|
+
raise ArgumentError, "Options must be a Hash, but got #{options.inspect}" unless options.is_a?(Hash)
|
94
|
+
if options.has_key?(:xpath)
|
95
|
+
xpath = options[:xpath]
|
96
|
+
else
|
97
|
+
xpath = "//#{@element_name}"
|
98
|
+
# TODO: add support for :root and :conditions (XPath predicates)
|
99
|
+
end
|
100
|
+
all = xml_doc.elements.to_a(xpath)
|
101
|
+
case quantifier
|
102
|
+
when :all
|
103
|
+
all.collect { |el| new(el) }
|
104
|
+
when :first
|
105
|
+
all.empty? ? nil : new(all.first)
|
106
|
+
when :last
|
107
|
+
all.empty? ? nil : new(all.last)
|
108
|
+
end
|
120
109
|
end
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
if @attributes.has_key?(meth.to_sym)
|
125
|
-
@attributes[meth.to_sym]
|
126
|
-
elsif @element.respond_to?(meth)
|
127
|
-
@element.send(meth, *args, &block)
|
128
|
-
else
|
129
|
-
super
|
110
|
+
|
111
|
+
def self.inherited(subclass) # :nodoc:
|
112
|
+
subclass.element_name = subclass.to_s.underscore
|
130
113
|
end
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
114
|
+
|
115
|
+
def initialize(el) # :nodoc:
|
116
|
+
unless el.is_a?(REXML::Element)
|
117
|
+
raise ArgumentError, "Quarto::ElementWrapper.new must be passed a REXML::Element, but got #{el.inspect}"
|
118
|
+
end
|
119
|
+
@element = el
|
120
|
+
@attributes = {}
|
121
|
+
@element.attributes.each do |a_name, value|
|
122
|
+
@attributes[a_name.to_sym] = typecast_text(value)
|
123
|
+
end
|
124
|
+
if element_attrs = self.class.read_inheritable_attribute(:element_attrs)
|
125
|
+
element_attrs.each do |el_name|
|
126
|
+
raise ArgumentError, "Expected <#{@element.name}> to have child <#{el_name}>" if @element.elements[el_name.to_s].nil?
|
127
|
+
@attributes[el_name.to_sym] = typecast_text(@element.elements[el_name.to_s].text)
|
128
|
+
end
|
129
|
+
end
|
130
|
+
if text_attr = self.class.read_inheritable_attribute(:text_attr)
|
131
|
+
@attributes[text_attr.to_sym] = typecast_text(@element.text)
|
132
|
+
end
|
138
133
|
end
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
134
|
+
|
135
|
+
def method_missing(meth, *args, &block) # :nodoc:
|
136
|
+
if @attributes.has_key?(meth.to_sym)
|
137
|
+
@attributes[meth.to_sym]
|
138
|
+
elsif @element.respond_to?(meth)
|
139
|
+
@element.send(meth, *args, &block)
|
140
|
+
else
|
141
|
+
super
|
142
|
+
end
|
143
|
+
end
|
144
|
+
|
145
|
+
def respond_to?(meth, include_private = false) # :nodoc:
|
146
|
+
if @element.respond_to?(meth, include_private) or @attributes.has_key?(meth.to_sym)
|
147
|
+
true
|
148
|
+
else
|
149
|
+
super
|
150
|
+
end
|
151
|
+
end
|
152
|
+
|
153
|
+
# Creates a read-only attribute from the wrapped element's text.
|
154
|
+
# +attr_name+ can be anything you want; it doesn't have to correspond
|
155
|
+
# to anything in the XML. Example:
|
156
|
+
#
|
157
|
+
# <company>
|
158
|
+
# <product>Shoes</product>
|
159
|
+
# <company>
|
160
|
+
#
|
161
|
+
# class Product < ElementWrapper
|
162
|
+
# text_attr :name
|
163
|
+
# end
|
164
|
+
def self.text_attr(attr_name)
|
165
|
+
write_inheritable_attribute(:text_attr, attr_name)
|
166
|
+
end
|
167
|
+
|
168
|
+
protected
|
169
|
+
|
170
|
+
# When an ElementWrapper is instantiated from an XML node, all values start out as strings. This method typecasts those values.
|
171
|
+
def typecast_text(t)
|
172
|
+
if t.nil? or (t.is_a?(String) and t.empty?)
|
173
|
+
nil
|
174
|
+
elsif t =~ /^-?[0-9]+$/
|
175
|
+
t.to_i
|
176
|
+
elsif t =~ /^-?[0-9]*\.[0-9]+$/
|
177
|
+
t.to_f
|
178
|
+
else
|
179
|
+
t
|
180
|
+
end
|
153
181
|
end
|
154
182
|
end
|
155
183
|
end
|
data/lib/quarto/url_helper.rb
CHANGED
@@ -1,6 +1,9 @@
|
|
1
1
|
require 'cgi'
|
2
2
|
|
3
3
|
module Quarto
|
4
|
+
|
5
|
+
# This module is included in Generator and thus made available in <tt>generate.rb</tt> files.
|
6
|
+
|
4
7
|
module UrlHelper
|
5
8
|
# Generates an absolute URL, using the <tt>:site_root</tt> config value. (To change <tt>:site_root</tt>,
|
6
9
|
# put something like this in <tt>generate.rb</tt>:
|
data/lib/quarto.rb
CHANGED
@@ -13,4 +13,30 @@ require 'quarto/element_wrapper'
|
|
13
13
|
require 'quarto/children'
|
14
14
|
require 'quarto/rendering'
|
15
15
|
require 'quarto/generator'
|
16
|
-
require 'quarto/init_project'
|
16
|
+
require 'quarto/init_project'
|
17
|
+
|
18
|
+
# Quarto is a Ruby framework for generating collections of documents from XML. Potential applications
|
19
|
+
# include web sites and e-books. It's built on top of ERB and REXML.
|
20
|
+
#
|
21
|
+
# Quarto was built with HTML output in mind, but there's nothing to prevent you from outputting
|
22
|
+
# to any other format. You could even output to an interpolated scripting language like PHP.
|
23
|
+
#
|
24
|
+
# =Why Quarto?
|
25
|
+
#
|
26
|
+
# Say you have a book in XML format, and you want to make a web site from it. You could transform it
|
27
|
+
# to HTML using XSLT. But what if you need logic that can't be implemented in XSLT?
|
28
|
+
#
|
29
|
+
# Enter Quarto. Instead of writing a series of XSLT sheets, you write ERB templates. You implement
|
30
|
+
# whatever custom logic you need in classes that wrap the DOM elements, then you pass variables to the templates.
|
31
|
+
#
|
32
|
+
# =Installation
|
33
|
+
#
|
34
|
+
# gem sources -a http://gems.github.com
|
35
|
+
# sudo gem install jarrett-quarto
|
36
|
+
#
|
37
|
+
# =Using Quarto
|
38
|
+
#
|
39
|
+
# Thorough documentation doesn't exist yet. For now, see spec/sample_project and the RDoc.
|
40
|
+
|
41
|
+
module Quarto
|
42
|
+
end
|
data/spec/children_spec.rb
CHANGED
@@ -1,22 +1,18 @@
|
|
1
1
|
require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
|
2
|
-
require File.expand_path(File.dirname(__FILE__) + '/
|
3
|
-
require File.expand_path(File.dirname(__FILE__) + '/sample_project/models/employee')
|
2
|
+
require File.expand_path(File.dirname(__FILE__) + '/sample_models')
|
4
3
|
|
5
|
-
describe Quarto::
|
4
|
+
describe Quarto::ElementWrapper::Children do
|
6
5
|
before :each do
|
7
6
|
Quarto.xml_source = File.open(SAMPLE_DIR + '/xml/companies.xml')
|
8
7
|
@xml = Quarto.xml_doc
|
9
8
|
end
|
10
9
|
|
11
|
-
context '
|
12
|
-
|
10
|
+
context '.children' do
|
11
|
+
it 'should create a children accessor' do
|
13
12
|
@element = @xml.elements['companies/company']
|
14
13
|
@company = Company.new(@element)
|
15
|
-
end
|
16
|
-
|
17
|
-
it 'should know its children' do
|
18
14
|
@company.should respond_to(:employees)
|
19
|
-
@company.employees.should be_a(Quarto::
|
15
|
+
@company.employees.should be_a(Quarto::ElementWrapper::ChildrenProxy)
|
20
16
|
@company.employees.length.should == 2 # In the sample XML file, 37Signals has two employees.
|
21
17
|
@company.employees.each do |employee|
|
22
18
|
employee.should be_a(Employee)
|
@@ -24,21 +20,177 @@ describe Quarto::ElementWrapperChildren do
|
|
24
20
|
end
|
25
21
|
end
|
26
22
|
|
27
|
-
context '
|
28
|
-
|
23
|
+
context '.children given :wrapper_class' do
|
24
|
+
it 'should use the specified class instead of the default' do
|
25
|
+
class CompanyWithWrapperClass < Quarto::ElementWrapper::Base
|
26
|
+
element_name = 'company'
|
27
|
+
children :employees, :wrapper_class => 'CrazyEmployee'
|
28
|
+
end
|
29
|
+
|
30
|
+
class CrazyEmployee < Quarto::ElementWrapper::Base; end
|
31
|
+
|
32
|
+
@element = @xml.elements['companies/company']
|
33
|
+
@company = CompanyWithWrapperClass.new(@element)
|
34
|
+
@company.employees.each do |employee|
|
35
|
+
employee.should be_a(CrazyEmployee)
|
36
|
+
end
|
37
|
+
|
38
|
+
Object.class_eval do
|
39
|
+
remove_const :CompanyWithWrapperClass
|
40
|
+
remove_const :CrazyEmployee
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
context '.children given :collection_element' do
|
46
|
+
it 'should use the specified collection element instead of the default' do
|
47
|
+
@company = Company.find(:first, :xpath => "//company[name='Mega-lo-Mart']")
|
48
|
+
@company.should respond_to(:products)
|
49
|
+
@company.products.should be_a(Quarto::ElementWrapper::ChildrenProxy)
|
50
|
+
@company.products.length.should == 2
|
51
|
+
@company.products.each do |product|
|
52
|
+
product.should be_a(Product)
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
context '.children given :collection_element => nil' do
|
58
|
+
it 'should create a children accessor that uses immediate children of the element' do
|
59
|
+
@company = Company.find(:first, :xpath => "//company[name='Mega-lo-Mart']")
|
60
|
+
@company.should respond_to(:locations)
|
61
|
+
@company.locations.should be_a(Quarto::ElementWrapper::ChildrenProxy)
|
62
|
+
@company.locations.length.should == 2
|
63
|
+
@company.locations.each do |location|
|
64
|
+
location.should be_a(Location)
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
context '.chldren given :element_name' do
|
70
|
+
it 'should use the specified element instead of the default' do
|
71
|
+
class CompanyWithElementName < Quarto::ElementWrapper::Base
|
72
|
+
children :the_employees, :element_name => 'employee'
|
73
|
+
end
|
74
|
+
|
75
|
+
@element = @xml.elements['companies/company']
|
76
|
+
@company = CompanyWithElementName.new(@element)
|
77
|
+
@company.the_employees.each do |employee|
|
78
|
+
employee.should be_a(Employee)
|
79
|
+
employee.element.name.should == 'employee'
|
80
|
+
end
|
81
|
+
|
82
|
+
Object.class_eval do
|
83
|
+
remove_const :CompanyWithElementName
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
context '.parent for an element in a collection' do
|
89
|
+
it 'should create an accessor for the parent object' do
|
29
90
|
@element = @xml.elements['companies/company/employees/employee']
|
30
91
|
@employee = Employee.new(@element)
|
31
|
-
end
|
32
|
-
|
33
|
-
it 'should have children which know their parent' do
|
34
92
|
@employee.should respond_to(:company)
|
35
93
|
@employee.company.should be_a(Company)
|
36
94
|
@employee.company.name.should == '37Signals'
|
37
95
|
end
|
38
96
|
end
|
97
|
+
|
98
|
+
context '.parent for an element that\'s not in a collection' do
|
99
|
+
it 'should create an accessor for the parent object' do
|
100
|
+
@company = Company.find(:first, :xpath => "//company[name='Mega-lo-Mart']")
|
101
|
+
@location= @company.locations.first
|
102
|
+
@location.company.should == @company
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
context '.parent given :element_name' do
|
107
|
+
it 'should test for something' do
|
108
|
+
# create classes
|
109
|
+
class EmployeeWithElementName < Quarto::ElementWrapper::Base
|
110
|
+
parent :the_company, :element_name => 'company'
|
111
|
+
end
|
112
|
+
|
113
|
+
@element = @xml.elements['//employee']
|
114
|
+
@employee = EmployeeWithElementName.new(@element)
|
115
|
+
@employee.the_company.should be_a(Company)
|
116
|
+
@employee.the_company.element.name.should == 'company'
|
117
|
+
|
118
|
+
Object.class_eval do
|
119
|
+
remove_const :EmployeeWithElementName
|
120
|
+
end
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
context '.parent given :wrapper_class' do
|
125
|
+
it 'should use the specified class instead of the default' do
|
126
|
+
class EmployeeWithWrapperClass < Quarto::ElementWrapper::Base
|
127
|
+
parent :company, :wrapper_class => 'CrazyCompany'
|
128
|
+
end
|
129
|
+
|
130
|
+
class CrazyCompany < Quarto::ElementWrapper::Base; end
|
131
|
+
|
132
|
+
@element = @xml.elements['//employee']
|
133
|
+
@employee = EmployeeWithWrapperClass.new(@element)
|
134
|
+
@employee.company.should be_a(CrazyCompany)
|
135
|
+
|
136
|
+
Object.class_eval do
|
137
|
+
remove_const :EmployeeWithWrapperClass
|
138
|
+
remove_const :CrazyCompany
|
139
|
+
end
|
140
|
+
end
|
141
|
+
end
|
142
|
+
|
143
|
+
context '.child' do
|
144
|
+
before :each do
|
145
|
+
@company = Company.find(:first, :xpath => "//company[name='Mega-lo-Mart']")
|
146
|
+
end
|
147
|
+
|
148
|
+
it 'should create an accessor that returns the child if it exists' do
|
149
|
+
@company.mascot.should be_a(Mascot)
|
150
|
+
end
|
151
|
+
|
152
|
+
it 'should create an accessor that returns nil if the child does not exist' do
|
153
|
+
@company = Company.find :first
|
154
|
+
@company.mascot.should == nil
|
155
|
+
end
|
156
|
+
end
|
157
|
+
|
158
|
+
context '.child given :wrapper_class' do
|
159
|
+
it 'should use the specified class instead of the default' do
|
160
|
+
class CompanyWithWrapperClass < Quarto::ElementWrapper::Base
|
161
|
+
child :mascot, :wrapper_class => 'CrazyMascot'
|
162
|
+
end
|
163
|
+
|
164
|
+
class CrazyMascot < Quarto::ElementWrapper::Base; end
|
165
|
+
|
166
|
+
@company = CompanyWithWrapperClass.find(:first, :xpath => "//company[name='Mega-lo-Mart']")
|
167
|
+
@company.mascot.should be_a(CrazyMascot)
|
168
|
+
|
169
|
+
Object.class_eval do
|
170
|
+
remove_const :CompanyWithWrapperClass
|
171
|
+
remove_const :CrazyMascot
|
172
|
+
end
|
173
|
+
end
|
174
|
+
end
|
175
|
+
|
176
|
+
context '.child given :element_name' do
|
177
|
+
it 'should use the specified element name instead of the default' do
|
178
|
+
class CompanyWithElementName < Quarto::ElementWrapper::Base
|
179
|
+
child :the_mascot, :element_name => 'mascot'
|
180
|
+
end
|
181
|
+
|
182
|
+
@company = CompanyWithElementName.find(:first, :xpath => "//company[name='Mega-lo-Mart']")
|
183
|
+
@company.the_mascot.should be_a(Mascot)
|
184
|
+
@company.the_mascot.element.name.should == 'mascot'
|
185
|
+
|
186
|
+
Object.class_eval do
|
187
|
+
remove_const :CompanyWithElementName
|
188
|
+
end
|
189
|
+
end
|
190
|
+
end
|
39
191
|
end
|
40
192
|
|
41
|
-
describe Quarto::
|
193
|
+
describe Quarto::ElementWrapper::ChildrenProxy do
|
42
194
|
before :each do
|
43
195
|
Quarto.xml_source = File.open(SAMPLE_DIR + '/xml/companies.xml')
|
44
196
|
@xml = Quarto.xml_doc
|
@@ -52,7 +204,17 @@ describe Quarto::Children do
|
|
52
204
|
end
|
53
205
|
end
|
54
206
|
|
55
|
-
it 'should support
|
207
|
+
it 'should support #first' do
|
208
|
+
@company.employees.should respond_to(:first)
|
209
|
+
@company.employees.first.should == @company.employees.to_a.first
|
210
|
+
end
|
211
|
+
|
212
|
+
it 'should support #last' do
|
213
|
+
@company.employees.should respond_to(:last)
|
214
|
+
@company.employees.last.should == @company.employees.to_a.last
|
215
|
+
end
|
216
|
+
|
217
|
+
it 'should support #empty?' do
|
56
218
|
@company.employees.should respond_to(:empty?)
|
57
219
|
@company.employees.empty?.should == false
|
58
220
|
end
|
@@ -1,24 +1,23 @@
|
|
1
1
|
require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
|
2
|
-
require File.expand_path(File.dirname(__FILE__) + '/
|
3
|
-
require File.expand_path(File.dirname(__FILE__) + '/sample_project/models/employee')
|
2
|
+
require File.expand_path(File.dirname(__FILE__) + '/sample_models')
|
4
3
|
|
5
|
-
describe Quarto::ElementWrapper do
|
4
|
+
describe Quarto::ElementWrapper::Base do
|
6
5
|
before :each do
|
7
6
|
Quarto.xml_source = File.open(SAMPLE_DIR + '/xml/companies.xml')
|
8
7
|
@xml = Quarto.xml_doc
|
9
8
|
end
|
10
9
|
|
11
|
-
context 'wrapping an element with attributes
|
10
|
+
context 'wrapping an element with attributes' do
|
12
11
|
before :each do
|
13
12
|
@element = @xml.elements['companies/company']
|
14
13
|
@company = Company.new(@element)
|
15
14
|
end
|
16
15
|
|
17
|
-
it 'should instantiate a subclass of Quarto::ElementWrapper' do
|
18
|
-
@company.should be_a(Quarto::ElementWrapper)
|
16
|
+
it 'should instantiate a subclass of Quarto::ElementWrapper::Base' do
|
17
|
+
@company.should be_a(Quarto::ElementWrapper::Base)
|
19
18
|
end
|
20
19
|
|
21
|
-
it 'should define
|
20
|
+
it 'should define attributes from specified elements' do
|
22
21
|
@company.should respond_to(:name)
|
23
22
|
@company.name.should == '37Signals'
|
24
23
|
end
|
@@ -41,6 +40,16 @@ describe Quarto::ElementWrapper do
|
|
41
40
|
end
|
42
41
|
end
|
43
42
|
|
43
|
+
context 'wrapping an element that only contains text' do
|
44
|
+
before :each do
|
45
|
+
@product = Product.find(:first, :xpath => "//product[text()='Propane']")
|
46
|
+
end
|
47
|
+
|
48
|
+
it 'should link an attribute to the element\'s text' do
|
49
|
+
@product.name.should == 'Propane'
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
44
53
|
context '.new' do
|
45
54
|
it 'should raise ArgumentError if it is passed anything other than a REXML::Element' do
|
46
55
|
[nil, 'foo', 1].each do |bad|
|
@@ -53,6 +62,7 @@ describe Quarto::ElementWrapper do
|
|
53
62
|
it 'should find matching elements based on :xpath' do
|
54
63
|
companies = Company.find(:all, :xpath => 'companies/company')
|
55
64
|
companies.should be_a(Array)
|
65
|
+
companies.length.should == 5
|
56
66
|
companies.each do |company|
|
57
67
|
company.should be_a(Company)
|
58
68
|
end
|
@@ -67,6 +77,15 @@ describe Quarto::ElementWrapper do
|
|
67
77
|
companies[0].name.should == 'Milliways'
|
68
78
|
end
|
69
79
|
|
80
|
+
it 'should return an empty array with :all and an XPath that yields nothing' do
|
81
|
+
Company.find(:all, :xpath => "companies/company[foo='bar']").should == []
|
82
|
+
end
|
83
|
+
|
84
|
+
it 'should return nil with :first or :last and an XPath that yields nothing' do
|
85
|
+
Company.find(:first, :xpath => "companies/company[foo='bar']").should == nil
|
86
|
+
Company.find(:last, :xpath => "companies/company[foo='bar']").should == nil
|
87
|
+
end
|
88
|
+
|
70
89
|
it 'should work without the :xpath parameter' do
|
71
90
|
Company.find(:all).should == Company.find(:all, :xpath => '//company')
|
72
91
|
Employee.find(:all).should == Employee.find(:all, :xpath => '//employee')
|
@@ -89,5 +108,24 @@ describe Quarto::ElementWrapper do
|
|
89
108
|
company.name.should == 'Milliways'
|
90
109
|
end
|
91
110
|
end
|
111
|
+
|
112
|
+
context '#==' do
|
113
|
+
before :each do
|
114
|
+
@element_1 = @xml.elements['companies/company']
|
115
|
+
@element_2 = @xml.elements['companies/company[last()]']
|
116
|
+
end
|
117
|
+
|
118
|
+
it 'should return true for two ElementWrapper::Base instances that wrap the same element' do
|
119
|
+
Quarto::ElementWrapper::Base.new(@element_1).should == Quarto::ElementWrapper::Base.new(@element_1)
|
120
|
+
end
|
121
|
+
|
122
|
+
it 'should return false for two ElementWrapper::Base instaces that wrap different elements' do
|
123
|
+
Quarto::ElementWrapper::Base.new(@element_1).should_not == Quarto::ElementWrapper::Base.new(@element_2)
|
124
|
+
end
|
125
|
+
|
126
|
+
it 'should return false if the second object is not an instance of ElementWrapper::Base' do
|
127
|
+
Quarto::ElementWrapper::Base.new(@element_1).should_not == 'foo'
|
128
|
+
end
|
129
|
+
end
|
92
130
|
end
|
93
131
|
|
data/spec/generator_spec.rb
CHANGED
@@ -32,7 +32,7 @@ describe Quarto do
|
|
32
32
|
@mock_generator.should_receive(:generate)
|
33
33
|
end
|
34
34
|
|
35
|
-
it 'should
|
35
|
+
it 'should pass the block to the Generator' do
|
36
36
|
@mock_generator.should_receive(:do_something_in_the_block)
|
37
37
|
end
|
38
38
|
end
|
@@ -114,7 +114,7 @@ describe Quarto::Generator do
|
|
114
114
|
|
115
115
|
it 'should pass the companies into the template' do
|
116
116
|
html = File.read(@generator.output_path + '/companies.html')
|
117
|
-
['37Signals', 'Mega-lo-
|
117
|
+
['37Signals', 'Mega-lo-Mart', 'Kwik-E-Mart', 'Good Burger', 'Milliways'].each do |name|
|
118
118
|
html.should include(name)
|
119
119
|
end
|
120
120
|
end
|
@@ -0,0 +1,5 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/sample_project/models/company')
|
2
|
+
require File.expand_path(File.dirname(__FILE__) + '/sample_project/models/employee')
|
3
|
+
require File.expand_path(File.dirname(__FILE__) + '/sample_project/models/product')
|
4
|
+
require File.expand_path(File.dirname(__FILE__) + '/sample_project/models/location')
|
5
|
+
require File.expand_path(File.dirname(__FILE__) + '/sample_project/models/mascot')
|
@@ -1,8 +1,14 @@
|
|
1
|
-
class Company < Quarto::ElementWrapper
|
1
|
+
class Company < Quarto::ElementWrapper::Base
|
2
2
|
element_attrs :name, :industry
|
3
3
|
|
4
4
|
children :employees
|
5
5
|
|
6
|
+
children :products, :collection_element => 'selling'
|
7
|
+
|
8
|
+
children :locations, :collection_element => nil
|
9
|
+
|
10
|
+
child :mascot
|
11
|
+
|
6
12
|
def competitors
|
7
13
|
@competitors ||= self.class.find(:all, :xpath => "companies/company[industry='#{industry}' and name!='#{name}']")
|
8
14
|
end
|
data/spec/spec_helper.rb
CHANGED
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: jarrett-quarto
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version:
|
4
|
+
version: 1.1.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Jarrett Colby
|
@@ -9,10 +9,19 @@ autorequire:
|
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
11
|
|
12
|
-
date: 2009-06-
|
12
|
+
date: 2009-06-06 00:00:00 -07:00
|
13
13
|
default_executable: quarto
|
14
|
-
dependencies:
|
15
|
-
|
14
|
+
dependencies:
|
15
|
+
- !ruby/object:Gem::Dependency
|
16
|
+
name: activesupport
|
17
|
+
type: :runtime
|
18
|
+
version_requirement:
|
19
|
+
version_requirements: !ruby/object:Gem::Requirement
|
20
|
+
requirements:
|
21
|
+
- - ">="
|
22
|
+
- !ruby/object:Gem::Version
|
23
|
+
version: 2.3.2
|
24
|
+
version:
|
16
25
|
description: Quarto is a Ruby framework for generating collections of documents from XML. It steps in where XSLT just won't cut it. Potential applications include web sites and e-books. It's built on top of ERB and REXML.
|
17
26
|
email: jarrett@uchicago.edu
|
18
27
|
executables:
|
@@ -36,8 +45,7 @@ files:
|
|
36
45
|
- lib/quarto/rendering.rb
|
37
46
|
- lib/quarto/url_helper.rb
|
38
47
|
- lib/quarto/xml_doc.rb
|
39
|
-
|
40
|
-
has_rdoc: true
|
48
|
+
has_rdoc: false
|
41
49
|
homepage: http://github.com/jarrett/quarto
|
42
50
|
post_install_message:
|
43
51
|
rdoc_options:
|
@@ -61,7 +69,7 @@ requirements: []
|
|
61
69
|
rubyforge_project:
|
62
70
|
rubygems_version: 1.2.0
|
63
71
|
signing_key:
|
64
|
-
specification_version:
|
72
|
+
specification_version: 3
|
65
73
|
summary: generates HTML or any other format from XML
|
66
74
|
test_files:
|
67
75
|
- spec/children_spec.rb
|
@@ -69,8 +77,12 @@ test_files:
|
|
69
77
|
- spec/generator_spec.rb
|
70
78
|
- spec/init_project_spec.rb
|
71
79
|
- spec/matchers/file_matchers.rb
|
80
|
+
- spec/sample_models.rb
|
72
81
|
- spec/sample_project/generate.rb
|
73
82
|
- spec/sample_project/models/company.rb
|
74
83
|
- spec/sample_project/models/employee.rb
|
84
|
+
- spec/sample_project/models/location.rb
|
85
|
+
- spec/sample_project/models/mascot.rb
|
86
|
+
- spec/sample_project/models/product.rb
|
75
87
|
- spec/spec_helper.rb
|
76
88
|
- spec/url_helper_spec.rb
|
data/quarto.gemspec
DELETED
@@ -1,62 +0,0 @@
|
|
1
|
-
# -*- encoding: utf-8 -*-
|
2
|
-
|
3
|
-
Gem::Specification.new do |s|
|
4
|
-
s.name = %q{quarto}
|
5
|
-
s.version = "0.3.1"
|
6
|
-
|
7
|
-
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
8
|
-
s.authors = ["Jarrett Colby"]
|
9
|
-
s.date = %q{2009-06-05}
|
10
|
-
s.default_executable = %q{quarto}
|
11
|
-
s.description = %q{Quarto is a Ruby framework for generating collections of documents from XML. It steps in where XSLT just won't cut it. Potential applications include web sites and e-books. It's built on top of ERB and REXML.}
|
12
|
-
s.email = %q{jarrett@uchicago.edu}
|
13
|
-
s.executables = ["quarto"]
|
14
|
-
s.extra_rdoc_files = [
|
15
|
-
"README"
|
16
|
-
]
|
17
|
-
s.files = [
|
18
|
-
"README",
|
19
|
-
"Rakefile",
|
20
|
-
"VERSION",
|
21
|
-
"bin/quarto",
|
22
|
-
"lib/quarto.rb",
|
23
|
-
"lib/quarto/children.rb",
|
24
|
-
"lib/quarto/config.rb",
|
25
|
-
"lib/quarto/element_wrapper.rb",
|
26
|
-
"lib/quarto/generator.rb",
|
27
|
-
"lib/quarto/inheritable_attributes.rb",
|
28
|
-
"lib/quarto/init_project.rb",
|
29
|
-
"lib/quarto/rendering.rb",
|
30
|
-
"lib/quarto/url_helper.rb",
|
31
|
-
"lib/quarto/xml_doc.rb",
|
32
|
-
"quarto.gemspec"
|
33
|
-
]
|
34
|
-
s.has_rdoc = true
|
35
|
-
s.homepage = %q{http://github.com/jarrett/quarto}
|
36
|
-
s.rdoc_options = ["--charset=UTF-8"]
|
37
|
-
s.require_paths = ["lib"]
|
38
|
-
s.rubygems_version = %q{1.3.1}
|
39
|
-
s.summary = %q{generates HTML or any other format from XML}
|
40
|
-
s.test_files = [
|
41
|
-
"spec/children_spec.rb",
|
42
|
-
"spec/element_wrapper_spec.rb",
|
43
|
-
"spec/generator_spec.rb",
|
44
|
-
"spec/init_project_spec.rb",
|
45
|
-
"spec/matchers/file_matchers.rb",
|
46
|
-
"spec/sample_project/generate.rb",
|
47
|
-
"spec/sample_project/models/company.rb",
|
48
|
-
"spec/sample_project/models/employee.rb",
|
49
|
-
"spec/spec_helper.rb",
|
50
|
-
"spec/url_helper_spec.rb"
|
51
|
-
]
|
52
|
-
|
53
|
-
if s.respond_to? :specification_version then
|
54
|
-
current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
|
55
|
-
s.specification_version = 2
|
56
|
-
|
57
|
-
if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
|
58
|
-
else
|
59
|
-
end
|
60
|
-
else
|
61
|
-
end
|
62
|
-
end
|