ini.js 4.9 KB

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