jarrett-quarto 0.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 +19 -0
- data/spec/children_spec.rb +68 -0
- data/spec/element_wrapper_spec.rb +91 -0
- data/spec/generator_spec.rb +129 -0
- data/spec/sample_project/generate.rb +11 -0
- data/spec/sample_project/models/company.rb +9 -0
- data/spec/sample_project/models/employee.rb +10 -0
- data/spec/spec_helper.rb +10 -0
- data/spec/url_helper_spec.rb +23 -0
- metadata +60 -0
data/README
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
Quarto is a Ruby framework for generating collections of documents from XML. Potential applications
|
2
|
+
include web sites and e-books. It's built on top of ERB and REXML.
|
3
|
+
|
4
|
+
Quarto was built with HTML output in mind, but there's nothing to prevent you from outputting
|
5
|
+
to any other format. You could even output to an interpolated scripting language like PHP.
|
6
|
+
|
7
|
+
Why Quarto?
|
8
|
+
============
|
9
|
+
|
10
|
+
Say you have a book in XML format, and you want to make a web site from it. You could transform it
|
11
|
+
to HTML using XSLT. But what if you need logic that can't be implemented in XSLT?
|
12
|
+
|
13
|
+
Enter Quarto. Instead of writing a series of XSLT sheets, you write ERB templates. You implement
|
14
|
+
whatever custom logic you need in classes that wrap the DOM elements, then you pass variables to the templates.
|
15
|
+
|
16
|
+
Using Quarto
|
17
|
+
=============
|
18
|
+
|
19
|
+
Thorough documentation doesn't exist yet. For now, see spec/sample_project.
|
@@ -0,0 +1,68 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
|
2
|
+
|
3
|
+
describe Quarto::ElementWrapperChildren do
|
4
|
+
before :each do
|
5
|
+
Quarto.xml_source = File.open(SAMPLE_DIR + '/xml/companies.xml')
|
6
|
+
@xml = Quarto.xml_doc
|
7
|
+
end
|
8
|
+
|
9
|
+
context 'an ElementWrapper instance with children' do
|
10
|
+
before :each do
|
11
|
+
@element = @xml.elements['companies/company']
|
12
|
+
@company = Company.new(@element)
|
13
|
+
end
|
14
|
+
|
15
|
+
it 'should know its children' do
|
16
|
+
@company.should respond_to(:employees)
|
17
|
+
@company.employees.should be_a(Quarto::Children)
|
18
|
+
@company.employees.length.should == 2 # In the sample XML file, 37Signals has two employees.
|
19
|
+
@company.employees.each do |employee|
|
20
|
+
employee.should be_a(Employee)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
context 'an ElementWrapper instance with a parent' do
|
26
|
+
before :each do
|
27
|
+
@element = @xml.elements['companies/company/employees/employee']
|
28
|
+
@employee = Employee.new(@element)
|
29
|
+
end
|
30
|
+
|
31
|
+
it 'should have children which know their parent' do
|
32
|
+
@employee.should respond_to(:company)
|
33
|
+
@employee.company.should be_a(Company)
|
34
|
+
@employee.company.name.should == '37Signals'
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
describe Quarto::Children do
|
40
|
+
before :each do
|
41
|
+
Quarto.xml_source = File.open(SAMPLE_DIR + '/xml/companies.xml')
|
42
|
+
@xml = Quarto.xml_doc
|
43
|
+
@element = @xml.elements['companies/company']
|
44
|
+
@company = Company.new(@element)
|
45
|
+
end
|
46
|
+
|
47
|
+
it 'should respond to Enumerable methods' do
|
48
|
+
[:collect, :select, :inject, :detect, :include?].each do |meth|
|
49
|
+
@company.employees.should respond_to(meth)
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
it 'should support empty?' do
|
54
|
+
@company.employees.should respond_to(:empty?)
|
55
|
+
@company.employees.empty?.should == false
|
56
|
+
end
|
57
|
+
|
58
|
+
context '#each' do
|
59
|
+
it 'should iterate over each child' do
|
60
|
+
expected_names = ['DHH', 'Jamis Buck']
|
61
|
+
actual_names = []
|
62
|
+
@company.employees.each do |employee|
|
63
|
+
actual_names << employee.name
|
64
|
+
end
|
65
|
+
actual_names.should == expected_names
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
@@ -0,0 +1,91 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
|
2
|
+
|
3
|
+
describe Quarto::ElementWrapper do
|
4
|
+
before :each do
|
5
|
+
Quarto.xml_source = File.open(SAMPLE_DIR + '/xml/companies.xml')
|
6
|
+
@xml = Quarto.xml_doc
|
7
|
+
end
|
8
|
+
|
9
|
+
context 'wrapping an element with attributes and children' do
|
10
|
+
before :each do
|
11
|
+
@element = @xml.elements['companies/company']
|
12
|
+
@company = Company.new(@element)
|
13
|
+
end
|
14
|
+
|
15
|
+
it 'should instantiate a subclass of Quarto::ElementWrapper' do
|
16
|
+
@company.should be_a(Quarto::ElementWrapper)
|
17
|
+
end
|
18
|
+
|
19
|
+
it 'should define methods from specified child elements' do
|
20
|
+
@company.should respond_to(:name)
|
21
|
+
@company.name.should == '37Signals'
|
22
|
+
end
|
23
|
+
|
24
|
+
it 'should define methods from XML attributes by default' do
|
25
|
+
@company.should respond_to(:reality)
|
26
|
+
@company.reality.should == 'real'
|
27
|
+
end
|
28
|
+
|
29
|
+
it 'should expose methods of the underlying REXML::Element' do
|
30
|
+
# Just test a few methods that don't require parameters
|
31
|
+
[:root_node, :text, :node_type, :document, :children].each do |meth|
|
32
|
+
@company.should respond_to(meth)
|
33
|
+
@company.send(meth).should == @element.send(meth)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
it 'should set the element_name based on the class name' do
|
38
|
+
Company.element_name.should == 'company'
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
context '.new' do
|
43
|
+
it 'should raise ArgumentError if it is passed anything other than a REXML::Element' do
|
44
|
+
[nil, 'foo', 1].each do |bad|
|
45
|
+
lambda { Company.new(bad) }.should raise_error(ArgumentError)
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
context '#find' do
|
51
|
+
it 'should find matching elements based on :xpath' do
|
52
|
+
companies = Company.find(:all, :xpath => 'companies/company')
|
53
|
+
companies.should be_a(Array)
|
54
|
+
companies.each do |company|
|
55
|
+
company.should be_a(Company)
|
56
|
+
end
|
57
|
+
companies = Company.find(:all, :xpath => "companies/company[@reality='real']")
|
58
|
+
companies.should be_a(Array)
|
59
|
+
companies.length.should == 1 # There is one real company in the example XML file: 37Signals
|
60
|
+
companies[0].name.should == '37Signals'
|
61
|
+
|
62
|
+
companies = Company.find(:all, :xpath => "companies/company[name='Milliways']")
|
63
|
+
companies.should be_a(Array)
|
64
|
+
companies.length.should == 1
|
65
|
+
companies[0].name.should == 'Milliways'
|
66
|
+
end
|
67
|
+
|
68
|
+
it 'should work without the :xpath parameter' do
|
69
|
+
Company.find(:all).should == Company.find(:all, :xpath => '//company')
|
70
|
+
Employee.find(:all).should == Employee.find(:all, :xpath => '//employee')
|
71
|
+
end
|
72
|
+
|
73
|
+
it 'should return all matching elements when the quantifier is :all' do
|
74
|
+
companies = Company.find(:all, :xpath => 'companies/company')
|
75
|
+
companies.length.should == 5 # There are five companies in the example XML file
|
76
|
+
end
|
77
|
+
|
78
|
+
it 'should return the first matching element when the quantifier is :first' do
|
79
|
+
company = Company.find(:first, :xpath => 'companies/company')
|
80
|
+
company.should be_a(Company)
|
81
|
+
company.name.should == '37Signals'
|
82
|
+
end
|
83
|
+
|
84
|
+
it 'should return the last matching element when the quantifier is :first' do
|
85
|
+
company = Company.find(:last, :xpath => 'companies/company')
|
86
|
+
company.should be_a(Company)
|
87
|
+
company.name.should == 'Milliways'
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
@@ -0,0 +1,129 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
|
2
|
+
require 'fileutils'
|
3
|
+
|
4
|
+
describe Quarto do
|
5
|
+
context '.generatate' do
|
6
|
+
class MockGenerator
|
7
|
+
def generate(&block)
|
8
|
+
instance_eval(&block)
|
9
|
+
end
|
10
|
+
|
11
|
+
def do_something_in_the_block; end
|
12
|
+
end
|
13
|
+
|
14
|
+
before :each do
|
15
|
+
@mock_generator = MockGenerator.new
|
16
|
+
Quarto::Generator.stub!(:new).and_return(@mock_generator)
|
17
|
+
end
|
18
|
+
|
19
|
+
after :each do
|
20
|
+
# This must happen after the mocking has been configured for each example
|
21
|
+
# Otherwise, none of the mocking will have any effect
|
22
|
+
Quarto.generate(SAMPLE_DIR) do
|
23
|
+
do_something_in_the_block
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
it 'should call Quarto::Generator.new and pass the project directory' do
|
28
|
+
Quarto::Generator.should_receive(:new).with(SAMPLE_DIR).and_return(@mock_generator)
|
29
|
+
end
|
30
|
+
|
31
|
+
it 'should call #generate on the Generator' do
|
32
|
+
@mock_generator.should_receive(:generate)
|
33
|
+
end
|
34
|
+
|
35
|
+
it 'should execute the block in the context of the Generator' do
|
36
|
+
@mock_generator.should_receive(:do_something_in_the_block)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
module Quarto
|
42
|
+
# This will be used in the specs below--see the method aliasing
|
43
|
+
def self.testable_generate(&block)
|
44
|
+
GenerationTesting.generation_block = block
|
45
|
+
end
|
46
|
+
|
47
|
+
module GenerationTesting
|
48
|
+
def self.generation_block
|
49
|
+
@generation_block
|
50
|
+
end
|
51
|
+
|
52
|
+
def self.generation_block=(block)
|
53
|
+
@generation_block = block
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
describe Quarto::Generator do
|
59
|
+
context '.new' do
|
60
|
+
it 'should accept a path to a project directory' do
|
61
|
+
Quarto::Generator.new(SAMPLE_DIR).should be_a(Quarto::Generator)
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
context '#generate from the sample project' do
|
66
|
+
include Quarto::UrlHelper
|
67
|
+
|
68
|
+
before :all do
|
69
|
+
# Quarto.generate will now return the passed block as a proc
|
70
|
+
# instead of envoking the whole framework
|
71
|
+
Quarto.module_eval do
|
72
|
+
class << self
|
73
|
+
|
74
|
+
alias_method :untestable_generate, :generate
|
75
|
+
alias_method :generate, :testable_generate
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
@generator = Quarto::Generator.new(SAMPLE_DIR)
|
80
|
+
|
81
|
+
# Clear the directories
|
82
|
+
FileUtils.rm_rf @generator.output_path
|
83
|
+
|
84
|
+
# Now that we've aliased the methods and load has been called GenerationTesting.generation_block should be set
|
85
|
+
load @generator.generate_file_path
|
86
|
+
@generator.generate(&Quarto::GenerationTesting.generation_block)
|
87
|
+
end
|
88
|
+
|
89
|
+
after :all do
|
90
|
+
# Reset Quarto.generate to its old behavior
|
91
|
+
Quarto.module_eval do
|
92
|
+
class << self
|
93
|
+
alias_method :generate, :untestable_generate
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
it 'should set #output_path' do
|
99
|
+
@generator.output_path.should == SAMPLE_DIR + '/output'
|
100
|
+
end
|
101
|
+
|
102
|
+
it 'should create a directory under #output_path' do
|
103
|
+
File.exists?(@generator.output_path).should be_true
|
104
|
+
end
|
105
|
+
|
106
|
+
it 'should create a single file for all the companies' do
|
107
|
+
Dir.glob(@generator.output_path + '/*.html').collect { |f| File.basename(f) }.should == ['companies.html']
|
108
|
+
end
|
109
|
+
|
110
|
+
it 'should create one file for each employee' do
|
111
|
+
expected_files = ['DHH', 'Jamis Buck', 'Hank Hill', 'Buckley', 'Apu Nahasapeemapetilon', 'Kenan Thompson', 'Kel Mitchell', 'Marvin'].sort.collect { |name| urlize(name) + '.html' }
|
112
|
+
Dir.glob(@generator.output_path + '/employees/*.html').collect { |f| File.basename(f) }.sort.should == expected_files
|
113
|
+
end
|
114
|
+
|
115
|
+
it 'should pass the companies into the template' do
|
116
|
+
html = File.read(@generator.output_path + '/companies.html')
|
117
|
+
['37Signals', 'Mega-lo-mart', 'Kwik-E-Mart', 'Good Burger', 'Milliways'].each do |name|
|
118
|
+
html.should include(name)
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
it 'should pass the employee names into the template' do
|
123
|
+
['DHH', 'Jamis Buck', 'Hank Hill', 'Buckley', 'Apu Nahasapeemapetilon', 'Kenan Thompson', 'Kel Mitchell', 'Marvin'].each do |name|
|
124
|
+
html = File.read(@generator.output_path + '/employees/' + urlize(name) + '.html')
|
125
|
+
html.should include(name)
|
126
|
+
end
|
127
|
+
end
|
128
|
+
end
|
129
|
+
end
|
@@ -0,0 +1,11 @@
|
|
1
|
+
Quarto.generate do
|
2
|
+
Quarto.config(:site_root => 'http://localhost/')
|
3
|
+
|
4
|
+
use_xml('companies.xml')
|
5
|
+
|
6
|
+
render 'companies.html.erb', '', 'companies.html', :companies => Company.find(:all)
|
7
|
+
|
8
|
+
Employee.find(:all).each do |employee|
|
9
|
+
render 'employee.html.erb', 'employees', "#{urlize(employee.name)}.html", :employee => employee
|
10
|
+
end
|
11
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,10 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'test/unit'
|
3
|
+
require 'spec'
|
4
|
+
|
5
|
+
SPEC_DIR = File.expand_path(File.dirname(__FILE__))
|
6
|
+
SAMPLE_DIR = SPEC_DIR + '/sample_project'
|
7
|
+
|
8
|
+
require(File.expand_path(File.dirname(__FILE__)) + '/../lib/quarto')
|
9
|
+
require(File.expand_path(File.dirname(__FILE__)) + '/sample_project/models/company')
|
10
|
+
require(File.expand_path(File.dirname(__FILE__)) + '/sample_project/models/employee')
|
@@ -0,0 +1,23 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
|
2
|
+
|
3
|
+
describe Quarto::UrlHelper do
|
4
|
+
include Quarto::UrlHelper
|
5
|
+
|
6
|
+
context '#urlize' do
|
7
|
+
it 'should automatically convert non-strings to strings' do
|
8
|
+
urlize(1).should == '1'
|
9
|
+
end
|
10
|
+
|
11
|
+
it 'should replace spaces with dashes' do
|
12
|
+
urlize('John Smith').should == 'John-Smith'
|
13
|
+
end
|
14
|
+
|
15
|
+
it 'should not change numbers' do
|
16
|
+
urlize('a10b').should == 'a10b'
|
17
|
+
end
|
18
|
+
|
19
|
+
it 'should not change dashes and underscores' do
|
20
|
+
urlize('foo-bar_baz').should == 'foo-bar_baz'
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
metadata
ADDED
@@ -0,0 +1,60 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: jarrett-quarto
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Jarrett Colby
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
|
12
|
+
date: 2009-06-04 00:00:00 -07:00
|
13
|
+
default_executable:
|
14
|
+
dependencies: []
|
15
|
+
|
16
|
+
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
|
+
email: jarrett@uchicago.edu
|
18
|
+
executables: []
|
19
|
+
|
20
|
+
extensions: []
|
21
|
+
|
22
|
+
extra_rdoc_files:
|
23
|
+
- README
|
24
|
+
files:
|
25
|
+
- README
|
26
|
+
has_rdoc: true
|
27
|
+
homepage: http://github.com/jarrett/quarto
|
28
|
+
post_install_message:
|
29
|
+
rdoc_options:
|
30
|
+
- --charset=UTF-8
|
31
|
+
require_paths:
|
32
|
+
- lib
|
33
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
34
|
+
requirements:
|
35
|
+
- - ">="
|
36
|
+
- !ruby/object:Gem::Version
|
37
|
+
version: "0"
|
38
|
+
version:
|
39
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
40
|
+
requirements:
|
41
|
+
- - ">="
|
42
|
+
- !ruby/object:Gem::Version
|
43
|
+
version: "0"
|
44
|
+
version:
|
45
|
+
requirements: []
|
46
|
+
|
47
|
+
rubyforge_project:
|
48
|
+
rubygems_version: 1.2.0
|
49
|
+
signing_key:
|
50
|
+
specification_version: 2
|
51
|
+
summary: generates HTML or any other format from XML
|
52
|
+
test_files:
|
53
|
+
- spec/children_spec.rb
|
54
|
+
- spec/element_wrapper_spec.rb
|
55
|
+
- spec/generator_spec.rb
|
56
|
+
- spec/sample_project/generate.rb
|
57
|
+
- spec/sample_project/models/company.rb
|
58
|
+
- spec/sample_project/models/employee.rb
|
59
|
+
- spec/spec_helper.rb
|
60
|
+
- spec/url_helper_spec.rb
|