quarto 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +18 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +53 -0
- data/Rakefile +6 -0
- data/lib/quarto.rb +289 -0
- data/lib/quarto/tasks.rb +85 -0
- data/lib/quarto/version.rb +3 -0
- data/quarto.gemspec +29 -0
- data/spec/quarto_spec.rb +235 -0
- data/spec/spec_helper.rb +38 -0
- data/spec/tasks/codex_spec.rb +49 -0
- data/spec/tasks/export_spec.rb +40 -0
- data/spec/tasks/highlight_spec.rb +42 -0
- data/spec/tasks/master_spec.rb +58 -0
- data/spec/tasks/sections_spec.rb +59 -0
- data/spec/tasks/skeleton_spec.rb +66 -0
- metadata +184 -0
data/.gitignore
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -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.
|
data/README.md
ADDED
@@ -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
|
data/Rakefile
ADDED
data/lib/quarto.rb
ADDED
@@ -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
|
data/lib/quarto/tasks.rb
ADDED
@@ -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
|
data/quarto.gemspec
ADDED
@@ -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
|
data/spec/quarto_spec.rb
ADDED
@@ -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
|
data/spec/spec_helper.rb
ADDED
@@ -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">"hello, world"</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">"Hello, world</span>
|
37
|
+
<span class="s">")</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:
|