Source: cjson.js

"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    var desc = Object.getOwnPropertyDescriptor(m, k);
    if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
      desc = { enumerable: true, get: function() { return m[k]; } };
    }
    Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
    Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
    o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
    if (mod && mod.__esModule) return mod;
    var result = {};
    if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
    __setModuleDefault(result, mod);
    return result;
};
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.Cjson = void 0;
const file_1 = require("./utils/file");
const path = __importStar(require("path"));
const is_1 = require("./utils/is");
const keywords_1 = __importDefault(require("./utils/keywords"));
const json_1 = require("./utils/json");
const errors_1 = require("./utils/errors");
const refinery_1 = require("./utils/refinery");
/**
 * Coded JSON is an extended format of JSON formatted data storage, which gives
 * you more previledge to organize data into more structured format.
 *
 * Here is an example for `CJSON` format:
 * @example
 *
 * {
    "source": $import "./source.json",
    "target": {
        "fruit": "Apple",
        "size": "Large",
        "color": "Red",
        "secColor": $.target.color,
        "colorList": [ $.target.color, $.target.secColor ],
        // You can add comments like this
        "digitCheck": 1.5,
        "digitImport": $.target.digitCheck,
        "digitArrayImport": [ $.target.digitCheck, $.target.digitImport ]
    }
}
 *
 *
 * The above `CJSON` snipped will be deserialized in JSON format and can be used
 * as same as other JSON files.
 *
 * For other details, please refer to official page: {@link https://subhendushekhar.github.io/cjson/}
 */
class Cjson extends is_1.Is {
    // private obj: any;
    filePath;
    content = "";
    json = undefined;
    isContentCJson;
    isContentJson = (isFilePath) => { return (0, json_1.isContentJson)(this.content, isFilePath); };
    /**
     * Coded JSON is an extended format of JSON formatted data storage, which gives
     * you more previledge to organize data into more structured format.
     *
     * Here is an example for `CJSON` format:
     * @example
     *
     * {
        "source": $import "./source.json",
        "target": {
            "fruit": "Apple",
            "size": "Large",
            "color": "Red",
            "secColor": $.target.color,
            "colorList": [ $.target.color, $.target.secColor ],
            // You can add comments like this
            "digitCheck": 1.5,
            "digitImport": $.target.digitCheck,
            "digitArrayImport": [ $.target.digitCheck, $.target.digitImport ]
        }
    }
     *
     *
     * The above `CJSON` snipped will be deserialized in JSON format and can be used
     * as same as other JSON files.
     *
     * For other details, please refer to official page: {@link https://subhendushekhar.github.io/cjson/}
     *
     * @param content Can be path to the CJSON file. In this case the second param can be optional
     * @param isContentCJson Set this true if you are passing raw CJSON content as `content`
     */
    constructor(content, isContentCJson) {
        super();
        this.obj = undefined;
        if ((0, file_1.isAbsolutePath)(content) && isContentCJson)
            throw (0, errors_1.CJSONContentInsteadOfFilePath)("CJSON flag is true, but got file path");
        if (isContentCJson) {
            this.filePath = __dirname;
            this.content = content;
            this.isContentCJson = isContentCJson;
        }
        else {
            this.filePath = content;
            this.content = (0, file_1.read)(this.filePath);
        }
        this.decodeKeywords();
    }
    /**
     * Root function for decoding keywords
     * Need to improve performance. `v1.0.0`
     */
    decodeKeywords() {
        var isChanged = false;
        while (true) {
            isChanged = false;
            if (this.isImport(this.content)) {
                this.content = this.decodeImport(this.content, path.dirname(this.filePath));
                isChanged = true;
            }
            if (this.isSingleLineComment(this.content)) {
                this.decodeSingleLineComment(this.content);
                isChanged = true;
            }
            this.content = this.decodeRuntimeKeys(this.content);
            if (!isChanged)
                break;
        }
        if (this.isRelativeJPath(this.content)) {
            this.content = this.decodeRelativePaths(this.content);
            isChanged = true;
        }
    }
    refineObj(content) {
        if (content)
            this.content = content;
        this.json = new json_1.Json(this.content, false);
        this.obj = JSON.parse(this.content);
    }
    /**
     * Import functions path to relative file is deocded.
     * Modifies `content`
     */
    decodeRelativePaths(content) {
        var uniqueKeys = content.match(keywords_1.default.relativeJPathRegex)?.filter((value, index, array) => { return array.indexOf(value) === index; });
        if (uniqueKeys)
            content = (0, refinery_1.refineRelativePaths)(content, uniqueKeys);
        this.refineObj(content);
        return content;
    }
    /**
     * Deserializes the keywords.
     * @returns `JSON` if no errors. Else `undefined`
     */
    deserialize() {
        this.content = this.decodeRelativePathValues(this.content);
        this.refineObj(this.content);
        return this.obj;
    }
    /**
     * Returns file path from `import` keyword
     * @param lineItem Comma separated line item in string
     * @returns File path in string
     */
    getFilePath(lineItem) {
        return lineItem.split(keywords_1.default.importKey)[1].split("\"")[0];
    }
    /**
     * Decodes `import` keyword
     * @param lineItem Comma separated line item in string
     */
    decodeImport(content, curPath) {
        var filePath = this.getFilePath(content);
        var importFilePath;
        if ((0, file_1.isAbsolutePath)(filePath))
            importFilePath = filePath;
        else if (!(0, file_1.isAbsolutePath)(filePath) && this.isContentCJson)
            throw (0, errors_1.AbsolutePathConstraintError)("Only absolute path is supported in import statements");
        else
            var importFilePath = path.join(curPath, filePath);
        var innerContent = (0, file_1.read)(importFilePath);
        var qr = "";
        if (this.isImport(innerContent))
            qr = this.decodeImport(innerContent, path.dirname(importFilePath));
        else
            qr = innerContent;
        content = content.replace(keywords_1.default.importKey + filePath + "\"", qr);
        if (this.isImport(content))
            content = this.decodeImport(content, curPath);
        return content;
    }
    /**
     * Identifies comment lines. Can identify multiple lined comments
     * @param lineItem Comma separated line item in string
     */
    decodeSingleLineComment(lineItem) {
        let lineSplit = lineItem.split("\r\n");
        for (let i = 0; i < lineSplit.length; i++) {
            if (lineSplit[i].trim() !== "" && lineSplit[i].trim().startsWith(keywords_1.default.singleLineComment))
                this.content = this.content.replace(lineSplit[i], "");
        }
    }
    decodeRuntimeVals(content, uniqueKeys, injectingObj) {
        if (uniqueKeys !== undefined) {
            content = (0, refinery_1.refineRuntimeVals)(content, uniqueKeys);
            uniqueKeys?.map(eachKey => {
                eachKey = eachKey.split("<")[1].split(">")[0];
                let keyRegex = new RegExp("<-" + eachKey + "->", 'g');
                if (typeof injectingObj[eachKey] === "string")
                    content = content.replace(keyRegex, injectingObj[eachKey]);
                else {
                    keyRegex = new RegExp("\"<-" + eachKey + "->\"", 'g');
                    content = content.replace(keyRegex, JSON.stringify(injectingObj[eachKey]));
                }
            });
        }
        return content;
    }
    /**
     * Use this for injecting variable at runtime.
     *
     * Value need to be replaced can be stored with a key like `<key>`.
     *
     * Pass the value as json object in `injectingObj`
     * @param injectingObj Runtime values to be injected in json format
     * @returns `JSON` if no errors. Else `undefined`
     */
    inject(injectingObj) {
        let content = this.content;
        var uniqueKeys = this.content.match(keywords_1.default.decodedRuntimeKeys)?.filter((value, index, array) => { return array.indexOf(value) === index; });
        uniqueKeys = uniqueKeys?.flatMap(eachElem => eachElem.split("<-")[1].split("->")[0]);
        var injectObjKeys = Object.keys(injectingObj);
        for (let i = 0; i < injectObjKeys.length; i++) {
            if (uniqueKeys?.includes(injectObjKeys[i])) {
                if (typeof injectingObj[injectObjKeys[i]] === "boolean" ||
                    typeof injectingObj[injectObjKeys[i]] === "bigint" ||
                    typeof injectingObj[injectObjKeys[i]] === "number")
                    content = content.replace(new RegExp("\"<-" + injectObjKeys[i] + "->\"", "g"), injectingObj[injectObjKeys[i]]);
                else if (typeof injectingObj[injectObjKeys[i]] === "object")
                    content = content.replace(new RegExp("\"<-" + injectObjKeys[i] + "->\"", "g"), JSON.stringify(injectingObj[injectObjKeys[i]]));
                else
                    content = content.replace(new RegExp("<-" + injectObjKeys[i] + "->", "g"), injectingObj[injectObjKeys[i]]);
                if (this.decodedRuntimeKeyList !== undefined)
                    this.removeFromStringArray(this.decodedRuntimeKeyList, injectObjKeys[i]);
                else
                    console.error("Cannot update Base.decodedRuntimeKeyList as it is undefined");
            }
        }
        if (this.content.match(keywords_1.default.decodedRuntimeKeys)?.filter((value, index, array) => { return array.indexOf(value) === index; }).length !== 0)
            console.warn("Still some runtime keys are expecting values... Run cjson.getAllRuntimeKeys()");
        else
            this.isInjectExist = false;
        content = this.decodeRelativePathValues(content);
        this.refineObj(content);
        return this;
    }
    /**
     * Converts JSON object to string. Just a wrapper over `JSON.stringify()`
     * @param obj JSON object
     * @returns JSON string
     */
    static toString(obj) {
        if (obj === null)
            return "{}";
        else if (!(0, json_1.isContentJson)(JSON.stringify(obj)))
            throw new Error("Object is not a JSON");
        else
            return JSON.stringify(obj);
    }
    /**
     * Deserializes `CJSON` content and returns content as string.
     *
     * Content will be of pure JSON content and can be parsed as `JSON`
     * @returns `JSON` equivalent of `CJSON` content in `string`
     */
    deserializeAsString() {
        this.deserialize();
        return this.content;
    }
    /**
     * Removes a key:value from the CJSON context. Key will be JPath in `$.full.path` format
     *
     * The function automatically deserializes before removing. So, no need to explicitely deserialize it.
     * @param key JPath to the key to be removed.
     * @returns Resultant content in `JSON` object
     */
    remove(key) {
        this.obj = this.json?.removeWithKey(key, this.content);
        this.content = Cjson.toString(this.obj);
        return this;
    }
    /**
     * Replace a JPath to a specified value. The function context is of JSON and cannot be used in CJSON context.
     *
     * In order to use this in CJSON context, follow below steps:
     * <ol>
     * <li>Create CJson object</li>
     * <li>Deserialize</li>
     * <li>Deserialize</li>
     * <li>cjson.json?.replace("$.jpath", value, object)</li>
     * </ol>
     * @param key
     * @param value
     * @param jsonObject
     * @returns
     */
    replace = (jPath, value) => {
        if (jPath.startsWith("$."))
            jPath = jPath.split("$.")[1];
        this.obj = this.json?.replace(jPath, value, this.obj);
        this.refineObj(Cjson.toString(this.obj));
        return this;
    };
    decodeRuntimeKeys(content) {
        var runtimeKeys = content.match(keywords_1.default.runtimeVals)?.filter((value, index, array) => { return array.indexOf(value) === index; });
        if (runtimeKeys !== undefined && runtimeKeys !== null) {
            this.isInjectExist = true;
            for (let i = 0; i < runtimeKeys.length; i++) {
                let variable = runtimeKeys[i].split("<")[1].split(">")[0];
                let ignoreUnderQuotes = content.match("\".*" + runtimeKeys[i] + ".*\"");
                if (ignoreUnderQuotes === null) {
                    variable = "\"<-" + variable + "->\"";
                    content = content.replace(new RegExp(runtimeKeys[i], "g"), variable);
                }
            }
        }
        return content;
    }
    decodeRelativePathValues(content) {
        var encodedKeys = content.match(keywords_1.default.encodedRelativeJPathRegex)?.filter((value, index, array) => { return array.indexOf(value) === index; });
        if (encodedKeys) {
            for (let i = 0; i < encodedKeys.length; i++) {
                let key = encodedKeys[i].split("<")[1].split(">")[0];
                let value = this.json?.parse(key);
                if (value !== null) {
                    while (JSON.stringify(value).includes("$."))
                        value = this.json?.parse(value.split("<")[1].split(">")[0]);
                    if (typeof value === "string")
                        content = content.replace(new RegExp(encodedKeys[i].replace("$", "\\$"), "g"), value);
                    else
                        content = content.replace(new RegExp("\"" + encodedKeys[i].replace("$", "\\$") + "\"", "g"), value);
                }
                else
                    content = content.replace(new RegExp("\"" + encodedKeys[i].replace("$", "\\$") + "\"", "g"), "null");
            }
        }
        return content;
    }
}
exports.Cjson = Cjson;