File: index.js

package info (click to toggle)
node-cjson 0.4.0-2
  • links: PTS, VCS
  • area: main
  • in suites: bullseye, buster, sid, stretch
  • size: 184 kB
  • ctags: 28
  • sloc: makefile: 5; sh: 2
file content (242 lines) | stat: -rw-r--r-- 6,564 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
var fs = require('fs'),
    Path = require('path'),
    jph = require('json-parse-helpfulerror');

/**
 * Default options.
 *
 * @type {Object}
 */
exports.options = {
    // merge all passed/found config files, see `cjson.extend`
    merge: false,
    // allows you to do some string replacements, see `cjson.replace`.
    replace: null,
    // freeze config recursively, see `cjson.freeze`
    freeze: false,
    // you can use any other extension for your config files, f.e. *.cjson
    ext: '.json',
    // you can use any parser you want. the default uses JSON.parse for maximum
    // speed, if it throws it uses uses an alternative parser to give more
    // helpful errors
    parse: jph.parse
}

/**
 * Remove single and multilie comments. Make sure to
 * leave them inside of strings.
 *
 * @param {String} json file.
 * @return {String} json without comments.
 */
exports.decomment = function(str) {
    var i,
        curChar, nextChar,
        inString = false,
        inComment = false,
        newStr = '';

    for (i = 0; i < str.length; ++i) {
        curChar = str.charAt(i);
        nextChar = str.charAt(i + 1);

        // it's either closing or opening inString and it is not escaped
        if (!inComment && curChar === '"' && str.charAt(i - 1) !== '\\') {
            inString = !inString;
        }

        // we are not inside of a string
        if (!inString) {
            // singleline comment start
            if (!inComment && curChar + nextChar === '//') {
                ++i;
                inComment = 1;
            // singleline comment end
            } else if (inComment === 1 && curChar === '\n') {
                inComment = false;
            // multiline comment start
            } else if (!inComment && curChar + nextChar === '/*') {
                ++i;
                inComment = 2;
                curChar = '';
            // multiline comment end
            } else if (inComment === 2 && curChar + nextChar === '*/') {
                ++i;
                inComment = false;
                curChar = '';
            }

            if (inComment) {
                curChar = '';
            }
        }

        newStr += curChar;
    }


    return newStr;
};

/**
 * Decomment the string and parse json.
 *
 * @param {String} json.
 * @param {Function} [reviver] will be called for every key and value at every
 *     level of the final result.
 * @return {Object} parsed json object.
 */
exports.parse = function(str, reviver) {
    return exports.options.parse(exports.decomment(str), reviver);
};

/**
 * Replace templates with data. {{toReplace}}
 *
 * @param {String} json.
 * @param {Object} data data hash.
 * @return {String} json string with replaced data.
 */
exports.replace = function(str, data) {
    return str.replace(/\{\{([^}]+)\}\}/g, function(match, search) {
        if (data.hasOwnProperty(search)) {
            // If the variable is an object, stringify it before replacement.
            // The false positive of "null" is fine in this case.
            if (typeof data[search] === 'object') {
                return JSON.stringify(data[search]);
            }
            return data[search];
        }
        return match;
    });
};

/**
 * Merge objects to the first one
 *
 * @param {Boolean|Object} deep if set true, deep merge will be done.
 * @param {Object} obj1 any object.
 * @param {Object} obj2 any object.
 * @return {Object} target merged object.
 */
exports.extend = (function() {
    var toString = Object.prototype.toString,
        obj = '[object Object]';

    return function extend(deep, obj1, obj2 /*, obj1, obj2, obj3 */) {
        // take first argument, if its not a boolean
        var args = arguments,
            i = deep === true ? 1 : 0,
            key,
            target = args[i];

        for (++i; i < args.length; ++i) {
            for (key in args[i]) {
                if (deep === true &&
                    target[key] &&
                    // if not doing this check you may end in
                    // endless loop if using deep option
                    toString.call(args[i][key]) === obj &&
                    toString.call(target[key]) === obj) {

                    // create a copy of target object to avoid subobjects changes
                    target[key] = extend(deep, {}, target[key]);

                    extend(deep, target[key], args[i][key]);
                } else {
                    target[key] = args[i][key];
                }
            }
        }

        return target;
    };
}());

/**
 * Freeze the object recursively.
 *
 * @param {Object} obj.
 * @return {Object}
 */
exports.freeze = function freeze(obj) {
    var key;

    if (obj instanceof Object) {
        for (key in obj) {
            freeze(obj[key]);
        }

        Object.freeze(obj);
    }
};

/**
 * Load and parse a config file/files.
 *
 * @param {String|Array} path absolute path/paths to the file/files or dir.
 * @param {Object|Boolean} [options] if true, extend all jsons to the first one,
 *     it can be also object {merge: true, replace: {key: 'value'}}
 * @return {Object} conf parsed json object.
 */
exports.load = function load(path, options) {
    var data, paths, conf;

    if (options === true) {
        options = {merge: true};
    }

    options = exports.extend({}, exports.options, options);

    if (Array.isArray(path)) {
        conf = {};
        path.forEach(function(path) {
            var data = load(path, options),
                filename;

            if (options.merge) {
                exports.extend(true, conf, data);
            } else {
                filename = Path.basename(path, options.ext);
                conf[filename] = data;
            }
        });

        return conf;
    }

    if (fs.statSync(path).isDirectory()) {
        paths = [];
        fs.readdirSync(path).forEach(function(filename) {
            var file = Path.join(path, filename);

            if (Path.extname(file) == options.ext && fs.statSync(file).isFile()) {
                paths.push(file);
            }
        });

        return load(paths, options);
    }

    data = fs.readFileSync(path, 'utf-8');

    // replace BOM Character
    data = data.replace(/\ufeff/g, '');

    if (options.replace) {
        data = exports.replace(data, options.replace);
    }

    try {
        data = exports.parse(data);
    } catch(err) {
        err.message += '\nFile: "' + path + '"';
        throw err;
    }

    if (options.freeze) {
        exports.freeze(data);
    }

    return data;
};