Start generalizing to work for Ward too

* Updates the command-line options in various ways, notably changing --start-url to --book
* Renames the "book" directory to "staging"
* Abstracts out most book-specific data into books.js (but not yet cover or substitutions.json)
* Makes per-book subfolders under cache and staging directories
This commit is contained in:
Domenic Denicola 2020-07-01 15:10:12 -04:00
commit fd914b43f5
5 changed files with 87 additions and 49 deletions

41
lib/books.js Normal file
View file

@ -0,0 +1,41 @@
"use strict";
// TODO cover
exports.worm = {
startURL: "https://parahumans.wordpress.com/2011/06/11/1-1/",
title: "Worm",
author: "wildbow",
id: "e7f3532d-8db6-4888-be80-1976166b7059",
// First paragraph of https://parahumans.wordpress.com/about/
description: `
An introverted teenage girl with an unconventional superpower, Taylor goes out in costume to find escape from a deeply
unhappy and frustrated civilian life. Her first attempt at taking down a supervillain sees her mistaken for one,
thrusting her into the midst of the local cape scenes politics, unwritten rules, and ambiguous morals. As she risks
life and limb, Taylor faces the dilemma of having to do the wrong things for the right reasons.
`.trim()
};
exports.ward = {
startURL: "https://www.parahumans.net/2017/09/11/daybreak-1-1/",
title: "Ward",
author: "wildbow",
id: "a6b6b156-2f17-43c0-8bb1-bfa91f3ef62a",
// Synposis from https://www.parahumans.net/
description: `
The unwritten rules that govern the fights and outright wars between capes have been amended: everyone gets their
second chance. Its an uneasy thing to come to terms with when notorious supervillains and even monsters are playing at
being hero. The world ended two years ago, and as humanity straddles the old world and the new, there arent records,
witnesses, or facilities to answer the villains past actions in the present. One of many compromises, uneasy truces and
deceptions that are starting to splinter as humanity rebuilds.
None feel the injustice of this new status quo or the lack of established footing more than the past residents of the
parahuman asylums. The facilities hosted parahumans and their victims, but the facilities are ruined or gone; one of
many fragile ex-patients is left to find a place in a fractured world. Shes perhaps the person least suited to have
anything to do with this tenuous peace or to stand alongside these false heroes. Shes put in a position to make the
decision: will she compromise to help forge what they call, with dark sentiment, a second golden age? Or will she stand
tall as a gilded dark age dawns?
`.trim()
};

View file

