json 57 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685
  1. #!/usr/bin/env node
  2. /**
  3. * Copyright (c) 2014 Trent Mick. All rights reserved.
  4. * Copyright (c) 2014 Joyent Inc. All rights reserved.
  5. *
  6. * json -- JSON love for your command line.
  7. *
  8. * See <https://github.com/trentm/json> and <https://trentm.com/json/>
  9. */
  10. var VERSION = '9.0.6';
  11. var p = console.warn;
  12. var util = require('util');
  13. var assert = require('assert');
  14. var path = require('path');
  15. var vm = require('vm');
  16. var fs = require('fs');
  17. var warn = console.warn;
  18. var EventEmitter = require('events').EventEmitter;
  19. //--- exports for module usage
  20. exports.main = main;
  21. exports.getVersion = getVersion;
  22. exports.parseLookup = parseLookup;
  23. // As an exported API, these are still experimental:
  24. exports.lookupDatum = lookupDatum;
  25. exports.printDatum = printDatum; // DEPRECATED
  26. //---- globals and constants
  27. // Output modes.
  28. var OM_JSONY = 1;
  29. var OM_JSON = 2;
  30. var OM_INSPECT = 3;
  31. var OM_COMPACT = 4;
  32. var OM_FROM_NAME = {
  33. 'jsony': OM_JSONY,
  34. 'json': OM_JSON,
  35. 'inspect': OM_INSPECT,
  36. 'compact': OM_COMPACT
  37. };
  38. //---- support functions
  39. function getVersion() {
  40. return VERSION;
  41. }
  42. /**
  43. * Return a *shallow* copy of the given object.
  44. *
  45. * Only support objects that you get out of JSON, i.e. no functions.
  46. */
  47. function objCopy(obj) {
  48. var copy;
  49. if (Array.isArray(obj)) {
  50. copy = obj.slice();
  51. } else if (typeof (obj) === 'object') {
  52. copy = {};
  53. Object.keys(obj).forEach(function (k) {
  54. copy[k] = obj[k];
  55. });
  56. } else {
  57. copy = obj; // immutable type
  58. }
  59. return copy;
  60. }
  61. if (util.format) {
  62. format = util.format;
  63. } else {
  64. // From <https://github.com/joyent/node/blob/master/lib/util.js#L22>:
  65. var formatRegExp = /%[sdj%]/g;
  66. function format(f) {
  67. var i;
  68. if (typeof (f) !== 'string') {
  69. var objects = [];
  70. for (i = 0; i < arguments.length; i++) {
  71. objects.push(util.inspect(arguments[i]));
  72. }
  73. return objects.join(' ');
  74. }
  75. i = 1;
  76. var args = arguments;
  77. var len = args.length;
  78. var str = String(f).replace(formatRegExp, function (x) {
  79. if (i >= len)
  80. return x;
  81. switch (x) {
  82. case '%s':
  83. return String(args[i++]);
  84. case '%d':
  85. return Number(args[i++]);
  86. case '%j':
  87. return JSON.stringify(args[i++]);
  88. case '%%':
  89. return '%';
  90. default:
  91. return x;
  92. }
  93. });
  94. for (var x = args[i]; i < len; x = args[++i]) {
  95. if (x === null || typeof (x) !== 'object') {
  96. str += ' ' + x;
  97. } else {
  98. str += ' ' + util.inspect(x);
  99. }
  100. }
  101. return str;
  102. }
  103. }
  104. /**
  105. * Parse the given string into a JS string. Basically: handle escapes.
  106. */
  107. function _parseString(s) {
  108. /* JSSTYLED */
  109. var quoted = '"' + s.replace(/\\"/, '"').replace('"', '\\"') + '"';
  110. return eval(quoted);
  111. }
  112. // json_parse.js (<https://github.com/douglascrockford/JSON-js>)
  113. /* BEGIN JSSTYLED */
  114. // START json_parse
  115. var json_parse=function(){"use strict";var a,b,c={'"':'"',"\\":"\\","/":"/",b:"\b",f:"\f",n:"\n",r:"\r",t:"\t"},d,e=function(b){throw{name:"SyntaxError",message:b,at:a,text:d}},f=function(c){return c&&c!==b&&e("Expected '"+c+"' instead of '"+b+"'"),b=d.charAt(a),a+=1,b},g=function(){var a,c="";b==="-"&&(c="-",f("-"));while(b>="0"&&b<="9")c+=b,f();if(b==="."){c+=".";while(f()&&b>="0"&&b<="9")c+=b}if(b==="e"||b==="E"){c+=b,f();if(b==="-"||b==="+")c+=b,f();while(b>="0"&&b<="9")c+=b,f()}a=+c;if(!isFinite(a))e("Bad number");else return a},h=function(){var a,d,g="",h;if(b==='"')while(f()){if(b==='"')return f(),g;if(b==="\\"){f();if(b==="u"){h=0;for(d=0;d<4;d+=1){a=parseInt(f(),16);if(!isFinite(a))break;h=h*16+a}g+=String.fromCharCode(h)}else if(typeof c[b]=="string")g+=c[b];else break}else g+=b}e("Bad string")},i=function(){while(b&&b<=" ")f()},j=function(){switch(b){case"t":return f("t"),f("r"),f("u"),f("e"),!0;case"f":return f("f"),f("a"),f("l"),f("s"),f("e"),!1;case"n":return f("n"),f("u"),f("l"),f("l"),null}e("Unexpected '"+b+"'")},k,l=function(){var a=[];if(b==="["){f("["),i();if(b==="]")return f("]"),a;while(b){a.push(k()),i();if(b==="]")return f("]"),a;f(","),i()}}e("Bad array")},m=function(){var a,c={};if(b==="{"){f("{"),i();if(b==="}")return f("}"),c;while(b){a=h(),i(),f(":"),Object.hasOwnProperty.call(c,a)&&e('Duplicate key "'+a+'"'),c[a]=k(),i();if(b==="}")return f("}"),c;f(","),i()}}e("Bad object")};return k=function(){i();switch(b){case"{":return m();case"[":return l();case'"':return h();case"-":return g();default:return b>="0"&&b<="9"?g():j()}},function(c,f){var g;return d=c,a=0,b=" ",g=k(),i(),b&&e("Syntax error"),typeof f=="function"?function h(a,b){var c,d,e=a[b];if(e&&typeof e=="object")for(c in e)Object.prototype.hasOwnProperty.call(e,c)&&(d=h(e,c),d!==undefined?e[c]=d:delete e[c]);return f.call(a,b,e)}({"":g},""):g}}();
  116. // END json_parse
  117. /* END JSSTYLED */
  118. function printHelp() {
  119. /* BEGIN JSSTYLED */
  120. var w = console.log;
  121. w('Usage:');
  122. w(' <something generating JSON on stdout> | json [OPTIONS] [LOOKUPS...]');
  123. w(' json -f FILE [OPTIONS] [LOOKUPS...]');
  124. w('');
  125. w('Pipe in your JSON for pretty-printing, JSON validation, filtering, ');
  126. w('and modification. Supply one or more `LOOKUPS` to extract a ');
  127. w('subset of the JSON. HTTP header blocks are skipped by default.');
  128. w('Roughly in order of processing, features are:');
  129. w('');
  130. w('Grouping:');
  131. w(' Use "-g" or "--group" to group adjacent objects, separated by');
  132. w(' by no space or a by a newline, or adjacent arrays, separate by');
  133. w(' by a newline. This can be helpful for, e.g.: ');
  134. w(' $ cat *.json | json -g ... ');
  135. w(' and similar.');
  136. w('');
  137. w('Execution:');
  138. w(' Use the "-e CODE" option to execute JavaScript code on the input JSON.');
  139. w(' $ echo \'{"name":"trent","age":38}\' | json -e \'this.age++\'');
  140. w(' {');
  141. w(' "name": "trent",');
  142. w(' "age": 39');
  143. w(' }');
  144. w(' If input is an array, this will automatically process each');
  145. w(' item separately.');
  146. w('');
  147. w('Conditional filtering:');
  148. w(' Use the "-c CODE" option to filter the input JSON.');
  149. w(' $ echo \'[{"age":38},{"age":4}]\' | json -c \'this.age>21\'');
  150. w(' [{\'age\':38}]');
  151. w(' If input is an array, this will automatically process each');
  152. w(' item separately. Note: "CODE" is JavaScript code.');
  153. w('');
  154. w('Lookups:');
  155. w(' Use lookup arguments to extract particular values:');
  156. w(' $ echo \'{"name":"trent","age":38}\' | json name');
  157. w(' trent');
  158. w('');
  159. w(' Use "-a" for *array processing* of lookups and *tabular output*:');
  160. w(' $ echo \'{"name":"trent","age":38}\' | json name age');
  161. w(' trent');
  162. w(' 38');
  163. w(' $ echo \'[{"name":"trent","age":38},');
  164. w(' {"name":"ewan","age":4}]\' | json -a name age');
  165. w(' trent 38');
  166. w(' ewan 4');
  167. w('');
  168. w('In-place editing:');
  169. w(' Use "-I, --in-place" to edit a file in place:');
  170. w(' $ json -I -f config.json # reformat');
  171. w(' $ json -I -f config.json -c \'this.logLevel="debug"\' # add field');
  172. w('');
  173. w('Pretty-printing:');
  174. w(' Output is "jsony" by default: 2-space indented JSON, except a');
  175. w(' single string value is printed without quotes.');
  176. w(' $ echo \'{"name": "trent", "age": 38}\' | json');
  177. w(' {');
  178. w(' "name": "trent",');
  179. w(' "age": 38');
  180. w(' }');
  181. w(' $ echo \'{"name": "trent", "age": 38}\' | json name');
  182. w(' trent');
  183. w('');
  184. w(" Use '-j' or '-o json' for explicit JSON, '-o json-N' for N-space indent:");
  185. w(' $ echo \'{"name": "trent", "age": 38}\' | json -o json-0');
  186. w(' {"name":"trent","age":38}');
  187. w('');
  188. w('Options:');
  189. w(' -h, --help Print this help info and exit.');
  190. w(' --version Print version of this command and exit.');
  191. w(' -q, --quiet Don\'t warn if input isn\'t valid JSON.');
  192. w('');
  193. w(' -f FILE Path to a file to process. If not given, then');
  194. w(' stdin is used.');
  195. w(' -I, --in-place In-place edit of the file given with "-f".');
  196. w(' Lookups are not allow with in-place editing');
  197. w(' because it makes it too easy to lose content.');
  198. w('');
  199. w(' -H Drop any HTTP header block (as from `curl -i ...`).');
  200. w(' -g, --group Group adjacent objects or arrays into an array.');
  201. w(' --merge Merge adjacent objects into one. Keys in last ');
  202. w(' object win.');
  203. w(' --deep-merge Same as "--merge", but will recurse into objects ');
  204. w(' under the same key in both.')
  205. w(' -a, --array Process input as an array of separate inputs');
  206. w(' and output in tabular form.');
  207. w(' -A Process input as a single object, i.e. stop');
  208. w(' "-e" and "-c" automatically processing each');
  209. w(' item of an input array.');
  210. w(' -d DELIM Delimiter char for tabular output (default is " ").');
  211. w(' -D DELIM Delimiter char between lookups (default is "."). E.g.:');
  212. w(' $ echo \'{"a.b": {"b": 1}}\' | json -D / a.b/b');
  213. w('');
  214. w(' -M, --items Itemize an object into an array of ');
  215. w(' {"key": <key>, "value": <value>}');
  216. w(' objects for easier processing.');
  217. w('');
  218. w(' -e CODE Execute the given JavaScript code on the input. If input');
  219. w(' is an array, then each item of the array is processed');
  220. w(' separately (use "-A" to override).');
  221. w(' -c CODE Filter the input with JavaScript `CODE`. If `CODE`');
  222. w(' returns false-y, then the item is filtered out. If');
  223. w(' input is an array, then each item of the array is ');
  224. w(' processed separately (use "-A" to override).');
  225. w('');
  226. w(' -k, --keys Output the input object\'s keys.');
  227. w(' -n, --validate Just validate the input (no processing or output).');
  228. w(' Use with "-q" for silent validation (exit status).');
  229. w('');
  230. w(' -o, --output MODE');
  231. w(' Specify an output mode. One of:');
  232. w(' jsony (default): JSON with string quotes elided');
  233. w(' json: JSON output, 2-space indent');
  234. w(' json-N: JSON output, N-space indent, e.g. "json-4"');
  235. w(' inspect: node.js `util.inspect` output');
  236. w(' -i Shortcut for `-o inspect`');
  237. w(' -j Shortcut for `-o json`');
  238. w(' -0, -2, -4 Set indentation to the given value w/o setting MODE.');
  239. w(' -0 => -o jsony-0');
  240. w(' -4 => -o jsony-4');
  241. w(' -j0 => -o json-0');
  242. w('');
  243. w('See <http://trentm.com/json> for more docs and ');
  244. w('<https://github.com/trentm/json> for project details.');
  245. /* END JSSTYLED */
  246. }
  247. /**
  248. * Parse the command-line options and arguments into an object.
  249. *
  250. * {
  251. * 'args': [...] // arguments
  252. * 'help': true, // true if '-h' option given
  253. * // etc.
  254. * }
  255. *
  256. * @return {Object} The parsed options. `.args` is the argument list.
  257. * @throws {Error} If there is an error parsing argv.
  258. */
  259. function parseArgv(argv) {
  260. var parsed = {
  261. args: [],
  262. help: false,
  263. quiet: false,
  264. dropHeaders: false,
  265. exeSnippets: [],
  266. condSnippets: [],
  267. outputMode: OM_JSONY,
  268. jsonIndent: 2,
  269. array: null,
  270. delim: ' ',
  271. lookupDelim: '.',
  272. items: false,
  273. outputKeys: false,
  274. group: false,
  275. merge: null, // --merge -> 'shallow', --deep-merge -> 'deep'
  276. inputFiles: [],
  277. validate: false,
  278. inPlace: false
  279. };
  280. // Turn '-iH' into '-i -H', except for argument-accepting options.
  281. var args = argv.slice(2); // drop ['node', 'scriptname']
  282. var newArgs = [];
  283. var optTakesArg = {
  284. 'd': true,
  285. 'o': true,
  286. 'D': true
  287. };
  288. for (var i = 0; i < args.length; i++) {
  289. if (args[i] === '--') {
  290. newArgs = newArgs.concat(args.slice(i));
  291. break;
  292. }
  293. if (args[i].charAt(0) === '-' && args[i].charAt(1) !== '-' &&
  294. args[i].length > 2)
  295. {
  296. var splitOpts = args[i].slice(1).split('');
  297. for (var j = 0; j < splitOpts.length; j++) {
  298. newArgs.push('-' + splitOpts[j])
  299. if (optTakesArg[splitOpts[j]]) {
  300. var optArg = splitOpts.slice(j + 1).join('');
  301. if (optArg.length) {
  302. newArgs.push(optArg);
  303. }
  304. break;
  305. }
  306. }
  307. } else {
  308. newArgs.push(args[i]);
  309. }
  310. }
  311. args = newArgs;
  312. endOfOptions = false;
  313. while (args.length > 0) {
  314. var arg = args.shift();
  315. if (endOfOptions) {
  316. parsed.args.push(arg);
  317. break;
  318. }
  319. switch (arg) {
  320. case '--':
  321. endOfOptions = true;
  322. break;
  323. case '-h': // display help and exit
  324. case '--help':
  325. parsed.help = true;
  326. break;
  327. case '--version':
  328. parsed.version = true;
  329. break;
  330. case '-q':
  331. case '--quiet':
  332. parsed.quiet = true;
  333. break;
  334. case '-H': // drop any headers
  335. parsed.dropHeaders = true;
  336. break;
  337. case '-o':
  338. case '--output':
  339. var name = args.shift();
  340. if (!name) {
  341. throw new Error('no argument given for "-o|--output" option');
  342. }
  343. var idx = name.lastIndexOf('-');
  344. if (idx !== -1) {
  345. var indent = name.slice(idx + 1);
  346. if (/^\d+$/.test(indent)) {
  347. parsed.jsonIndent = Number(indent);
  348. name = name.slice(0, idx);
  349. } else if (indent === 'tab') {
  350. parsed.jsonIndent = '\t';
  351. name = name.slice(0, idx);
  352. }
  353. }
  354. parsed.outputMode = OM_FROM_NAME[name];
  355. if (parsed.outputMode === undefined) {
  356. throw new Error('unknown output mode: "' + name + '"');
  357. }
  358. break;
  359. case '-0':
  360. parsed.jsonIndent = 0;
  361. break;
  362. case '-2':
  363. parsed.jsonIndent = 2;
  364. break;
  365. case '-4':
  366. parsed.jsonIndent = 4;
  367. break;
  368. case '-I':
  369. case '--in-place':
  370. parsed.inPlace = true;
  371. break;
  372. case '-i': // output with util.inspect
  373. parsed.outputMode = OM_INSPECT;
  374. break;
  375. case '-j': // output with JSON.stringify
  376. parsed.outputMode = OM_JSON;
  377. break;
  378. case '-a':
  379. case '--array':
  380. parsed.array = true;
  381. break;
  382. case '-A':
  383. parsed.array = false;
  384. break;
  385. case '-d':
  386. parsed.delim = _parseString(args.shift());
  387. break;
  388. case '-D':
  389. parsed.lookupDelim = args.shift();
  390. if (parsed.lookupDelim.length !== 1) {
  391. throw new Error(format(
  392. 'invalid lookup delim "%s" (must be a single char)',
  393. parsed.lookupDelim));
  394. }
  395. break;
  396. case '-e':
  397. case '-E': // DEPRECATED in v9
  398. parsed.exeSnippets.push(args.shift());
  399. break;
  400. case '-c':
  401. case '-C': // DEPRECATED in v9
  402. parsed.condSnippets.push(args.shift());
  403. break;
  404. case '-M':
  405. case '--items':
  406. parsed.items = true;
  407. break;
  408. case '-k':
  409. case '--keys':
  410. parsed.outputKeys = true;
  411. break;
  412. case '-g':
  413. case '--group':
  414. parsed.group = true;
  415. break;
  416. case '--merge':
  417. parsed.merge = 'shallow';
  418. break;
  419. case '--deep-merge':
  420. parsed.merge = 'deep';
  421. break;
  422. case '-f':
  423. parsed.inputFiles.push(args.shift());
  424. break;
  425. case '-n':
  426. case '--validate':
  427. parsed.validate = true;
  428. break;
  429. default: // arguments
  430. if (!endOfOptions && arg.length > 0 && arg[0] === '-') {
  431. throw new Error('unknown option "' + arg + '"');
  432. }
  433. parsed.args.push(arg);
  434. break;
  435. }
  436. }
  437. if (parsed.group && parsed.merge) {
  438. throw new Error('cannot use -g|--group and --merge options together');
  439. }
  440. if (parsed.outputKeys && parsed.args.length > 0) {
  441. throw new Error(
  442. 'cannot use -k|--keys option and lookup arguments together');
  443. }
  444. if (parsed.inPlace && parsed.inputFiles.length !== 1) {
  445. throw new Error('must specify exactly one file with "-f FILE" to ' +
  446. 'use -I/--in-place');
  447. }
  448. if (parsed.inPlace && parsed.args.length > 0) {
  449. throw new Error('lookups cannot be specified with in-place editing ' +
  450. '(-I/--in-place), too easy to lose content');
  451. }
  452. return parsed;
  453. }
  454. /**
  455. * Streams chunks from given file paths or stdin.
  456. *
  457. * @param opts {Object} Parsed options.
  458. * @returns {Object} An emitter that emits 'chunk', 'error', and 'end'.
  459. * - `emit('chunk', chunk, [obj])` where chunk is a complete block of JSON
  460. * ready to parse. If `obj` is provided, it is the already parsed
  461. * JSON.
  462. * - `emit('error', error)` when an underlying stream emits an error
  463. * - `emit('end')` when all streams are done
  464. */
  465. function chunkEmitter(opts) {
  466. var emitter = new EventEmitter();
  467. var streaming = true;
  468. var chunks = [];
  469. var leftover = '';
  470. var finishedHeaders = false;
  471. function stripHeaders(s) {
  472. // Take off a leading HTTP header if any and pass it through.
  473. while (true) {
  474. if (s.slice(0, 5) === 'HTTP/') {
  475. var index = s.indexOf('\r\n\r\n');
  476. var sepLen = 4;
  477. if (index == -1) {
  478. index = s.indexOf('\n\n');
  479. sepLen = 2;
  480. }
  481. if (index != -1) {
  482. if (!opts.dropHeaders) {
  483. emit(s.slice(0, index + sepLen));
  484. }
  485. var is100Continue = (
  486. s.slice(0, 21) === 'HTTP/1.1 100 Continue');
  487. s = s.slice(index + sepLen);
  488. if (is100Continue) {
  489. continue;
  490. }
  491. finishedHeaders = true;
  492. }
  493. } else {
  494. finishedHeaders = true;
  495. }
  496. break;
  497. }
  498. //console.warn('stripHeaders done, finishedHeaders=%s', finishedHeaders)
  499. return s;
  500. }
  501. function emitChunks(block, emitter) {
  502. //console.warn('emitChunks start: block="%s"', block)
  503. /* JSSTYLED */
  504. var splitter = /(})(\s*\n\s*)?({\s*")/;
  505. var leftTrimmedBlock = block.trimLeft();
  506. if (leftTrimmedBlock && leftTrimmedBlock[0] !== '{') {
  507. // Currently only support streaming consecutive *objects*.
  508. streaming = false;
  509. chunks.push(block);
  510. return '';
  511. }
  512. /**
  513. * Example:
  514. * > '{"a":"b"}\n{"a":"b"}\n{"a":"b"}'.split(/(})(\s*\n\s*)?({\s*")/)
  515. * [ '{"a":"b"',
  516. * '}',
  517. * '\n',
  518. * '{"',
  519. * 'a":"b"',
  520. * '}',
  521. * '\n',
  522. * '{"',
  523. * 'a":"b"}' ]
  524. */
  525. var bits = block.split(splitter);
  526. //console.warn('emitChunks: bits (length %d): %j', bits.length, bits);
  527. if (bits.length === 1) {
  528. /*
  529. * An unwanted side-effect of using a regex to find
  530. * newline-separated objects *with a regex*, is that we are looking
  531. * for the end of one object leading into the start of a another.
  532. * That means that we can end up buffering a complete object until
  533. * a subsequent one comes in. If the input stream has large delays
  534. * between objects, then this is unwanted buffering.
  535. *
  536. * One solution would be full stream parsing of objects a la
  537. * <https://github.com/creationix/jsonparse>. This would nicely
  538. * also remove the artibrary requirement that the input stream be
  539. * newline separated. jsonparse apparently has some issues tho, so
  540. * I don't want to use it right now. It also isn't *small* so not
  541. * sure I want to inline it (`json` doesn't have external deps).
  542. *
  543. * An alternative: The block we have so far one of:
  544. * 1. some JSON that we don't support grouping (e.g. a stream of
  545. * non-objects),
  546. * 2. a JSON object fragment, or
  547. * 3. a complete JSON object (with a possible trailing '{')
  548. *
  549. * If #3, then we can just emit this as a chunk right now.
  550. *
  551. * TODO(PERF): Try out avoiding the first more complete regex split
  552. * for a presumed common case of single-line newline-separated JSON
  553. * objects (e.g. a bunyan log).
  554. */
  555. // An object must end with '}'. This is an early out to avoid
  556. // `JSON.parse` which I'm *presuming* is slower.
  557. var trimmed = block.split(/\s*\r?\n/)[0];
  558. if (trimmed[trimmed.length - 1] === '}') {
  559. var obj;
  560. try {
  561. obj = JSON.parse(block);
  562. } catch (e) {
  563. /* pass through */
  564. }
  565. if (obj !== undefined) {
  566. // Emit the parsed `obj` to avoid re-parsing it later.
  567. emitter.emit('chunk', block, obj);
  568. block = '';
  569. }
  570. }
  571. return block;
  572. } else {
  573. var n = bits.length - 2;
  574. var s;
  575. s = bits[0] + bits[1];
  576. emitter.emit('chunk', s, JSON.parse(s));
  577. for (var i = 3; i < n; i += 4) {
  578. s = bits[i] + bits[i + 1] + bits[i + 2];
  579. emitter.emit('chunk', s, JSON.parse(s));
  580. }
  581. return bits[n] + bits[n + 1];
  582. }
  583. }
  584. function addDataListener(stream) {
  585. stream.on('data', function (chunk) {
  586. var s = leftover + chunk;
  587. if (!finishedHeaders) {
  588. s = stripHeaders(s);
  589. }
  590. if (!finishedHeaders) {
  591. leftover = s;
  592. } else {
  593. if (!streaming) {
  594. chunks.push(chunk);
  595. return;
  596. }
  597. if (chunk.lastIndexOf('\n') >= 0) {
  598. leftover = emitChunks(s, emitter);
  599. } else {
  600. leftover = s;
  601. }
  602. }
  603. });
  604. }
  605. if (opts.inputFiles.length > 0) {
  606. // Stream each file in order.
  607. var i = 0;
  608. function addErrorListener(file) {
  609. file.on('error', function (err) {
  610. emitter.emit(
  611. 'error',
  612. format('could not read "%s": %s', opts.inputFiles[i], e)
  613. );
  614. });
  615. }
  616. function addEndListener(file) {
  617. file.on('end', function () {
  618. if (i < opts.inputFiles.length) {
  619. var next = opts.inputFiles[i++];
  620. var nextFile = fs.createReadStream(next,
  621. {encoding: 'utf8'});
  622. addErrorListener(nextFile);
  623. addEndListener(nextFile);
  624. addDataListener(nextFile);
  625. } else {
  626. if (!streaming) {
  627. emitter.emit('chunk', chunks.join(''));
  628. } else if (leftover) {
  629. leftover = emitChunks(leftover, emitter);
  630. emitter.emit('chunk', leftover);
  631. }
  632. emitter.emit('end');
  633. }
  634. });
  635. }
  636. var first = fs.createReadStream(opts.inputFiles[i++],
  637. {encoding: 'utf8'});
  638. addErrorListener(first);
  639. addEndListener(first);
  640. addDataListener(first);
  641. } else {
  642. // Streaming from stdin.
  643. var stdin = process.openStdin();
  644. stdin.setEncoding('utf8');
  645. addDataListener(stdin);
  646. stdin.on('end', function () {
  647. if (!streaming) {
  648. emitter.emit('chunk', chunks.join(''));
  649. } else if (leftover) {
  650. leftover = emitChunks(leftover, emitter);
  651. emitter.emit('chunk', leftover);
  652. }
  653. emitter.emit('end');
  654. });
  655. }
  656. return emitter;
  657. }
  658. /**
  659. * Get input from either given file paths or stdin. If `opts.inPlace` then
  660. * this calls the callback once for each `opts.inputFiles`.
  661. *
  662. * @param opts {Object} Parsed options.
  663. * @param callback {Function} `function (err, content, filename)` where err
  664. * is an error string if there was a problem, `content` is the read
  665. * content and `filename` is the associated file name from which content
  666. * was loaded if applicable.
  667. */
  668. function getInput(opts, callback) {
  669. if (opts.inputFiles.length === 0) {
  670. // Read from stdin.
  671. var chunks = [];
  672. var stdin = process.openStdin();
  673. stdin.setEncoding('utf8');
  674. stdin.on('data', function (chunk) {
  675. chunks.push(chunk);
  676. });
  677. stdin.on('end', function () {
  678. callback(null, chunks.join(''));
  679. });
  680. } else if (opts.inPlace) {
  681. for (var i = 0; i < opts.inputFiles.length; i++) {
  682. var file = opts.inputFiles[i];
  683. var content;
  684. try {
  685. content = fs.readFileSync(file, 'utf8');
  686. } catch (e) {
  687. callback(e, null, file);
  688. }
  689. if (content) {
  690. callback(null, content, file);
  691. }
  692. }
  693. } else {
  694. // Read input files.
  695. var i = 0;
  696. var chunks = [];
  697. try {
  698. for (; i < opts.inputFiles.length; i++) {
  699. chunks.push(fs.readFileSync(opts.inputFiles[i], 'utf8'));
  700. }
  701. } catch (e) {
  702. return callback(
  703. format('could not read "%s": %s', opts.inputFiles[i], e));
  704. }
  705. callback(null, chunks.join(''),
  706. (opts.inputFiles.length === 1 ? opts.inputFiles[0] : undefined));
  707. }
  708. }
  709. function isInteger(s) {
  710. return (s.search(/^-?[0-9]+$/) == 0);
  711. }
  712. /**
  713. * Parse a lookup string into a list of lookup bits. E.g.:
  714. *
  715. * 'a.b.c' -> ["a","b","c"]
  716. * 'b["a"]' -> ["b","a"]
  717. * 'b["a" + "c"]' -> ["b","ac"]
  718. *
  719. * Optionally receives an alternative lookup delimiter (other than '.')
  720. */
  721. function parseLookup(lookup, lookupDelim) {
  722. var debug = function () {};
  723. //var debug = console.warn;
  724. var bits = [];
  725. debug('\n*** ' + lookup + ' ***');
  726. bits = [];
  727. lookupDelim = lookupDelim || '.';
  728. var bit = '';
  729. var states = [null];
  730. var escaped = false;
  731. var ch = null;
  732. for (var i = 0; i < lookup.length; ++i) {
  733. var escaped = (!escaped && ch === '\\');
  734. var ch = lookup[i];
  735. debug('-- i=' + i + ', ch=' + JSON.stringify(ch) + ' escaped=' +
  736. JSON.stringify(escaped));
  737. debug('states: ' + JSON.stringify(states));
  738. if (escaped) {
  739. bit += ch;
  740. continue;
  741. }
  742. switch (states[states.length - 1]) {
  743. case null:
  744. switch (ch) {
  745. case '"':
  746. case '\'':
  747. states.push(ch);
  748. bit += ch;
  749. break;
  750. case '[':
  751. states.push(ch);
  752. if (bit !== '') {
  753. bits.push(bit);
  754. bit = ''
  755. }
  756. bit += ch;
  757. break;
  758. case lookupDelim:
  759. if (bit !== '') {
  760. bits.push(bit);
  761. bit = ''
  762. }
  763. break;
  764. default:
  765. bit += ch;
  766. break;
  767. }
  768. break;
  769. case '[':
  770. bit += ch;
  771. switch (ch) {
  772. case '"':
  773. case '\'':
  774. case '[':
  775. states.push(ch);
  776. break;
  777. case ']':
  778. states.pop();
  779. if (states[states.length - 1] === null) {
  780. var evaled = vm.runInNewContext(
  781. '(' + bit.slice(1, -1) + ')', {}, '<lookup>');
  782. bits.push(evaled);
  783. bit = ''
  784. }
  785. break;
  786. }
  787. break;
  788. case '"':
  789. bit += ch;
  790. switch (ch) {
  791. case '"':
  792. states.pop();
  793. if (states[states.length - 1] === null) {
  794. bits.push(bit);
  795. bit = ''
  796. }
  797. break;
  798. }
  799. break;
  800. case '\'':
  801. bit += ch;
  802. switch (ch) {
  803. case '\'':
  804. states.pop();
  805. if (states[states.length - 1] === null) {
  806. bits.push(bit);
  807. bit = ''
  808. }
  809. break;
  810. }
  811. break;
  812. }
  813. debug('bit: ' + JSON.stringify(bit));
  814. debug('bits: ' + JSON.stringify(bits));
  815. }
  816. if (bit !== '') {
  817. bits.push(bit);
  818. bit = ''
  819. }
  820. // Negative-intify: strings that are negative ints we change to a Number for
  821. // special handling in `lookupDatum`: Python-style negative array indexing.
  822. var negIntPat = /^-\d+$/;
  823. for (var i = 0; i < bits.length; i++) {
  824. if (negIntPat.test(bits[i])) {
  825. bits[i] = Number(bits[i]);
  826. }
  827. }
  828. debug(JSON.stringify(lookup) + ' -> ' + JSON.stringify(bits));
  829. return bits
  830. }
  831. /**
  832. * Parse the given stdin input into:
  833. * {
  834. * 'error': ... error object if there was an error ...,
  835. * 'datum': ... parsed object if content was JSON ...
  836. * }
  837. *
  838. * @param buffer {String} The text to parse as JSON.
  839. * @param obj {Object} Optional. Set when in streaming mode to avoid
  840. * re-interpretation of `group`. Also avoids reparsing.
  841. * @param group {Boolean} Default false. If true, then non-JSON input
  842. * will be attempted to be 'arrayified' (see inline comment).
  843. * @param merge {Boolean} Default null. Can be 'shallow' or 'deep'. An
  844. * attempt will be made to interpret the input as adjacent objects to
  845. * be merged, last key wins. See inline comment for limitations.
  846. */
  847. function parseInput(buffer, obj, group, merge) {
  848. if (obj) {
  849. return {
  850. datum: obj
  851. };
  852. } else if (group) {
  853. /**
  854. * Special case: Grouping (previously called auto-arrayification)
  855. * of unjoined list of objects:
  856. * {"one": 1}{"two": 2}
  857. * and auto-concatenation of unjoined list of arrays:
  858. * ["a", "b"]["c", "d"]
  859. *
  860. * This can be nice to process a stream of JSON objects generated from
  861. * multiple calls to another tool or `cat *.json | json`.
  862. *
  863. * Rules:
  864. * - Only JS objects and arrays. Don't see strong need for basic
  865. * JS types right now and this limitation simplifies.
  866. * - The break between JS objects has to include a newline:
  867. * {"one": 1}
  868. * {"two": 2}
  869. * or no spaces at all:
  870. * {"one": 1}{"two": 2}
  871. * I.e., not this:
  872. * {"one": 1} {"two": 2}
  873. * This condition should be fine for typical use cases and ensures
  874. * no false matches inside JS strings.
  875. * - The break between JS *arrays* has to include a newline:
  876. * ["one", "two"]
  877. * ["three"]
  878. * The 'no spaces' case is NOT supported for JS arrays as of v6.0.0
  879. * because <https://github.com/trentm/json/issues/55> shows that that
  880. * is not safe.
  881. */
  882. var newBuffer = buffer;
  883. /* JSSTYLED */
  884. [/(})\s*\n\s*({)/g, /(})({")/g].forEach(function (pat) {
  885. newBuffer = newBuffer.replace(pat, '$1,\n$2');
  886. });
  887. [/(\])\s*\n\s*(\[)/g].forEach(function (pat) {
  888. newBuffer = newBuffer.replace(pat, ',\n');
  889. });
  890. newBuffer = newBuffer.trim();
  891. if (newBuffer[0] !== '[') {
  892. newBuffer = '[\n' + newBuffer;
  893. }
  894. if (newBuffer.slice(-1) !== ']') {
  895. newBuffer = newBuffer + '\n]\n';
  896. }
  897. try {
  898. return {
  899. datum: JSON.parse(newBuffer)
  900. };
  901. } catch (e2) {
  902. return {
  903. error: e2
  904. };
  905. }
  906. } else if (merge) {
  907. // See the 'Rules' above for limitations on boundaries for 'adjacent'
  908. // objects: KISS.
  909. var newBuffer = buffer;
  910. /* JSSTYLED */
  911. [/(})\s*\n\s*({)/g, /(})({")/g].forEach(function (pat) {
  912. newBuffer = newBuffer.replace(pat, '$1,\n$2');
  913. });
  914. newBuffer = '[\n' + newBuffer + '\n]\n';
  915. var objs;
  916. try {
  917. objs = JSON.parse(newBuffer);
  918. } catch (e) {
  919. return {
  920. error: e
  921. };
  922. }
  923. var merged = objs[0];
  924. if (merge === 'shallow') {
  925. for (var i = 1; i < objs.length; i++) {
  926. var obj = objs[i];
  927. Object.keys(obj).forEach(function (k) {
  928. merged[k] = obj[k];
  929. });
  930. }
  931. } else if (merge === 'deep') {
  932. function deepExtend(a, b) {
  933. Object.keys(b).forEach(function (k) {
  934. if (a[k] && b[k] &&
  935. toString.call(a[k]) === '[object Object]' &&
  936. toString.call(b[k]) === '[object Object]')
  937. {
  938. deepExtend(a[k], b[k])
  939. } else {
  940. a[k] = b[k];
  941. }
  942. });
  943. }
  944. for (var i = 1; i < objs.length; i++) {
  945. deepExtend(merged, objs[i]);
  946. }
  947. } else {
  948. throw new Error(format('unknown value for "merge": "%s"', merge));
  949. }
  950. return {
  951. datum: merged
  952. };
  953. } else {
  954. try {
  955. return {
  956. datum: JSON.parse(buffer)
  957. };
  958. } catch (e) {
  959. return {
  960. error: e
  961. };
  962. }
  963. }
  964. }
  965. /**
  966. * Apply a lookup to the given datum.
  967. *
  968. * @argument datum {Object}
  969. * @argument lookup {Array} The parsed lookup (from
  970. * `parseLookup(<string>, <string>)`). Might be empty.
  971. * @returns {Object} The result of the lookup.
  972. */
  973. function lookupDatum(datum, lookup) {
  974. var d = datum;
  975. for (var i = 0; i < lookup.length; i++) {
  976. var bit = lookup[i];
  977. if (d === null) {
  978. return undefined;
  979. } else if (typeof (bit) === 'number' && bit < 0) {
  980. d = d[d.length + bit];
  981. } else {
  982. d = d[bit];
  983. }
  984. if (d === undefined) {
  985. return undefined;
  986. }
  987. }
  988. return d;
  989. }
  990. /**
  991. * Output the given datasets.
  992. *
  993. * @param datasets {Array} Array of data sets to print, in the form:
  994. * `[ [<datum>, <sep>, <alwaysPrintSep>], ... ]`
  995. * @param filename {String} The filename to which to write the output. If
  996. * not set, then emit to stdout.
  997. * @param headers {String} The HTTP header block string, if any, to emit
  998. * first.
  999. * @param opts {Object} Parsed tool options.
  1000. */
  1001. function printDatasets(datasets, filename, headers, opts) {
  1002. var isTTY = (filename ? false : process.stdout.isTTY)
  1003. var write = emit;
  1004. if (filename) {
  1005. var tmpPath = path.resolve(path.dirname(filename),
  1006. format('.%s-json-%s-%s.tmp', path.basename(filename), process.pid,
  1007. Date.now()));
  1008. var stats = fs.statSync(filename);
  1009. var f = fs.createWriteStream(tmpPath,
  1010. {encoding: 'utf8', mode: stats.mode});
  1011. write = f.write.bind(f);
  1012. }
  1013. if (headers && headers.length > 0) {
  1014. write(headers)
  1015. }
  1016. for (var i = 0; i < datasets.length; i++) {
  1017. var dataset = datasets[i];
  1018. var output = stringifyDatum(dataset[0], opts, isTTY);
  1019. var sep = dataset[1];
  1020. if (output && output.length) {
  1021. write(output);
  1022. write(sep);
  1023. } else if (dataset[2]) {
  1024. write(sep);
  1025. }
  1026. }
  1027. if (filename) {
  1028. f.on('open', function () {
  1029. f.end();
  1030. fs.renameSync(tmpPath, filename);
  1031. if (!opts.quiet) {
  1032. warn('json: updated "%s" in-place', filename);
  1033. }
  1034. });
  1035. }
  1036. }
  1037. /**
  1038. * Stringify the given datum according to the given output options.
  1039. */
  1040. function stringifyDatum(datum, opts, isTTY) {
  1041. var output = null;
  1042. switch (opts.outputMode) {
  1043. case OM_INSPECT:
  1044. output = util.inspect(datum, false, Infinity, isTTY);
  1045. break;
  1046. case OM_JSON:
  1047. if (typeof (datum) !== 'undefined') {
  1048. output = JSON.stringify(datum, null, opts.jsonIndent);
  1049. }
  1050. break;
  1051. case OM_COMPACT:
  1052. // Dev Note: A still relatively experimental attempt at a more
  1053. // compact ouput somewhat a la Python's repr of a dict. I.e. try to
  1054. // fit elements on one line as much as reasonable.
  1055. if (datum === undefined) {
  1056. // pass
  1057. } else if (Array.isArray(datum)) {
  1058. var bits = ['[\n'];
  1059. datum.forEach(function (d) {
  1060. bits.push(' ')
  1061. bits.push(JSON.stringify(d, null, 0).replace(
  1062. /* JSSTYLED */
  1063. /,"(?![,:])/g, ', "'));
  1064. bits.push(',\n');
  1065. });
  1066. bits.push(bits.pop().slice(0, -2) + '\n') // drop last comma
  1067. bits.push(']');
  1068. output = bits.join('');
  1069. } else {
  1070. output = JSON.stringify(datum, null, 0);
  1071. }
  1072. break;
  1073. case OM_JSONY:
  1074. if (typeof (datum) === 'string') {
  1075. output = datum;
  1076. } else if (typeof (datum) !== 'undefined') {
  1077. output = JSON.stringify(datum, null, opts.jsonIndent);
  1078. }
  1079. break;
  1080. default:
  1081. throw new Error('unknown output mode: ' + opts.outputMode);
  1082. }
  1083. return output;
  1084. }
  1085. /**
  1086. * Print out a single result, considering input options.
  1087. *
  1088. * @deprecated
  1089. */
  1090. function printDatum(datum, opts, sep, alwaysPrintSep) {
  1091. var output = stringifyDatum(datum, opts);
  1092. if (output && output.length) {
  1093. emit(output);
  1094. emit(sep);
  1095. } else if (alwaysPrintSep) {
  1096. emit(sep);
  1097. }
  1098. }
  1099. var stdoutFlushed = true;
  1100. function emit(s) {
  1101. // TODO:PERF If this is try/catch is too slow (too granular): move up to
  1102. // mainline and be sure to only catch this particular error.
  1103. if (drainingStdout) {
  1104. return;
  1105. }
  1106. try {
  1107. stdoutFlushed = process.stdout.write(s);
  1108. } catch (e) {
  1109. // Handle any exceptions in stdout writing in the 'error' event above.
  1110. }
  1111. }
  1112. process.stdout.on('error', function (err) {
  1113. if (err.code === 'EPIPE') {
  1114. // See <https://github.com/trentm/json/issues/9>.
  1115. drainStdoutAndExit(0);
  1116. } else {
  1117. warn(err)
  1118. drainStdoutAndExit(1);
  1119. }
  1120. });
  1121. /**
  1122. * A hacked up version of 'process.exit' that will first drain stdout
  1123. * before exiting. *WARNING: This doesn't stop event processing.* IOW,
  1124. * callers have to be careful that code following this call isn't
  1125. * accidentally executed.
  1126. *
  1127. * In node v0.6 "process.stdout and process.stderr are blocking when they
  1128. * refer to regular files or TTY file descriptors." However, this hack might
  1129. * still be necessary in a shell pipeline.
  1130. */
  1131. var drainingStdout = false;
  1132. function drainStdoutAndExit(code) {
  1133. if (drainingStdout) {
  1134. return;
  1135. }
  1136. drainingStdout = true;
  1137. process.stdout.on('drain', function () {
  1138. process.exit(code);
  1139. });
  1140. process.stdout.on('close', function () {
  1141. process.exit(code);
  1142. });
  1143. if (stdoutFlushed) {
  1144. process.exit(code);
  1145. }
  1146. }
  1147. /**
  1148. * Return a function for the given JS code that returns.
  1149. *
  1150. * If no 'return' in the given javascript snippet, then assume we are a single
  1151. * statement and wrap in 'return (...)'. This is for convenience for short
  1152. * '-c ...' snippets.
  1153. */
  1154. function funcWithReturnFromSnippet(js) {
  1155. // auto-"return"
  1156. if (js.indexOf('return') === -1) {
  1157. if (js.substring(js.length - 1) === ';') {
  1158. js = js.substring(0, js.length - 1);
  1159. }
  1160. js = 'return (' + js + ')';
  1161. }
  1162. return (new Function(js));
  1163. }
  1164. //---- mainline
  1165. function main(argv) {
  1166. var opts;
  1167. try {
  1168. opts = parseArgv(argv);
  1169. } catch (e) {
  1170. warn('json: error: %s', e.message)
  1171. return drainStdoutAndExit(1);
  1172. }
  1173. //warn(opts);
  1174. if (opts.help) {
  1175. printHelp();
  1176. return;
  1177. }
  1178. if (opts.version) {
  1179. if (opts.outputMode === OM_JSON) {
  1180. var v = {
  1181. version: getVersion(),
  1182. author: 'Trent Mick',
  1183. project: 'https://github.com/trentm/json'
  1184. };
  1185. console.log(JSON.stringify(v, null, opts.jsonIndent));
  1186. } else {
  1187. console.log('json ' + getVersion());
  1188. console.log('written by Trent Mick');
  1189. console.log('https://github.com/trentm/json');
  1190. }
  1191. return;
  1192. }
  1193. var lookupStrs = opts.args;
  1194. // Prepare condition and execution funcs (and vm scripts) for -c/-e.
  1195. var execVm = Boolean(process.env.JSON_EXEC &&
  1196. process.env.JSON_EXEC === 'vm');
  1197. var i;
  1198. var condFuncs = [];
  1199. if (!execVm) {
  1200. for (i = 0; i < opts.condSnippets.length; i++) {
  1201. condFuncs[i] = funcWithReturnFromSnippet(opts.condSnippets[i]);
  1202. }
  1203. }
  1204. var condScripts = [];
  1205. if (execVm) {
  1206. for (i = 0; i < opts.condSnippets.length; i++) {
  1207. condScripts[i] = vm.createScript(opts.condSnippets[i]);
  1208. }
  1209. }
  1210. var cond = Boolean(condFuncs.length + condScripts.length);
  1211. var exeFuncs = [];
  1212. if (!execVm) {
  1213. for (i = 0; i < opts.exeSnippets.length; i++) {
  1214. exeFuncs[i] = new Function(opts.exeSnippets[i]);
  1215. }
  1216. }
  1217. var exeScripts = [];
  1218. if (execVm) {
  1219. for (i = 0; i < opts.exeSnippets.length; i++) {
  1220. exeScripts[i] = vm.createScript(opts.exeSnippets[i]);
  1221. }
  1222. }
  1223. var exe = Boolean(exeFuncs.length + exeScripts.length);
  1224. var lookups = lookupStrs.map(function (lookup) {
  1225. return parseLookup(lookup, opts.lookupDelim);
  1226. });
  1227. if (opts.group && opts.array && opts.outputMode !== OM_JSON) {
  1228. // streaming
  1229. var chunker = chunkEmitter(opts);
  1230. chunker.on('error', function (error) {
  1231. warn('json: error: %s', err.message);
  1232. return drainStdoutAndExit(1);
  1233. });
  1234. chunker.on('chunk', parseChunk);
  1235. } else if (opts.inPlace) {
  1236. assert.equal(opts.inputFiles.length, 1,
  1237. 'cannot handle more than one file with -I');
  1238. getInput(opts, function (err, content, filename) {
  1239. if (err) {
  1240. warn('json: error: %s', err.message)
  1241. return drainStdoutAndExit(1);
  1242. }
  1243. // Take off a leading HTTP header if any and pass it through.
  1244. var headers = [];
  1245. while (true) {
  1246. if (content.slice(0, 5) === 'HTTP/') {
  1247. var index = content.indexOf('\r\n\r\n');
  1248. var sepLen = 4;
  1249. if (index == -1) {
  1250. index = content.indexOf('\n\n');
  1251. sepLen = 2;
  1252. }
  1253. if (index != -1) {
  1254. if (!opts.dropHeaders) {
  1255. headers.push(content.slice(0, index + sepLen));
  1256. }
  1257. var is100Continue = (
  1258. content.slice(0, 21) === 'HTTP/1.1 100 Continue');
  1259. content = content.slice(index + sepLen);
  1260. if (is100Continue) {
  1261. continue;
  1262. }
  1263. }
  1264. }
  1265. break;
  1266. }
  1267. parseChunk(content, undefined, filename, true, headers.join(''));
  1268. });
  1269. } else {
  1270. // not streaming
  1271. getInput(opts, function (err, buffer, filename) {
  1272. if (err) {
  1273. warn('json: error: %s', err.message)
  1274. return drainStdoutAndExit(1);
  1275. }
  1276. // Take off a leading HTTP header if any and pass it through.
  1277. while (true) {
  1278. if (buffer.slice(0, 5) === 'HTTP/') {
  1279. var index = buffer.indexOf('\r\n\r\n');
  1280. var sepLen = 4;
  1281. if (index == -1) {
  1282. index = buffer.indexOf('\n\n');
  1283. sepLen = 2;
  1284. }
  1285. if (index != -1) {
  1286. if (!opts.dropHeaders) {
  1287. emit(buffer.slice(0, index + sepLen));
  1288. }
  1289. var is100Continue = (
  1290. buffer.slice(0, 21) === 'HTTP/1.1 100 Continue');
  1291. buffer = buffer.slice(index + sepLen);
  1292. if (is100Continue) {
  1293. continue;
  1294. }
  1295. }
  1296. }
  1297. break;
  1298. }
  1299. parseChunk(buffer, null, filename, false);
  1300. });
  1301. }
  1302. /**
  1303. * Parse a single chunk of JSON. This may be called more than once
  1304. * (when streaming or when operating on multiple files).
  1305. *
  1306. * @param chunk {String} The JSON-encoded string.
  1307. * @param obj {Object} Optional. For some code paths while streaming `obj`
  1308. * will be provided. This is an already parsed JSON object.
  1309. * @param filename {String} Optional. The filename from which this content
  1310. * came, if relevant.
  1311. * @param inPlace {Boolean} Optional. If true, then output will be written
  1312. * to `filename`.
  1313. * @param headers {String} Optional. Leading HTTP headers, if any to emit.
  1314. */
  1315. function parseChunk(chunk, obj, filename, inPlace, headers) {
  1316. // Expect the chunk to be JSON.
  1317. if (!chunk.length) {
  1318. return;
  1319. }
  1320. // parseInput() -> {datum: <input object>, error: <error object>}
  1321. var input = parseInput(chunk, obj, opts.group, opts.merge);
  1322. if (input.error) {
  1323. // Doesn't look like JSON. Just print it out and move on.
  1324. if (!opts.quiet) {
  1325. // Use JSON-js' "json_parse" parser to get more detail on the
  1326. // syntax error.
  1327. var details = '';
  1328. var normBuffer = chunk.replace(/\r\n|\n|\r/, '\n');
  1329. try {
  1330. json_parse(normBuffer);
  1331. details = input.error;
  1332. } catch (err) {
  1333. // err.at has the position. Get line/column from that.
  1334. var at = err.at - 1; // `err.at` looks to be 1-based.
  1335. var lines = chunk.split('\n');
  1336. var line, col, pos = 0;
  1337. for (line = 0; line < lines.length; line++) {
  1338. pos += lines[line].length + 1;
  1339. if (pos > at) {
  1340. col = at - (pos - lines[line].length - 1);
  1341. break;
  1342. }
  1343. }
  1344. var spaces = '';
  1345. for (var i = 0; i < col; i++) {
  1346. spaces += '.';
  1347. }
  1348. details = err.message + ' at line ' + (line + 1) +
  1349. ', column ' + (col + 1) + ':\n ' +
  1350. lines[line] + '\n ' + spaces + '^';
  1351. }
  1352. warn('json: error: %s is not JSON: %s',
  1353. filename ? '"' + filename + '"' : 'input', details);
  1354. }
  1355. if (!opts.validate) {
  1356. emit(chunk);
  1357. if (chunk.length && chunk[chunk.length - 1] !== '\n') {
  1358. emit('\n');
  1359. }
  1360. }
  1361. return drainStdoutAndExit(1);
  1362. }
  1363. if (opts.validate) {
  1364. return drainStdoutAndExit(0);
  1365. }
  1366. var data = input.datum;
  1367. // Process: items (-M, --items)
  1368. if (opts.items) {
  1369. if (!Array.isArray(data)) {
  1370. var key;
  1371. var array = [];
  1372. for (key in data) {
  1373. if (data.hasOwnProperty(key)) {
  1374. array.push({
  1375. key: key,
  1376. value: data[key]
  1377. });
  1378. }
  1379. }
  1380. data = array;
  1381. }
  1382. }
  1383. // Process: executions (-e, -E)
  1384. var i, j;
  1385. if (!exe) {
  1386. /* pass */
  1387. } else if (opts.array || (opts.array === null && Array.isArray(data))) {
  1388. var arrayified = false;
  1389. if (!Array.isArray(data)) {
  1390. arrayified = true;
  1391. data = [data];
  1392. }
  1393. for (i = 0; i < data.length; i++) {
  1394. var datum = data[i];
  1395. for (j = 0; j < exeFuncs.length; j++) {
  1396. exeFuncs[j].call(datum);
  1397. }
  1398. for (j = 0; j < exeScripts.length; j++) {
  1399. exeScripts[j].runInNewContext(datum);
  1400. }
  1401. }
  1402. if (arrayified) {
  1403. data = data[0];
  1404. }
  1405. } else {
  1406. for (j = 0; j < exeFuncs.length; j++) {
  1407. exeFuncs[j].call(data);
  1408. }
  1409. for (j = 0; j < exeScripts.length; j++) {
  1410. exeScripts[j].runInNewContext(data);
  1411. }
  1412. }
  1413. // Process: conditionals (-c)
  1414. if (!cond) {
  1415. /* pass */
  1416. } else if (opts.array || (opts.array === null && Array.isArray(data))) {
  1417. var arrayified = false;
  1418. if (!Array.isArray(data)) {
  1419. arrayified = true;
  1420. data = [data];
  1421. }
  1422. var filtered = [];
  1423. for (i = 0; i < data.length; i++) {
  1424. var datum = data[i];
  1425. var datumCopy = objCopy(datum);
  1426. var keep = true;
  1427. // TODO(perf): Perhaps drop the 'datumCopy'? "this is a gun"
  1428. for (j = 0; j < condFuncs.length; j++) {
  1429. if (!condFuncs[j].call(datumCopy)) {
  1430. keep = false;
  1431. break;
  1432. }
  1433. }
  1434. if (keep) {
  1435. for (j = 0; j < condScripts.length; j++) {
  1436. if (!condScripts[j].runInNewContext(datumCopy)) {
  1437. keep = false;
  1438. break;
  1439. }
  1440. }
  1441. if (keep) {
  1442. filtered.push(datum);
  1443. }
  1444. }
  1445. }
  1446. if (arrayified) {
  1447. data = (filtered.length ? filtered[0] : []);
  1448. } else {
  1449. data = filtered;
  1450. }
  1451. } else {
  1452. var keep = true;
  1453. var dataCopy = objCopy(data);
  1454. for (j = 0; j < condFuncs.length; j++) {
  1455. // TODO(perf): Perhaps drop the 'dataCopy'? "this is a gun"
  1456. if (!condFuncs[j].call(dataCopy)) {
  1457. keep = false;
  1458. break;
  1459. }
  1460. }
  1461. if (keep) {
  1462. for (j = 0; j < condScripts.length; j++) {
  1463. if (!condScripts[j].runInNewContext(dataCopy)) {
  1464. keep = false;
  1465. break;
  1466. }
  1467. }
  1468. }
  1469. if (!keep) {
  1470. data = undefined;
  1471. }
  1472. }
  1473. // Process: lookups
  1474. var lookupsAreIndeces = false;
  1475. if (lookups.length) {
  1476. if (opts.array) {
  1477. if (!Array.isArray(data)) data = [data];
  1478. var table = [];
  1479. for (j = 0; j < data.length; j++) {
  1480. var datum = data[j];
  1481. var row = {};
  1482. for (i = 0; i < lookups.length; i++) {
  1483. var lookup = lookups[i];
  1484. var value = lookupDatum(datum, lookup);
  1485. if (value !== undefined) {
  1486. row[lookup.join('.')] = value;
  1487. }
  1488. }
  1489. table.push(row);
  1490. }
  1491. data = table;
  1492. } else {
  1493. // Special case handling: Note if the 'lookups' are indeces into
  1494. // an array. This may be used below to change the output
  1495. // representation.
  1496. if (Array.isArray(data)) {
  1497. lookupsAreIndeces = true;
  1498. for (i = 0; i < lookups.length; i++) {
  1499. if (lookups[i].length !== 1 ||
  1500. isNaN(Number(lookups[i])))
  1501. {
  1502. lookupsAreIndeces = false;
  1503. break;
  1504. }
  1505. }
  1506. }
  1507. var row = {};
  1508. for (i = 0; i < lookups.length; i++) {
  1509. var lookup = lookups[i];
  1510. var value = lookupDatum(data, lookup);
  1511. if (value !== undefined) {
  1512. row[lookup.join('.')] = value;
  1513. }
  1514. }
  1515. data = row;
  1516. }
  1517. }
  1518. // --keys
  1519. if (opts.outputKeys) {
  1520. var data = Object.keys(data);
  1521. }
  1522. // Output
  1523. var datasets = [];
  1524. if (opts.outputMode === OM_JSON) {
  1525. if (lookups.length === 1 && !opts.array) {
  1526. /**
  1527. * Special case: For JSON output of a *single* lookup, *don't*
  1528. * use the full table structure, else there is no way to get
  1529. * string quoting for a single value:
  1530. * $ echo '{"a": [], "b": "[]"}' | json -j a
  1531. * []
  1532. * $ echo '{"a": [], "b": "[]"}' | json -j b
  1533. * '[]'
  1534. * See <https://github.com/trentm/json/issues/35> for why.
  1535. */
  1536. data = data[lookups[0].join('.')];
  1537. } else if (lookupsAreIndeces) {
  1538. /**
  1539. * Special case: Lookups that are all indeces into an input
  1540. * array are more likely to be wanted as an array of selected
  1541. * items rather than a 'JSON table' thing that we use otherwise.
  1542. */
  1543. var flattened = [];
  1544. for (i = 0; i < lookups.length; i++) {
  1545. var lookupStr = lookups[i].join('.');
  1546. if (data.hasOwnProperty(lookupStr)) {
  1547. flattened.push(data[lookupStr])
  1548. }
  1549. }
  1550. data = flattened;
  1551. }
  1552. // If JSON output mode, then always just output full set of data to
  1553. // ensure valid JSON output.
  1554. datasets.push([data, '\n', false]);
  1555. } else if (lookups.length) {
  1556. if (opts.array) {
  1557. // Output `data` as a 'table' of lookup results.
  1558. for (j = 0; j < data.length; j++) {
  1559. var row = data[j];
  1560. for (i = 0; i < lookups.length - 1; i++) {
  1561. datasets.push([row[lookups[i].join('.')],
  1562. opts.delim, true]);
  1563. }
  1564. datasets.push([row[lookups[i].join('.')], '\n', true]);
  1565. }
  1566. } else {
  1567. for (i = 0; i < lookups.length; i++) {
  1568. datasets.push([data[lookups[i].join('.')], '\n', false]);
  1569. }
  1570. }
  1571. } else if (opts.array) {
  1572. if (!Array.isArray(data)) data = [data];
  1573. for (j = 0; j < data.length; j++) {
  1574. datasets.push([data[j], '\n', false]);
  1575. }
  1576. } else {
  1577. // Output `data` as is.
  1578. datasets.push([data, '\n', false]);
  1579. }
  1580. printDatasets(datasets, inPlace ? filename : undefined, headers, opts);
  1581. }
  1582. }
  1583. if (require.main === module) {
  1584. // HACK guard for <https://github.com/trentm/json/issues/24>.
  1585. // We override the `process.stdout.end` guard that core node.js puts in
  1586. // place. The real fix is that `.end()` shouldn't be called on stdout
  1587. // in node core. Hopefully node v0.6.9 will fix that. Only guard
  1588. // for v0.6.0..v0.6.8.
  1589. var nodeVer = process.versions.node.split('.').map(Number);
  1590. if ([0, 6, 0] <= nodeVer && nodeVer <= [0, 6, 8]) {
  1591. var stdout = process.stdout;
  1592. stdout.end = stdout.destroy = stdout.destroySoon = function () {
  1593. /* pass */
  1594. };
  1595. }
  1596. main(process.argv);
  1597. }