summaryrefslogtreecommitdiff
path: root/node_modules/jju/lib/document.js
diff options
context:
space:
mode:
Diffstat (limited to 'node_modules/jju/lib/document.js')
-rw-r--r--node_modules/jju/lib/document.js480
1 files changed, 0 insertions, 480 deletions
diff --git a/node_modules/jju/lib/document.js b/node_modules/jju/lib/document.js
deleted file mode 100644
index e5ba893..0000000
--- a/node_modules/jju/lib/document.js
+++ /dev/null
@@ -1,480 +0,0 @@
-
-var assert = require('assert')
-var tokenize = require('./parse').tokenize
-var stringify = require('./stringify').stringify
-var analyze = require('./analyze').analyze
-
-function isObject(x) {
- return typeof(x) === 'object' && x !== null
-}
-
-function value_to_tokenlist(value, stack, options, is_key, indent) {
- options = Object.create(options)
- options._stringify_key = !!is_key
-
- if (indent) {
- options._prefix = indent.prefix.map(function(x) {
- return x.raw
- }).join('')
- }
-
- if (options._splitMin == null) options._splitMin = 0
- if (options._splitMax == null) options._splitMax = 0
-
- var stringified = stringify(value, options)
-
- if (is_key) {
- return [ { raw: stringified, type: 'key', stack: stack, value: value } ]
- }
-
- options._addstack = stack
- var result = tokenize(stringified, {
- _addstack: stack,
- })
- result.data = null
- return result
-}
-
-// '1.2.3' -> ['1','2','3']
-function arg_to_path(path) {
- // array indexes
- if (typeof(path) === 'number') path = String(path)
-
- if (path === '') path = []
- if (typeof(path) === 'string') path = path.split('.')
-
- if (!Array.isArray(path)) throw Error('Invalid path type, string or array expected')
- return path
-}
-
-// returns new [begin, end] or false if not found
-//
-// {x:3, xxx: 111, y: [111, {q: 1, e: 2} ,333] }
-// f('y',0) returns this B^^^^^^^^^^^^^^^^^^^^^^^^E
-// then f('1',1) would reduce it to B^^^^^^^^^^E
-function find_element_in_tokenlist(element, lvl, tokens, begin, end) {
- while(tokens[begin].stack[lvl] != element) {
- if (begin++ >= end) return false
- }
- while(tokens[end].stack[lvl] != element) {
- if (end-- < begin) return false
- }
- return [begin, end]
-}
-
-function is_whitespace(token_type) {
- return token_type === 'whitespace'
- || token_type === 'newline'
- || token_type === 'comment'
-}
-
-function find_first_non_ws_token(tokens, begin, end) {
- while(is_whitespace(tokens[begin].type)) {
- if (begin++ >= end) return false
- }
- return begin
-}
-
-function find_last_non_ws_token(tokens, begin, end) {
- while(is_whitespace(tokens[end].type)) {
- if (end-- < begin) return false
- }
- return end
-}
-
-/*
- * when appending a new element of an object/array, we are trying to
- * figure out the style used on the previous element
- *
- * return {prefix, sep1, sep2, suffix}
- *
- * ' "key" : "element" \r\n'
- * prefix^^^^ sep1^ ^^sep2 ^^^^^^^^suffix
- *
- * begin - the beginning of the object/array
- * end - last token of the last element (value or comma usually)
- */
-function detect_indent_style(tokens, is_array, begin, end, level) {
- var result = {
- sep1: [],
- sep2: [],
- suffix: [],
- prefix: [],
- newline: [],
- }
-
- if (tokens[end].type === 'separator' && tokens[end].stack.length !== level+1 && tokens[end].raw !== ',') {
- // either a beginning of the array (no last element) or other weird situation
- //
- // just return defaults
- return result
- }
-
- // ' "key" : "value" ,'
- // skipping last separator, we're now here ^^
- if (tokens[end].type === 'separator')
- end = find_last_non_ws_token(tokens, begin, end - 1)
- if (end === false) return result
-
- // ' "key" : "value" ,'
- // skipping value ^^^^^^^
- while(tokens[end].stack.length > level) end--
-
- if (!is_array) {
- while(is_whitespace(tokens[end].type)) {
- if (end < begin) return result
- if (tokens[end].type === 'whitespace') {
- result.sep2.unshift(tokens[end])
- } else {
- // newline, comment or other unrecognized codestyle
- return result
- }
- end--
- }
-
- // ' "key" : "value" ,'
- // skipping separator ^
- assert.equal(tokens[end].type, 'separator')
- assert.equal(tokens[end].raw, ':')
- while(is_whitespace(tokens[--end].type)) {
- if (end < begin) return result
- if (tokens[end].type === 'whitespace') {
- result.sep1.unshift(tokens[end])
- } else {
- // newline, comment or other unrecognized codestyle
- return result
- }
- }
-
- assert.equal(tokens[end].type, 'key')
- end--
- }
-
- // ' "key" : "value" ,'
- // skipping key ^^^^^
- while(is_whitespace(tokens[end].type)) {
- if (end < begin) return result
- if (tokens[end].type === 'whitespace') {
- result.prefix.unshift(tokens[end])
- } else if (tokens[end].type === 'newline') {
- result.newline.unshift(tokens[end])
- return result
- } else {
- // comment or other unrecognized codestyle
- return result
- }
- end--
- }
-
- return result
-}
-
-function Document(text, options) {
- var self = Object.create(Document.prototype)
-
- if (options == null) options = {}
- //options._structure = true
- var tokens = self._tokens = tokenize(text, options)
- self._data = tokens.data
- tokens.data = null
- self._options = options
-
- var stats = analyze(text, options)
- if (options.indent == null) {
- options.indent = stats.indent
- }
- if (options.quote == null) {
- options.quote = stats.quote
- }
- if (options.quote_keys == null) {
- options.quote_keys = stats.quote_keys
- }
- if (options.no_trailing_comma == null) {
- options.no_trailing_comma = !stats.has_trailing_comma
- }
- return self
-}
-
-// return true if it's a proper object
-// throw otherwise
-function check_if_can_be_placed(key, object, is_unset) {
- //if (object == null) return false
- function error(add) {
- return Error("You can't " + (is_unset ? 'unset' : 'set') + " key '" + key + "'" + add)
- }
-
- if (!isObject(object)) {
- throw error(' of an non-object')
- }
- if (Array.isArray(object)) {
- // array, check boundary
- if (String(key).match(/^\d+$/)) {
- key = Number(String(key))
- if (object.length < key || (is_unset && object.length === key)) {
- throw error(', out of bounds')
- } else if (is_unset && object.length !== key+1) {
- throw error(' in the middle of an array')
- } else {
- return true
- }
- } else {
- throw error(' of an array')
- }
- } else {
- // object
- return true
- }
-}
-
-// usage: document.set('path.to.something', 'value')
-// or: document.set(['path','to','something'], 'value')
-Document.prototype.set = function(path, value) {
- path = arg_to_path(path)
-
- // updating this._data and check for errors
- if (path.length === 0) {
- if (value === undefined) throw Error("can't remove root document")
- this._data = value
- var new_key = false
-
- } else {
- var data = this._data
-
- for (var i=0; i<path.length-1; i++) {
- check_if_can_be_placed(path[i], data, false)
- data = data[path[i]]
- }
- if (i === path.length-1) {
- check_if_can_be_placed(path[i], data, value === undefined)
- }
-
- var new_key = !(path[i] in data)
-
- if (value === undefined) {
- if (Array.isArray(data)) {
- data.pop()
- } else {
- delete data[path[i]]
- }
- } else {
- data[path[i]] = value
- }
- }
-
- // for inserting document
- if (!this._tokens.length)
- this._tokens = [ { raw: '', type: 'literal', stack: [], value: undefined } ]
-
- var position = [
- find_first_non_ws_token(this._tokens, 0, this._tokens.length - 1),
- find_last_non_ws_token(this._tokens, 0, this._tokens.length - 1),
- ]
- for (var i=0; i<path.length-1; i++) {
- position = find_element_in_tokenlist(path[i], i, this._tokens, position[0], position[1])
- if (position == false) throw Error('internal error, please report this')
- }
- // assume that i == path.length-1 here
-
- if (path.length === 0) {
- var newtokens = value_to_tokenlist(value, path, this._options)
- // all good
-
- } else if (!new_key) {
- // replace old value with a new one (or deleting something)
- var pos_old = position
- position = find_element_in_tokenlist(path[i], i, this._tokens, position[0], position[1])
-
- if (value === undefined && position !== false) {
- // deleting element (position !== false ensures there's something)
- var newtokens = []
-
- if (!Array.isArray(data)) {
- // removing element from an object, `{x:1, key:CURRENT} -> {x:1}`
- // removing sep, literal and optional sep
- // ':'
- var pos2 = find_last_non_ws_token(this._tokens, pos_old[0], position[0] - 1)
- assert.equal(this._tokens[pos2].type, 'separator')
- assert.equal(this._tokens[pos2].raw, ':')
- position[0] = pos2
-
- // key
- var pos2 = find_last_non_ws_token(this._tokens, pos_old[0], position[0] - 1)
- assert.equal(this._tokens[pos2].type, 'key')
- assert.equal(this._tokens[pos2].value, path[path.length-1])
- position[0] = pos2
- }
-
- // removing comma in arrays and objects
- var pos2 = find_last_non_ws_token(this._tokens, pos_old[0], position[0] - 1)
- assert.equal(this._tokens[pos2].type, 'separator')
- if (this._tokens[pos2].raw === ',') {
- position[0] = pos2
- } else {
- // beginning of the array/object, so we should remove trailing comma instead
- pos2 = find_first_non_ws_token(this._tokens, position[1] + 1, pos_old[1])
- assert.equal(this._tokens[pos2].type, 'separator')
- if (this._tokens[pos2].raw === ',') {
- position[1] = pos2
- }
- }
-
- } else {
- var indent = pos2 !== false
- ? detect_indent_style(this._tokens, Array.isArray(data), pos_old[0], position[1] - 1, i)
- : {}
- var newtokens = value_to_tokenlist(value, path, this._options, false, indent)
- }
-
- } else {
- // insert new key, that's tricky
- var path_1 = path.slice(0, i)
-
- // find a last separator after which we're inserting it
- var pos2 = find_last_non_ws_token(this._tokens, position[0] + 1, position[1] - 1)
- assert(pos2 !== false)
-
- var indent = pos2 !== false
- ? detect_indent_style(this._tokens, Array.isArray(data), position[0] + 1, pos2, i)
- : {}
-
- var newtokens = value_to_tokenlist(value, path, this._options, false, indent)
-
- // adding leading whitespaces according to detected codestyle
- var prefix = []
- if (indent.newline && indent.newline.length)
- prefix = prefix.concat(indent.newline)
- if (indent.prefix && indent.prefix.length)
- prefix = prefix.concat(indent.prefix)
-
- // adding '"key":' (as in "key":"value") to object values
- if (!Array.isArray(data)) {
- prefix = prefix.concat(value_to_tokenlist(path[path.length-1], path_1, this._options, true))
- if (indent.sep1 && indent.sep1.length)
- prefix = prefix.concat(indent.sep1)
- prefix.push({raw: ':', type: 'separator', stack: path_1})
- if (indent.sep2 && indent.sep2.length)
- prefix = prefix.concat(indent.sep2)
- }
-
- newtokens.unshift.apply(newtokens, prefix)
-
- // check if prev token is a separator AND they're at the same level
- if (this._tokens[pos2].type === 'separator' && this._tokens[pos2].stack.length === path.length-1) {
- // previous token is either , or [ or {
- if (this._tokens[pos2].raw === ',') {
- // restore ending comma
- newtokens.push({raw: ',', type: 'separator', stack: path_1})
- }
- } else {
- // previous token isn't a separator, so need to insert one
- newtokens.unshift({raw: ',', type: 'separator', stack: path_1})
- }
-
- if (indent.suffix && indent.suffix.length)
- newtokens.push.apply(newtokens, indent.suffix)
-
- assert.equal(this._tokens[position[1]].type, 'separator')
- position[0] = pos2+1
- position[1] = pos2
- }
-
- newtokens.unshift(position[1] - position[0] + 1)
- newtokens.unshift(position[0])
- this._tokens.splice.apply(this._tokens, newtokens)
-
- return this
-}
-
-// convenience method
-Document.prototype.unset = function(path) {
- return this.set(path, undefined)
-}
-
-Document.prototype.get = function(path) {
- path = arg_to_path(path)
-
- var data = this._data
- for (var i=0; i<path.length; i++) {
- if (!isObject(data)) return undefined
- data = data[path[i]]
- }
- return data
-}
-
-Document.prototype.has = function(path) {
- path = arg_to_path(path)
-
- var data = this._data
- for (var i=0; i<path.length; i++) {
- if (!isObject(data)) return false
- data = data[path[i]]
- }
- return data !== undefined
-}
-
-// compare old object and new one, and change differences only
-Document.prototype.update = function(value) {
- var self = this
- change([], self._data, value)
- return self
-
- function change(path, old_data, new_data) {
- if (!isObject(new_data) || !isObject(old_data)) {
- // if source or dest is primitive, just replace
- if (new_data !== old_data)
- self.set(path, new_data)
-
- } else if (Array.isArray(new_data) != Array.isArray(old_data)) {
- // old data is an array XOR new data is an array, replace as well
- self.set(path, new_data)
-
- } else if (Array.isArray(new_data)) {
- // both values are arrays here
-
- if (new_data.length > old_data.length) {
- // adding new elements, so going forward
- for (var i=0; i<new_data.length; i++) {
- path.push(String(i))
- change(path, old_data[i], new_data[i])
- path.pop()
- }
-
- } else {
- // removing something, so going backward
- for (var i=old_data.length-1; i>=0; i--) {
- path.push(String(i))
- change(path, old_data[i], new_data[i])
- path.pop()
- }
- }
-
- } else {
- // both values are objects here
- for (var i in new_data) {
- path.push(String(i))
- change(path, old_data[i], new_data[i])
- path.pop()
- }
-
- for (var i in old_data) {
- if (i in new_data) continue
- path.push(String(i))
- change(path, old_data[i], new_data[i])
- path.pop()
- }
- }
- }
-}
-
-Document.prototype.toString = function() {
- return this._tokens.map(function(x) {
- return x.raw
- }).join('')
-}
-
-module.exports.Document = Document
-
-module.exports.update = function updateJSON(source, new_value, options) {
- return Document(source, options).update(new_value).toString()
-}
-