UNPKG

property-4-json

Version:

Automatically enrich files containing JavaScript objects with new properties

269 lines (229 loc) 8.14 kB
const fs = require('fs'); const path = require('path'); const minimist = require('minimist'); /** * Library for inserting new properties (i.e. IDs) into JSON-like structures * * @example * Run using "node ./index [options] with following option possibilities: * --folder=folder/subfolder * --filetype=.json * --prop=name * --quotation=none * --stopwords=import,export * --startvalue=100 * --delimiter='=' * --endline=semicolon * * TODO read only one file by filename * * @author Jan Suwart * @licence MIT */ class Poperty4Json { constructor(args) { if (!args.folder) { throw new Error('Please provide a foldername relative to your project root directory'); } if (args.depth > 1) { console.warn('Depth of >1 is experimental and not yet recommended - use with caution'); } // Configuration variables this.folder = args.folder; this.prop = args.prop || 'id'; this.filetype = args.filetype || '.json'; this.stopwords = args.stopwords || null; this.delimiter = args.delimiter ? args.delimiter.replace(/'/g, '') : ':'; this.endline = args.endline === 'semicolon' ? ';' : ','; this.startvalue = args.startvalue || 0; this.depth = args.depth || 1; switch (args.quotation) { case ('none'): this.quotation = ''; break; case ('single'): this.quotation = "'"; break; default: this.quotation = '"'; break; } // Private variables this.count = this.startvalue; this.linesChanged = 0; // @see https://regexr.com/4em88 // BASE: \{[^}{]*\} // PREFIX: \{(?:[^}{]+| // POSTFIX: )*\} this.regex = new RegExp( (this.stopwords ? `^\\s*(?!${this.stopwords.split(',').join('|')}).*?` : '') + `\{(?:[^}{]+|\{(?:[^}{]+|\{(?:[^}{]+|\{(?:[^}{]+|\{(?:[^}{]+|\{[^}{]*\})*\})*\})*\})*\})*\}`, 'gm' ); // console.log('REGEX =>', this.regex); } /** * Define path and filename, read the folder and filter for files, start splitting algorithm */ parseFiles() { const workingPath = path.resolve(process.cwd(), this.folder); console.log('Running property-4-json...'); this.readDirectory(this.folder, this.filetype).then(files => { if (!files.length) { console.log(`No files inside the folder "${workingPath}" matched the file type "${this.filetype}"`); console.log('No files modified'); return; } const fullFilePath = files.map(file => { return path.resolve(workingPath, file); }); // console.log('fullFilePath =>', fullFilePath); fullFilePath.forEach((fileName) => { let file = fs.readFileSync(fileName, 'utf8'); // console.log('\nProcess file', fileName, '\n'); const output = this.splitStingAndIterate(file, this.depth); if (file != output) { this.writeFile(fileName, output); } else { console.log('No matches in', fileName, 'file remains unchanged'); } }); }).catch((error) => { console.log('Could not read directory because of error', error); console.log('No files modified'); }); } /** * Splits a file into several matches of JSON-like objects * @param {string} file - full path and name of current file * @param {number} depth - at which nesting level should the property be added * @return {string} the modified file as string */ splitStingAndIterate(file, depth) { const existingPropRe = new RegExp( // @see https://regexr.com/4em88 `(${this.quotation}${this.prop}${this.quotation}${this.delimiter})\\s*([^},]+)*?(,|\\s|\})` ); const matches = this.getAllMatchesByRe(file, this.regex); this.linesChanged = 0; // console.log('EXISTING REGEX =>', existingPropRe); matches.forEach((match) => { const matchedObject = match[0]; // console.log('=> match', i, '- depth', depth, '-', matchedObject); if (/^{\n*\s*}$/.test(matchedObject)) { // The matched object is an empty leaf object file = file.replace( matchedObject, matchedObject.replace(/^{(\s*)}/m, `{$1${this.quotation}${this.prop}${this.quotation}${this.delimiter} ${this.count}$1}`) ); this.linesChanged++; } else { // The matched object includes properties switch (depth) { case 1: // The property is already inside this object, update it if (existingPropRe.test(matchedObject)) { file = file.replace( matchedObject, matchedObject.replace( existingPropRe, `$1 ${this.count}$3` ) ); this.linesChanged++; } else { // The property is not yet in this object, create it file = file.replace( matchedObject, matchedObject.replace( // @see https://regexr.com/4emol /^(.*?\s*){(\s*)/m, `$1{$2${this.quotation}${this.prop}${this.quotation}${this.delimiter} ${this.count}${this.endline}$2` ) ); this.linesChanged++; } // console.log('=> REPLACED string', file); break; case 2: // file = file.replace(matchedObject, matchedObject.replace(/^(?:{)[^{]*(\s*)({)(\s*)((?:.|\s)*?)(?:})$/, `{"id": ${this.count},`)); // Remove the outer object brackets const removedOuterBrackets = matchedObject.replace(/^{([\S\s]*)}$/, '$1'); console.log('removedOuterBrackets =>', removedOuterBrackets); // console.log('innerMatches =>', innerMatches[0]); // const innerMatches = this.getAllMatchesByRe(matchedObject, /{/g); // console.log('=> innerMatches', innerMatches); // RECURSION file = this.splitStingAndIterate(removedOuterBrackets, 1); break; default: break; } } this.count++; }); return file; } /** * Reads the directory and returns array of files that match filetype (i.e. .json) * @param {string} dirname - full directory name relative to the working directory of the script * @param {string} filetype - substring that the file should be tested for, i.e. '.json' */ readDirectory(dirname, filetype) { console.log('Reading folder "' + dirname + '" and filtering for type "' + filetype + '"'); return new Promise((resolve, reject) => { let fileList = []; fs.readdir(dirname, (error, files) => { if (error) { reject(error); } else { files.forEach((file) => { if (file.includes(filetype)) { console.log('Adding', file); fileList.push(file); } }); resolve(fileList); } }); }); } /** * Overwrites the file into the same folder * @param {string} fullFileName - folder and filename * @param {string} outputString - the output that should be written */ writeFile(fullFileName, outputString) { console.log('Writing', this.linesChanged, 'changes to file', fullFileName); fs.writeFile(fullFileName, outputString, 'utf8', err => { if (err) { console.log('Error writing file', err); } }); } /** * @param {string} string - input string * @param {RegExp} regexp - regular expression * @return {Array} array of regex matches */ getAllMatchesByRe(string, regexp) { let matches = []; let match; if (regexp.global) { regexp.lastIndex = 0; } else { regexp = new RegExp(regexp.source, 'g' + (regexp.multiline ? 'm' : '')); } while (match = regexp.exec(string)) { matches.push(match); if (regexp.lastIndex === match.index) { regexp.lastIndex++; } } return matches; } } const argv = minimist(process.argv.slice(2)); // Create the instance and set the options const prop4json = new Poperty4Json(argv); prop4json.parseFiles();