node-properties.js 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777
  1. /**
  2. * The MIT License (MIT)
  3. *
  4. * Copyright (c) 2014 Gabriel Llamas
  5. *
  6. * Permission is hereby granted, free of charge, to any person obtaining a copy
  7. * of this software and associated documentation files (the "Software"), to deal
  8. * in the Software without restriction, including without limitation the rights
  9. * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  10. * copies of the Software, and to permit persons to whom the Software is
  11. * furnished to do so, subject to the following conditions:
  12. *
  13. * The above copyright notice and this permission notice shall be included in
  14. * all copies or substantial portions of the Software.
  15. *
  16. * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  17. * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  18. * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  19. * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  20. * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  21. * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
  22. * THE SOFTWARE.
  23. *
  24. */
  25. "use strict";
  26. var hex = function (c){
  27. switch (c){
  28. case "0": return 0;
  29. case "1": return 1;
  30. case "2": return 2;
  31. case "3": return 3;
  32. case "4": return 4;
  33. case "5": return 5;
  34. case "6": return 6;
  35. case "7": return 7;
  36. case "8": return 8;
  37. case "9": return 9;
  38. case "a": case "A": return 10;
  39. case "b": case "B": return 11;
  40. case "c": case "C": return 12;
  41. case "d": case "D": return 13;
  42. case "e": case "E": return 14;
  43. case "f": case "F": return 15;
  44. }
  45. };
  46. var parse = function (data, options, handlers, control){
  47. var c;
  48. var code;
  49. var escape;
  50. var skipSpace = true;
  51. var isCommentLine;
  52. var isSectionLine;
  53. var newLine = true;
  54. var multiLine;
  55. var isKey = true;
  56. var key = "";
  57. var value = "";
  58. var section;
  59. var unicode;
  60. var unicodeRemaining;
  61. var escapingUnicode;
  62. var keySpace;
  63. var sep;
  64. var ignoreLine;
  65. var line = function (){
  66. if (key || value || sep){
  67. handlers.line (key, value);
  68. key = "";
  69. value = "";
  70. sep = false;
  71. }
  72. };
  73. var escapeString = function (key, c, code){
  74. if (escapingUnicode && unicodeRemaining){
  75. unicode = (unicode << 4) + hex (c);
  76. if (--unicodeRemaining) return key;
  77. escape = false;
  78. escapingUnicode = false;
  79. return key + String.fromCharCode (unicode);
  80. }
  81. //code 117: u
  82. if (code === 117){
  83. unicode = 0;
  84. escapingUnicode = true;
  85. unicodeRemaining = 4;
  86. return key;
  87. }
  88. escape = false;
  89. //code 116: t
  90. //code 114: r
  91. //code 110: n
  92. //code 102: f
  93. if (code === 116) return key + "\t";
  94. else if (code === 114) return key + "\r";
  95. else if (code === 110) return key + "\n";
  96. else if (code === 102) return key + "\f";
  97. return key + c;
  98. };
  99. var isComment;
  100. var isSeparator;
  101. if (options._strict){
  102. isComment = function (c, code, options){
  103. return options._comments[c];
  104. };
  105. isSeparator = function (c, code, options){
  106. return options._separators[c];
  107. };
  108. }else{
  109. isComment = function (c, code, options){
  110. //code 35: #
  111. //code 33: !
  112. return code === 35 || code === 33 || options._comments[c];
  113. };
  114. isSeparator = function (c, code, options){
  115. //code 61: =
  116. //code 58: :
  117. return code === 61 || code === 58 || options._separators[c];
  118. };
  119. }
  120. for (var i=~~control.resume; i<data.length; i++){
  121. if (control.abort) return;
  122. if (control.pause){
  123. //The next index is always the start of a new line, it's a like a fresh
  124. //start, there's no need to save the current state
  125. control.resume = i;
  126. return;
  127. }
  128. c = data[i];
  129. code = data.charCodeAt (i);
  130. //code 13: \r
  131. if (code === 13) continue;
  132. if (isCommentLine){
  133. //code 10: \n
  134. if (code === 10){
  135. isCommentLine = false;
  136. newLine = true;
  137. skipSpace = true;
  138. }
  139. continue;
  140. }
  141. //code 93: ]
  142. if (isSectionLine && code === 93){
  143. handlers.section (section);
  144. //Ignore chars after the section in the same line
  145. ignoreLine = true;
  146. continue;
  147. }
  148. if (skipSpace){
  149. //code 32: " " (space)
  150. //code 9: \t
  151. //code 12: \f
  152. if (code === 32 || code === 9 || code === 12){
  153. continue;
  154. }
  155. //code 10: \n
  156. if (!multiLine && code === 10){
  157. //Empty line or key w/ separator and w/o value
  158. isKey = true;
  159. keySpace = false;
  160. newLine = true;
  161. line ();
  162. continue;
  163. }
  164. skipSpace = false;
  165. multiLine = false;
  166. }
  167. if (newLine){
  168. newLine = false;
  169. if (isComment (c, code, options)){
  170. isCommentLine = true;
  171. continue;
  172. }
  173. //code 91: [
  174. if (options.sections && code === 91){
  175. section = "";
  176. isSectionLine = true;
  177. control.skipSection = false;
  178. continue;
  179. }
  180. }
  181. //code 10: \n
  182. if (code !== 10){
  183. if (control.skipSection || ignoreLine) continue;
  184. if (!isSectionLine){
  185. if (!escape && isKey && isSeparator (c, code, options)){
  186. //sep is needed to detect empty key and empty value with a
  187. //non-whitespace separator
  188. sep = true;
  189. isKey = false;
  190. keySpace = false;
  191. //Skip whitespace between separator and value
  192. skipSpace = true;
  193. continue;
  194. }
  195. }
  196. //code 92: "\" (backslash)
  197. if (code === 92){
  198. if (escape){
  199. if (escapingUnicode) continue;
  200. if (keySpace){
  201. //Line with whitespace separator
  202. keySpace = false;
  203. isKey = false;
  204. }
  205. if (isSectionLine) section += "\\";
  206. else if (isKey) key += "\\";
  207. else value += "\\";
  208. }
  209. escape = !escape;
  210. }else{
  211. if (keySpace){
  212. //Line with whitespace separator
  213. keySpace = false;
  214. isKey = false;
  215. }
  216. if (isSectionLine){
  217. if (escape) section = escapeString (section, c, code);
  218. else section += c;
  219. }else if (isKey){
  220. if (escape){
  221. key = escapeString (key, c, code);
  222. }else{
  223. //code 32: " " (space)
  224. //code 9: \t
  225. //code 12: \f
  226. if (code === 32 || code === 9 || code === 12){
  227. keySpace = true;
  228. //Skip whitespace between key and separator
  229. skipSpace = true;
  230. continue;
  231. }
  232. key += c;
  233. }
  234. }else{
  235. if (escape) value = escapeString (value, c, code);
  236. else value += c;
  237. }
  238. }
  239. }else{
  240. if (escape){
  241. if (!escapingUnicode){
  242. escape = false;
  243. }
  244. skipSpace = true;
  245. multiLine = true;
  246. }else{
  247. if (isSectionLine){
  248. isSectionLine = false;
  249. if (!ignoreLine){
  250. //The section doesn't end with ], it's a key
  251. control.error = new Error ("The section line \"" + section +
  252. "\" must end with \"]\"");
  253. return;
  254. }
  255. ignoreLine = false;
  256. }
  257. newLine = true;
  258. skipSpace = true;
  259. isKey = true;
  260. line ();
  261. }
  262. }
  263. }
  264. control.parsed = true;
  265. if (isSectionLine && !ignoreLine){
  266. //The section doesn't end with ], it's a key
  267. control.error = new Error ("The section line \"" + section + "\" must end" +
  268. "with \"]\"");
  269. return;
  270. }
  271. line ();
  272. };
  273. var INCLUDE_KEY = "include";
  274. var INDEX_FILE = "index.properties";
  275. var cast = function (value){
  276. if (value === null || value === "null") return null;
  277. if (value === "undefined") return undefined;
  278. if (value === "true") return true;
  279. if (value === "false") return false;
  280. var v = Number (value);
  281. return isNaN (v) ? value : v;
  282. };
  283. var expand = function (o, str, options, cb){
  284. if (!options.variables || !str) return cb (null, str);
  285. var stack = [];
  286. var c;
  287. var cp;
  288. var key = "";
  289. var section = null;
  290. var v;
  291. var holder;
  292. var t;
  293. var n;
  294. for (var i=0; i<str.length; i++){
  295. c = str[i];
  296. if (cp === "$" && c === "{"){
  297. key = key.substring (0, key.length - 1);
  298. stack.push ({
  299. key: key,
  300. section: section
  301. });
  302. key = "";
  303. section = null;
  304. continue;
  305. }else if (stack.length){
  306. if (options.sections && c === "|"){
  307. section = key;
  308. key = "";
  309. continue;
  310. }else if (c === "}"){
  311. holder = section !== null ? searchValue (o, section, true) : o;
  312. if (!holder){
  313. return cb (new Error ("The section \"" + section + "\" does not " +
  314. "exist"));
  315. }
  316. v = options.namespaces ? searchValue (holder, key) : holder[key];
  317. if (v === undefined){
  318. //Read the external vars
  319. v = options.namespaces
  320. ? searchValue (options._vars, key)
  321. : options._vars[key]
  322. if (v === undefined){
  323. return cb (new Error ("The property \"" + key + "\" does not " +
  324. "exist"));
  325. }
  326. }
  327. t = stack.pop ();
  328. section = t.section;
  329. key = t.key + (v === null ? "" : v);
  330. continue;
  331. }
  332. }
  333. cp = c;
  334. key += c;
  335. }
  336. if (stack.length !== 0){
  337. return cb (new Error ("Malformed variable: " + str));
  338. }
  339. cb (null, key);
  340. };
  341. var searchValue = function (o, chain, section){
  342. var n = chain.split (".");
  343. var str;
  344. for (var i=0; i<n.length-1; i++){
  345. str = n[i];
  346. if (o[str] === undefined) return;
  347. o = o[str];
  348. }
  349. var v = o[n[n.length - 1]];
  350. if (section){
  351. if (typeof v !== "object") return;
  352. return v;
  353. }else{
  354. if (typeof v === "object") return;
  355. return v;
  356. }
  357. };
  358. var namespaceKey = function (o, key, value){
  359. var n = key.split (".");
  360. var str;
  361. for (var i=0; i<n.length-1; i++){
  362. str = n[i];
  363. if (o[str] === undefined){
  364. o[str] = {};
  365. }else if (typeof o[str] !== "object"){
  366. throw new Error ("Invalid namespace chain in the property name '" +
  367. key + "' ('" + str + "' has already a value)");
  368. }
  369. o = o[str];
  370. }
  371. o[n[n.length - 1]] = value;
  372. };
  373. var namespaceSection = function (o, section){
  374. var n = section.split (".");
  375. var str;
  376. for (var i=0; i<n.length; i++){
  377. str = n[i];
  378. if (o[str] === undefined){
  379. o[str] = {};
  380. }else if (typeof o[str] !== "object"){
  381. throw new Error ("Invalid namespace chain in the section name '" +
  382. section + "' ('" + str + "' has already a value)");
  383. }
  384. o = o[str];
  385. }
  386. return o;
  387. };
  388. var merge = function (o1, o2){
  389. for (var p in o2){
  390. try{
  391. if (o1[p].constructor === Object){
  392. o1[p] = merge (o1[p], o2[p]);
  393. }else{
  394. o1[p] = o2[p];
  395. }
  396. }catch (e){
  397. o1[p] = o2[p];
  398. }
  399. }
  400. return o1;
  401. }
  402. var build = function (data, options, dirname, cb){
  403. var o = {};
  404. if (options.namespaces){
  405. var n = {};
  406. }
  407. var control = {
  408. abort: false,
  409. skipSection: false
  410. };
  411. if (options.include){
  412. var remainingIncluded = 0;
  413. var include = function (value){
  414. if (currentSection !== null){
  415. return abort (new Error ("Cannot include files from inside a " +
  416. "section: " + currentSection));
  417. }
  418. var p = path.resolve (dirname, value);
  419. if (options._included[p]) return;
  420. options._included[p] = true;
  421. remainingIncluded++;
  422. control.pause = true;
  423. read (p, options, function (error, included){
  424. if (error) return abort (error);
  425. remainingIncluded--;
  426. merge (options.namespaces ? n : o, included);
  427. control.pause = false;
  428. if (!control.parsed){
  429. parse (data, options, handlers, control);
  430. if (control.error) return abort (control.error);
  431. }
  432. if (!remainingIncluded) cb (null, options.namespaces ? n : o);
  433. });
  434. };
  435. }
  436. if (!data){
  437. if (cb) return cb (null, o);
  438. return o;
  439. }
  440. var currentSection = null;
  441. var currentSectionStr = null;
  442. var abort = function (error){
  443. control.abort = true;
  444. if (cb) return cb (error);
  445. throw error;
  446. };
  447. var handlers = {};
  448. var reviver = {
  449. assert: function (){
  450. return this.isProperty ? reviverLine.value : true;
  451. }
  452. };
  453. var reviverLine = {};
  454. //Line handler
  455. //For speed reasons, if "namespaces" is enabled, the old object is still
  456. //populated, e.g.: ${a.b} reads the "a.b" property from { "a.b": 1 }, instead
  457. //of having a unique object { a: { b: 1 } } which is slower to search for
  458. //the "a.b" value
  459. //If "a.b" is not found, then the external vars are read. If "namespaces" is
  460. //enabled, the var "a.b" is split and it searches the a.b value. If it is not
  461. //enabled, then the var "a.b" searches the "a.b" value
  462. var line;
  463. var error;
  464. if (options.reviver){
  465. if (options.sections){
  466. line = function (key, value){
  467. if (options.include && key === INCLUDE_KEY) return include (value);
  468. reviverLine.value = value;
  469. reviver.isProperty = true;
  470. reviver.isSection = false;
  471. value = options.reviver.call (reviver, key, value, currentSectionStr);
  472. if (value !== undefined){
  473. if (options.namespaces){
  474. try{
  475. namespaceKey (currentSection === null ? n : currentSection,
  476. key, value);
  477. }catch (error){
  478. abort (error);
  479. }
  480. }else{
  481. if (currentSection === null) o[key] = value;
  482. else currentSection[key] = value;
  483. }
  484. }
  485. };
  486. }else{
  487. line = function (key, value){
  488. if (options.include && key === INCLUDE_KEY) return include (value);
  489. reviverLine.value = value;
  490. reviver.isProperty = true;
  491. reviver.isSection = false;
  492. value = options.reviver.call (reviver, key, value);
  493. if (value !== undefined){
  494. if (options.namespaces){
  495. try{
  496. namespaceKey (n, key, value);
  497. }catch (error){
  498. abort (error);
  499. }
  500. }else{
  501. o[key] = value;
  502. }
  503. }
  504. };
  505. }
  506. }else{
  507. if (options.sections){
  508. line = function (key, value){
  509. if (options.include && key === INCLUDE_KEY) return include (value);
  510. if (options.namespaces){
  511. try{
  512. namespaceKey (currentSection === null ? n : currentSection, key,
  513. value);
  514. }catch (error){
  515. abort (error);
  516. }
  517. }else{
  518. if (currentSection === null) o[key] = value;
  519. else currentSection[key] = value;
  520. }
  521. };
  522. }else{
  523. line = function (key, value){
  524. if (options.include && key === INCLUDE_KEY) return include (value);
  525. if (options.namespaces){
  526. try{
  527. namespaceKey (n, key, value);
  528. }catch (error){
  529. abort (error);
  530. }
  531. }else{
  532. o[key] = value;
  533. }
  534. };
  535. }
  536. }
  537. //Section handler
  538. var section;
  539. if (options.sections){
  540. if (options.reviver){
  541. section = function (section){
  542. currentSectionStr = section;
  543. reviverLine.section = section;
  544. reviver.isProperty = false;
  545. reviver.isSection = true;
  546. var add = options.reviver.call (reviver, null, null, section);
  547. if (add){
  548. if (options.namespaces){
  549. try{
  550. currentSection = namespaceSection (n, section);
  551. }catch (error){
  552. abort (error);
  553. }
  554. }else{
  555. currentSection = o[section] = {};
  556. }
  557. }else{
  558. control.skipSection = true;
  559. }
  560. };
  561. }else{
  562. section = function (section){
  563. currentSectionStr = section;
  564. if (options.namespaces){
  565. try{
  566. currentSection = namespaceSection (n, section);
  567. }catch (error){
  568. abort (error);
  569. }
  570. }else{
  571. currentSection = o[section] = {};
  572. }
  573. };
  574. }
  575. }
  576. //Variables
  577. if (options.variables){
  578. handlers.line = function (key, value){
  579. expand (options.namespaces ? n : o, key, options, function (error, key){
  580. if (error) return abort (error);
  581. expand (options.namespaces ? n : o, value, options,
  582. function (error, value){
  583. if (error) return abort (error);
  584. line (key, cast (value || null));
  585. });
  586. });
  587. };
  588. if (options.sections){
  589. handlers.section = function (s){
  590. expand (options.namespaces ? n : o, s, options, function (error, s){
  591. if (error) return abort (error);
  592. section (s);
  593. });
  594. };
  595. }
  596. }else{
  597. handlers.line = function (key, value){
  598. line (key, cast (value || null));
  599. };
  600. if (options.sections){
  601. handlers.section = section;
  602. }
  603. }
  604. parse (data, options, handlers, control);
  605. if (control.error) return abort (control.error);
  606. if (control.abort || control.pause) return;
  607. if (cb) return cb (null, options.namespaces ? n : o);
  608. return options.namespaces ? n : o;
  609. };
  610. var read = function (f, options, cb){
  611. fs.stat (f, function (error, stats){
  612. if (error) return cb (error);
  613. var dirname;
  614. if (stats.isDirectory ()){
  615. dirname = f;
  616. f = path.join (f, INDEX_FILE);
  617. }else{
  618. dirname = path.dirname (f);
  619. }
  620. fs.readFile (f, { encoding: "utf8" }, function (error, data){
  621. if (error) return cb (error);
  622. build (data, options, dirname, cb);
  623. });
  624. });
  625. };
  626. module.exports = function (data, options, cb){
  627. if (typeof options === "function"){
  628. cb = options;
  629. options = {};
  630. }
  631. options = options || {};
  632. var code;
  633. if (options.include){
  634. if (!cb) throw new Error ("A callback must be passed if the 'include' " +
  635. "option is enabled");
  636. options._included = {};
  637. }
  638. options = options || {};
  639. options._strict = options.strict && (options.comments || options.separators);
  640. options._vars = options.vars || {};
  641. var comments = options.comments || [];
  642. if (!Array.isArray (comments)) comments = [comments];
  643. var c = {};
  644. comments.forEach (function (comment){
  645. code = comment.charCodeAt (0);
  646. if (comment.length > 1 || code < 33 || code > 126){
  647. throw new Error ("The comment token must be a single printable ASCII " +
  648. "character");
  649. }
  650. c[comment] = true;
  651. });
  652. options._comments = c;
  653. var separators = options.separators || [];
  654. if (!Array.isArray (separators)) separators = [separators];
  655. var s = {};
  656. separators.forEach (function (separator){
  657. code = separator.charCodeAt (0);
  658. if (separator.length > 1 || code < 33 || code > 126){
  659. throw new Error ("The separator token must be a single printable ASCII " +
  660. "character");
  661. }
  662. s[separator] = true;
  663. });
  664. options._separators = s;
  665. if (options.path){
  666. if (!cb) throw new Error ("A callback must be passed if the 'path' " +
  667. "option is enabled");
  668. if (options.include){
  669. read (data, options, cb);
  670. }else{
  671. fs.readFile (data, { encoding: "utf8" }, function (error, data){
  672. if (error) return cb (error);
  673. build (data, options, ".", cb);
  674. });
  675. }
  676. }else{
  677. return build (data, options, ".", cb);
  678. }
  679. };