From 83b6a93684c6893a2a2a2e70d4eee8f2b07bb142 Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Fri, 1 Feb 2013 16:15:54 -0800 Subject: [PATCH] Add initial CSON pretty printer --- spec/stdlib/cson-spec.coffee | 56 ++++++++++++++++++++++++ src/stdlib/cson.coffee | 84 ++++++++++++++++++++++++++++++++++++ 2 files changed, 140 insertions(+) create mode 100644 spec/stdlib/cson-spec.coffee create mode 100644 src/stdlib/cson.coffee diff --git a/spec/stdlib/cson-spec.coffee b/spec/stdlib/cson-spec.coffee new file mode 100644 index 000000000..b865ca33d --- /dev/null +++ b/spec/stdlib/cson-spec.coffee @@ -0,0 +1,56 @@ +CSON = require 'cson' + +fdescribe "CSON", -> + describe "@stringify(object)", -> + describe "when the object is undefined", -> + it "throws an exception", -> + expect(-> CSON.stringify()).toThrow() + + describe "when the object is a function", -> + it "throws an exception", -> + expect(-> CSON.stringify(-> 'function')).toThrow() + + describe "when the object contains a function", -> + it "throws an exception", -> + expect(-> CSON.stringify(a: -> 'function')).toThrow() + + describe "when formatting an undefined key", -> + it "does not include the key in the formatted CSON", -> + expect(CSON.stringify(b: 1, c: undefined)).toBe "'b': 1\n" + + describe "when formatting an empty object", -> + it "returns the empty string", -> + expect(CSON.stringify({})).toBe "" + + describe "when formatting a string", -> + it "returns formatted CSON", -> + expect(CSON.stringify(a: 'b')).toBe "'a': 'b'\n" + + it "escapes single quotes", -> + expect(CSON.stringify(a: "'b'")).toBe "'a': '\\\'b\\\''\n" + + it "doesn't escape double quotes", -> + expect(CSON.stringify(a: '"b"')).toBe "'a': '\"b\"'\n" + + describe "when formatting a boolean", -> + it "returns formatted CSON", -> + expect(CSON.stringify(a: true)).toBe "'a': true\n" + expect(CSON.stringify(a: false)).toBe "'a': false\n" + + describe "when formatting a number", -> + it "returns formatted CSON", -> + expect(CSON.stringify(a: 14)).toBe "'a': 14\n" + expect(CSON.stringify(a: 1.23)).toBe "'a': 1.23\n" + + describe "when formatting null", -> + it "returns formatted CSON", -> + expect(CSON.stringify(a: null)).toBe "'a': null\n" + + describe "when formatting an array", -> + it "returns formatted CSON", -> + expect(CSON.stringify(a: ['b'])).toBe "'a': [\n 'b'\n]\n" + expect(CSON.stringify(a: ['b', 4])).toBe "'a': [\n 'b'\n 4\n]\n" + + describe "when formatting an object", -> + it "returns formatted CSON", -> + expect(CSON.stringify(a: {b: 'c'})).toBe "'a':\n 'b': 'c'\n" diff --git a/src/stdlib/cson.coffee b/src/stdlib/cson.coffee new file mode 100644 index 000000000..8dd627c71 --- /dev/null +++ b/src/stdlib/cson.coffee @@ -0,0 +1,84 @@ +_ = require 'underscore' + +module.exports = + stringifyIndent: (level=0) -> _.multiplyString(' ', Math.max(level, 0)) + + stringifyString: (string) -> + string = JSON.stringify(string) + string = string[1...-1] # Remove surrounding double quotes + string = string.replace(/\\"/g, '"') # Unescape escaped double quotes + string = string.replace(/'/g, '\\\'') # Escape single quotes + "'#{string}'" # Wrap in single quotes + + stringifyBoolean: (boolean) -> "#{boolean}" + + stringifyNumber: (number) -> "#{number}" + + stringifyNull: -> 'null' + + stringifyArray: (array, indentLevel=0) -> + cson = '[\n' + for value in array + cson += @stringifyIndent(indentLevel + 2) + if _.isString(value) + cson += @stringifyString(value) + else if _.isBoolean(value) + cson += @stringifyBoolean(value) + else if _.isNumber(value) + cson += @stringifyNumber(value) + else if _.isNull(value) + cson += @stringifyNull(value) + else if _.isArray(value) + cson += @stringifyArray(value, indentLevel + 2) + else if _.isObject(value) + cson += @stringifyObject(value, indentLevel + 2) + else + throw new Error("Unrecognized type for array value: #{value}") + cson += '\n' + "#{cson}#{@stringifyIndent(indentLevel)}]" + + stringifyObject: (object, indentLevel=0) -> + cson = '' + prefix = '' + for key, value of object + continue if value is undefined + if _.isFunction(value) + throw new Error("Function specified as value to key: #{key}") + + cson += "#{prefix}#{@stringifyIndent(indentLevel)}'#{key}':" + if _.isString(value) + cson += " #{@stringifyString(value)}" + else if _.isBoolean(value) + cson += " #{@stringifyBoolean(value)}" + else if _.isNumber(value) + cson += " #{@stringifyNumber(value)}" + else if _.isNull(value) + cson += " #{@stringifyNull(value)}" + else if _.isArray(value) + cson += " #{@stringifyArray(value, indentLevel)}" + else if _.isObject(value) + cson += "\n#{@stringifyObject(value, indentLevel + 2)}" + else + throw new Error("Unrecognized value type for key: #{key} with value: #{value}") + prefix = '\n' + cson + + stringify: (object) -> + throw new Error("Cannot stringify undefined object") if object is undefined + if _.isFunction(object) + throw new Error("Cannot stringify function: #{object}") + + return @stringifyString(object) if _.isString(object) + return @stringifyBoolean(object) if _.isBoolean(object) + return @stringifyNumber(object) if _.isNumber(object) + return @stringifyNull(object) if _.isNull(object) + if _.isArray(object) + cson = @stringifyArray(object) + cson = "#{cson}\n" if cson + return cson + if _.isObject(object) + cson = @stringifyObject(object) + cson = "#{cson}\n" if cson + return cson + + throw new Error("Unrecognized type to stringify: #{object}")