From 61a27b5c5f582a7565d3d5afdd1620ea1d54a92b Mon Sep 17 00:00:00 2001 From: Gregory Schier Date: Tue, 7 Feb 2017 09:13:59 -0800 Subject: [PATCH] No-parse JSON prettify (#80) * Multiple recursive rendering * Added custom JSON prettify that doesn't require parsing to JS * Fixed spacing issues --- app/common/__tests__/render.test.js | 29 ++++++++- app/common/prettify.js | 97 +++++++++++++++++++++++++++++ app/common/render.js | 11 +++- app/ui/components/base/Editor.js | 20 +++--- 4 files changed, 145 insertions(+), 12 deletions(-) create mode 100644 app/common/prettify.js diff --git a/app/common/__tests__/render.test.js b/app/common/__tests__/render.test.js index 376f6f51b..7dab78e6b 100644 --- a/app/common/__tests__/render.test.js +++ b/app/common/__tests__/render.test.js @@ -71,7 +71,34 @@ describe('buildRenderContext()', () => { const context = renderUtils.buildRenderContext(ancestors); - expect(context).toEqual({recursive: '{{ recursive }}/hello/hello'}); + // This is longer than 3 because it multiplies every time (1 -> 2 -> 4 -> 8) + expect(context).toEqual({ + recursive: '{{ recursive }}/hello/hello/hello/hello/hello/hello/hello/hello' + }); + }); + + it('render up to 3 recursion levels', () => { + const ancestors = [{ + // Sub Environment + type: models.requestGroup.type, + environment: { + d: '/d', + c: '/c{{ d }}', + b: '/b{{ c }}', + a: '/a{{ b }}', + test: 'http://insomnia.rest{{ a }}' + } + }]; + + const context = renderUtils.buildRenderContext(ancestors); + + expect(context).toEqual({ + d: '/d', + c: '/c/d', + b: '/b/c/d', + a: '/a/b/c/d', + test: 'http://insomnia.rest/a/b/c/d', + }); }); it('rendered sibling environment variables', () => { diff --git a/app/common/prettify.js b/app/common/prettify.js new file mode 100644 index 000000000..a7e0c51ca --- /dev/null +++ b/app/common/prettify.js @@ -0,0 +1,97 @@ +/** + * Format a JSON string without parsing it as JavaScript. + * + * Code taken from jsonlint (http://zaa.ch/jsonlint/) + * + * @param json + * @param indentChars + * @returns {string} + */ +export function prettifyJson (json, indentChars) { + let i = 0; + let il = json.length; + let tab = (typeof indentChars !== 'undefined') ? indentChars : ' '; + let newJson = ''; + let indentLevel = 0; + let inString = false; + let currentChar = null; + let previousChar = null; + let nextChar = null; + + for (;i < il; i += 1) { + currentChar = json.charAt(i); + previousChar = json.charAt(i - 1); + nextChar = json.charAt(i + 1); + + switch (currentChar) { + case '{': + if (!inString && nextChar !== '}') { + newJson += currentChar + '\n' + _repeatString(tab, indentLevel + 1); + indentLevel += 1; + } else { + newJson += currentChar; + } + break; + case '[': + if (!inString && nextChar !== ']') { + newJson += currentChar + '\n' + _repeatString(tab, indentLevel + 1); + indentLevel += 1; + } else { + newJson += currentChar; + } + break; + case '}': + if (!inString && previousChar !== '{') { + indentLevel -= 1; + newJson += '\n' + _repeatString(tab, indentLevel) + currentChar; + } else { + newJson += currentChar; + } + break; + case ']': + if (!inString && previousChar !== '[') { + indentLevel -= 1; + newJson += '\n' + _repeatString(tab, indentLevel) + currentChar; + } else { + newJson += currentChar; + } + break; + case ',': + if (!inString) { + newJson += ',\n' + _repeatString(tab, indentLevel); + } else { + newJson += currentChar; + } + break; + case ':': + if (!inString) { + newJson += ': '; + } else { + newJson += currentChar; + } + break; + case ' ': + case '\n': + case '\t': + if (inString) { + newJson += currentChar; + } + break; + case '"': + if (i > 0 && previousChar !== '\\') { + inString = !inString; + } + newJson += currentChar; + break; + default: + newJson += currentChar; + break; + } + } + + return newJson; +} + +function _repeatString(s, count) { + return new Array(count + 1).join(s); +} diff --git a/app/common/render.js b/app/common/render.js index 35f3d7fdd..f0ea601aa 100644 --- a/app/common/render.js +++ b/app/common/render.js @@ -92,7 +92,14 @@ export function buildRenderContext (ancestors, rootEnvironment, subEnvironment) } // Render the context with itself to fill in the rest. - return recursiveRender(renderContext, renderContext); + let finalRenderContext = renderContext; + + // Render up to 5 levels of recursive references. + for (let i = 0; i < 3; i++) { + finalRenderContext = recursiveRender(finalRenderContext, finalRenderContext); + } + + return finalRenderContext; } export function recursiveRender (obj, context) { @@ -178,7 +185,7 @@ function _objectDeepAssignRender (base, obj) { * * A regular Object.assign would yield { base_url: '{{ base_url }}/foo' } and the * original base_url of google.com would be lost. - */ + */ if (typeof base[key] === 'string') { base[key] = render(obj[key], base); } else { diff --git a/app/ui/components/base/Editor.js b/app/ui/components/base/Editor.js index bc91c9db1..9f9ff6f65 100644 --- a/app/ui/components/base/Editor.js +++ b/app/ui/components/base/Editor.js @@ -49,6 +49,7 @@ import * as misc from '../../../common/misc'; import {trackEvent} from '../../../analytics/index'; // Make jsonlint available to the jsonlint plugin import {parser as jsonlint} from 'jsonlint'; +import {prettifyJson} from '../../../common/prettify'; global.jsonlint = jsonlint; @@ -170,36 +171,37 @@ class Editor extends Component { this._prettify(this.codeMirror.getValue()); } - async _prettify (code) { + _prettify (code) { if (this._isXML(this.props.mode)) { - code = this._formatXML(code); + code = this._prettifyXML(code); } else { - code = this._formatJSON(code); + code = this._prettifyJSON(code); } this.codeMirror.setValue(code); } - _formatJSON (code) { + _prettifyJSON (code) { try { - let obj = JSON.parse(code); + let jsonString = code; if (this.props.updateFilter && this.state.filter) { + let obj = JSON.parse(code); try { - obj = jq.query(obj, this.state.filter); + jsonString = JSON.stringify(jq.query(obj, this.state.filter)); } catch (err) { - obj = '[]'; + jsonString = '[]'; } } - return vkBeautify.json(obj, '\t'); + return prettifyJson(jsonString, '\t'); } catch (e) { // That's Ok, just leave it return code; } } - _formatXML (code) { + _prettifyXML (code) { if (this.props.updateFilter && this.state.filter) { try { const dom = new DOMParser().parseFromString(code);