Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 12 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,26 @@
Bulk generation of images from Gosling visualization specifications

```bash
node gosling-screenshot.js <input-dir> <output-dir> [format]
# supported formats: png, jpeg, webp
node gosling-screenshot.js
# Usage: node gosling-screenshot.js [options] <input>
#
# Options:
# --format=[png|jpeg|webp] Output image format (default: png)
# --outdir=<directory> Directory to save the output image
# (required if input is a directory, optional otherwise)
#
# Arguments:
# input A Gosling specification file (JSON) or a directory of specifications
Comment on lines +6 to +15
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@huyen-nguyen know you're crunched with time, but let me know if this new API is ok to merge!

```

- Processing all JSON files in input directory
- Creating output directory if it doesn't already exist
- Maintaining original filenames with new extension

### Examples

```bash
node gosling-screenshot.js ./input ./output png
node gosling-screenshot.js --format=png --outdir=./output ./input
```

![Generated Gosling visualization](https://user-images.githubusercontent.com/24403730/163602155-96b48c3b-f9b7-440f-a26c-7ba6fd25782c.jpeg)
Expand Down
97 changes: 61 additions & 36 deletions gosling-screenshot.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import puppeteer from "puppeteer";
import * as fs from "node:fs/promises";
import path from "path";
import * as fs from "node:fs";
import * as path from "node:path";
import * as util from "node:util";

/**
* @param {string} spec
Expand Down Expand Up @@ -35,7 +36,7 @@ function html(spec, {
let api = await gosling.embed(document.getElementById("vis"), JSON.parse(\`${spec}\`))
globalThis.api = api;
</script>
</html>`
</html>`;
}

/**
Expand All @@ -57,31 +58,39 @@ async function screenshot(spec, opts) {
return buffer;
}

async function processFile(file, outputDir, fileType) {
const outputPath = path.join(
outputDir,
`${path.parse(file).name}.${fileType}`,
);
try {
let spec = await fs.promises.readFile(file, "utf8");
// to use escape characters as pure text (e.g., separator: '\t') in `.setContent()`
spec = spec.replaceAll("\\", "\\\\");
await screenshot(spec, { path: outputPath });
console.log(`Generated ${outputPath}`);
} catch (err) {
console.error(`Error processing ${file}:`, err);
}
}

async function processFiles(inputDir, outputDir, fileType) {
if (outputDir === null) {
console.error("--outdir required to process a directory.");
process.exit(1);
}
try {
// Create output directory if it doesn't exist
await fs.mkdir(outputDir, { recursive: true });
await fs.promises.mkdir(outputDir, { recursive: true });

// Read all files from input directory
const files = await fs.readdir(inputDir);
const files = await fs.promises.readdir(inputDir, { withFileTypes: true });

// Process each JSON file
for (const file of files) {
if (path.extname(file).toLowerCase() === '.json') {
const inputPath = path.join(inputDir, file);
const outputPath = path.join(outputDir, `${path.parse(file).name}.${fileType}`);

// console.log(`Processing ${file}...`); // Uncomment for notifying current processing file

try {
let spec = await fs.readFile(inputPath, "utf8");
// to use escape characters as pure text (e.g., separator: '\t') in `.setContent()`
spec = spec.replaceAll('\\', '\\\\');
await screenshot(spec, { path: outputPath });
console.log(`Generated ${outputPath}`);
} catch (err) {
console.error(`Error processing ${file}:`, err);
}
if (file.isFile() && file.name.endsWith(".json")) {
const inputPath = path.join(inputDir, file.name);
await processFile(inputPath, outputDir, fileType);
}
}
} catch (err) {
Expand All @@ -91,31 +100,47 @@ async function processFiles(inputDir, outputDir, fileType) {
}

// Command line argument parsing
const args = process.argv.slice(2);
const args = util.parseArgs({
allowPositionals: true,
options: {
format: { type: "string" }, // --format=png
outdir: { type: "string" }, // --outdir=output-images
},
});

if (args.length < 2) {
console.error(
"Usage: node gosling-screenshot.js <input-directory> <output-directory> [file-type]"
);
console.error("file-type options: png, jpeg, webp (default: png)");
if (args.positionals.length !== 1) {
console.error(`Usage: node gosling-screenshot.js [options] <input>

Options:
--format=[png|jpeg|webp] Output image format (default: png)
--outdir=<directory> Directory to save the output image
(required if input is a directory, optional otherwise)

Arguments:
input A Gosling specification file (JSON) or a directory of specifications`);
process.exit(1);
}

const inputDir = args[0];
const outputDir = args[1];
const fileType = args[2]?.toLowerCase() || 'png';
const input = path.resolve(args.positionals[0]);
const fileType = args.values.format ?? "png";
const outputDir = args.values.outdir ?? null;

// Validate file type
const validFileTypes = ['png', 'jpeg', 'webp'];
const validFileTypes = ["png", "jpeg", "webp"];
if (!validFileTypes.includes(fileType)) {
console.error(`Invalid file type. Supported types are: ${validFileTypes.join(', ')}`);
console.error(
`Invalid file type. Supported types are: ${validFileTypes.join(", ")}`,
);
process.exit(1);
}

// Process the files
processFiles(inputDir, outputDir, fileType)
.then(() => console.log('Processing complete!'))
.catch(err => {
console.error('Error:', err);
const main = fs.statSync(input).isDirectory()
? () => processFiles(input, outputDir, fileType)
: () => processFile(input, path.dirname(input), fileType);

main()
.then(() => console.log("Processing complete!"))
.catch((err) => {
console.error("Error:", err);
process.exit(1);
});