Commits (4)
......@@ -13,11 +13,15 @@ insert_final_newline = true
max_line_length = 80
tab_width = 4
trim_trailing_whitespace = true
curly_bracket_next_line = true
curly_bracket_next_line = false
[*.{js,ts}]
quote_type = double
[*.json]
[*.yaml]
indent_size = 2
tab_width = 2
[package.json]
indent_size = 2
tab_width = 2
*~
.tscache
/.ntvs_analysis.dat
/.ntvs_analysis.dat.tmp
.baseDir.ts
npm-debug.log
*.tgz
*.js
*.js.map
*.d.ts
src/*.js
src/*.js.map
src/*.d.ts
lib/
es6/
amd/
umd/
dist/
commonjs/
spec/*.js
spec/*.js.map
spec/*.d.ts
npm-debug.log
node_modules/
typings/
build/
......@@ -5,7 +5,6 @@ TODO.markdown
.editorconfig
.jsbeautifyrc
src/
lib/spec/
typings/
tsd.json
#!/bin/bash
rdlkf() { [ -L "$1" ] && (local lk="$(readlink "$1")"; local d="$(dirname "$1")"; cd "$d"; local l="$(rdlkf "$lk")"; ([[ "$l" = /* ]] && echo "$l" || echo "$d/$l")) || echo "$1"; }
DIR="$(dirname "$(rdlkf "$0")")"
exec /usr/bin/env node --harmony "$DIR/../lib/cli.js" "$@"
exec /usr/bin/env node --harmony "$DIR/../src/cli.js" "$@"
This diff is collapsed.
{
"name": "mfgames-writing-format",
"version": "0.10.5",
"version": "1.0.0",
"description": "A command-line framework for formatting books into a variety of formats.",
"repository": {
"type": "git",
"url": "git+https://gitlab.com/mfgames-writing/mfgames-writing-format-js.git"
"url": "https://gitlab.com/mfgames-writing/mfgames-writing-format-js.git"
},
"keywords": [
"ebook",
......@@ -24,8 +24,8 @@
"url": "https://gitlab.com/mfgames-writing/mfgames-writing-format-js/issues"
},
"homepage": "https://gitlab.com/mfgames-writing/mfgames-writing-format-js#README",
"main": "lib/index.js",
"types": "lib/index.d.ts",
"main": "src/index.js",
"types": "src/index.d.ts",
"bin": {
"mfgames-writing-format": "./bin/mfgames-writing-format"
},
......@@ -36,25 +36,24 @@
"typescript": "^2.2.1"
},
"dependencies": {
"@types/bluebird": "^2.0.22-alpha",
"@types/fs-finder": "^1.8.21-alpha",
"@types/js-yaml": "^3.5.21-alpha",
"@types/marked": "0.0.26-alpha",
"@types/node": "^4.0.22-alpha",
"@types/uuidjs": "^3.3.1",
"@types/yargs": "0.0.21-alpha",
"bluebird": "^3.4.1",
"fs-finder": "^1.8.1",
"incremental": "^1.0.1",
"jimp": "^0.2.27",
"js-yaml": "^3.6.1",
"liquid-node": "^2.6.1",
"marked": "^0.3.5",
"mfgames-ncx": "0.0.0",
"mfgames-opf": "0.0.0",
"mfgames-writing-contracts": "^0.5.0",
"mfgames-ncx": "^0.0.2",
"mfgames-opf": "^0.0.2",
"mfgames-writing-contracts": "^3.0.0",
"mkdirp": "^0.5.1",
"mkdirp-promise": "^2.0.0",
"mz": "^2.7.0",
"source-map-support": "^0.4.2",
"tracer": "^0.8.3",
"uuid": "^2.0.2",
......
import * as path from "path";
import * as fs from "fs";
import { PublicationData, EditionData, ContentData, Formatter, Theme, PublicationArgs, EditionArgs } from "mfgames-writing-contracts";
import * as Promise from "bluebird";
import { loadModule } from "./plugins";
import { loadConfig } from "./configs";
import { loadContents, appendContents, renderContents } from "./content";
......@@ -17,7 +16,7 @@ export function runBuild(argv: any, packageJson: any, logger: any) {
loadPublication(args, argv)
.then(chooseEditions)
.then(data => { args.logger.info("Finished processing output"); });
.then((data: any) => { args.logger.info("Finished processing output"); });
}
/**
......@@ -55,6 +54,11 @@ function loadPublication(args: PublicationArgs, argv: any): Promise<PublicationA
// Load the configuration file.
let config = loadConfig<PublicationData>(args.logger, publicationFile);
if (!config) {
throw new Error("Cannot load configuration");
}
args.rootDirectory = path.dirname(publicationFile);
args.publicationFile = publicationFile;
args.publication = config;
......@@ -125,51 +129,61 @@ function buildEdition(args: EditionArgs): Promise<EditionArgs> {
// We need to resolve the output directory and filename.
let promise: Promise<EditionArgs> = Promise.resolve(args);
promise = promise.then(a => {
promise = promise.then((a: any) => {
// Resolve the output directory.
let promise: Promise<any> = new Promise((resolve, reject) => {
let engine = new liquid.Engine();
resolve(engine.parse(args.edition.outputDirectory));
});
promise = promise.then(t => {
promise = promise.then((t: any) => {
let parameters = {
edition: args.edition
};
return t.render(parameters);
});
promise = promise.then(directory => {
promise = promise.then((directory: any) => {
args.edition.outputDirectory = directory;
return args;
});
// Resolve the filename.
promise = promise.then(a => {
promise = promise.then((a: any) => {
let engine = new liquid.Engine();
return engine.parse(args.edition.outputFilename);
});
promise = promise.then(t => {
promise = promise.then((t: any) => {
let parameters = {
edition: args.edition
};
return t.render(parameters);
});
promise = promise.then(filename => {
promise = promise.then((filename: any) => {
args.edition.outputFilename = filename;
return args;
});
// Finish up with the proper output.
return promise.then(a => args);
return promise.then((a: any) => args);
});
// Format the edition.
promise = promise.then(a => {
promise = promise.then((a: any) => {
// Pull out the edition data.
args.logger.debug("edition", args.name, args.edition);
// Load the formatter and theme.
args.format = <Formatter>loadModule(args, args.edition.format)();
args.theme = <Theme>loadModule(args, args.edition.theme)();
args.format = <Formatter>loadModule(args, args.edition.format)(args);
args.theme = <Theme>loadModule(args, args.edition.theme)(args);
if (!args.format) {
args.logger.error("Could not load format plugin: " + args.edition.format);
throw new Error();
}
if (!args.theme) {
args.logger.error("Could not load theme plugin: " + args.edition.theme);
throw new Error();
}
// Make sure the output directory exists. This creates a promise but
// we don't care about the output, only that it was created.
......@@ -181,26 +195,27 @@ function buildEdition(args: EditionArgs): Promise<EditionArgs> {
//
// We have to pass the "a" so we can reference the class method.
promise = promise
.then(v => args)
.then(a => args.format.start(a))
.then(a => args.theme.start(a))
.then(a => appendContents(a))
.then(a => loadContents(a))
.then(a => renderContents(a))
.then(a => args.theme.finish(a))
.then(a => args.format.finish(a));
.then((v: any) => args)
.then((a: any) => args.format.start(a))
.then((a: any) => args.theme.start(a))
.then((a: any) => appendContents(a))
.then((a: any) => loadContents(a))
.then((a: any) => renderContents(a))
.then((a: any) => args.theme.finish(a))
.then((a: any) => args.format.finish(a));
return promise;
});
return promise;
}
function prepareContents(contents: ContentData[], parent: ContentData = undefined) {
function prepareContents(contents: ContentData[], parent: ContentData | undefined = undefined) {
if (contents) {
for (let content of contents) {
content.parent = parent;
content.process = {};
prepareContents(content.contents, content);
prepareContents(content.contents || [], content);
}
}
}
......@@ -227,17 +242,7 @@ function extendObjects(edition: EditionData, publication: PublicationData): Edit
}
function mergeObjects(object1: any, object2: any): any {
var result = {};
if (object2) {
for (var name in object2) { result[name] = object2[name]; }
}
if (object1) {
for (var name in object1) { result[name] = object1[name]; }
}
return result;
return {... object2, ... object1};
}
function getDefaults() {
......
......@@ -7,7 +7,7 @@ require('source-map-support').install();
// Set up the build command.
var build_help = "Builds one or more editions of the book.";
function build_yargs(yargs) {
function build_yargs(yargs: any) {
yargs
.help("help")
.alias("c", "config")
......
......@@ -2,7 +2,7 @@ import * as path from "path";
import * as fs from "fs";
import * as yaml from "js-yaml";
export function loadConfig<T>(logger, filename: string): T
export function loadConfig<T>(logger: any, filename: string): T | undefined
{
// We have the file, so attempt to load it into memory. This will either be
// a YAML or a JSON file.
......@@ -20,7 +20,8 @@ export function loadConfig<T>(logger, filename: string): T
// If we fell through to this point, we don't know how to parse that file
// type.
logger.error(`Cannot parse ${extension} file types`);
return null;
return undefined;
}
function loadYaml(filename: string): any {
......
import * as path from "path";
import * as fs from "fs";
import * as Promise from "bluebird";
const fs = require("mz/fs");
import * as marked from "marked";
import { EditionArgs, ContentArgs, ContentData } from "mfgames-writing-contracts";
const incremental = require("incremental");
const liquid = require("liquid-node");
const yamlFrontMatter = require("yaml-front-matter");
const zpad = require("zpad");
const readFileAsync = Promise.promisify(fs.readFile);
import { processImages } from "./image";
import { loadModule } from "./plugins";
class AppendArgs
{
public editionArgs: EditionArgs;
public contentIndex: number = 0;
constructor(editionArgs: EditionArgs) {
this.editionArgs = editionArgs;
}
}
/**
* Goes through the contents list of the publication and expands out any source
* patterns from the results, populating them into `args.contents`.
*/
export function appendContents(args: EditionArgs): Promise<EditionArgs> {
return appendContentsRecursion(args, args.publication.contents);
}
/**
* Recursively goes through the given contents and expands out each one into a
* flattened structure in the `args.contents`.
*/
function appendContentsRecursion(args: EditionArgs, contents: ContentData[]): Promise<EditionArgs> {
// If we don't have contents, then just return a simple resolve.
let promise = Promise.resolve(args);
var appendArgs = new AppendArgs(args);
let promise = Promise.resolve(appendArgs);
if (contents) {
// Otherwise, go through the contents and build up some promises.
for (let content of contents) {
promise = promise.then(a => appendContent(a, content));
promise = promise.then(a => appendContentsRecursion(a, content.contents));
if (args.publication.contents) {
for (let content of args.publication.contents) {
promise = promise.then(a => appendContent(appendArgs, content));
}
// Ensure we have an ID for everything we build up.
promise = promise.then(a => {
let index = 0;
for (let content of a.contents) {
if (!content.id) {
content.id = `c-${zpad(index++, 4)}-${content.element}`;
}
}
return a;
});
}
// Return the resulting promise.
return promise;
return promise.then((aa: AppendArgs) => aa.editionArgs);
}
/**
* Appends the contents of a single item to `args.contents`.
*/
function appendContent(args: EditionArgs, originalContent: ContentData): Promise<EditionArgs> {
function appendContent(args: AppendArgs, originalContent: ContentData): Promise<AppendArgs> {
// We need to work with a copy of the content.
let content = { ...originalContent };
// See if this content has been filtered out.
if (content.exclude) {
if (content.exclude.editions &&
content.exclude.editions.indexOf(args.name) >= 0) {
args.logger.debug(content.id, "excluding edition", args.name);
content.exclude.editions.indexOf(args.editionArgs.name) >= 0) {
args.editionArgs.logger.debug( "excluding edition", args.editionArgs.name);
return Promise.resolve(args);
}
}
// Otherwise, create the contents.
return new Promise<EditionArgs>((resolve, reject) => {
// Otherwise, create the contents and process theme. We need to sequence the
// content from the beginning to give them a unique identifier.
return new Promise<AppendArgs>((resolve, reject) => {
// Figure out the directory we'll be processing for this.
let directory = content.directory
? path.join(args.rootDirectory, content.directory)
: args.rootDirectory;
? path.join(args.editionArgs.rootDirectory, content.directory)
: args.editionArgs.rootDirectory;
// Figure out if we have a parent.
let parentArgs = content.parent && "ContentArgs" in content.parent.process
......@@ -99,7 +84,6 @@ function appendContent(args: EditionArgs, originalContent: ContentData): Promise
// Create a copy of the content with the pattern populated.
let filename = file;
let patternContent: ContentData = {
id: content.id,
directory: directory,
element: content.element,
number: content["number"],
......@@ -113,9 +97,12 @@ function appendContent(args: EditionArgs, originalContent: ContentData): Promise
};
// Add the promise to handle this into the pipeline.
let contentArgs = new ContentArgs(args, patternContent);
let contentArgs = new ContentArgs(
args.editionArgs,
patternContent,
++args.contentIndex);
contentArgs.parent = parentArgs;
args.contents.push(contentArgs);
args.editionArgs.contents.push(contentArgs);
// Only the first one in a match is going to be the first one.
// Likewise, we reset the forced page counter.
......@@ -130,9 +117,12 @@ function appendContent(args: EditionArgs, originalContent: ContentData): Promise
}
}
else {
let contentArgs = new ContentArgs(args, content);
let contentArgs = new ContentArgs(
args.editionArgs,
content,
++args.contentIndex);
contentArgs.parent = parentArgs;
args.contents.push(contentArgs);
args.editionArgs.contents.push(contentArgs);
content.process["ContentArgs"] = contentArgs;
}
......@@ -209,9 +199,8 @@ function loadContentImage(content: ContentArgs): Promise<ContentArgs> {
content.source = content.source + ".html";
content.metadata = {
title: isCover ? "Cover" : "Image",
element: content["element"],
number: content["number"],
directory: content["directory"]
element: content.element,
directory: content.directory
};
content.text = `<p class="cover">
<img src="${filename}" alt="${alt}" />
......@@ -228,7 +217,7 @@ function loadContentImage(content: ContentArgs): Promise<ContentArgs> {
*/
function loadContentText(content: ContentArgs): Promise<ContentArgs> {
// Start the promise chain with reading files into memory.
var promise: Promise<any> = readFileAsync(content.filename);
var promise: Promise<any> = fs.readFile(content.filename);
// Pull out the contents into a text buffer.
promise = promise.then(buffer => {
......@@ -365,24 +354,24 @@ function renderContent(args: ContentArgs): Promise<ContentArgs> {
function renderContentLiquid(content: ContentArgs): Promise<ContentArgs> {
return new Promise<ContentArgs>((resolve, reject) => {
// Report what we are doing.
content.logger.info(`${content.id}: Rendering Liquid contents`);
content.logger.info(`Rendering Liquid contents`);
// Create a template from the text of the contents. The final one is
// actually a promise.
let engine = new liquid.Engine();
let template = content.text;
let promise = engine.parse(template)
.then(t => {
let parameters = {
.then((t: any) => {
const parameters = {
content: content.metadata,
edition: content.edition,
theme: this
};
parameters.content.depth = content.depth;
parameters.content.id = content.id;
return t.render(parameters);
})
.then(rendered => {
.then((rendered: any) => {
content.text = rendered;
return content;
});
......
import * as crypto from "crypto";
import * as fs from "fs";
import * as path from "path";
import * as Promise from "bluebird";
let jimp = require("jimp");
import { ContentArgs, FormatImageRequest, FormatImageResponse, EditionImagesData } from "mfgames-writing-contracts";
......@@ -32,11 +31,11 @@ export function processImages(content: ContentArgs): Promise<ContentArgs> {
if (formatImageResponse.include) {
// Process the image as a process.
promise = promise.then(c => processImage(content, imageData));
promise = promise.then((c: any) => processImage(content, imageData));
// Add the image into the format's promise.
promise = promise.then(request =>
Promise.resolve(request).then(img => formatImageResponse.callback(img)));
promise = promise.then((request: any) =>
Promise.resolve(request).then((img: any) => formatImageResponse.callback(img)));
}
// Rebuild the substitution to avoid removing it.
......@@ -45,7 +44,7 @@ export function processImages(content: ContentArgs): Promise<ContentArgs> {
// Normalize the output, changing the buffer. There won't be any major
// problem if the HTML changes or not.
promise = promise.then(a => {
promise = promise.then((a: any) => {
content.text = html;
content.buffer = new Buffer(html, "utf-8");
return content;
......@@ -94,7 +93,6 @@ function getFormatImageRequest(content: ContentArgs, href: string, imagePath: st
// Return the new path.
var results: FormatImageRequest = {
id: imageId,
href: href,
imagePath: imagePath,
buffer: imageBuffer,
......@@ -110,25 +108,25 @@ function getFormatImageRequest(content: ContentArgs, href: string, imagePath: st
function processImage(content: ContentArgs, imageRequest: FormatImageRequest): Promise<FormatImageRequest> {
// Make a little noise.
content.logger.debug(`Processing image ${imageRequest.id}: ${imageRequest.imagePath}`);
content.logger.debug(`Processing image: ${imageRequest.imagePath}`);
// Start by reading in the image.
var promise: Promise<any> = jimp.read(imageRequest.imagePath);
promise = promise.then(image => {
promise = promise.then((image: any) => {
return image;
});
// Grayscaling the image.
if (imageRequest.grayscale) {
promise = promise.then(image => {
promise = promise.then((image: any) => {
return image.greyscale();
});
}
// Making the image opaque.
if (imageRequest.opaque) {
promise = promise.then(image => {
promise = promise.then((image: any) => {
var width = image.bitmap.width;
var height = image.bitmap.height;
var color = 0xFFFFFF;
......@@ -139,21 +137,21 @@ function processImage(content: ContentArgs, imageRequest: FormatImageRequest): P
// Scaling images.
if (imageRequest.scale !== 1.0) {
promise = promise.then(image => {
promise = promise.then((image: any) => {
return image.scale(imageRequest.scale);
});
}
// After we're done processing, pull it out and return it.
promise = promise.then(image => {
return new Promise((resolve, reject) => {
let buffer = image.getBuffer(imageRequest.mime, (err, buffer) => {
promise = promise.then((image: any) => {
return new Promise((resolve: any, reject: any) => {
let buffer = image.getBuffer(imageRequest.mime, (err: any, buffer: any) => {
if (err) reject(err);
resolve(buffer);
});
});
});
promise = promise.then(buffer => {
promise = promise.then((buffer: any) => {
imageRequest.buffer = buffer;
return imageRequest;
});
......@@ -163,12 +161,12 @@ function processImage(content: ContentArgs, imageRequest: FormatImageRequest): P
/*
return jimp.read(data.buffer)
.then(image => {
.then((image: any) => {
if (data.scale !== 1.00) {
image = image.scale(data.scale);
}
return image;
})
.then(image => {
.then((image: any) => {
let buffer = image.getBuffer(data.mime, (err, buffer) => {
*/
......@@ -2,7 +2,7 @@ import {EditionArgs} from "mfgames-writing-contracts";
import * as fs from "fs";
import * as path from "path";
var loaded = {};
var loaded: { [index: string]: any } = {};
export function loadModule(args: EditionArgs, name: string): any {
// Report what we're doing. We want to avoid doing this a lot, so we only
......@@ -47,7 +47,6 @@ export function loadModule(args: EditionArgs, name: string): any {
function loadModulePath(args: EditionArgs, modulePath: string): any {
try {
//console.log("args", modulePath, args);
let results = require(modulePath);
args.logger.debug(`Loaded ${modulePath}`);
return results.default;
......
{
"compileOnSave": true,
"compilerOptions": {
"alwaysStrict": true,
"declaration": true,
"experimentalDecorators": true,
"forceConsistentCasingInFileNames": true,
"lib": ["es6", "dom"],
"module": "commonjs",
"moduleResolution": "node",
"noFallthroughCasesInSwitch": true,
"noImplicitAny": true,
"noImplicitReturns": true,
"sourceMap": true,
"strictFunctionTypes": true,
"strictNullChecks": true,
"target": "es6",
"types": ["node"]
}
}