Files
portfolio2023/build/node_modules/prop-ini/prop-ini.js
T
2023-11-24 22:35:41 +01:00

604 lines
17 KiB
JavaScript

/* global require,exports */
/**
* @module {PropINI} NodeJS module/utility for reading/writing/managing INI/Property files
* @author Paul Bennet
*/
(function () {
"use strict";
var _extend = require("extend");
var _fs = require("fs");
var GLOBAL_SECTION = "_global";
var SECTION_REGEX = /^(?:\[(.+)\])$/i;
var PROPERTY_REGEX = /(?:([^=]+)\=(.+))/i;
var DEFAULT_LINE_SEPARATOR = "\n";
/**
* Utility function which calls 'extend' module to clone a object
* Note: Private function
* @function
* @param {object} [sourceObject={}] The object to be cloned. Can be Object/Array.
* @returns {object} The cloned object will be returned
*/
var _cloneObject = function (sourceObject) {
sourceObject = sourceObject || {};
if (sourceObject.constructor !== Array && typeof sourceObject !== "object") {
return sourceObject;
}
var newObject = (sourceObject.constructor === Array) ? [] : {};
return _extend(true, newObject, sourceObject);
};
/**
* Utility function which writes given data to a file
* Note: Private function
* @function
* @param {object} [args={file,data}] Object hash which has the path to output 'file', encoding 'charset' & the text 'data' to be written on the file
* @returns {boolean} On success/failure returns true/false
*/
var _writeFile = function (args) {
args = args || {};
args.file = args.file || "";
args.charset = args.charset || "utf-8";
args.data = args.data || "";
try {
_fs.writeFileSync(args.file, args.data, args.charset);
return true;
} catch (err) {}
return false;
};
/**
* Utility function which reads a file a returns the text data
* Note: Private function
* @function
* @param {object} [args={file}] Object hash which has the path to output 'file' & encoding 'charset'
* @returns {string} On sucess/failure returns string-data/undefined
*/
var _readFile = function (args) {
args = args || {};
args.file = args.file || "";
args.charset = args.charset || "utf-8";
var data;
try {
data = _fs.readFileSync(args.file, args.charset);
} catch (err) {}
return data;
};
/**
* Utility function which checks if a line of text from a property file is a comment
* Note: Private function
* @function
* @param {string} [line=""] A single line of text
* @returns {boolean} If line is a comment returns true, otherwise false
*/
var _isComment = function (line) {
line = line || "";
line = line.trim();
if (line.charAt(0) === "#") {
return true;
}
return false;
};
/**
* Utility function which checks if a line of text from a property file is empty
* Note: Private function
* @function
* @param {string} [line=""] A single line of text
* @returns {boolean} If line is empty returns true, otherwise false
*/
var _isEmpty = function (line) {
line = line || "";
line = line.trim();
if (line.length === 0) {
return true;
}
return false;
};
/**
* Utility function to parse a 'section' info from a line of text from a property file
* Note: Private function
* @function
* @param {string} [line=""] A single line of text
* @returns {string} If section info is found returns 'section' as string, otherwise undefined is returned
*/
var _getSection = function (line) {
line = line || "";
line = line.trim();
var section;
SECTION_REGEX.lastIndex = 0;
var result = SECTION_REGEX.exec(line);
if (result && result[1]) {
section = result[1];
}
return section;
};
/**
* Utility function which checks if a line of text from a property file is a section
* Note: Private function
* @function
* @param {string} [line=""] A single line of text
* @returns {boolean} If line is a section returns true, otherwise false
*/
var _isSection = function (line) {
var section = _getSection(line);
if (section) {
return true;
}
return false;
};
/**
* Utility function to parse a 'property' info from a line of text from a property file
* Note: Private function
* @function
* @param {string} [line=""] A single line of text
* @returns {object} If property info is found returns an object with 'key', 'value', otherwise undefined is returned
*/
var _getProperty = function (line) {
line = line || "";
PROPERTY_REGEX.lastIndex = 0;
var result = PROPERTY_REGEX.exec(line);
var property;
if (result && result[1] && result[2]) {
property = {
key: (result[1]).trim(),
value: (result[2])
};
}
return property;
};
/**
* Utility function which checks if a line of text from a property file is a property
* Note: Private function
* @function
* @param {string} [line=""] A single line of text
* @returns {boolean} If line contains a property definition returns true, otherwise false
*/
var _isProperty = function (line) {
var property = _getProperty(line);
if (property) {
return true;
}
return false;
};
/**
* Utility funtion which finds the 'type' and appropriate 'data' from a line of text from a property file
* Note: Private function
* @function
* @param {string} line A single line of text
* @returns {object} Returns an object having 'type', 'data' keys. 'type' will have values from [unknown, comment, empty, section, property]. 'data' may contain section or {key, value} property object
*/
var _getLineType = function (line) {
var result = {
type: "unknown",
data: undefined
};
if (_isComment(line)) {
result.type = "comment";
} else if (_isEmpty(line)) {
result.type = "empty";
} else if (_isSection(line)) {
result.type = "section";
result.data = _getSection(line);
} else if (_isProperty(line)) {
result.type = "property";
result.data = _getProperty(line);
}
return result;
};
/**
* Parses INI/Property file format string data and returns appropriate JS objects
* Note: Private function
* @function
* @param {object} [config={data}] Object hash having,
* 1. 'data' - string text.
* 2. 'lineSeparator' - default is "\n"
* @returns {object} On successfull parse returns an Object having,
* 1. 'sections' - section 'titles' at top level and all properties within them as child properties for each section. A special section called '_global' will have global properties which don't come under any section.
* 2. 'sectionOrder' - An array containing name of sections in the order they were defined in the ini/properties file
*/
var _parseINIFormatData = function (config) {
config = config || {};
var data = config.data || "";
var lineSep = config.lineSeparator || DEFAULT_LINE_SEPARATOR;
var lines = data.split(lineSep);
var sections = {};
sections[GLOBAL_SECTION] = {};
var sectionOrder = [GLOBAL_SECTION];
var globalSection = sections[GLOBAL_SECTION];
var currentSection = globalSection;
lines.forEach(function (line) {
var lineType = _getLineType(line);
if (lineType.type === "section") {
sections[lineType.data] = {};
currentSection = sections[lineType.data];
sectionOrder.push(lineType.data);
} else if (lineType.type === "property") {
currentSection[lineType.data.key] = lineType.data.value;
}
});
return {
sections: sections,
sectionOrder: sectionOrder
};
};
/**
* Dumps JS objects, which was parsed by this module, back to INI/Property file format string data
* Note: Private function
* @function
* @param {object} [args={data}] Object hash having,
* 1. 'data' - Object with child properties 'sections', 'sectionOrder'.
* -- 'sections' object should have '_global' section
* -- 'sectionOrder' array is required
* 2. 'lineSeparator' - default is "\n".
* 3. 'sortSections' - true/false - Sections will be sorted using 'localCompare'
* 4. 'sortKeys' - true/false - Keys will be sorted using 'localCompare'
* @returns {string} On successfull dump, INI/Property format string data will be returned
*/
var _dumpINIFormatData = function (args) {
args = args || {};
var data = args.data || {};
var lineSep = args.lineSeparator || DEFAULT_LINE_SEPARATOR;
if (!data.sections || !data.sections[GLOBAL_SECTION] || !data.sectionOrder) {
return;
}
data.sections = _cloneObject(data.sections);
data.sectionOrder = _cloneObject(data.sectionOrder);
var lines = [];
var _alphaSorter = function (op1, op2) {
return op1.localeCompare(op2);
};
if (args.sortSections) {
data.sectionOrder = data.sectionOrder.sort(_alphaSorter);
data.sectionOrder.splice(data.sectionOrder.indexOf(GLOBAL_SECTION), 1);
data.sectionOrder.unshift(GLOBAL_SECTION);
}
data.sectionOrder.forEach(function (section, index) {
var isLast = (data.sectionOrder.length === index + 1);
if (section !== GLOBAL_SECTION) {
lines.push(["[", section, "]"].join(""));
}
var sectionData = data.sections[section];
var sectionKeys = Object.keys(sectionData);
if (args.sortKeys) {
sectionKeys = sectionKeys.sort(_alphaSorter);
}
sectionKeys.forEach(function (key) {
lines.push([key, sectionData[key]].join("="));
});
if (!isLast) {
lines.push("");
}
});
lines = lines.join(lineSep);
return lines;
};
// <class> PropINI
/**
* PropINI - Wrapper class for INI/Property file decode/encode functionalities
* Note: Private class. Exported via factory function.
* @class
* @constructs PropINI
* @param {object} [config={}] Initialization config data. Currently not used.
*/
var PropINI = function (config) {
config = config || {};
this.sectionOrder = [];
this.sections = {};
this.sections[GLOBAL_SECTION] = {};
};
/**
* Encodes JS object data into INI/Property format string data
* @function
* @param {object} [config={lineSeparator,sortSections,sortKeys,file,charset}] Object hash which has encode config
* 1. 'lineSeparator' - default "\n". Output data will be line separated by the given character
* 2. 'sortSections' - sort sections with 'localeCompare'
* 3. 'sortKeys' - sort keys under each section with 'localCompare'
* 4. 'file' - Optional output 'file'. Data will be written to file
* 5. 'charset' - default "utf-8" - File will be written with this encoding
* @returns {string} INI/Property formatted string data
*/
PropINI.prototype.encode = function (config) {
config = config || {};
var dumpedData = _dumpINIFormatData({
data: {
sections: this.sections,
sectionOrder: this.sectionOrder
},
lineSeparator: config.lineSeparator,
sortSections: config.sortSections,
sortKeys: config.sortKeys,
sortValues: config.sortValues
});
if (config.file) {
_writeFile({
file: config.file,
data: dumpedData,
charset: config.charset
});
}
return dumpedData;
};
/**
* Decodes INI/Property format string data into JS object data
* @function
* @param {object} [config={data,file,charset}] Object hash which has encode config
* 1. 'lineSeparator' - default "\n". Input data will be line splitted by the given character
* 2. 'file' - Optional input 'file'. Data will be read from this file
* 3. 'charset' - default "utf-8" - File will be read with this encoding
* @returns {object} Object hash containing 'sections' & 'sectionOrder' will be returned. Also data will be saved to this class instance
*/
PropINI.prototype.decode = function (config) {
config = config || {};
var data = config.data || "";
var file = config.file || "";
if (file) {
data = _readFile({
file: file,
charset: config.charset
});
}
var parsedData = _parseINIFormatData({
data: data,
lineSeparator: config.lineSeparator
});
this.sections = parsedData.sections;
this.sectionOrder = parsedData.sectionOrder;
return {
sections: _cloneObject(this.sections),
sectionOrder: _cloneObject(this.sectionOrder)
};
};
/**
* Removes a particular key from a section or a whole section or the whole data from this instance
* - If only 'section' is provided. Whole section will be removed. If global section is given it will be reset.
* - If only 'key' is provided. Key from global section will be removed
* - If no section, key is provided, whole data will be reset
* @function
* @param {string} section Section to delete
* @param {string} key Key/Property to delete
* @returns {boolean} On success/failure returns true/false
*/
PropINI.prototype.removeData = function (section, key) {
var targetSection;
if (!key && !section) {
this.sections = {};
this.sections[GLOBAL_SECTION] = {};
this.sectionOrder = [GLOBAL_SECTION];
} else if (key && section) {
targetSection = this.sections[section];
delete targetSection[key];
} else if (section) {
if (section === GLOBAL_SECTION) {
this.sections[section] = {};
} else {
delete this.sections[section];
this.sectionOrder.splice(this.sectionOrder.indexOf(section), 1);
}
} else if (key) {
targetSection = this.sections[GLOBAL_SECTION];
delete targetSection[key];
}
return true;
};
/**
* Adds a key to a section, a whole section or the replaces the whole data
* - If both section name, key name is given - value should be a string
* - If section name is given - value should be an object of key-value pairs
* - If key name is given - value should be a string, and key will be added to global section
* - If no section, key is given - value should be an object containing 'sections', 'sectionOrder' properties. 'sections' should have a '_global' property.
* @function
* @param {object/string} value Based on the values of section & key the type of this param will vary
* @param {string} section Section name
* @param {string} key Key/Property name
* @returns {boolean} On success/failure returns true/false
*/
PropINI.prototype.addData = function (value, section, key) {
var targetSection;
if (key) {
if (typeof key !== "string" || typeof value !== "string") {
return false;
}
if (section && section !== GLOBAL_SECTION) {
this.sections[section] = this.sections[section] || {};
targetSection = this.sections[section];
} else {
targetSection = this.sections[GLOBAL_SECTION];
}
targetSection[key] = value;
} else if (section) {
if (typeof value !== "object") {
return false;
}
if (section && section !== GLOBAL_SECTION) {
this.sections[section] = this.sections[section] || {};
this.sections[section] = value;
if (this.sectionOrder.indexOf(section) === -1) {
this.sectionOrder.push(section);
}
} else {
this.sections[GLOBAL_SECTION] = value;
}
} else {
if (typeof value !== "object") {
return false;
}
if (!value.sections || !value.sections[GLOBAL_SECTION]) {
return false;
}
this.sections = value.sections;
if (!value.sections.sectionOrder) {
value.sections.sectionOrder = Object.keys(value.sections);
value.sections.sectionOrder.splice(value.sections.sectionOrder.indexOf(GLOBAL_SECTION), 1);
value.sections.sectionOrder.unshift(GLOBAL_SECTION);
}
}
return true;
};
/**
* Returns data from the current instance for the passed params
* - If section, key is given - string value of the key will be returned
* - If only section is given - Object of properties of that section is returned
* - If only key is given - string value of key from global section will be returned
* - If none is given - Object with 'sections', 'sectionOrder' properties will be given
* @function
* @param {string} section Name of section
* @param {string} key Name of key/property
* @returns {object/string} Based on the input types output type will vary
*/
PropINI.prototype.getData = function (section, key) {
var data = this.sections;
if (section) {
data = _cloneObject(data[section]);
}
if (key) {
if (!section) {
section = GLOBAL_SECTION;
data = _cloneObject(data[section]);
}
data = data[key];
}
if (!section && !key) {
data = {
sections: _cloneObject(this.sections),
sectionOrder: _cloneObject(this.sectionOrder)
};
}
return data;
};
/**
* Returns section names as an Array in order as same in the sectionOrder property of this instance
* - Global section is excluded
* @function
* @returns {array} Array of section names
*/
PropINI.prototype.getSections = function () {
var sectionNames = _cloneObject(this.sectionOrder);
var globalIndex = sectionNames.indexOf(GLOBAL_SECTION);
sectionNames.splice(globalIndex, 1);
return sectionNames;
};
// <class> PropINI
/**
* Factory function to create instance for PropINI class
* Note: Module Export
* @function
* @param {object} config Initialize object for the constructor
* @returns {object} new instance of PropINI class
*/
exports.createInstance = function (config) {
return (new PropINI(config));
};
}());