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 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
@@ -0,0 +1,9 @@
1
+ class Company < Quarto::ElementWrapper
2
+ element_attrs :name, :industry
3
+
4
+ children :employees
5
+
6
+ def competitors
7
+ @competitors ||= self.class.find(:all, :xpath => "companies/company[industry='#{industry}' and name!='#{name}']")
8
+ end
9
+ end
@@ -0,0 +1,10 @@
1
+ class Employee < Quarto::ElementWrapper
2
+ element_attrs :name
3
+
4
+ parent :company
5
+
6
+ def self.from_element(el)
7
+ employee = new(el)
8
+ employee.ivs_from_elements('name')
9
+ end
10
+ end
@@ -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