quarto 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,18 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
18
+ /scratch*
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in quarto.gemspec
4
+ gemspec
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2013 Avdi Grimm
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,53 @@
1
+ # Quarto
2
+
3
+ Yet another ebook generation toolchain.
4
+
5
+ A "Quarto" is a bookbinding term, and this is my fourth attempt at an ebook toolchain.
6
+
7
+ ## Requirements
8
+
9
+ Quarto depends on several external programs which you will need to install before using it.
10
+
11
+ - Pandoc
12
+ - Pygments
13
+ - xmllint
14
+ - PrinceXML
15
+
16
+ ## Installation/Usage
17
+
18
+ 1. Install the gem (`gem install quarto`)
19
+ 2. Create a `Rakefile` in your book project root.
20
+ 3. Add `require "quarto/tasks"` to the top of the Rakefile.
21
+ 4. Run `rake -T` to see the available tasks.
22
+
23
+ ## Concepts
24
+
25
+ Quarto is a set of Rake tasks backed up by a Ruby library, which in turn relies heavily on Nokogiri and a number of external tools.
26
+
27
+ ### XHTML5 is king
28
+
29
+ The central philosophy of Quarto is to do as much work as possible with XHTML5 files. All input formats (e.g. Markdown) are first converted to XHTML5 before any other work is done. Then various transformations occur. Finally, at the end of the line, an XHTML5 "master" file is converted to various deliverable formats such as PDF. The reason for this philosophy is simple: Nokogiri makes it really easy to perform arbitrary semantic transformations on XHTML documents, without a lot of tedious mucking about with text munging. The more of the work that is done on DOM object trees, the easier it is to do.
30
+
31
+ ### The assembly line
32
+
33
+ Quarto is a set of Rake tasks, so execution normally starts with an end product and works backwards through the dependency chain to figure out what needs to be done to produce that product. However, it's probably easier to understand everything Quarto does by viewing it as an assembly line starting with source files and ending with deliverables. Here are the steps along the way.
34
+
35
+ Note that all files generated by Quarto are placed in a `build` subdirectory of your project's root. It will be created if needed.
36
+
37
+ 1. **Source files**. These are manuscript files in supported source formats (currently only Markdown). They might be in the root of your project, or in subdirectories.
38
+ 2. Source files are *exported* into **export files** in `build/export`. Export files are HTML, produced using whatever tool is appropriate for the input format. E.g. `pandoc` is used to export Markdown source files to HTML equivalents.
39
+ 3. The source files are then normalized into XHTML **section files** (in `build/sections`). During this normalization process any idiosyncrasies in the HTML produced by the export tool are dealt with.
40
+ 4. A **spine file** is generated. This XHTML file will be used to tie together all of the section files. The body of this file contains references to (but not the content of) all of the section files. It also contains stylesheets and other metadata.
41
+ 5. The spine file is then expanded into an XHTML **codex file**. This file contains the body content of all of the section files. Only body content is taken from the section files, everything else is ignored. From this point forward, all operations will be done on monolithic files rather than on partial files corresponding to the original sources.
42
+ 6. The spine file is searched for source code listings. Each listing is extracted out as text into a **listing file** (in `build/listings`). Listing files are named based on the SHA1 of the listing and its language, e.g. `build/listings/3361c5f02e08bd44bde2d42633a2c9be201f7ec4.rb`. Using the SHA1 in naming is an optimization which ensures that only changed code listings ever need to be re-highlighted (see the next step). During this step a **skeleton file** is also created. This XHTML file mirrors the codex file, except that all of the source code listings have been replaced with references to highlight files (see next step).
43
+ 7. The next step is to perform source code highlighting on the listing files, using Pygments. This produces **highlight files**, which are HTML files in the `build/highlights` directory. They named based on the SHA1 of the corresponding code listing.
44
+ 8. The skeleton file and the highlights file are then stitched back together into a **master file**. This XHTML file is the "gold standard" from which all deliverables will be generated.
45
+ 9. **Deliverable files** suitable for distributing to end-users, such as PDF or Epub files, are produced using the master file.
46
+
47
+ ## Contributing
48
+
49
+ 1. Fork it
50
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
51
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
52
+ 4. Push to the branch (`git push origin my-new-feature`)
53
+ 5. Create new Pull Request
@@ -0,0 +1,6 @@
1
+ require "bundler/gem_tasks"
2
+ require 'rspec/core/rake_task'
3
+
4
+ RSpec::Core::RakeTask.new(:spec)
5
+
6
+ task :default => :spec
@@ -0,0 +1,289 @@
1
+ require "quarto/version"
2
+ require 'rake'
3
+ require 'nokogiri'
4
+ require 'open3'
5
+ require 'digest/sha1'
6
+
7
+ module Quarto
8
+ include Rake::DSL
9
+
10
+ def self.configure
11
+ yield self
12
+ end
13
+
14
+ def self.stylesheets
15
+ @stylesheets ||= [code_stylesheet]
16
+ end
17
+
18
+ def configuration
19
+ Quarto
20
+ end
21
+
22
+ module_function
23
+
24
+ XINCLUDE_NS = "http://www.w3.org/2001/XInclude"
25
+
26
+ EXTENSIONS_TO_SOURCE_FORMATS = {
27
+ "md" => "markdown",
28
+ "markdown" => "markdown",
29
+ "org" => "orgmode"
30
+ }
31
+
32
+ SECTION_TEMPLATE = <<-EOF
33
+ <!DOCTYPE html>
34
+ <html xmlns="http://www.w3.org/1999/xhtml">
35
+ <head>
36
+ <title></title>
37
+ </head>
38
+ <body>
39
+ </body>
40
+ </html>
41
+ EOF
42
+
43
+ SPINE_TEMPLATE = <<-EOF
44
+ <!DOCTYPE html>
45
+ <html xmlns="http://www.w3.org/1999/xhtml">
46
+ <head>
47
+ <title>Untitled Book</title>
48
+ </head>
49
+ <body>
50
+ </body>
51
+ </html>
52
+ EOF
53
+
54
+ def build_dir
55
+ "build"
56
+ end
57
+
58
+ def source_exts
59
+ EXTENSIONS_TO_SOURCE_FORMATS.keys
60
+ end
61
+
62
+ def format_of_source_file(source_file)
63
+ ext = source_file.pathmap("%x")[1..-1]
64
+ EXTENSIONS_TO_SOURCE_FORMATS.fetch(ext)
65
+ end
66
+
67
+ def source_files
68
+ FileList["**/*.{#{source_exts.join(',')}}"]
69
+ end
70
+
71
+ def export_dir
72
+ "build/exports"
73
+ end
74
+
75
+ def export_files
76
+ source_files.pathmap("#{export_dir}/%p").ext('.html')
77
+ end
78
+
79
+ def source_for_export_file(export_file)
80
+ base = export_file.sub(/^#{export_dir}\//,'').ext('')
81
+ pattern = "#{base}.{#{source_exts.join(',')}}"
82
+ FileList[pattern].first
83
+ end
84
+
85
+ def export_command_for(source_file, export_file)
86
+ %W[pandoc --no-highlight -w html5 -o #{export_file} #{source_file}]
87
+ end
88
+
89
+ def section_dir
90
+ "build/sections"
91
+ end
92
+
93
+ def section_files
94
+ export_files.pathmap("%{^#{export_dir},#{section_dir}}X%{html,xhtml}x")
95
+ end
96
+
97
+ def export_for_section_file(section_file)
98
+ section_file.pathmap("%{^#{section_dir},#{export_dir}}X%{xhtml,html}x")
99
+ end
100
+
101
+ def normalize_export(export_file, section_file, format)
102
+ format ||= "NO_FORMAT_GIVEN"
103
+ send("normalize_#{format}_export", export_file, section_file)
104
+ end
105
+
106
+ def normalize_markdown_export(export_file, section_file)
107
+ puts "normalize #{export_file} to #{section_file}"
108
+ doc = open(export_file) do |f|
109
+ Nokogiri::HTML(f)
110
+ end
111
+ normal_doc = Nokogiri::XML.parse(SECTION_TEMPLATE)
112
+ normal_doc.at_css("body").replace(doc.at_css("body"))
113
+ normal_doc.at_css("title").content = export_file.pathmap("%n")
114
+ open(section_file, "w") do |f|
115
+ format_xml(f) do |pipe_input|
116
+ normal_doc.write_xml_to(pipe_input)
117
+ end
118
+ end
119
+ end
120
+
121
+ def source_list_file
122
+ "build/sources"
123
+ end
124
+
125
+ def code_stylesheet
126
+ "#{build_dir}/code.css"
127
+ end
128
+
129
+ def spine_file
130
+ "build/spine.xhtml"
131
+ end
132
+
133
+ def create_spine_file(spine_file, section_files, options={})
134
+ options = {stylesheets: configuration.stylesheets}.merge(options)
135
+ puts "create #{spine_file}"
136
+ doc = Nokogiri::XML.parse(SPINE_TEMPLATE)
137
+ doc.root.add_namespace("xi", "http://www.w3.org/2001/XInclude")
138
+ head_elt = doc.root.at_css("head")
139
+ stylesheets = Array(options[:stylesheets])
140
+ stylesheets.each do |stylesheet|
141
+ head_elt.add_child(
142
+ doc.create_element(
143
+ "style",
144
+ File.read(stylesheet)))
145
+ end
146
+ section_files.each do |section_file|
147
+ doc.root["xml:base"] = ".."
148
+ body = doc.root.at_css("body")
149
+ body.add_child(doc.create_element("xi:include") do |inc_elt|
150
+ inc_elt["href"] = section_file
151
+ inc_elt["xpointer"] = "xmlns(ns=http://www.w3.org/1999/xhtml)xpointer(//ns:body/*)"
152
+ inc_elt.add_child(doc.create_element("xi:fallback") do |fallback_elt|
153
+ fallback_elt.add_child(doc.create_element("p",
154
+ "[Missing section: #{section_file}]"))
155
+ end)
156
+ end)
157
+ end
158
+ open(spine_file, 'w') do |f|
159
+ format_xml(f) do |format_input|
160
+ doc.write_to(format_input)
161
+ end
162
+ end
163
+ end
164
+
165
+ def codex_file
166
+ "build/codex.xhtml"
167
+ end
168
+
169
+ def create_codex_file(codex_file, spine_file)
170
+ expand_xinclude(codex_file, spine_file)
171
+ end
172
+
173
+ def skeleton_file
174
+ "#{build_dir}/skeleton.xhtml"
175
+ end
176
+
177
+ def listings_dir
178
+ "#{build_dir}/listings"
179
+ end
180
+
181
+ def create_skeleton_file(skeleton_file, codex_file)
182
+ puts "scan #{codex_file} for source code listings"
183
+ skel_doc = open(codex_file) do |f|
184
+ Nokogiri::XML(f)
185
+ end
186
+ skel_doc.css("pre.sourceCode").each_with_index do |pre_elt, i|
187
+ lang = pre_elt["class"].split[1]
188
+ ext = {"ruby" => "rb"}.fetch(lang){ lang.downcase }
189
+ code = pre_elt.at_css("code").text
190
+ digest = Digest::SHA1.hexdigest(code)
191
+ listing_path = "#{listings_dir}/#{digest}.#{ext}"
192
+ puts "extract listing #{i} to #{listing_path}"
193
+ open(listing_path, 'w') do |f|
194
+ f.write(strip_listing(code))
195
+ end
196
+ highlight_path = "#{highlights_dir}/#{digest}.html"
197
+ inc_elt = skel_doc.create_element("xi:include") do |elt|
198
+ elt["href"] = highlight_path
199
+ elt.add_child(
200
+ "<xi:fallback>"\
201
+ "<p>[Missing code listing: #{highlight_path}]</p>"\
202
+ "</xi:fallback>")
203
+ end
204
+ pre_elt.replace(inc_elt)
205
+ end
206
+ puts "create #{skeleton_file}"
207
+ open(skeleton_file, "w") do |f|
208
+ format_xml(f) do |format_input|
209
+ skel_doc.write_xml_to(format_input)
210
+ end
211
+ end
212
+ end
213
+
214
+ def highlights_dir
215
+ "#{build_dir}/highlights"
216
+ end
217
+
218
+ def highlights_needed_by(skeleton_file)
219
+ doc = open(skeleton_file) do |f|
220
+ Nokogiri::XML(f)
221
+ end
222
+ doc.xpath("//xi:include", "xi" => XINCLUDE_NS).map{|e| e["href"]}
223
+ end
224
+
225
+ def listing_for_highlight_file(highlight_file)
226
+ base = highlight_file.pathmap("%n")
227
+ FileList["#{listings_dir}/#{base}.*"].first
228
+ end
229
+
230
+ # Strip extraneous whitespace from around a code listing
231
+ def strip_listing(code)
232
+ code.gsub!(/\t/, " ")
233
+ lines = code.split("\n")
234
+ first_code_line = lines.index{|l| l =~ /\S/}
235
+ last_code_line = lines.rindex{|l| l =~ /\S/}
236
+ lines = lines[first_code_line..last_code_line]
237
+ indent = lines.map{|l| l.index(/[^ ]/)}.min
238
+ lines.map{|l| l.slice(indent..-1)}.join("\n") + "\n"
239
+ end
240
+
241
+ def master_file
242
+ "#{build_dir}/master.xhtml"
243
+ end
244
+
245
+ def create_master_file(master_file, skeleton_file)
246
+ expand_xinclude(master_file, skeleton_file, format: false)
247
+ end
248
+
249
+ def deliverable_dir
250
+ "#{build_dir}/deliverables"
251
+ end
252
+
253
+ def deliverable_files
254
+ [pdf_file]
255
+ end
256
+
257
+ def pdf_file
258
+ "#{deliverable_dir}/book.pdf"
259
+ end
260
+
261
+ private
262
+
263
+ def format_xml(output_io)
264
+ Open3.popen2(*%W[xmllint --format --xmlout -]) do
265
+ |stdin, stdout, wait_thr|
266
+ yield(stdin)
267
+ stdin.close
268
+ IO.copy_stream(stdout, output_io)
269
+ end
270
+ end
271
+
272
+ def expand_xinclude(output_file, input_file, options={})
273
+ options = {format: true}.merge(options)
274
+ puts "expand #{input_file} to #{output_file}"
275
+ cleanup_args = %W[--nsclean --xmlout]
276
+ if options[:format]
277
+ cleanup_args << "--format"
278
+ end
279
+ Open3.pipeline_r(
280
+ %W[xmllint --xinclude --xmlout #{input_file}],
281
+ # In order to clean up extraneous namespace declarations we need a second
282
+ # xmllint process
283
+ ["xmllint", *cleanup_args, "-"]) do |output, wait_thr|
284
+ open(output_file, 'w') do |f|
285
+ IO.copy_stream(output, f)
286
+ end
287
+ end
288
+ end
289
+ end
@@ -0,0 +1,85 @@
1
+ require 'quarto'
2
+
3
+ include Quarto
4
+
5
+ desc "Export from source formats to HTML"
6
+ task :export => [*export_files]
7
+
8
+ desc "Generate normalized XHTML versions of exports"
9
+ task :sections => [*section_files]
10
+
11
+ desc "Build a single XHTML file codex combining all sections"
12
+ task :codex => codex_file
13
+
14
+ desc "Strip out code listings for highlighting"
15
+ task :skeleton => skeleton_file
16
+
17
+ desc "Create master file suitable for conversion into deliverable formats"
18
+ task :master => master_file
19
+
20
+ desc "Create finished documents suitable for end-users"
21
+ task :deliverables => deliverable_files
22
+
23
+ desc "Perform source-code highlighting"
24
+ task :highlight => [skeleton_file] do |t|
25
+ highlights_needed = highlights_needed_by(skeleton_file)
26
+ missing_highlights = highlights_needed - FileList["#{highlights_dir}/*.html"]
27
+ sub_task = Rake::MultiTask.new("highlight_dynamic", Rake.application)
28
+ sub_task.enhance(missing_highlights)
29
+ sub_task.invoke
30
+ end
31
+
32
+ directory build_dir
33
+ directory export_dir => [build_dir]
34
+
35
+ export_files.each do |export_file|
36
+ file export_file => [export_dir, source_for_export_file(export_file)] do |t|
37
+ source_file = source_for_export_file(export_file)
38
+ mkdir_p export_file.pathmap("%d")
39
+ sh *export_command_for(source_file, export_file)
40
+ end
41
+ end
42
+
43
+ section_files.each do |section_file|
44
+ file section_file => export_for_section_file(section_file) do |t|
45
+ export_file = export_for_section_file(section_file)
46
+ source_file = source_for_export_file(export_file)
47
+ source_format = format_of_source_file(source_file)
48
+ mkdir_p section_file.pathmap("%d")
49
+ normalize_export(export_file, section_file, source_format)
50
+ end
51
+ end
52
+
53
+ file code_stylesheet do |t|
54
+ sh "pygmentize -S colorful -f html > #{t.name}"
55
+ end
56
+
57
+ file spine_file => [build_dir, code_stylesheet] do |t|
58
+ create_spine_file(t.name, section_files, stylesheets: Quarto.stylesheets)
59
+ end
60
+
61
+ file codex_file => [spine_file, *section_files] do |t|
62
+ create_codex_file(t.name, spine_file)
63
+ end
64
+
65
+ directory listings_dir
66
+
67
+ file skeleton_file => [codex_file, listings_dir] do |t|
68
+ create_skeleton_file(t.name, codex_file)
69
+ end
70
+
71
+ rule /^#{highlights_dir}\/[[:xdigit:]]+\.html$/ =>
72
+ [->(highlight_file){listing_for_highlight_file(highlight_file)}] do |t|
73
+ dir = t.name.pathmap("%d")
74
+ mkdir_p dir unless File.exist?(dir)
75
+ sh *%W[pygmentize -o #{t.name} #{t.source}]
76
+ end
77
+
78
+ file master_file => [skeleton_file, :highlight] do |t|
79
+ create_master_file(t.name, skeleton_file)
80
+ end
81
+
82
+ file pdf_file => [master_file] do |t|
83
+ mkdir_p t.name.pathmap("%d")
84
+ sh *%W[prince #{master_file} -o #{t.name}]
85
+ end
@@ -0,0 +1,3 @@
1
+ module Quarto
2
+ VERSION = "0.0.1"
3
+ end
@@ -0,0 +1,29 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'quarto/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "quarto"
8
+ spec.version = Quarto::VERSION
9
+ spec.authors = ["Avdi Grimm"]
10
+ spec.email = ["avdi@avdi.org"]
11
+ spec.description = %q{Yet another ebook publishing toolchain}
12
+ spec.summary = %q{Yet another ebook publishing toolchain}
13
+ spec.homepage = ""
14
+ spec.license = "MIT"
15
+
16
+ spec.files = `git ls-files`.split($/)
17
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
+ spec.require_paths = ["lib"]
20
+
21
+ spec.add_dependency "nokogiri", "~> 1.6"
22
+
23
+ spec.add_development_dependency "bundler", "~> 1.3"
24
+ spec.add_development_dependency "rake", "~> 10.0"
25
+ spec.add_development_dependency "rspec", "~> 2.14"
26
+ spec.add_development_dependency "rspec-given", "~> 3.1"
27
+ spec.add_development_dependency "test-construct", "~> 1.2"
28
+ spec.add_development_dependency "debugger"
29
+ end
@@ -0,0 +1,235 @@
1
+ require 'spec_helper'
2
+ require 'quarto'
3
+
4
+ describe Quarto do
5
+ include Quarto
6
+ describe 'figuring out sources, exports, and section paths' do
7
+ Given {
8
+ @construct.file "ch1.md"
9
+ @construct.file "Rakefile"
10
+ @construct.directory "subdir" do |d|
11
+ d.file "ch2.markdown"
12
+ d.file "ch3.org"
13
+ d.file "README.txt"
14
+ end
15
+ }
16
+
17
+ Then {
18
+ source_files.should == [
19
+ "ch1.md",
20
+ "subdir/ch2.markdown",
21
+ "subdir/ch3.org"
22
+ ]
23
+ }
24
+
25
+ And {
26
+ export_files.should == [
27
+ "build/exports/ch1.html",
28
+ "build/exports/subdir/ch2.html",
29
+ "build/exports/subdir/ch3.html",
30
+ ]
31
+ }
32
+
33
+ And {
34
+ section_files.should == [
35
+ "build/sections/ch1.xhtml",
36
+ "build/sections/subdir/ch2.xhtml",
37
+ "build/sections/subdir/ch3.xhtml",
38
+ ]
39
+ }
40
+
41
+ And {
42
+ source_for_export_file("build/exports/ch1.html") == "ch1.md"
43
+ }
44
+
45
+ And {
46
+ source_for_export_file("build/exports/subdir/ch3.org") == "subdir/ch3.org"
47
+ }
48
+
49
+ And {
50
+ export_for_section_file("build/sections/subdir/ch3.xhtml") ==
51
+ "build/exports/subdir/ch3.html"
52
+ }
53
+
54
+ end
55
+
56
+ describe 'export commands' do
57
+ Given(:command) {
58
+ export_command_for("ch1.md", "ch1.html")
59
+ }
60
+
61
+ Then { command == %W[pandoc --no-highlight -w html5 -o ch1.html ch1.md] }
62
+ end
63
+
64
+ describe 'source formats' do
65
+ it 'recognizes .md and .markdown as Makdown' do
66
+ expect(format_of_source_file("foo.md")).to eq("markdown")
67
+ expect(format_of_source_file("foo.markdown")).to eq("markdown")
68
+ end
69
+
70
+ it 'recognizes .org as OrgMode' do
71
+ expect(format_of_source_file("foo.org")).to eq("orgmode")
72
+ end
73
+ end
74
+
75
+ describe 'normalizing markdown exports' do
76
+ Given {
77
+ @construct.file "export.html", <<END
78
+ <h1>This is the title</h1>
79
+ <p>This is the content</p>
80
+ END
81
+ }
82
+
83
+ Given(:result_doc) {
84
+ open("section.xhtml") do |f|
85
+ Nokogiri::XML(f)
86
+ end
87
+ }
88
+
89
+ When { normalize_export("export.html", "section.xhtml", "markdown") }
90
+ Then {
91
+ expect(result_doc.to_s).to eq(<<END)
92
+ <?xml version="1.0"?>
93
+ <!DOCTYPE html>
94
+ <html xmlns="http://www.w3.org/1999/xhtml">
95
+ <head>
96
+ <title>export</title>
97
+ </head>
98
+ <body>
99
+ <h1>This is the title</h1>
100
+ <p>This is the content</p>
101
+ </body>
102
+ </html>
103
+ END
104
+ }
105
+ end
106
+
107
+ describe 'creating a spine file' do
108
+ Given(:sources) {
109
+ %W[ch1.xhtml ch2.xhtml]
110
+ }
111
+
112
+ When{ create_spine_file("spine.xhtml", sources, stylesheets: []) }
113
+ Then {
114
+ expect(File.read("spine.xhtml")).to eq(<<END)
115
+ <?xml version="1.0"?>
116
+ <!DOCTYPE html>
117
+ <html xmlns="http://www.w3.org/1999/xhtml" xmlns:xi="http://www.w3.org/2001/XInclude" xml:base="..">
118
+ <head>
119
+ <title>Untitled Book</title>
120
+ </head>
121
+ <body>
122
+ <xi:include href="ch1.xhtml" xpointer="xmlns(ns=http://www.w3.org/1999/xhtml)xpointer(//ns:body/*)">
123
+ <xi:fallback>
124
+ <p>[Missing section: ch1.xhtml]</p>
125
+ </xi:fallback>
126
+ </xi:include>
127
+ <xi:include href="ch2.xhtml" xpointer="xmlns(ns=http://www.w3.org/1999/xhtml)xpointer(//ns:body/*)">
128
+ <xi:fallback>
129
+ <p>[Missing section: ch2.xhtml]</p>
130
+ </xi:fallback>
131
+ </xi:include>
132
+ </body>
133
+ </html>
134
+ END
135
+ }
136
+ end
137
+
138
+
139
+ describe 'creating a codex file' do
140
+ Given {
141
+ @construct.file("ch1.xhtml", <<EOF)
142
+ <?xml version="1.0"?>
143
+ <!DOCTYPE html>
144
+ <html xmlns="http://www.w3.org/1999/xhtml">
145
+ <head>
146
+ <title>ch1</title>
147
+ </head>
148
+ <body>
149
+ <p>This is chapter 1</p>
150
+ <p>Also chapter 1</p>
151
+ </body>
152
+ </html>
153
+ EOF
154
+ @construct.file("ch2.xhtml", <<EOF)
155
+ <?xml version="1.0"?>
156
+ <!DOCTYPE html>
157
+ <html xmlns="http://www.w3.org/1999/xhtml">
158
+ <head>
159
+ <title>ch1</title>
160
+ </head>
161
+ <body>
162
+ <p>This is chapter 2</p>
163
+ </body>
164
+ </html>
165
+ EOF
166
+ @construct.file("spine.xhtml", <<EOF)
167
+ <?xml version="1.0"?>
168
+ <!DOCTYPE html>
169
+ <html xmlns="http://www.w3.org/1999/xhtml" xmlns:xi="http://www.w3.org/2001/XInclude">
170
+ <head>
171
+ <title>Untitled Book</title>
172
+ </head>
173
+ <body>
174
+ <xi:include href="ch1.xhtml" xpointer="xmlns(ns=http://www.w3.org/1999/xhtml)xpointer(//ns:body/*)">
175
+ <xi:fallback>
176
+ <p>[Missing section: ch1.xhtml]</p>
177
+ </xi:fallback>
178
+ </xi:include>
179
+ <xi:include href="ch2.xhtml" xpointer="xmlns(ns=http://www.w3.org/1999/xhtml)xpointer(//ns:body/*)">
180
+ <xi:fallback>
181
+ <p>[Missing section: ch2.xhtml]</p>
182
+ </xi:fallback>
183
+ </xi:include>
184
+ <xi:include href="ch3.xhtml" xpointer="xmlns(ns=http://www.w3.org/1999/xhtml)xpointer(//ns:body/*)">
185
+ <xi:fallback>
186
+ <p>[Missing section: ch3.xhtml]</p>
187
+ </xi:fallback>
188
+ </xi:include>
189
+ </body>
190
+ </html>
191
+ EOF
192
+ }
193
+
194
+ When{ create_codex_file("codex.xhtml", "spine.xhtml") }
195
+ Then {
196
+ expect(File.read("codex.xhtml")).to eq(<<END)
197
+ <?xml version="1.0"?>
198
+ <!DOCTYPE html>
199
+ <html xmlns="http://www.w3.org/1999/xhtml" xmlns:xi="http://www.w3.org/2001/XInclude">
200
+ <head>
201
+ <title>Untitled Book</title>
202
+ </head>
203
+ <body>
204
+ <p>This is chapter 1</p>
205
+ <p>Also chapter 1</p>
206
+ <p>This is chapter 2</p>
207
+ <p>[Missing section: ch3.xhtml]</p>
208
+ </body>
209
+ </html>
210
+ END
211
+ }
212
+ end
213
+
214
+ describe "stripping source code" do
215
+ Given(:code) {
216
+ code = <<END
217
+
218
+
219
+ puts "hello, world
220
+ if true
221
+ puts "goodbye, world
222
+ end
223
+
224
+ END
225
+ }
226
+ Then{
227
+ expect(strip_listing(code)).to eq(<<END)
228
+ puts "hello, world
229
+ if true
230
+ puts "goodbye, world
231
+ end
232
+ END
233
+ }
234
+ end
235
+ end
@@ -0,0 +1,38 @@
1
+ $LOAD_PATH.unshift(File.expand_path('../../lib', __FILE__))
2
+
3
+ begin
4
+ # use `bundle install --standalone' to get this...
5
+ require_relative '../bundle/bundler/setup'
6
+ rescue LoadError
7
+ # fall back to regular bundler if the developer hasn't bundled standalone
8
+ require 'bundler'
9
+ Bundler.setup
10
+ end
11
+
12
+ require 'rspec/given'
13
+ require 'construct'
14
+
15
+ module TaskSpecHelpers
16
+ def run(command)
17
+ @output, @status = Open3.capture2e(command)
18
+ unless @status.success?
19
+ raise "Command `#{command}` failed with output:\n#{@output}"
20
+ end
21
+ end
22
+
23
+ def contents(filename)
24
+ File.read(filename)
25
+ end
26
+ end
27
+
28
+ RSpec.configure do |config|
29
+ config.include Construct::Helpers
30
+ config.include TaskSpecHelpers, task: true
31
+
32
+ config.around :each do |example|
33
+ within_construct do |c|
34
+ @construct = c
35
+ example.run
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,49 @@
1
+ require 'spec_helper'
2
+ require 'open3'
3
+
4
+ describe 'sections task', task: true do
5
+ Given {
6
+ @construct.file "Rakefile", <<END
7
+ require 'quarto/tasks'
8
+ Quarto.configure do |config|
9
+ config.stylesheets.clear
10
+ end
11
+ END
12
+ @construct.file "intro.md", <<END
13
+ # Hello, world
14
+
15
+ This is the intro
16
+ END
17
+ @construct.directory "section1" do |d|
18
+ d.file "ch1.md", <<END
19
+ # Hello again
20
+
21
+ This is chapter 1
22
+ END
23
+ end
24
+ }
25
+
26
+ When {
27
+ run "rake codex"
28
+ }
29
+
30
+ Then {
31
+ expect(contents("build/codex.xhtml")).to eq(<<END)
32
+ <?xml version="1.0"?>
33
+ <!DOCTYPE html>
34
+ <html xmlns="http://www.w3.org/1999/xhtml" xmlns:xi="http://www.w3.org/2001/XInclude" xml:base="..">
35
+ <head>
36
+ <title>Untitled Book</title>
37
+ </head>
38
+ <body>
39
+ <h1 id="hello-world" xml:base="build/sections/intro.xhtml">Hello, world</h1>
40
+ <p xml:base="build/sections/intro.xhtml">This is the intro</p>
41
+ <h1 id="hello-again" xml:base="build/sections/section1/ch1.xhtml">Hello again</h1>
42
+ <p xml:base="build/sections/section1/ch1.xhtml">This is chapter 1</p>
43
+ </body>
44
+ </html>
45
+ END
46
+ }
47
+
48
+
49
+ end
@@ -0,0 +1,40 @@
1
+ require 'spec_helper'
2
+ require 'open3'
3
+
4
+ describe 'export task', task: true do
5
+ Given {
6
+ @construct.file "Rakefile", <<END
7
+ require 'quarto/tasks'
8
+
9
+ END
10
+ @construct.file "intro.md", <<END
11
+ # Hello, world
12
+
13
+ This is the intro
14
+ END
15
+ @construct.directory "section1" do |d|
16
+ d.file "ch1.md", <<END
17
+ # Hello again
18
+
19
+ This is chapter 1
20
+ END
21
+ end
22
+ }
23
+
24
+ When {
25
+ run "rake export"
26
+ }
27
+
28
+ Then {
29
+ expect(contents("build/exports/intro.html")).to eq(<<END)
30
+ <h1 id="hello-world">Hello, world</h1>
31
+ <p>This is the intro</p>
32
+ END
33
+ }
34
+ And {
35
+ expect(contents("build/exports/section1/ch1.html")).to eq(<<END)
36
+ <h1 id="hello-again">Hello again</h1>
37
+ <p>This is chapter 1</p>
38
+ END
39
+ }
40
+ end
@@ -0,0 +1,42 @@
1
+ require 'spec_helper'
2
+ require 'open3'
3
+
4
+ describe 'highlight task', task: true do
5
+ Given {
6
+ @construct.file "Rakefile", <<END
7
+ require 'quarto/tasks'
8
+ END
9
+ @construct.file "ch1.md", <<END
10
+ ```ruby
11
+ puts "hello, world"
12
+ ```
13
+ END
14
+ @construct.file "ch2.md", <<END
15
+ ```c
16
+ int main(int argc, char** argv) {
17
+ printf("Hello, world\n")
18
+ }
19
+ ```
20
+ END
21
+ }
22
+
23
+ When {
24
+ run "rake highlight"
25
+ }
26
+
27
+ Then {
28
+ expect(contents("build/highlights/3361c5f02e08bd44bde2d42633a2c9be201f7ec4.html")).to eq(<<END)
29
+ <div class="highlight"><pre><span class="nb">puts</span> <span class="s2">&quot;hello, world&quot;</span>
30
+ </pre></div>
31
+ END
32
+ }
33
+ And {
34
+ expect(contents("build/highlights/e7b17ea0eeebbd00d08674cf9070d287e24dc68e.html")).to eq(<<END)
35
+ <div class="highlight"><pre><span class="kt">int</span> <span class="nf">main</span><span class="p">(</span><span class="kt">int</span> <span class="n">argc</span><span class="p">,</span> <span class="kt">char</span><span class="o">**</span> <span class="n">argv</span><span class="p">)</span> <span class="p">{</span>
36
+ <span class="n">printf</span><span class="p">(</span><span class="s">&quot;Hello, world</span>
37
+ <span class="s">&quot;)</span>
38
+ <span class="p">}</span>
39
+ </pre></div>
40
+ END
41
+ }
42
+ end
@@ -0,0 +1,58 @@
1
+ require 'spec_helper'
2
+ require 'open3'
3
+
4
+ describe 'master task', task: true do
5
+ Given {
6
+ @construct.file "Rakefile", <<END
7
+ require 'quarto/tasks'
8
+ Quarto.configure do |config|
9
+ config.stylesheets.clear
10
+ end
11
+ END
12
+ @construct.file "ch1.md", <<END
13
+ <p>Before listing 0</p>
14
+ ```ruby
15
+ puts "hello, world"
16
+ ```
17
+ <p>After listing 0</p>
18
+ END
19
+ @construct.file "ch2.md", <<END
20
+ ```c
21
+ int main(int argc, char** argv) {
22
+ printf("Hello, world\n")
23
+ }
24
+ ```
25
+ END
26
+ }
27
+
28
+ When {
29
+ run "rake master"
30
+ }
31
+
32
+ Then {
33
+ expect(contents("build/master.xhtml")).to eq(<<END)
34
+ <?xml version="1.0"?>
35
+ <!DOCTYPE html>
36
+ <html xmlns="http://www.w3.org/1999/xhtml" xmlns:xi="http://www.w3.org/2001/XInclude" xml:base="..">
37
+ <head>
38
+ <title>Untitled Book</title>
39
+ </head>
40
+ <body>
41
+ <p xml:base="build/sections/ch1.xhtml">
42
+ Before listing 0
43
+ </p>
44
+ <div class="highlight" xml:base="build/highlights/3361c5f02e08bd44bde2d42633a2c9be201f7ec4.html"><pre><span class="nb">puts</span> <span class="s2">"hello, world"</span>
45
+ </pre></div>
46
+ <p xml:base="build/sections/ch1.xhtml">
47
+ After listing 0
48
+ </p>
49
+ <div class="highlight" xml:base="build/highlights/e7b17ea0eeebbd00d08674cf9070d287e24dc68e.html"><pre><span class="kt">int</span> <span class="nf">main</span><span class="p">(</span><span class="kt">int</span> <span class="n">argc</span><span class="p">,</span> <span class="kt">char</span><span class="o">**</span> <span class="n">argv</span><span class="p">)</span> <span class="p">{</span>
50
+ <span class="n">printf</span><span class="p">(</span><span class="s">"Hello, world</span>
51
+ <span class="s">")</span>
52
+ <span class="p">}</span>
53
+ </pre></div>
54
+ </body>
55
+ </html>
56
+ END
57
+ }
58
+ end
@@ -0,0 +1,59 @@
1
+ require 'spec_helper'
2
+ require 'open3'
3
+
4
+ describe 'sections task', task: true do
5
+ Given {
6
+ @construct.file "Rakefile", <<END
7
+ require 'quarto/tasks'
8
+ END
9
+ @construct.file "intro.md", <<END
10
+ # Hello, world
11
+
12
+ This is the intro
13
+ END
14
+ @construct.directory "section1" do |d|
15
+ d.file "ch1.md", <<END
16
+ # Hello again
17
+
18
+ This is chapter 1
19
+ END
20
+ end
21
+ }
22
+
23
+ When {
24
+ run "rake sections"
25
+ }
26
+
27
+ Then {
28
+ expect(contents("build/sections/intro.xhtml")).to eq(<<END)
29
+ <?xml version="1.0"?>
30
+ <!DOCTYPE html>
31
+ <html xmlns="http://www.w3.org/1999/xhtml">
32
+ <head>
33
+ <title>intro</title>
34
+ </head>
35
+ <body>
36
+ <h1 id="hello-world">Hello, world</h1>
37
+ <p>This is the intro</p>
38
+ </body>
39
+ </html>
40
+ END
41
+ }
42
+ And {
43
+ expect(contents("build/sections/section1/ch1.xhtml")).to eq(<<END)
44
+ <?xml version="1.0"?>
45
+ <!DOCTYPE html>
46
+ <html xmlns="http://www.w3.org/1999/xhtml">
47
+ <head>
48
+ <title>ch1</title>
49
+ </head>
50
+ <body>
51
+ <h1 id="hello-again">Hello again</h1>
52
+ <p>This is chapter 1</p>
53
+ </body>
54
+ </html>
55
+ END
56
+ }
57
+
58
+
59
+ end
@@ -0,0 +1,66 @@
1
+ require 'spec_helper'
2
+ require 'open3'
3
+
4
+ describe 'skeleton task', task: true do
5
+ Given {
6
+ @construct.file "Rakefile", <<END
7
+ require 'quarto/tasks'
8
+ Quarto.configure do |config|
9
+ config.stylesheets.clear
10
+ end
11
+ END
12
+ @construct.file "ch1.md", <<END
13
+ ```ruby
14
+ puts "hello, world"
15
+ ```
16
+ END
17
+ @construct.file "ch2.md", <<END
18
+ ```c
19
+ int main(int argc, char** argv) {
20
+ printf("Hello, world\n")
21
+ }
22
+ ```
23
+ END
24
+ }
25
+
26
+ When {
27
+ run "rake skeleton"
28
+ }
29
+
30
+ Then {
31
+ expect(contents("build/skeleton.xhtml")).to eq(<<END)
32
+ <?xml version="1.0"?>
33
+ <!DOCTYPE html>
34
+ <html xmlns="http://www.w3.org/1999/xhtml" xmlns:xi="http://www.w3.org/2001/XInclude" xml:base="..">
35
+ <head>
36
+ <title>Untitled Book</title>
37
+ </head>
38
+ <body>
39
+ <xi:include href="build/highlights/3361c5f02e08bd44bde2d42633a2c9be201f7ec4.html">
40
+ <xi:fallback>
41
+ <p>[Missing code listing: build/highlights/3361c5f02e08bd44bde2d42633a2c9be201f7ec4.html]</p>
42
+ </xi:fallback>
43
+ </xi:include>
44
+ <xi:include href="build/highlights/e7b17ea0eeebbd00d08674cf9070d287e24dc68e.html">
45
+ <xi:fallback>
46
+ <p>[Missing code listing: build/highlights/e7b17ea0eeebbd00d08674cf9070d287e24dc68e.html]</p>
47
+ </xi:fallback>
48
+ </xi:include>
49
+ </body>
50
+ </html>
51
+ END
52
+ }
53
+
54
+ And {
55
+ expect(contents("build/listings/3361c5f02e08bd44bde2d42633a2c9be201f7ec4.rb")).to eq(<<END)
56
+ puts "hello, world"
57
+ END
58
+ }
59
+ And {
60
+ expect(contents("build/listings/e7b17ea0eeebbd00d08674cf9070d287e24dc68e.c")).to eq(<<END)
61
+ int main(int argc, char** argv) {
62
+ printf("Hello, world\n")
63
+ }
64
+ END
65
+ }
66
+ end
metadata ADDED
@@ -0,0 +1,184 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: quarto
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Avdi Grimm
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2013-08-01 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: nokogiri
16
+ requirement: !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ~>
20
+ - !ruby/object:Gem::Version
21
+ version: '1.6'
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ~>
28
+ - !ruby/object:Gem::Version
29
+ version: '1.6'
30
+ - !ruby/object:Gem::Dependency
31
+ name: bundler
32
+ requirement: !ruby/object:Gem::Requirement
33
+ none: false
34
+ requirements:
35
+ - - ~>
36
+ - !ruby/object:Gem::Version
37
+ version: '1.3'
38
+ type: :development
39
+ prerelease: false
40
+ version_requirements: !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - ~>
44
+ - !ruby/object:Gem::Version
45
+ version: '1.3'
46
+ - !ruby/object:Gem::Dependency
47
+ name: rake
48
+ requirement: !ruby/object:Gem::Requirement
49
+ none: false
50
+ requirements:
51
+ - - ~>
52
+ - !ruby/object:Gem::Version
53
+ version: '10.0'
54
+ type: :development
55
+ prerelease: false
56
+ version_requirements: !ruby/object:Gem::Requirement
57
+ none: false
58
+ requirements:
59
+ - - ~>
60
+ - !ruby/object:Gem::Version
61
+ version: '10.0'
62
+ - !ruby/object:Gem::Dependency
63
+ name: rspec
64
+ requirement: !ruby/object:Gem::Requirement
65
+ none: false
66
+ requirements:
67
+ - - ~>
68
+ - !ruby/object:Gem::Version
69
+ version: '2.14'
70
+ type: :development
71
+ prerelease: false
72
+ version_requirements: !ruby/object:Gem::Requirement
73
+ none: false
74
+ requirements:
75
+ - - ~>
76
+ - !ruby/object:Gem::Version
77
+ version: '2.14'
78
+ - !ruby/object:Gem::Dependency
79
+ name: rspec-given
80
+ requirement: !ruby/object:Gem::Requirement
81
+ none: false
82
+ requirements:
83
+ - - ~>
84
+ - !ruby/object:Gem::Version
85
+ version: '3.1'
86
+ type: :development
87
+ prerelease: false
88
+ version_requirements: !ruby/object:Gem::Requirement
89
+ none: false
90
+ requirements:
91
+ - - ~>
92
+ - !ruby/object:Gem::Version
93
+ version: '3.1'
94
+ - !ruby/object:Gem::Dependency
95
+ name: test-construct
96
+ requirement: !ruby/object:Gem::Requirement
97
+ none: false
98
+ requirements:
99
+ - - ~>
100
+ - !ruby/object:Gem::Version
101
+ version: '1.2'
102
+ type: :development
103
+ prerelease: false
104
+ version_requirements: !ruby/object:Gem::Requirement
105
+ none: false
106
+ requirements:
107
+ - - ~>
108
+ - !ruby/object:Gem::Version
109
+ version: '1.2'
110
+ - !ruby/object:Gem::Dependency
111
+ name: debugger
112
+ requirement: !ruby/object:Gem::Requirement
113
+ none: false
114
+ requirements:
115
+ - - ! '>='
116
+ - !ruby/object:Gem::Version
117
+ version: '0'
118
+ type: :development
119
+ prerelease: false
120
+ version_requirements: !ruby/object:Gem::Requirement
121
+ none: false
122
+ requirements:
123
+ - - ! '>='
124
+ - !ruby/object:Gem::Version
125
+ version: '0'
126
+ description: Yet another ebook publishing toolchain
127
+ email:
128
+ - avdi@avdi.org
129
+ executables: []
130
+ extensions: []
131
+ extra_rdoc_files: []
132
+ files:
133
+ - .gitignore
134
+ - Gemfile
135
+ - LICENSE.txt
136
+ - README.md
137
+ - Rakefile
138
+ - lib/quarto.rb
139
+ - lib/quarto/tasks.rb
140
+ - lib/quarto/version.rb
141
+ - quarto.gemspec
142
+ - spec/quarto_spec.rb
143
+ - spec/spec_helper.rb
144
+ - spec/tasks/codex_spec.rb
145
+ - spec/tasks/export_spec.rb
146
+ - spec/tasks/highlight_spec.rb
147
+ - spec/tasks/master_spec.rb
148
+ - spec/tasks/sections_spec.rb
149
+ - spec/tasks/skeleton_spec.rb
150
+ homepage: ''
151
+ licenses:
152
+ - MIT
153
+ post_install_message:
154
+ rdoc_options: []
155
+ require_paths:
156
+ - lib
157
+ required_ruby_version: !ruby/object:Gem::Requirement
158
+ none: false
159
+ requirements:
160
+ - - ! '>='
161
+ - !ruby/object:Gem::Version
162
+ version: '0'
163
+ required_rubygems_version: !ruby/object:Gem::Requirement
164
+ none: false
165
+ requirements:
166
+ - - ! '>='
167
+ - !ruby/object:Gem::Version
168
+ version: '0'
169
+ requirements: []
170
+ rubyforge_project:
171
+ rubygems_version: 1.8.24
172
+ signing_key:
173
+ specification_version: 3
174
+ summary: Yet another ebook publishing toolchain
175
+ test_files:
176
+ - spec/quarto_spec.rb
177
+ - spec/spec_helper.rb
178
+ - spec/tasks/codex_spec.rb
179
+ - spec/tasks/export_spec.rb
180
+ - spec/tasks/highlight_spec.rb
181
+ - spec/tasks/master_spec.rb
182
+ - spec/tasks/sections_spec.rb
183
+ - spec/tasks/skeleton_spec.rb
184
+ has_rdoc: