#!/usr/bin/env node
/**
* @file json-timeline
* @module json-timeline
* @description CLI for generating Timelines
* @param {string} adventsPath Path to Advents Directory
* @param {string} outputPath Path to Output Directory
* @param {string} flags Flags: v = verbose, f = force
*/
const path = require('path');
const fs = require('fs');
const loadAdvents = require('./functions/loadAdvents');
const sortAdvents = require('./functions/sortAdvents');
const groupAdvents = require('./functions/groupAdvents');
/**
* @constant
* @type {Object}
* @name FLAGS
* @description Boolean flags for CLI Argument flags
*/
const FLAGS = {
VERBOSE: false,
FORCE: false
};
(function main() {
try {
const [adventsInput, outputInput] = handleArguments(process.argv.slice(2));
vLog(`json-timeline initiated with options { verbose: ${FLAGS.VERBOSE}, force: ${FLAGS.FORCE} }`);
const adventsPath = checkAdventsDirectory(adventsInput);
const adventsContent = processAdvents(adventsPath);
const outputPath = checkOutputDirectory(outputInput);
writeViewFiles(outputPath);
writeAdvents(`${outputPath}/advents.js`, adventsContent);
vLog('json-timeline finished');
} catch (error) {
vLog(error.message, { level: 'ERROR', verbose: true });
process.exit(1);
}
})();
/**
* @function
* @name handleArguments
* @description Handles CLI Arguments for Paths and Flags
* @param {string[]} args [advents, output, flags] CLI arguments
* @returns {string[]} Advents and Output Paths
* @throws An Error if advents or output are undefined
*/
function handleArguments([advents, output, flags]) {
if (advents === undefined || output === undefined) {
throw new Error('Usage: json-timeline path/to/advents/ path/to/output/ [vf]');
}
FLAGS.VERBOSE = flags !== undefined && flags.includes('v');
FLAGS.FORCE = flags !== undefined && flags.includes('f');
return [path.resolve(advents), path.resolve(output)];
}
/**
* @function
* @name checkAdventsDirectory
* @description Check if the Advents Input Path is an Existing Directory
* @param {string} adventsPath Advents Input Path
* @returns {string} Advents Path
* @throws An Error if the Advents Input Path is not an Existing Directory
*/
function checkAdventsDirectory(adventsPath) {
vLog(`Checking Advents Directory: "${adventsPath}"`);
if (fs.existsSync(adventsPath)) {
if (fs.lstatSync(adventsPath).isDirectory()) {
vLog(`Advents Directory "${adventsPath}" already exists`);
} else {
throw new Error(`Advents Path "${adventsPath}" is not a directory`);
}
} else {
throw new Error(`Advents Path "${adventsPath}" does not exist`);
}
return adventsPath;
}
/**
* @function
* @name processAdvents
* @description Process and Return Advents from Advents Directory
* @param {string} adventsPath Advents Path
* @returns {string} advents.js File Contents
* @throws An Error if failed to load Advents
*/
function processAdvents(adventsPath) {
vLog(`Processing Advents from "${adventsPath}"`);
const { advents, tags } = loadAdvents(adventsPath);
const sortedAdvents = sortAdvents(advents);
const groupedAdvents = groupAdvents(sortedAdvents);
return (
`export const advents=${str(groupedAdvents)};` +
`export const tags=new Map(${str([...tags.entries()])});`
);
}
/**
* @function
* @name str
* @description Wrapper for consistent JSON.stringify
* @param {*} input Input Argument
* @returns {string} Stringified Input
*/
function str(input) {
return JSON.stringify(input);
}
/**
* @function
* @name checkAdventsDirectory
* @description Check and Make a Directory of the Output Input Path
* @param {string} outputPath Output Input Path
* @returns {string} Output Path
* @throws An Error if the Output Input Path Exists but is not a Directory
*/
function checkOutputDirectory(outputPath) {
vLog(`Checking Output Directory: "${outputPath}"`);
if (fs.existsSync(outputPath)) {
if (fs.lstatSync(outputPath).isDirectory()) {
vLog(`Output Directory "${outputPath}" already exists`);
} else {
throw new Error(`Output Path "${outputPath}" is not a directory`);
}
} else {
fs.mkdirSync(outputPath);
vLog(`Created Output Directory "${outputPath}"`);
}
return outputPath;
}
/**
* @function
* @name writeViewFiles
* @description Write files in view/ to Output Directory, overwriting if the
* force flag is true
* @param {string} outputPath Output Path
* @throws An Error if an Output Destination File Path is not a file
*/
function writeViewFiles(outputPath) {
vLog(`Writing View Files to "${outputPath}"`);
const viewsDirectory = path.resolve(__dirname, '../views/');
for (const file of fs.readdirSync(viewsDirectory)) {
const sourcePath = `${viewsDirectory}/${file}`;
const destPath = `${outputPath}/${file}`;
if (fs.existsSync(destPath)) {
if (fs.lstatSync(destPath).isFile()) {
if (FLAGS.FORCE) {
copyFile(sourcePath, destPath);
vLog(`Overwritten file "${destPath}"`);
} else {
vLog(`File "${destPath}" already exists`);
}
} else {
throw new Error(`Output Path "${destPath}" is not a file`);
}
} else {
copyFile(sourcePath, destPath);
vLog(`Created file "${destPath}"`);
}
}
}
/**
* @function
* @name copyFile
* @description Copies Source File to Destination File
* @param {string} sourcePath Path to Source File
* @param {string} destPath Path to Destination File
*/
function copyFile(sourcePath, destPath) {
fs.createReadStream(sourcePath).pipe(fs.createWriteStream(destPath));
}
/**
* @function
* @name writeAdvents
* @description Write advents.js content to Destination File
* @param {string} destPath Destination Path
* @param {string} content Content to write to Destination File
* @throws An Error if the Destination Path is not a file
*/
function writeAdvents(destPath, content) {
vLog(`Writing Advents to "${destPath}"`);
if (fs.existsSync(destPath)) {
if (fs.lstatSync(destPath).isFile()) {
fs.writeFileSync(destPath, content);
vLog(`Overwritten file "${destPath}"`);
} else {
throw new Error(`Output Path "${destPath}" is not a file`);
}
} else {
fs.writeFileSync(destPath, content);
vLog(`Created file "${destPath}"`);
}
}
/**
* @function
* @name vLog
* @description Log a message with Level based on Verbose
* @param {string} message Message to Log
* @param {Object} opts Options for Level and Verbose
*/
function vLog(message, opts) {
const options = {
level: 'INFO',
verbose: FLAGS.VERBOSE,
...opts
};
options.verbose && console.log(`[${options.level}]: ${message}`);
}