pandemics
Version:
Simplified academic writing.
219 lines (200 loc) • 7.67 kB
JavaScript
const fs = require('fs');
const path = require('path');
const fsTools = require('./fs-tools.js');
function splitFormat (format = []) {
const prefix = format.split('.').slice(0, -1).join('.');
const ext = format.split('.').pop();
return { prefix, ext };
}
module.exports = class Instructions {
/* ****************************************************************************
*
***************************************************************************** */
constructor (recipe, format) {
this.recipe = recipe;
/* ====================================================================== */
// Default recipe
/* ====================================================================== */
if (this.recipe.isDefault) {
this.format = format;
this._record = this._loadRecord();
/* ====================================================================== */
// Provided recipe
/* ====================================================================== */
} else {
// see if we can find an unambiguous record
const uniqueRecordFormat = this._uniqueRecordFormat(format);
if (uniqueRecordFormat) {
this.format = uniqueRecordFormat;
this._record = this._loadRecord(this.recipe.fullPath);
// otherwise, see if we can find an unambiguous template
} else {
const uniqueTemplate = this._uniqueTemplate(format);
console.log(uniqueTemplate)
if (uniqueTemplate) {
this.format = uniqueTemplate.format;
this._record = this._loadRecord();
this._record.template = uniqueTemplate.template;
}
// if still nothing, we failed
if (!this._record) {
throw new Error(`Could not find recipe.${format}.json and could not guess template.`);
}
}
}
}
/* ****************************************************************************
* return content of the json instruction file for the requested format
***************************************************************************** */
_loadRecord (recipePath) {
if (!recipePath) {
recipePath = path.join(
__dirname,
'..',
'_defaults'
);
}
// path to the record file
const recordPath = path.join(
recipePath,
`recipe.${this.format}.json`
);
// exit if file does not exist (eg for default)
if (!fs.existsSync(recordPath)) {
return {};
}
// parse and return json
try {
return JSON.parse(fs.readFileSync(recordPath, 'utf8'));
} catch (err) {
throw new Error(`Invalid recipe. Check that ${recordPath} contains correctly defined JSON.`);
}
}
/* ****************************************************************************
* list all recipe.*.json file in the recipe folder
***************************************************************************** */
_listRecordFormats () {
const recipeRgxp = /^recipe\.(.*)\.json$/;
return fsTools.listFiles(
this.recipe.fullPath,
{
type: 'file',
filter: fileName => recipeRgxp.test(fileName)
}
).map((fileName) => fileName.match(recipeRgxp)[1]); // keep only format
}
/* ****************************************************************************
* Return the format for which there is a unique record, using the format
* selector if needed. If there is no record for the requested format, or not
* exactly one for undefined format, this will return undefined
***************************************************************************** */
_uniqueRecordFormat (format) {
// list availble records
let recordFormats = this._listRecordFormats();
// filter records ending with requested format
if (format) {
recordFormats = recordFormats.filter(fmt => fmt.endsWith(format));
}
// if nested formats, keep only shortest
let formatLengths = recordFormats.map(fmt => fmt.split('.').length);
recordFormats = recordFormats.filter(fmt => fmt.split('.').length == Math.min(...formatLengths))
// if unique record left, return it
if (recordFormats.length === 1) {
return recordFormats[0];
}
}
/* **************************************************************************
* list all potential templates in the recipe folder
*************************************************************************** */
_listTemplates(format) {
// map target formats to their possible templates.
const formatMap = [
{ ext: ['pdf','tex','latex'],
templateExt: ['.tex', '.latex', '.xelatex'] },
{ ext: ['docx'],
templateExt: ['.doc', '.docx'] },
{ ext: ['html'],
templateExt: ['.html', '.html5'] }
];
/* If a format is provided, list only templates for it
* ---------------------------------------------------------------------- */
if (format) {
// get requested format and potential prefix
const {prefix, ext} = splitFormat(format);
// list potential template extension for the requested format and
// recombine with the prefix
const templateFormats = formatMap
.find(fmtMap => fmtMap.ext.includes(ext))
.templateExt
.map(e => [prefix, e].join(''));
// list all files matching the prefixed template format
const templates = fsTools.listFiles(
this.recipe.fullPath,
{
type: 'file',
filter: fileName => templateFormats.some(fmt => fileName.endsWith(fmt))
}
);
// return both format and found templates
return [{format, templates}];
/* If no specific format requested, return all potential templates and their
* associate formats
* ---------------------------------------------------------------------- */
} else {
return formatMap.map((fmtMap) => this._listTemplates(fmtMap.ext[0])[0]);
}
}
/* ***************************************************************************
* Return a pair of format and template, if this pair can be identified
* unambigusouly in the recipe folder, eventually restricting the search to the
* requested format. If suche pair does not exist (no or too many templaetes)
* this function will return undefined
*************************************************************************** */
_uniqueTemplate (format) {
let candidates = this._listTemplates(format)
.filter(c => c.templates.length === 1)
if (candidates.length === 1) {
return {
format: candidates[0].format,
template: candidates[0].templates[0]
}
}
}
/* ***************************************************************************
* Getters, nothing to see here
*************************************************************************** */
get extension () {
return splitFormat(this.format).ext;
}
get prefix () {
return splitFormat(this.format).prefix;
}
get template () {
if (this._record.template) {
return path.join(
this.recipe.fullPath,
this._record.template
);
};
}
get options () {
if (typeof this._record.options === 'string') {
return [this._record.options];
} else if (Array.isArray(this._record.options)){
return this._record.options;
} else if (this._record.options === undefined){
return;
} else {
throw new Error(`Invalid recipe instructions options.`);
}
}
get filters () {
return (this._record || {}).filters;
}
get preprocessing () {
return (this._record || {}).preprocessing;
}
toString () {
return `format: ${this.prefix} + ${this.extension}`;
}
};