Skip to content

Commit

Permalink
Refactored to simplify code and to handle input directory separate fr…
Browse files Browse the repository at this point in the history
…om working directory.
  • Loading branch information
KDean-Dolphin committed Jan 30, 2025
1 parent b2c0aca commit 062ee0d
Show file tree
Hide file tree
Showing 5 changed files with 239 additions and 75 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
/package-lock.json
/dist/
/node_modules/
/pandoc-spec.options.json
4 changes: 3 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,8 @@
],
"scripts": {
"lint": "eslint .",
"build-dist": "tsup src/index.ts --format esm --dts --minify"
"build-dist": "tsup src/index.ts --format esm --dts --minify",
"run": "tsx --eval \"import('./src/index.ts').then((m) => { m.exec() }).catch(e => console.error(e));\""
},
"bin": {
"pandoc-spec": "bin/pandoc-spec"
Expand All @@ -38,6 +39,7 @@
},
"dependencies": {
"@legreq/pandoc-defref": "^1.0.5-beta",
"glob": "^11.0.1",
"mermaid-filter": "^1.4.7"
}
}
74 changes: 74 additions & 0 deletions src/file.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
import fs from "fs";
import { globIterateSync } from "glob";
import path from "node:path";

/**
* Get the module path of path relative to the module root.
*
* @param relativePath
* Path relative to the module root.
*
* @returns
* Module path.
*/
export function modulePath(relativePath: string): string {
return decodeURI(new URL(relativePath, import.meta.url).pathname);
}

/**
* Get the file path of path relative to the current working directory.
*
* @param relativePath
* Path relative to the current working directory.
*
* @returns
* File path.
*/
export function workingPath(relativePath: string): string;

/**
* Get the file path of path relative to the current working directory.
*
* @param relativePath
* Path relative to the current working directory or undefined.
*
* @returns
* File path or undefined.
*/
export function workingPath(relativePath: string | undefined): string | undefined;

// eslint-disable-next-line jsdoc/require-jsdoc -- Overload implementation.
export function workingPath(relativePath: string | undefined): string | undefined {
return relativePath !== undefined ? path.resolve(relativePath) : undefined;
}

/**
* Copy files matching glob patterns to a directory.
*
* @param pattern
* Glob pattern(s).
*
* @param toDirectory
* Directory to which to copy files.
*/
export function copyFiles(pattern: string | string[], toDirectory: string): void {
// Source files are expected to be relative to current directory.
for (const sourceFile of globIterateSync(pattern)) {
const destinationFile = path.resolve(toDirectory, sourceFile);

if (destinationFile === path.resolve(sourceFile)) {
throw new Error(`File ${sourceFile} cannot be copied to itself.`);
}

const destinationDirectory = path.dirname(destinationFile);

// Create destination directory if it doesn't exist.
if (!fs.existsSync(destinationDirectory)) {
fs.mkdirSync(destinationDirectory, {
recursive: true
});
}

fs.copyFileSync(sourceFile, destinationFile);
}
}
117 changes: 43 additions & 74 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,29 +3,8 @@
import { spawnSync } from "child_process";
import fs from "fs";
import * as path from "node:path";

/**
* Configuration layout of .puppeteer.json (relevant attributes only).
*/
interface PuppeteerConfiguration {
/**
* Arguments.
*/
args: string[] | undefined;
}

/**
* Get the module path of path relative to the module root.
*
* @param relativePath
* Path relative to the module root.
*
* @returns
* Module path.
*/
function modulePath(relativePath: string): string {
return decodeURI(new URL(relativePath, import.meta.url).pathname);
}
import { copyFiles, modulePath, workingPath } from "./file.js";
import { PuppeteerConfigurator } from "./puppeteer.js";

