diff --git a/package-lock.json b/package-lock.json index 2db987a50..01ed68c0e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -14,6 +14,7 @@ "packages/bruno-js", "packages/bruno-lang", "packages/bruno-testbench", + "packages/bruno-toml", "packages/bruno-graphql-docs" ], "devDependencies": { @@ -2679,6 +2680,11 @@ "graphql-ws": ">= 4.5.0" } }, + "node_modules/@iarna/toml": { + "version": "2.2.5", + "resolved": "https://registry.npmjs.org/@iarna/toml/-/toml-2.2.5.tgz", + "integrity": "sha512-trnsAYxU3xnS1gPHPyU961coFyLkh4gAD/0zQ5mymY4yOZ+CYvsPqUbOFSw0aDM4y0tV7tiFxL/1XfXPNC6IPg==" + }, "node_modules/@istanbuljs/load-nyc-config": { "version": "1.1.0", "dev": true, @@ -4917,6 +4923,10 @@ "resolved": "packages/bruno-testbench", "link": true }, + "node_modules/@usebruno/toml": { + "resolved": "packages/bruno-toml", + "link": true + }, "node_modules/@webassemblyjs/ast": { "version": "1.11.1", "dev": true, @@ -18089,6 +18099,15 @@ "bin": { "js-yaml": "bin/js-yaml.js" } + }, + "packages/bruno-toml": { + "name": "@usebruno/toml", + "version": "0.1.0", + "license": "MIT", + "dependencies": { + "@iarna/toml": "^2.2.5", + "lodash": "^4.17.21" + } } }, "dependencies": { @@ -19965,6 +19984,11 @@ "meros": "^1.1.4" } }, + "@iarna/toml": { + "version": "2.2.5", + "resolved": "https://registry.npmjs.org/@iarna/toml/-/toml-2.2.5.tgz", + "integrity": "sha512-trnsAYxU3xnS1gPHPyU961coFyLkh4gAD/0zQ5mymY4yOZ+CYvsPqUbOFSw0aDM4y0tV7tiFxL/1XfXPNC6IPg==" + }, "@istanbuljs/load-nyc-config": { "version": "1.1.0", "dev": true, @@ -22027,6 +22051,13 @@ } } }, + "@usebruno/toml": { + "version": "file:packages/bruno-toml", + "requires": { + "@iarna/toml": "^2.2.5", + "lodash": "^4.17.21" + } + }, "@webassemblyjs/ast": { "version": "1.11.1", "dev": true, diff --git a/package.json b/package.json index 35dcf175a..7ba991b56 100644 --- a/package.json +++ b/package.json @@ -10,6 +10,7 @@ "packages/bruno-js", "packages/bruno-lang", "packages/bruno-testbench", + "packages/bruno-toml", "packages/bruno-graphql-docs" ], "homepage": "https://usebruno.com", diff --git a/packages/bruno-toml/lib/stringify b/packages/bruno-toml/lib/stringify new file mode 100644 index 000000000..e7313814c --- /dev/null +++ b/packages/bruno-toml/lib/stringify @@ -0,0 +1,298 @@ +/** + * Copyright (c) 2016, Rebecca Turner + * + * 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) +} \ No newline at end of file diff --git a/packages/bruno-toml/package.json b/packages/bruno-toml/package.json new file mode 100644 index 000000000..20d0c7e6f --- /dev/null +++ b/packages/bruno-toml/package.json @@ -0,0 +1,18 @@ +{ + "name": "@usebruno/toml", + "version": "0.1.0", + "license": "MIT", + "main": "src/index.js", + "files": [ + "lib", + "src", + "package.json" + ], + "scripts": { + "test": "jest" + }, + "dependencies": { + "@iarna/toml": "^2.2.5", + "lodash": "^4.17.21" + } +} diff --git a/packages/bruno-toml/src/jsonToToml.js b/packages/bruno-toml/src/jsonToToml.js new file mode 100644 index 000000000..04d07b5b3 --- /dev/null +++ b/packages/bruno-toml/src/jsonToToml.js @@ -0,0 +1,7 @@ +const stringify = require('../lib/stringify'); + +const jsonToToml = (json) => { + return stringify(json); +}; + +module.exports = jsonToToml; diff --git a/packages/bruno-toml/tests/jsonToToml/simple-get.spec.js b/packages/bruno-toml/tests/jsonToToml/simple-get.spec.js new file mode 100644 index 000000000..b4817bc72 --- /dev/null +++ b/packages/bruno-toml/tests/jsonToToml/simple-get.spec.js @@ -0,0 +1,36 @@ +const TOML = require('@iarna/toml'); +const jsonToToml = require('../../src/jsonToToml'); + +const json = { + meta: { + name: 'Get users', + type: 'http', + seq: '1' + }, + http: { + method: 'get', + url: 'https://reqres.in/api/users' + }, + headers: { + Accept: 'application/json' + } +}; + +const toml = `[meta] +name = 'Get users' +type = 'http' +seq = '1' + +[http] +method = 'get' +url = 'https://reqres.in/api/users' + +[headers] +Accept = 'application/json' +`; + +describe('jsonToToml - simple get', () => { + it('should parse the json file', () => { + expect(jsonToToml(json)).toEqual(toml); + }); +});