This commit is contained in:
Tutur33
2023-11-24 22:35:41 +01:00
parent 3c0b507a93
commit 7644b2a0f7
45165 changed files with 4803356 additions and 3 deletions
+22
View File
@@ -0,0 +1,22 @@
The MIT License (MIT)
Copyright (c) 2015 paulbennet
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
+103
View File
@@ -0,0 +1,103 @@
# PropINI
Read, Write, Encode, Decode & Manage INI/Property files with ease.
#
##### Install
#
> npm install prop-ini
#
##### Usage
#
1] Require the module in your code
```javascript
var PropINI = require("prop-ini");
```
#
2] Create new instance for PropINI
```javascript
var piTheOne = PropIni.createInstance();
```
#
3] Decode/Read data
```javascript
var iniData = piTheOne.decode(<config>);
```
`<config>` is an object with following props
- `config.data` - Pass the INI/Properties format data directly as a string
- `config.file` - Tell the function to get the data from this file
- `config.charset` - Tell function to read file by this encoding
- `config.lineSeparator` - Tell function to split lines based on this char
The returned `iniData` is an object with following props
- `iniData.sections` - Object containing sections as top-level nodes and properties of appropriate sections as child nodes for them. `_global` is a special section where properties without any section are available
- `iniData.sectionOrder` - Array of section names in exact order matching the parsed data. `_global` will be the first entry
On successfull decode the parsed values will be stored with the PropINI instance as properties. You may access them using `piTheOne.sections` and `piThOne.sectionOrder`. Handle with care !
#
4] Encode/Write data
```javascript
var iniString = piTheOne.encode(<config>);
```
`<config>` is an object with following props
- `config.sortSections` - Sort section names using `String.prototype.localeCompare`
- `config.sortKeys` - Sort properties/keys under each section with `localeCompare`
- `config.file` - Tell function write the encoded data to this file
- `config.charset` - Tell function to write file with this encoding
- `config.lineSeparator` - Tell function to join lines based on this char
The returned `iniString` is a string type variable containg INI/Property file format data
#
##### Other convenience functions (After parsing data ...)
#
1] Get section names as an Array
```javascript
var sections = piTheOne.getSections();
```
- `sections` is an Array containing all sections in the parsed instance data
- `_global` is excluded
- Parsed section order is maintained
#
2] Get anything you want !
```javascript
var data = piTheOne.getData(<section>, <key>);
```
- `section` is the name of the section. In absense of `key` whole section's object with child properties will be returned
- `key` is the name of the property under some section. In absense of `section`, `_global` section will be used. String value of a key will be returned
- In absense of both `section` & `key`, whole data will be returned as an object with `sections`, `sectionOrder` as properties
#
3] Set anything you want !
```javascript
var success = piTheOne.addData(<value>, <section>, <key>);
```
- `section` is the name of the section. `value` should be given as an object in this case
- `key` is the name of the property under some section. In absense of `section`, `_global` section will be used. `value` should be of String type in this case
- In absense of both `section` & `key`, you can set the whole data for the instance. An object with `sections`, `sectionOrder` as properties should be given for `value`. `sections` property should have `_global` child property.
#
4] Remove anything you want !
```javascript
var success = piTheOne.removeData(<section>, <key>);
```
- `section` is the name of the section. In absense of `key` whole section will be removed.
- `key` is the name of the property under some section. In absense of `section`, `_global` section will be used.
- In absense of both `section` & `key`, whole instance data will be reset to empty state.
#
##### And, one more thing ...
#
- Since the data is exposed as instance properties you can directly process them as you wish. But please respect the format PropINI expects the data to be in.
- `piTheOne.sections` should always be an object
- `piTheOne.sectionOrder` should always be an array and should be in sync with `piTheOne.sections` data
- `piTheOne.sections` should always have a child object `_global`
#
##### LEGAL STUFF
#
- Check project's license info
+30
View File
@@ -0,0 +1,30 @@
{
"name": "prop-ini",
"version": "0.0.2",
"description": "Reads, Writes, Manages INI/Property files",
"main": "prop-ini.js",
"directories": {
"test": "test"
},
"dependencies": {
"extend": "^3.0.0"
},
"devDependencies": {},
"scripts": {
"test": "test/test.js"
},
"keywords": [
"propertyfile",
"ini"
],
"author": "Paul Bennet",
"license": "MIT",
"repository": {
"type": "git",
"url": "git+https://github.com/paulbennet/propini.git"
},
"bugs": {
"url": "https://github.com/paulbennet/propini/issues"
},
"homepage": "https://github.com/paulbennet/propini#readme"
}
+603
View File
@@ -0,0 +1,603 @@
/* 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));
};
}());
+15
View File
@@ -0,0 +1,15 @@
/* globals require,console */
var PropINI = require("../prop-ini.js");
var instance = PropINI.createInstance({});
instance.decode({
file: "./sample/test.properties"
});
instance.encode({
sortSections: true,
sortKeys: true,
file: "./sample/test_new.properties"
});