jarrett-quarto 0.3.0 → 0.3.1
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/Rakefile +16 -0
- data/VERSION +1 -0
- data/lib/quarto/children.rb +152 -0
- data/lib/quarto/config.rb +10 -0
- data/lib/quarto/element_wrapper.rb +156 -0
- data/lib/quarto/generator.rb +160 -0
- data/lib/quarto/inheritable_attributes.rb +31 -0
- data/lib/quarto/init_project.rb +38 -0
- data/lib/quarto/rendering.rb +22 -0
- data/lib/quarto/url_helper.rb +17 -0
- data/lib/quarto/xml_doc.rb +10 -0
- data/lib/quarto.rb +16 -0
- data/quarto.gemspec +62 -0
- metadata +16 -2
data/Rakefile
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
begin
|
2
|
+
require 'jeweler'
|
3
|
+
Jeweler::Tasks.new do |gemspec|
|
4
|
+
gemspec.name = 'quarto'
|
5
|
+
gemspec.summary = 'generates HTML or any other format from XML'
|
6
|
+
gemspec.email = 'jarrett@uchicago.edu'
|
7
|
+
gemspec.homepage = 'http://github.com/jarrett/quarto'
|
8
|
+
gemspec.description = 'Quarto is a Ruby framework for generating collections of documents from XML. It steps in where XSLT just won\'t cut it. ' +
|
9
|
+
'Potential applications include web sites and e-books. It\'s built on top of ERB and REXML.'
|
10
|
+
gemspec.authors = ['Jarrett Colby']
|
11
|
+
gemspec.files = FileList['[A-Z]*', '{bin,lib,test}/**/*']
|
12
|
+
gemspec.executables = 'quarto'
|
13
|
+
end
|
14
|
+
rescue LoadError
|
15
|
+
puts "Jeweler not available. Install it with: sudo gem install technicalpickles-jeweler -s http://gems.github.com"
|
16
|
+
end
|
data/VERSION
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
0.3.1
|
@@ -0,0 +1,152 @@
|
|
1
|
+
module Quarto
|
2
|
+
# ElementWrapper subclasses can define parent and child elements, resulting
|
3
|
+
# in handy accessor methods. For example:
|
4
|
+
#
|
5
|
+
# class Company < ElementWrapper
|
6
|
+
# children :employees
|
7
|
+
# end
|
8
|
+
#
|
9
|
+
# class Employee < ElementWrapper
|
10
|
+
# parent :company
|
11
|
+
# element_attr 'name'
|
12
|
+
# end
|
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?
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def method_missing_with_children(meth, *args) # :nodoc:
|
33
|
+
if self.class.has_children_named?(meth)
|
34
|
+
children_proxy(meth)
|
35
|
+
elsif self.class.has_parent_named?(meth)
|
36
|
+
wrapped_parent
|
37
|
+
else
|
38
|
+
method_missing_without_children(meth, *args)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
def respond_to_with_children?(meth, include_private = false) # :nodoc:
|
43
|
+
if self.class.has_children_named?(meth) or self.class.has_parent_named?(meth)
|
44
|
+
true
|
45
|
+
else
|
46
|
+
respond_to_without_children?(meth, include_private)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
protected
|
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
|
56
|
+
|
57
|
+
def wrapped_parent # :nodoc:
|
58
|
+
parent_el_name = self.class.read_inheritable_attribute(:parent)
|
59
|
+
parent_class_name = parent_el_name.classify
|
60
|
+
Kernel.const_get(parent_class_name).new(@element.parent.parent) # Go up two levels, since each child is expected to be inside a collection element
|
61
|
+
end
|
62
|
+
|
63
|
+
module ClassMethods # :nodoc:
|
64
|
+
# :singleton-method:
|
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])
|
82
|
+
end
|
83
|
+
|
84
|
+
def has_children_named?(collection_el_name) # :nodoc:
|
85
|
+
return false if read_inheritable_attribute(:children).nil?
|
86
|
+
read_inheritable_attribute(:children).include?(collection_el_name.to_s)
|
87
|
+
end
|
88
|
+
|
89
|
+
def has_parent_named?(parent_el_name) # :nodoc:
|
90
|
+
read_inheritable_attribute(:parent) == parent_el_name.to_s
|
91
|
+
end
|
92
|
+
|
93
|
+
# :singleton-method:
|
94
|
+
# Defines the element's parent. Example:
|
95
|
+
# Example:
|
96
|
+
#
|
97
|
+
# <company>
|
98
|
+
# <employees>
|
99
|
+
# <employee>
|
100
|
+
# </employee>
|
101
|
+
# </employees>
|
102
|
+
# </company>
|
103
|
+
#
|
104
|
+
# class Employee < ElementWrapper
|
105
|
+
# parent :company
|
106
|
+
# end
|
107
|
+
def parent(el_name)
|
108
|
+
write_inheritable_attribute(:parent, el_name.to_s)
|
109
|
+
end
|
110
|
+
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
|
+
|
129
|
+
def initialize(wrapped_parent, el_name, options = {}) # :nodoc:
|
130
|
+
@wrapped_parent = wrapped_parent
|
131
|
+
@el_name = el_name.to_s
|
132
|
+
@collection_element = @wrapped_parent.element.elements[options[:collection_el_name] || @el_name.pluralize]
|
133
|
+
@wrapper_class = options[:wrapper_class] || Kernel.const_get(@el_name.classify)
|
134
|
+
end
|
135
|
+
|
136
|
+
# Returns the number of children.
|
137
|
+
def length
|
138
|
+
to_a.length
|
139
|
+
end
|
140
|
+
|
141
|
+
# Returns an array of all children. Each is an instance of ElementWrapper. If +xpath+ is provided, the results will be filtered. +xpath+ is relative to the parent element
|
142
|
+
def to_a(xpath = nil)
|
143
|
+
@all ||= @collection_element.elements.to_a(xpath || @el_name).collect do |el|
|
144
|
+
@wrapper_class.new(el)
|
145
|
+
end
|
146
|
+
end
|
147
|
+
|
148
|
+
alias_method :size, :length
|
149
|
+
end
|
150
|
+
end
|
151
|
+
|
152
|
+
Quarto::ElementWrapper.send(:include, Quarto::ElementWrapperChildren)
|
@@ -0,0 +1,156 @@
|
|
1
|
+
module Quarto
|
2
|
+
# Abstract base class for your models. Put your ElementWrapper subclasses inside the "models"
|
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
|
34
|
+
|
35
|
+
class ElementWrapper
|
36
|
+
include InheritableAttributes
|
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.
|
53
|
+
#
|
54
|
+
# For example, if your "employee" element has a child element called "name," you can use:
|
55
|
+
#
|
56
|
+
# element_attrs 'name'
|
57
|
+
#
|
58
|
+
# ...which will then expose a #name method for every instance of your class. Also see the usage example in the class description.
|
59
|
+
#
|
60
|
+
# Remember, XML attributes will automatically have corresponding ElementWrapper attributes. You only need to tell
|
61
|
+
# ElementWrapper which child elements to use.
|
62
|
+
def self.element_attrs(*element_names)
|
63
|
+
write_inheritable_array :element_attrs, element_names.collect { |en| en.to_sym}
|
64
|
+
end
|
65
|
+
|
66
|
+
# Returns the XML element name.
|
67
|
+
def self.element_name
|
68
|
+
@element_name
|
69
|
+
end
|
70
|
+
|
71
|
+
# Overrides the XML element name. The default is the class name in snake_case.
|
72
|
+
def self.element_name=(el_name)
|
73
|
+
@element_name = el_name
|
74
|
+
end
|
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).
|
78
|
+
#
|
79
|
+
# Options:
|
80
|
+
#
|
81
|
+
# * <tt>:xpath</tt> - An XPath expression to limit the search. If this option is not given, the default XPath is "//element_name"
|
82
|
+
def self.find(quantifier, options = {})
|
83
|
+
raise ArgumentError, "Quantifier must be :all, :first, or :last, but got #{quantifier.inspect}" unless [:all, :first, :last].include?(quantifier)
|
84
|
+
raise ArgumentError, "Options must be a Hash, but got #{options.inspect}" unless options.is_a?(Hash)
|
85
|
+
if options.has_key?(:xpath)
|
86
|
+
xpath = options[:xpath]
|
87
|
+
else
|
88
|
+
xpath = "//#{@element_name}"
|
89
|
+
# TODO: add support for :root and :conditions (XPath predicates)
|
90
|
+
end
|
91
|
+
all = xml_doc.elements.to_a(xpath).collect do |el|
|
92
|
+
new(el)
|
93
|
+
end
|
94
|
+
case quantifier
|
95
|
+
when :all
|
96
|
+
all
|
97
|
+
when :first
|
98
|
+
all.first
|
99
|
+
when :last
|
100
|
+
all.last
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
def self.inherited(subclass) # :nodoc:
|
105
|
+
subclass.element_name = subclass.to_s.underscore
|
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}"
|
111
|
+
end
|
112
|
+
@element = el
|
113
|
+
@attributes = {}
|
114
|
+
@element.attributes.each do |a_name, value|
|
115
|
+
@attributes[a_name.to_sym] = typecast_text(value)
|
116
|
+
end
|
117
|
+
self.class.read_inheritable_attribute(:element_attrs).each do |el_name|
|
118
|
+
raise ArgumentError, "Expected <#{@element.name}> to contain <#{el_name}>" if @element.elements[el_name.to_s].nil?
|
119
|
+
@attributes[el_name.to_sym] = typecast_text(@element.elements[el_name.to_s].text)
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
def method_missing(meth, *args, &block) # :nodoc:
|
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
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
133
|
+
def respond_to?(meth, include_private = false) # :nodoc:
|
134
|
+
if @element.respond_to?(meth, include_private) or @attributes.has_key?(meth.to_sym)
|
135
|
+
true
|
136
|
+
else
|
137
|
+
super
|
138
|
+
end
|
139
|
+
end
|
140
|
+
|
141
|
+
protected
|
142
|
+
|
143
|
+
# When an ElementWrapper is instantiated from an XML node, all values start out as strings. This method typecasts those values.
|
144
|
+
def typecast_text(t)
|
145
|
+
if t.nil? or (t.is_a?(String) and t.empty?)
|
146
|
+
nil
|
147
|
+
elsif t =~ /^-?[0-9]+$/
|
148
|
+
t.to_i
|
149
|
+
elsif t =~ /^-?[0-9]*\.[0-9]+$/
|
150
|
+
t.to_f
|
151
|
+
else
|
152
|
+
t
|
153
|
+
end
|
154
|
+
end
|
155
|
+
end
|
156
|
+
end
|
@@ -0,0 +1,160 @@
|
|
1
|
+
module Quarto
|
2
|
+
# Generates the project according to the directives in the block.
|
3
|
+
# A block must be supplied. The block will be evaluated within the context of
|
4
|
+
# a Quarto::Generator object.
|
5
|
+
#
|
6
|
+
# If the optional +project_path+ is given, the directives in the block willl
|
7
|
+
# be process for the project residing +project_path+. Otherwise, the current
|
8
|
+
# working directory will be used.
|
9
|
+
#
|
10
|
+
# This method is typically called from <tt>generate.rb</tt>. There's probably
|
11
|
+
# no reason for you to call it from any other context.
|
12
|
+
def self.generate(project_path = nil, &block)
|
13
|
+
unless block_given?
|
14
|
+
raise ArgumentError, 'Quarto.generate must be given a block'
|
15
|
+
end
|
16
|
+
# caller[0] returns the trace for the context that called generate. So, if generate.rb is invoked directly, Quarto will still work.
|
17
|
+
trace = caller()
|
18
|
+
unless trace.empty?
|
19
|
+
calling_file = trace[0].split(':')[-2]
|
20
|
+
if File.basename(calling_file) == 'generate.rb'
|
21
|
+
project_path = project_path || File.expand_path(File.dirname(calling_file))
|
22
|
+
end
|
23
|
+
end
|
24
|
+
unless project_path.is_a?(String) and !project_path.empty?
|
25
|
+
raise ArgumentError, 'project_path is required when Quarto.generate is called from any file other than generate.rb'
|
26
|
+
end
|
27
|
+
Dir.glob(project_path + '/models/*.rb').each do |model_file|
|
28
|
+
require model_file
|
29
|
+
end
|
30
|
+
generator = Quarto::Generator.new(project_path)
|
31
|
+
generator.generate(&block)
|
32
|
+
generator
|
33
|
+
end
|
34
|
+
|
35
|
+
# Generates the project at the specified path using its generate.rb.
|
36
|
+
def self.generate_from_project_path(project_path)
|
37
|
+
raise ArgumentError, "Expected string, but got #{project_path.inspect}" unless project_path.is_a?(String) and !project_path.empty?
|
38
|
+
load(project_path + '/generate.rb')
|
39
|
+
end
|
40
|
+
|
41
|
+
# This class responds to all the directives that are available for use within
|
42
|
+
# a generate.rb file.
|
43
|
+
class Generator
|
44
|
+
include UrlHelper
|
45
|
+
|
46
|
+
# Sets the name of the default layout file in the layouts directory. If
|
47
|
+
# default_layout isn't specified, the default layout is the first file matching <tt>default.*</tt>.
|
48
|
+
attr_accessor :default_layout
|
49
|
+
|
50
|
+
# Generate the project according to the directives given in the block.
|
51
|
+
def generate(&block)
|
52
|
+
raise ArgumentError, 'generate must be called with a block' unless block_given?
|
53
|
+
if !File.exists? @output_path
|
54
|
+
Dir.mkdir @output_path
|
55
|
+
end
|
56
|
+
instance_eval(&block)
|
57
|
+
end
|
58
|
+
|
59
|
+
def generate_file_path # :nodoc:
|
60
|
+
@project_path + '/generate.rb'
|
61
|
+
end
|
62
|
+
|
63
|
+
# Options:
|
64
|
+
# * <tt>:console_output</tt> - Boolean. If true, the generator will print what it's currently doing.
|
65
|
+
# * <tt>:console</tt> - By default, console messages will be printed to stdout. You can override this
|
66
|
+
# by passing in an object that responds to <tt>puts</tt>.
|
67
|
+
def initialize(project_path, options = {})
|
68
|
+
raise ArgumentError, "Expected string, but got #{project_path.inspect}" unless project_path.is_a?(String) and !project_path.empty?
|
69
|
+
raise ArgumentError, "Project path #{project_path} doesn't exist" unless File.exists?(project_path)
|
70
|
+
@project_path = project_path
|
71
|
+
@output_path = project_path + '/output'
|
72
|
+
@options = {:console_output => true, :console => Kernel}.merge(options)
|
73
|
+
@console = @options[:console]
|
74
|
+
end
|
75
|
+
|
76
|
+
attr_reader :output_path # :nodoc:
|
77
|
+
|
78
|
+
protected
|
79
|
+
|
80
|
+
# Set a configuration for Quarto to use during generation, e.g. <tt>:site_root</tt>
|
81
|
+
def config(key, value)
|
82
|
+
Quarto.config[key] = value
|
83
|
+
end
|
84
|
+
|
85
|
+
# Render the given +template+, and save the output in +filename+ under +directory+.
|
86
|
+
# +locals+ is a hash where they keys are the names of local variables in the template
|
87
|
+
#
|
88
|
+
# Options:
|
89
|
+
# * <tt>:layout</tt> - Render inside the specified layout. Must be the name of
|
90
|
+
# a file in the layouts directory, e.g. <tt>my_layout.html.erb</tt>. If not given,
|
91
|
+
# the <tt>default_layout</tt> will be used.
|
92
|
+
#
|
93
|
+
# Example:
|
94
|
+
# employees.each do |employee|
|
95
|
+
# render 'employee.html.erb', 'employees', urlize(employee.name) + '.html', :employee => employee
|
96
|
+
# end
|
97
|
+
#
|
98
|
+
# That example will create a number of files with names like "John-Smith.html"
|
99
|
+
# in the "employees" directory
|
100
|
+
def render(template, directory, filename, locals, options = {})
|
101
|
+
if @options[:console_output]
|
102
|
+
if directory.is_a?(String) and !directory.empty?
|
103
|
+
@console.puts "Writing from template #{template} to output/#{directory}/#{filename}"
|
104
|
+
else
|
105
|
+
@console.puts "Writing from template #{template} to output/#{filename}"
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
if directory.nil? or directory.empty?
|
110
|
+
path = "#{@output_path}/#{filename}"
|
111
|
+
else
|
112
|
+
subdir = "#{@output_path}/#{directory}"
|
113
|
+
if !File.exists? subdir
|
114
|
+
Dir.mkdir subdir
|
115
|
+
end
|
116
|
+
path = "#{subdir}/#{filename}"
|
117
|
+
end
|
118
|
+
|
119
|
+
File.open(path, 'w') do |file|
|
120
|
+
file.print render_to_s(template, locals, options)
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
# Renders +template+ to a string. Sets local variables within the template to the values given
|
125
|
+
# in +locals+.
|
126
|
+
def render_to_s(template, locals, options = {})
|
127
|
+
page_template_path = "#{@project_path}/pages/#{template}"
|
128
|
+
page_template = ERB.new(File.read(page_template_path))
|
129
|
+
page_content = Rendering.render(page_template, locals)
|
130
|
+
|
131
|
+
if options.has_key?(:layout)
|
132
|
+
layout = options[:layout]
|
133
|
+
elsif (@default_layout and File.exists?("#{@project_path}/layouts/#{@default_layout}"))
|
134
|
+
layout = @default_layout
|
135
|
+
elsif @default_layout = Dir.glob("#{@project_path}/layouts/default.*.erb")[0]
|
136
|
+
@default_layout = File.basename(@default_layout)
|
137
|
+
layout = @default_layout
|
138
|
+
else
|
139
|
+
layout = nil
|
140
|
+
end
|
141
|
+
|
142
|
+
if layout
|
143
|
+
layout_template_path = "#{@project_path}/layouts/#{layout}"
|
144
|
+
layout_template = ERB.new(File.read(layout_template_path))
|
145
|
+
Rendering.render(layout_template, locals) do
|
146
|
+
page_content
|
147
|
+
end
|
148
|
+
else
|
149
|
+
page_content
|
150
|
+
end
|
151
|
+
end
|
152
|
+
|
153
|
+
# Specifies which XML file to use. Must be the name of a file in the xml directory, e.g. <tt>companies.xml</tt>.
|
154
|
+
# This method absolutely must be called within <tt>generate.rb</tt>. Otherwise, Quarto won't know what XML
|
155
|
+
# to use as its source.
|
156
|
+
def use_xml(xml_filename)
|
157
|
+
Quarto.xml_source = File.open("#{@project_path}/xml/#{xml_filename}")
|
158
|
+
end
|
159
|
+
end
|
160
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
# Thanks to ActiveSupport for this stuff
|
2
|
+
|
3
|
+
module Quarto
|
4
|
+
module InheritableAttributes # :nodoc: all
|
5
|
+
def self.included(base)
|
6
|
+
base.extend(ClassMethods)
|
7
|
+
end
|
8
|
+
|
9
|
+
module ClassMethods
|
10
|
+
def read_inheritable_attribute(key)
|
11
|
+
@inheritable_attributes ||= {}
|
12
|
+
@inheritable_attributes[key]
|
13
|
+
end
|
14
|
+
|
15
|
+
def write_inheritable_array(key, elements)
|
16
|
+
write_inheritable_attribute(key, []) if read_inheritable_attribute(key).nil?
|
17
|
+
write_inheritable_attribute(key, read_inheritable_attribute(key) + elements)
|
18
|
+
end
|
19
|
+
|
20
|
+
def write_inheritable_attribute(key, value)
|
21
|
+
@inheritable_attributes ||= {}
|
22
|
+
@inheritable_attributes[key] = value
|
23
|
+
end
|
24
|
+
|
25
|
+
def write_inheritable_hash(key, hash)
|
26
|
+
write_inheritable_attribute(key, {}) if read_inheritable_attribute(key).nil?
|
27
|
+
write_inheritable_attribute(key, read_inheritable_attribute(key).merge(hash))
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
module Quarto
|
2
|
+
PROJECT_SUBFOLDERS = [
|
3
|
+
'layouts',
|
4
|
+
'models',
|
5
|
+
'output',
|
6
|
+
'pages',
|
7
|
+
'xml'
|
8
|
+
]
|
9
|
+
|
10
|
+
STARTER_GENERATE_FILE = %q(
|
11
|
+
Quarto.generate do
|
12
|
+
# Your code here
|
13
|
+
# e.g.:
|
14
|
+
# render 'companies.html.erb', '', 'companies.html', :companies => Company.find(:all)
|
15
|
+
end
|
16
|
+
)
|
17
|
+
# Initialize a new Quarto project at the specified path. Creates a generate.rb file and the necessary subfolders.
|
18
|
+
def self.init_project(project_path)
|
19
|
+
raise ArgumentError, "Expected string, but got #{project_path.inspect}" unless project_path.is_a?(String) and !project_path.empty?
|
20
|
+
project_path = File.expand_path(project_path)
|
21
|
+
unless File.exists?(project_path)
|
22
|
+
Dir.mkdir project_path
|
23
|
+
end
|
24
|
+
PROJECT_SUBFOLDERS.each do |subfolder|
|
25
|
+
subfolder = project_path + '/' + subfolder
|
26
|
+
unless File.exists?(subfolder)
|
27
|
+
Dir.mkdir subfolder
|
28
|
+
end
|
29
|
+
end
|
30
|
+
generate_file = project_path + '/generate.rb'
|
31
|
+
unless File.exists?(generate_file)
|
32
|
+
File.open(generate_file, 'w') do |file|
|
33
|
+
file.print(STARTER_GENERATE_FILE)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
true
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
module Quarto
|
2
|
+
class Rendering # :nodoc: all
|
3
|
+
include UrlHelper
|
4
|
+
|
5
|
+
def initialize(__erb_template, __locals)
|
6
|
+
__b = binding
|
7
|
+
__locals.each_key do |var_name|
|
8
|
+
# In the context of this method (rather than of this block),
|
9
|
+
# define the local variables
|
10
|
+
eval "#{var_name} = __locals[:#{var_name}]", __b
|
11
|
+
end
|
12
|
+
|
13
|
+
@result = __erb_template.result(__b)
|
14
|
+
end
|
15
|
+
|
16
|
+
def self.render(erb_template, locals, &block)
|
17
|
+
new(erb_template, locals, &block).result
|
18
|
+
end
|
19
|
+
|
20
|
+
attr_reader :result
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
require 'cgi'
|
2
|
+
|
3
|
+
module Quarto
|
4
|
+
module UrlHelper
|
5
|
+
# Generates an absolute URL, using the <tt>:site_root</tt> config value. (To change <tt>:site_root</tt>,
|
6
|
+
# put something like this in <tt>generate.rb</tt>:
|
7
|
+
# config(:site_root, 'http://your_domain.com/whatever')
|
8
|
+
def abs_url(str)
|
9
|
+
"#{Quarto.config[:site_root]}#{str}"
|
10
|
+
end
|
11
|
+
|
12
|
+
# Replaces spaces with dashes and deletes special characters.
|
13
|
+
def urlize(str)
|
14
|
+
str.to_s.gsub(/[^ a-zA-Z0-9_-]/, '').tr(' ', '-')
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
data/lib/quarto.rb
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
$: << File.expand_path(File.dirname(__FILE__))
|
2
|
+
|
3
|
+
require 'rubygems'
|
4
|
+
require 'rexml/document'
|
5
|
+
require 'erb'
|
6
|
+
require 'active_support/inflector'
|
7
|
+
|
8
|
+
require 'quarto/config'
|
9
|
+
require 'quarto/xml_doc'
|
10
|
+
require 'quarto/inheritable_attributes'
|
11
|
+
require 'quarto/url_helper'
|
12
|
+
require 'quarto/element_wrapper'
|
13
|
+
require 'quarto/children'
|
14
|
+
require 'quarto/rendering'
|
15
|
+
require 'quarto/generator'
|
16
|
+
require 'quarto/init_project'
|
data/quarto.gemspec
ADDED
@@ -0,0 +1,62 @@
|
|
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
|
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: 0.3.
|
4
|
+
version: 0.3.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Jarrett Colby
|
@@ -9,7 +9,7 @@ autorequire:
|
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
11
|
|
12
|
-
date: 2009-06-
|
12
|
+
date: 2009-06-05 00:00:00 -07:00
|
13
13
|
default_executable: quarto
|
14
14
|
dependencies: []
|
15
15
|
|
@@ -23,6 +23,20 @@ extra_rdoc_files:
|
|
23
23
|
- README
|
24
24
|
files:
|
25
25
|
- README
|
26
|
+
- Rakefile
|
27
|
+
- VERSION
|
28
|
+
- bin/quarto
|
29
|
+
- lib/quarto.rb
|
30
|
+
- lib/quarto/children.rb
|
31
|
+
- lib/quarto/config.rb
|
32
|
+
- lib/quarto/element_wrapper.rb
|
33
|
+
- lib/quarto/generator.rb
|
34
|
+
- lib/quarto/inheritable_attributes.rb
|
35
|
+
- lib/quarto/init_project.rb
|
36
|
+
- lib/quarto/rendering.rb
|
37
|
+
- lib/quarto/url_helper.rb
|
38
|
+
- lib/quarto/xml_doc.rb
|
39
|
+
- quarto.gemspec
|
26
40
|
has_rdoc: true
|
27
41
|
homepage: http://github.com/jarrett/quarto
|
28
42
|
post_install_message:
|