bruno/packages/bruno-toml/lib/stringify

298 lines
8.5 KiB
Plaintext

/**
* Copyright (c) 2016, Rebecca Turner <me@re-becca.org>
*
* Permission to use, copy, modify, and/or distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*
* We have made some modifications to this file to support the bruno-toml
* You can search for "modified for bruno-toml" to see the changes
*/
'use strict'
module.exports = stringify
module.exports.value = stringifyInline
function stringify (obj) {
if (obj === null) throw typeError('null')
if (obj === void (0)) throw typeError('undefined')
if (typeof obj !== 'object') throw typeError(typeof obj)
if (typeof obj.toJSON === 'function') obj = obj.toJSON()
if (obj == null) return null
const type = tomlType(obj)
if (type !== 'table') throw typeError(type)
return stringifyObject('', '', obj)
}
function typeError (type) {
return new Error('Can only stringify objects, not ' + type)
}
function getInlineKeys (obj) {
return Object.keys(obj).filter(key => isInline(obj[key]))
}
function getComplexKeys (obj) {
return Object.keys(obj).filter(key => !isInline(obj[key]))
}
function toJSON (obj) {
let nobj = Array.isArray(obj) ? [] : Object.prototype.hasOwnProperty.call(obj, '__proto__') ? {['__proto__']: undefined} : {}
for (let prop of Object.keys(obj)) {
if (obj[prop] && typeof obj[prop].toJSON === 'function' && !('toISOString' in obj[prop])) {
nobj[prop] = obj[prop].toJSON()
} else {
nobj[prop] = obj[prop]
}
}
return nobj
}
function stringifyObject (prefix, indent, obj) {
obj = toJSON(obj)
let inlineKeys
let complexKeys
inlineKeys = getInlineKeys(obj)
complexKeys = getComplexKeys(obj)
const result = []
const inlineIndent = indent || ''
inlineKeys.forEach(key => {
var type = tomlType(obj[key])
if (type !== 'undefined' && type !== 'null') {
result.push(inlineIndent + stringifyKey(key) + ' = ' + stringifyAnyInline(obj[key], true))
}
})
if (result.length > 0) result.push('')
const complexIndent = prefix && inlineKeys.length > 0 ? indent + ' ' : ''
complexKeys.forEach(key => {
result.push(stringifyComplex(prefix, complexIndent, key, obj[key]))
})
return result.join('\n')
}
function isInline (value) {
switch (tomlType(value)) {
case 'undefined':
case 'null':
case 'integer':
case 'nan':
case 'float':
case 'boolean':
case 'string':
case 'datetime':
return true
case 'array':
return value.length === 0 || tomlType(value[0]) !== 'table'
case 'table':
return Object.keys(value).length === 0
/* istanbul ignore next */
default:
return false
}
}
function tomlType (value) {
if (value === undefined) {
return 'undefined'
} else if (value === null) {
return 'null'
} else if (typeof value === 'bigint' || (Number.isInteger(value) && !Object.is(value, -0))) {
return 'integer'
} else if (typeof value === 'number') {
return 'float'
} else if (typeof value === 'boolean') {
return 'boolean'
} else if (typeof value === 'string') {
return 'string'
} else if ('toISOString' in value) {
return isNaN(value) ? 'undefined' : 'datetime'
} else if (Array.isArray(value)) {
return 'array'
} else {
return 'table'
}
}
function stringifyKey (key) {
const keyStr = String(key)
if (/^[-A-Za-z0-9_]+$/.test(keyStr)) {
return keyStr
} else {
return stringifyBasicString(keyStr)
}
}
function stringifyBasicString (str) {
// original
// return '"' + escapeString(str).replace(/"/g, '\\"') + '"'
// modified for bruno-toml
return "'" + escapeString(str).replace(/'/g, "\\'") + "'"
}
function stringifyLiteralString (str) {
return "'" + str + "'"
}
function numpad (num, str) {
while (str.length < num) str = '0' + str
return str
}
function escapeString (str) {
return str.replace(/\\/g, '\\\\')
.replace(/[\b]/g, '\\b')
.replace(/\t/g, '\\t')
.replace(/\n/g, '\\n')
.replace(/\f/g, '\\f')
.replace(/\r/g, '\\r')
.replace(/([\u0000-\u001f\u007f])/, c => '\\u' + numpad(4, c.codePointAt(0).toString(16)))
}
function stringifyMultilineString (str) {
// original
// let escaped = str.split(/\n/).map(str => {
// return escapeString(str).replace(/"(?="")/g, '\\"')
// }).join('\n')
// if (escaped.slice(-1) === '"') escaped += '\\\n'
// return '"""\n' + escaped + '"""'
// modified for bruno-toml
let escaped = str.split(/\n/).map(str => {
return escapeString(str).replace(/'(?='')/g, "\\'")
}).join('\n')
if (escaped.slice(-1) === "'") escaped += '\\\n'
return "'''\n" + escaped + "\n'''"
}
function stringifyAnyInline (value, multilineOk) {
let type = tomlType(value)
if (type === 'string') {
if (multilineOk && /\n/.test(value)) {
type = 'string-multiline'
} else if (!/[\b\t\n\f\r']/.test(value) && /"/.test(value)) {
type = 'string-literal'
}
}
return stringifyInline(value, type)
}
function stringifyInline (value, type) {
if (!type) type = tomlType(value)
switch (type) {
case 'string-multiline':
return stringifyMultilineString(value)
case 'string':
return stringifyBasicString(value)
case 'string-literal':
return stringifyLiteralString(value)
case 'integer':
return stringifyInteger(value)
case 'float':
return stringifyFloat(value)
case 'boolean':
return stringifyBoolean(value)
case 'datetime':
return stringifyDatetime(value)
case 'array':
return stringifyInlineArray(value.filter(_ => tomlType(_) !== 'null' && tomlType(_) !== 'undefined' && tomlType(_) !== 'nan'))
case 'table':
return stringifyInlineTable(value)
/* istanbul ignore next */
default:
throw typeError(type)
}
}
function stringifyInteger (value) {
return String(value).replace(/\B(?=(\d{3})+(?!\d))/g, '_')
}
function stringifyFloat (value) {
if (value === Infinity) {
return 'inf'
} else if (value === -Infinity) {
return '-inf'
} else if (Object.is(value, NaN)) {
return 'nan'
} else if (Object.is(value, -0)) {
return '-0.0'
}
const [int, dec] = String(value).split('.')
return stringifyInteger(int) + '.' + dec
}
function stringifyBoolean (value) {
return String(value)
}
function stringifyDatetime (value) {
return value.toISOString()
}
function stringifyInlineArray (values) {
values = toJSON(values)
let result = '['
const stringified = values.map(_ => stringifyInline(_))
if (stringified.join(', ').length > 60 || /\n/.test(stringified)) {
result += '\n ' + stringified.join(',\n ') + '\n'
} else {
result += ' ' + stringified.join(', ') + (stringified.length > 0 ? ' ' : '')
}
return result + ']'
}
function stringifyInlineTable (value) {
value = toJSON(value)
const result = []
Object.keys(value).forEach(key => {
result.push(stringifyKey(key) + ' = ' + stringifyAnyInline(value[key], false))
})
return '{ ' + result.join(', ') + (result.length > 0 ? ' ' : '') + '}'
}
function stringifyComplex (prefix, indent, key, value) {
const valueType = tomlType(value)
/* istanbul ignore else */
if (valueType === 'array') {
return stringifyArrayOfTables(prefix, indent, key, value)
} else if (valueType === 'table') {
return stringifyComplexTable(prefix, indent, key, value)
} else {
throw typeError(valueType)
}
}
function stringifyArrayOfTables (prefix, indent, key, values) {
values = toJSON(values)
const firstValueType = tomlType(values[0])
/* istanbul ignore if */
if (firstValueType !== 'table') throw typeError(firstValueType)
const fullKey = prefix + stringifyKey(key)
let result = ''
values.forEach(table => {
if (result.length > 0) result += '\n'
result += indent + '[[' + fullKey + ']]\n'
result += stringifyObject(fullKey + '.', indent, table)
})
return result
}
function stringifyComplexTable (prefix, indent, key, value) {
const fullKey = prefix + stringifyKey(key)
let result = ''
if (getInlineKeys(value).length > 0) {
result += indent + '[' + fullKey + ']\n'
}
return result + stringifyObject(fullKey + '.', indent, value)
}