@ -3,17 +3,7 @@ const fs = require("fs").promises;
const path = require("path");
const cpr = require("util").promisify(require("cpr"));
const BOOK_TITLE = "Worm";
const BOOK_AUTHOR = "wildbow";
const BOOK_PUBLISHER = "Domenic Denicola";
const BOOK_ID = "urn:uuid:e7f3532d-8db6-4888-be80-1976166b7059";
// First paragraph of https://parahumans.wordpress.com/about/
const BOOK_DESCRIPTION = `
An introverted teenage girl with an unconventional superpower, Taylor goes out in costume to find escape from a deeply
unhappy and frustrated civilian life. Her first attempt at taking down a supervillain sees her mistaken for one,
thrusting her into the midst of the local cape scenes politics, unwritten rules, and ambiguous morals. As she risks
life and limb, Taylor faces the dilemma of having to do the wrong things for the right reasons.`;
const NCX_FILENAME = "toc.ncx";
@ -21,13 +11,13 @@ const COVER_IMG_FILENAME = "cover.png";
const COVER_XHTML_FILENAME = "cover.xhtml";
const COVER_MIMETYPE = "image/png";
module.exports = async (scaffoldingPath, bookPath, contentPath, chaptersPath, manifestPath) => {
module.exports = async (scaffoldingPath, bookPath, contentPath, chaptersPath, manifestPath, bookInfo) => {
await Promise.all([
cpr(scaffoldingPath, bookPath, { overwrite: true, confirm: true, filter: noThumbs }),
getChapters(contentPath, chaptersPath, manifestPath).then(chapters => {
return Promise.all([
writeOPF(chapters, contentPath),
writeNcx(chapters, contentPath)
writeOPF(chapters, contentPath, bookInfo),
writeNcx(chapters, contentPath, bookInfo)
]);
})
]);
@ -38,7 +28,7 @@ function noThumbs(filePath) {
return path.basename(filePath) !== "Thumbs.db";
}
function writeOPF(chapters, contentPath) {
function writeOPF(chapters, contentPath, bookInfo) {
const manifestChapters = chapters.map(c => {
return `<item id="${c.id}" href="${c.href}" media-type="application/xhtml+xml"/>`;
}).join("\n");
@ -51,12 +41,12 @@ function writeOPF(chapters, contentPath) {
<package version="2.0" xmlns="http://www.idpf.org/2007/opf" unique-identifier="BookId">
<metadata xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:opf="http://www.idpf.org/2007/opf">
<dc:title>${BOOK_TITLE}</dc:title>
<dc:title>${bookInfo.title}</dc:title>
<dc:language>en</dc:language>
<dc:identifier id="BookId" opf:scheme="UUID">${BOOK_ID}</dc:identifier>
<dc:creator opf:file-as="${BOOK_AUTHOR}" opf:role="aut">${BOOK_AUTHOR}</dc:creator>
<dc:identifier id="BookId" opf:scheme="UUID">urn:uuid:${bookInfo.id}</dc:identifier>
<dc:creator opf:file-as="${bookInfo.author}" opf:role="aut">${bookInfo.author}</dc:creator>
<dc:publisher>${BOOK_PUBLISHER}</dc:publisher>
<dc:description>${BOOK_DESCRIPTION}</dc:description>
<dc:description>${bookInfo.description}</dc:description>
<meta name="cover" content="cover-image"/>
</metadata>
@ -80,7 +70,7 @@ ${spineChapters}
return fs.writeFile(path.resolve(contentPath, "content.opf"), contents);
}
function writeNcx(chapters, contentPath) {
function writeNcx(chapters, contentPath, bookInfo) {
const navPoints = chapters.map((c, i) => {
return `<navPoint class="chapter" id="${c.id}" playOrder="${i + 1}">
<navLabel><text>${c.title}</text></navLabel>
@ -92,18 +82,18 @@ function writeNcx(chapters, contentPath) {
<!DOCTYPE ncx PUBLIC "-//NISO//DTD ncx 2005-1//EN" "http://www.daisy.org/z3986/2005/ncx-2005-1.dtd">
<ncx version="2005-1" xml:lang="en" xmlns="http://www.daisy.org/z3986/2005/ncx/">
<head>
<meta name="dtb:uid" content="${BOOK_ID}"/>
<meta name="dtb:uid" content="urn:uuid:${bookInfo.id}"/>
<meta name="dtb:depth" content="1"/>
<meta name="dtb:totalPageCount" content="0"/>
<meta name="dtb:maxPageNumber" content="0"/>
</head>
<docTitle>
<text>${BOOK_TITLE}</text>
<text>${bookInfo.title}</text>
</docTitle>
<docAuthor>
<text>${BOOK_AUTHOR}</text>
<text>${bookInfo.author}</text>
</docAuthor>
<navMap>

View file

@ -6,51 +6,54 @@ const fs = require("fs").promises;
const yargs = require("yargs");
const packageJson = require("../package.json");
const books = require("./books.js");
const download = require("./download.js");
const convert = require("./convert.js");
const scaffold = require("./scaffold.js");
const zip = require("./zip.js");
const OUTPUT_DEFAULT = "(Book name).epub";
const argv = yargs
.usage(`${packageJson.description}\n\n${packageJson.name} [<command1> [<command2> [<command3> ...]]]\n\n` +
"Each command will fail if the previously-listed one has not yet been run (with matching options).")
.command("download", "download all chapters by crawling parahumans.wordpress.com")
.command("convert", "convert the raw chapter HTML files into cleaned-up ebook chapters")
.command("scaffold", "assemble the table of contents, etc. to complete the EPUB")
.command("zip", "zip up the EPUB files into a .epub output")
.option("s", {
alias: "start-url",
default: "https://parahumans.wordpress.com/2011/06/11/1-1/",
describe: "the URL from which to start crawling, for the download command",
.command("download", "download all chapters into the cache")
.command("convert", "convert the raw HTML into cleaned-up ebook chapters")
.command("scaffold", "assemble the table of contents, etc.")
.command("zip", "zip up the created files into a .epub output")
.option("b", {
alias: "book",
default: Object.keys(books)[0],
describe: "the book to operate on",
choices: Object.keys(books),
requiresArg: true,
global: true
})
.option("c", {
alias: "cache-directory",
alias: "cache",
default: "cache",
describe: "cache directory, for the download and convert commands",
describe: "cache directory for downloaded raw chapters",
requiresArg: true,
global: true
})
.option("b", {
alias: "book-directory",
default: "book",
describe: "directory in which to assemble the EPUB files before zipping, for the convert, scaffold, and zip " +
"commands",
.option("s", {
alias: "staging",
default: "staging",
describe: "directory in which to assemble the EPUB files",
requiresArg: true,
global: true
})
.option("o", {
alias: "out",
default: "Worm.epub",
describe: "output file destination, for the zip command",
default: OUTPUT_DEFAULT,
describe: "output file destination",
requiresArg: true,
global: true
})
.option("j", {
alias: "jobs",
default: 10,
describe: "Number of concurrent read/write jobs to run, for the convert command",
describe: "Number of concurrent read/write conversion jobs",
requiresArg: true,
global: true
})
@ -60,18 +63,21 @@ const argv = yargs
.version()
.argv;
const cachePath = path.resolve(argv.cacheDirectory);
const outputFilename = argv.out === OUTPUT_DEFAULT ? `${books[argv.book].title}.epub` : argv.out;
const cachePath = path.resolve(argv.cache, argv.book);
const manifestPath = path.resolve(cachePath, "manifest.json");
const scaffoldingPath = path.resolve(__dirname, "../scaffolding");
const bookPath = path.resolve(argv.bookDirectory);
const contentPath = path.resolve(bookPath, "OEBPS");
const stagingPath = path.resolve(argv.staging, argv.book);
const contentPath = path.resolve(stagingPath, "OEBPS");
const chaptersPath = path.resolve(contentPath, "chapters");
const commands = [];
if (argv._.includes("download")) {
commands.push(() => download(argv.startUrl, cachePath, manifestPath));
const startURL = books[argv.book].startURL;
commands.push(() => download(startURL, cachePath, manifestPath));
}
if (argv._.includes("convert")) {
@ -83,11 +89,12 @@ if (argv._.includes("convert")) {
}
if (argv._.includes("scaffold")) {
commands.push(() => scaffold(scaffoldingPath, bookPath, contentPath, chaptersPath, manifestPath));
const bookInfo = books[argv.book];
commands.push(() => scaffold(scaffoldingPath, stagingPath, contentPath, chaptersPath, manifestPath, bookInfo));
}
if (argv._.includes("zip")) {
commands.push(() => zip(bookPath, contentPath, path.resolve(argv.out)));
commands.push(() => zip(stagingPath, contentPath, path.resolve(outputFilename)));
}
(async () => {