/**
* Pandoc options.
Expand All @@ -35,6 +14,8 @@ interface Options {

verbose?: boolean;

autoDate?: boolean;

inputFormat?: string;

outputFormat?: string;
Expand All @@ -57,8 +38,16 @@ interface Options {

footerFile?: string;

inputDirectory?: string;

inputFile: string | string[];

resourceFiles?: string | string[];

outputDirectory?: string;

cleanOutput?: boolean;

outputFile: string;

additionalOptions?: Array<{
Expand Down Expand Up @@ -165,9 +154,16 @@ export function exec(parameterOptions: unknown): never {
throw new Error("Invalid Options from file and/or parameter");
}

const now = new Date();
const adjustedNow = new Date(now.getTime() - now.getTimezoneOffset() * 60 * 1000);

const inputDirectory = options.inputDirectory !== undefined ? path.resolve(options.inputDirectory) : process.cwd();
const outputDirectory = options.outputDirectory !== undefined ? path.resolve(options.outputDirectory) : process.cwd();

const args: string[] = [
"--standalone",
arg("--verbose", options.verbose),
arg("--metadata", options.autoDate ?? false ? `date:${adjustedNow.toISOString().substring(0, 10)}` : undefined),
arg("--from", options.inputFormat, "markdown"),
arg("--to", options.outputFormat, "html"),
arg("--shift-heading-level-by", options.shiftHeadingLevelBy, -1),
Expand All @@ -177,21 +173,28 @@ export function exec(parameterOptions: unknown): never {
arg("--lua-filter", modulePath("../pandoc/include-code-files.lua")),
arg("--filter", "mermaid-filter"),
arg("--filter", "pandoc-defref"),
...(options.filters ?? []).map(filter => arg(filter.type !== "json" ? "--lua-filter" : "--filter", filter.name)),
arg("--template", options.templateFile, modulePath("../pandoc/template.html")),
arg("--include-before-body", options.headerFile),
arg("--include-after-body", options.footerFile),
arg("--output", options.outputFile),
...(options.filters ?? []).map(filter => filter.type !== "json" ? arg("--lua-filter", workingPath(filter.name)) : arg("--filter", filter.name)),
arg("--template", workingPath(options.templateFile), modulePath("../pandoc/template.html")),
arg("--include-before-body", workingPath(options.headerFile)),
arg("--include-after-body", workingPath(options.footerFile)),
arg("--output", path.resolve(outputDirectory, options.outputFile)),
...(options.additionalOptions ?? []).map(additionalOption => arg(additionalOption.option, additionalOption.value)),
...(!Array.isArray(options.inputFile) ? [options.inputFile] : options.inputFile)
].filter(arg => arg !== "");

if (options.debug ?? false) {
console.error(`Base URL: ${import.meta.url}`);
console.error(`Input directory: ${inputDirectory}`);
console.error(`Output directory: ${outputDirectory}`);

console.error(`Pandoc arguments:\n${args.join("\n")}`);
}

const outputDirectory = path.dirname(path.resolve(options.outputFile));
if (options.cleanOutput ?? false) {
fs.rmSync(outputDirectory, {
recursive: true,
force: true
});
}

// Create output directory if it doesn't exist.
if (!fs.existsSync(outputDirectory)) {
Expand All @@ -200,42 +203,10 @@ export function exec(parameterOptions: unknown): never {
});
}

const puppeteerConfigurationFile = ".puppeteer.json";

// --no-sandbox is required in GitHub Actions due to issues in Ubuntu (https://github.com/puppeteer/puppeteer/issues/12818).
const noSandboxArg = "--no-sandbox";

let puppeteerConfiguration: PuppeteerConfiguration;

// Assume that Puppeteer configuration needs to be updated.
let updatePuppeteerConfiguration = true;
const puppeteerConfigurator = new PuppeteerConfigurator(inputDirectory);

// Need to roll back to original Puppeteer configuration afterward.
let puppeteerConfigurationContent: string | undefined = undefined;

if (fs.existsSync(puppeteerConfigurationFile)) {
puppeteerConfigurationContent = fs.readFileSync(puppeteerConfigurationFile).toString();

// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment -- Puppeteer configuration format is known.
puppeteerConfiguration = JSON.parse(puppeteerConfigurationContent);

if (puppeteerConfiguration.args === undefined) {
puppeteerConfiguration.args = [noSandboxArg];
} else if (!puppeteerConfiguration.args.includes(noSandboxArg)) {
puppeteerConfiguration.args.push(noSandboxArg);
} else {
// Puppeteer configuration already correct.
updatePuppeteerConfiguration = false;
}
} else {
puppeteerConfiguration = {
args: [noSandboxArg]
};
}

if (updatePuppeteerConfiguration) {
fs.writeFileSync(puppeteerConfigurationFile, `${JSON.stringify(puppeteerConfiguration, null, 2)}\n`);
}
// Run Pandoc in input directory.
process.chdir(inputDirectory);

let status = 0;

Expand All @@ -257,15 +228,8 @@ export function exec(parameterOptions: unknown): never {
status = spawnResult.status;
}
} finally {
if (updatePuppeteerConfiguration) {
if (puppeteerConfigurationContent === undefined) {
// No original Puppeteer configuration; delete.
fs.rmSync(puppeteerConfigurationFile);
} else {
// Restore original Puppeteer configuration.
fs.writeFileSync(puppeteerConfigurationFile, puppeteerConfigurationContent);
}
}
// Restore Puppeteer configuration.
puppeteerConfigurator.finalize();

const mermaidFilterErrorFile = "mermaid-filter.err";

Expand All @@ -275,6 +239,11 @@ export function exec(parameterOptions: unknown): never {
}
}

// Copy resource files if Pandoc succeeded and resource files are defined.
if (status === 0 && options.resourceFiles !== undefined) {
copyFiles(options.resourceFiles, outputDirectory);
}

// Exit with Pandoc status.
process.exit(status);
}
Loading

0 comments on commit 062ee0d

Please sign in to comment.