ini.js 4.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166
  1. exports.parse = exports.decode = decode
  2. exports.stringify = exports.encode = encode
  3. exports.safe = safe
  4. exports.unsafe = unsafe
  5. var eol = process.platform === "win32" ? "\r\n" : "\n"
  6. function encode (obj, section) {
  7. var children = []
  8. , out = ""
  9. Object.keys(obj).forEach(function (k, _, __) {
  10. var val = obj[k]
  11. if (val && Array.isArray(val)) {
  12. val.forEach(function(item) {
  13. out += safe(k + "[]") + " = " + safe(item) + "\n"
  14. })
  15. }
  16. else if (val && typeof val === "object") {
  17. children.push(k)
  18. } else {
  19. out += safe(k) + " = " + safe(val) + eol
  20. }
  21. })
  22. if (section && out.length) {
  23. out = "[" + safe(section) + "]" + eol + out
  24. }
  25. children.forEach(function (k, _, __) {
  26. var nk = dotSplit(k).join('\\.')
  27. var child = encode(obj[k], (section ? section + "." : "") + nk)
  28. if (out.length && child.length) {
  29. out += eol
  30. }
  31. out += child
  32. })
  33. return out
  34. }
  35. function dotSplit (str) {
  36. return str.replace(/\1/g, '\2LITERAL\\1LITERAL\2')
  37. .replace(/\\\./g, '\1')
  38. .split(/\./).map(function (part) {
  39. return part.replace(/\1/g, '\\.')
  40. .replace(/\2LITERAL\\1LITERAL\2/g, '\1')
  41. })
  42. }
  43. function decode (str) {
  44. var out = {}
  45. , p = out
  46. , section = null
  47. , state = "START"
  48. // section |key = value
  49. , re = /^\[([^\]]*)\]$|^([^=]+)(=(.*))?$/i
  50. , lines = str.split(/[\r\n]+/g)
  51. , section = null
  52. lines.forEach(function (line, _, __) {
  53. if (!line || line.match(/^\s*;/)) return
  54. var match = line.match(re)
  55. if (!match) return
  56. if (match[1] !== undefined) {
  57. section = unsafe(match[1])
  58. p = out[section] = out[section] || {}
  59. return
  60. }
  61. var key = unsafe(match[2])
  62. , value = match[3] ? unsafe((match[4] || "")) : true
  63. switch (value) {
  64. case 'true':
  65. case 'false':
  66. case 'null': value = JSON.parse(value)
  67. }
  68. // Convert keys with '[]' suffix to an array
  69. if (key.length > 2 && key.slice(-2) === "[]") {
  70. key = key.substring(0, key.length - 2)
  71. if (!p[key]) {
  72. p[key] = []
  73. }
  74. else if (!Array.isArray(p[key])) {
  75. p[key] = [p[key]]
  76. }
  77. }
  78. // safeguard against resetting a previously defined
  79. // array by accidentally forgetting the brackets
  80. if (Array.isArray(p[key])) {
  81. p[key].push(value)
  82. }
  83. else {
  84. p[key] = value
  85. }
  86. })
  87. // {a:{y:1},"a.b":{x:2}} --> {a:{y:1,b:{x:2}}}
  88. // use a filter to return the keys that have to be deleted.
  89. Object.keys(out).filter(function (k, _, __) {
  90. if (!out[k] || typeof out[k] !== "object" || Array.isArray(out[k])) return false
  91. // see if the parent section is also an object.
  92. // if so, add it to that, and mark this one for deletion
  93. var parts = dotSplit(k)
  94. , p = out
  95. , l = parts.pop()
  96. , nl = l.replace(/\\\./g, '.')
  97. parts.forEach(function (part, _, __) {
  98. if (!p[part] || typeof p[part] !== "object") p[part] = {}
  99. p = p[part]
  100. })
  101. if (p === out && nl === l) return false
  102. p[nl] = out[k]
  103. return true
  104. }).forEach(function (del, _, __) {
  105. delete out[del]
  106. })
  107. return out
  108. }
  109. function safe (val) {
  110. return ( typeof val !== "string"
  111. || val.match(/[\r\n]/)
  112. || val.match(/^\[/)
  113. || (val.length > 1
  114. && val.charAt(0) === "\""
  115. && val.slice(-1) === "\"")
  116. || val !== val.trim() )
  117. ? JSON.stringify(val)
  118. : val.replace(/;/g, '\\;')
  119. }
  120. function unsafe (val, doUnesc) {
  121. val = (val || "").trim()
  122. if (val.charAt(0) === "\"" && val.slice(-1) === "\"") {
  123. try { val = JSON.parse(val) } catch (_) {}
  124. } else {
  125. // walk the val to find the first not-escaped ; character
  126. var esc = false
  127. var unesc = "";
  128. for (var i = 0, l = val.length; i < l; i++) {
  129. var c = val.charAt(i)
  130. if (esc) {
  131. if (c === "\\" || c === ";")
  132. unesc += c
  133. else
  134. unesc += "\\" + c
  135. esc = false
  136. } else if (c === ";") {
  137. break
  138. } else if (c === "\\") {
  139. esc = true
  140. } else {
  141. unesc += c
  142. }
  143. }
  144. if (esc)
  145. unesc += "\\"
  146. return unesc
  147. }
  148. return val
  149. }