compound-ex4
Version:
Compound-ex4 - MVC framework for NodeJS (ExpressJs 4 version), fork compoundjs(https://github.com/1602/compound)
261 lines (223 loc) • 6.79 kB
JavaScript
exports.encode = encode
exports.decode = decode
var seen = []
function encode (obj, indent) {
var deep = arguments[2]
if (!indent) indent = " "
if (obj instanceof String ||
Object.prototype.toString.call(obj) === "[object String]") {
obj = obj.toString()
}
if (obj instanceof Number ||
Object.prototype.toString.call(obj) === "[object Number]") {
obj = obj.valueOf()
}
// take out the easy ones.
switch (typeof obj) {
case "string":
obj = obj.trim()
if (/[:\n\[\]]/g.test(obj)) {
return "|\n" + indent + obj.split(/\r?\n/).join("\n"+indent)
} else {
return (obj)
}
case "number":
return obj.toString(10)
case "function":
return encode(obj.toString(), indent, true)
case "boolean":
return obj.toString()
case "undefined":
// fallthrough
case "object":
// at this point we know it types as an object
if (!obj) return "~"
if (obj instanceof Date ||
Object.prototype.toString.call(obj) === "[object Date]") {
return JSON.stringify("[Date " + obj.toISOString() + "]")
}
if (obj instanceof RegExp ||
Object.prototype.toString.call(obj) === "[object RegExp]") {
return JSON.stringify(obj.toString())
}
if (obj instanceof Boolean ||
Object.prototype.toString.call(obj) === "[object Boolean]") {
return obj.toString()
}
if (seen.indexOf(obj) !== -1) {
return "[Circular]"
}
seen.push(obj)
if (typeof Buffer === "function" &&
typeof Buffer.isBuffer === "function" &&
Buffer.isBuffer(obj)) return obj.inspect()
if (obj instanceof Error) {
var o = { name: obj.name
, message: obj.message
, type: obj.type }
if (obj.code) o.code = obj.code
if (obj.errno) o.errno = obj.errno
if (obj.type) o.type = obj.type
obj = o
}
var out = ""
if (Array.isArray(obj)) {
var out = "\n" + indent + "- " +obj.map(function (item) {
return encode(item, indent + " ", true)
}).join("\n"+indent + "- ")
break
}
// an actual object
var keys = Object.keys(obj)
, niceKeys = keys.map(function (k) {
return (k.match(/^[a-zA-Z0-9_]+$/) ? k : JSON.stringify(k)) + ": "
})
//console.error(keys, niceKeys, obj)
var maxLength = Math.max.apply(Math, niceKeys.map(function (k) {
return k.length
}).concat(0))
//console.error(niceKeys, maxLength)
var spaces = new Array(maxLength + 1).join(" ")
if (!deep) indent += " "
out = "\n" + indent + keys.map(function (k, i) {
var niceKey = niceKeys[i]
return niceKey + spaces.substr(niceKey.length)
+ encode(obj[k], indent + " ", true)
}).join("\n" + indent)
break
default: return ""
}
if (!deep) seen.length = 0
return out
}
function decode (str) {
var v = str.trim()
, d
, dateRe = /^\[Date ([0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}(?::[0-9]{2})?(?:\.[0-9]{3})?(?:[A-Z]+)?)\]$/
if (v === "~") return null
try {
var jp = JSON.parse(str)
} catch (e) {
var jp = ""
}
if (jp &&
typeof jp === "string" &&
(d = jp.match(dateRe)) &&
(d = Date.parse(d[1]))) {
return new Date(d)
}
if (typeof jp === "boolean") return jp
if (v && !isNaN(v)) return parseInt(v, 10)
// something interesting.
var lines = str.split(/\r?\n/)
// check if it's some kind of string or something.
// if the first line is > or | then it's a wrapping indented string.
// if the first line is blank, and there are many lines,
// then it's an array or object.
// otherwise, it's just ""
var first = lines.shift().trim()
if (lines.length) lines = undent(lines)
switch (first) {
case "|":
return lines.join("\n")
case ">":
return lines.join("\n").split(/\n{2,}/).map(function (l) {
return l.split(/\n/).join(" ")
}).join("\n")
default:
if (!lines.length) return first
// array or object.
// the first line will be either "- value" or "key: value"
return lines[0].charAt(0) === "-" ? decodeArr(lines) : decodeObj(lines)
}
}
function decodeArr (lines) {
var out = []
, key = 0
, val = []
for (var i = 0, l = lines.length; i < l; i ++) {
// if it starts with a -, then it's a new thing
var line = lines[i]
if (line.charAt(0) === "-") {
if (val.length) {
out[key ++] = decode(val.join("\n"))
val.length = 0
}
val.push(line.substr(1).trim())
} else if (line.charAt(0) === " ") {
val.push(line)
} else return []
}
if (val.length) {
out[key ++] = decode(val.join("\n"))
}
return out
}
function decodeObj (lines) {
var out = {}
, val = []
, key = null
for (var i = 0, l = lines.length; i < l; i ++) {
var line = lines[i]
if (line.charAt(0) === " ") {
val.push(line)
continue
}
// some key:val
if (val.length) {
out[key] = decode(val.join("\n"))
val.length = 0
}
// parse out the quoted key
var first
if (line.charAt(0) === "\"") {
for (var ii = 1, ll = line.length, esc = false; ii < ll; ii ++) {
var c = line.charAt(ii)
if (c === "\\") {
esc = !esc
} else if (c === "\"" && !esc) {
break
}
}
key = JSON.parse(line.substr(0, ii + 1))
line = line.substr(ii + 1)
first = line.substr(line.indexOf(":") + 1).trim()
} else {
var kv = line.split(":")
key = kv.shift()
first = kv.join(":").trim()
}
// now we've set a key, and "first" has the first line of the value.
val.push(first.trim())
}
if (val.length) out[key] = decode(val.join("\n"))
return out
}
function undent (lines) {
var i = lines[0].match(/^\s*/)[0].length
return lines.map(function (line) {
return line.substr(i)
})
}
// XXX Turn this into proper tests.
if (require.main === module) {
var obj = [{"bigstring":new Error().stack}
,{ar:[{list:"of"},{some:"objects"}]}
,{date:new Date()}
,{"super huge string":new Error().stack}
]
Date.prototype.toJSON = function (k, val) {
console.error(k, val, this)
return this.toISOString() + " (it's a date)"
}
var enc = encode(obj)
, dec = decode(enc)
, encDec = encode(dec)
console.error(JSON.stringify({ obj : obj
, enc : enc.split(/\n/)
, dec : dec }, null, 2), encDec === enc)
var num = 100
, encNum = encode(num)
, decEncNum = decode(encNum)
console.error([num, encNum, decEncNum])
}