-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathjparse.js
More file actions
280 lines (241 loc) · 6.55 KB
/
jparse.js
File metadata and controls
280 lines (241 loc) · 6.55 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
#!/usr/bin/env node
var syntaxHighlight = require( "pygments" ).colorize
var highlight = true
/* === State Management Functions === */
// Hold current state
var current = {
item: { type: "", val: "" },
state: function(){}
}
// List of already constructed items
var completedItems = []
/*
* Complete the current item
*/
function completeItem(){
completedItems.push( current.item )
current.item = { type: "", val: "" }
}
/* Print out an error and exit
* @param {string} msg - Error message to print
*/
function parseError( msg ){
console.log( msg )
process.exit( 1 )
}
/*
* Consume characters for itentifiers in dot syntax
* @param {string} val - The next character being read in
*/
var readUnquoted = ( function(){
return function( val ){
var len = current.item.val.length
if( /\w/.test( val ) && len > 0 || /[a-z_]/i.test( val ) && len === 0 ){
// Val is alphanum or _, and is not first char, or is alphabetic or
// _, and first char
// Consume character
current.item.type = "unquoted"
current.item.val += val
} else if( val === "." ){
// Finish item
completeItem()
} else if( val === "[" ){
// New item is beginning in bracket notation
completeItem()
current.state = readBracket
} else {
parseError( "Invalid JS identifier: " + current.item.val + val )
}
}
}() )
/*
* Consume characters that are valid before & after quote state
* @param {string} val - The next character being read in
*/
var readBracket = ( function(){
var hasQuote = false
var hasDigit = false
return function( val ){
if( /\s/.test( val ) ){
// Ignore white space
} else if( /\d/.test( val ) && !hasQuote ){
// Digit encountered, and no quotes found
current.item.val = val
hasDigit = true
current.item.type = "number"
current.state = readNumeric
} else if( /['"]/.test( val ) && !hasQuote && !hasDigit ){
// First quote has been found
hasQuote = true
current.item.type = "quoted"
readQuoted.quoteType = val
current.state = readQuoted
} else if( val === "]" || val === null ){
hasQuote = false
hasDigit = false
current.state = readUnquoted
completeItem()
} else {
// Nothing else should be valid here
parseError( "Invaid (or missing) unquoted characters in bracket: " + val )
}
}
}() )
/*
* Consume numeric characters inside brackets
* @param {string} val - The next character being read in
*/
var readNumeric = ( function(){
var numEnded = false
return function( val ){
if( /\d/.test( val ) && !numEnded ){
// Read another digit
current.item.val += val
} else if( /\s/.test( val ) ){
// Allow white space after digits
numEnded = true
} else if( val === "]" ){
// End reading of digits
numEnded = false
readBracket( null )
current.state = readUnquoted
completeItem()
} else {
parseError( "Invalid array index:" + current.item.val + val )
}
}
}() )
/*
* Consume characters for itentifiers bracket syntax
* @param {string} val - The next character being read in
*/
var readQuoted = ( function(){
var lastVal = ""
return function( val ){
if( val !== readQuoted.quoteType || readQuoted.quoteType === "\x5c" ){
// Non-string ending char, or quote/tick escaped by backslash
current.item.val += val
lastVal = val
} else {
// End of string has been reached
lastVal = ""
readQuoted.quoteType = ""
current.state = readBracket
}
}
}() )
// Set initial state
current.state = readUnquoted
/* === JSON parsing code === */
/*
* Read input from stdin, until it closes
*/
function readInput(){
var rawInput = ""
// Read data as it comes in
process.stdin.on( "data", function( data ){
rawInput += data.toString()
} )
process.stdin.on( "close", function(){
var selectors = parseSelectors()
var selection = filterData( selectors, rawInput )
printOutput( selection )
} )
}
/*
* Parse object selectors out of the parameters
* @returns {array} A list of selectors
*/
function parseSelectors(){
// Loop through argments, grabbing raw selectos
var input = ""
var startpos = 2
var length = process.argv.length
// Detect --nohighlight flag
if( process.argv[2] === "--nohighlight" ){
highlight = false
startpos = 3
}
for( var i = startpos; i < length; i += 1 ){
input += process.argv[i]
}
// Loop through input, constructing selectors
var inputLength = input.length
for( var i = 0; i < inputLength; i += 1 ){
current.state( input[i] )
}
completeItem()
// Filter out null selectors
var selectors = []
var completed = completedItems.length
for( var i = 0; i < completed; i += 1 ){
if( completedItems[i].val.length > 0 ){
selectors.push( completedItems[i] )
}
}
return selectors
}
/*
* Given a list of object selectors, get the referenced child
* @param {array} selectors - List of object selectors
* @param {string} data - Input read from stdin
* @returns {object} - Child object referenced by user
*/
function filterData( selectors, data ){
var input = {}
// Parse JSON input
;( function(){
try{
input = JSON.parse( data, null, 2 )
} catch( e ) {
parseError( "Invalid JSON" )
}
}() )
// Grab selected item
var selection = input
var done = false
var selectorCount = selectors.length
for( var i = 0; i < selectorCount && !done; i += 1 ){
var selected = selection[ selectors[i].val ]
if( selected !== undefined ){
selection = selected
} else {
done = true
}
}
return selection
}
/*
* Pretty print output, with indentation & syntax highlighting
* @param {object} output - Object to print
*/
function printOutput( output ){
// Detect what type of data is being printed
var type = typeof output
if( type === "object" ){
if( output.sort ){
type = "array"
}
}
// Print output with different formats for each type
if( type === "number" || type === "string" ){
console.log( output )
} else if( type === "array" ){
// print out one item per line
console.log( output.join( "\n" ) )
} else if( type === "object" ){
// Pretty print, with syntax highlighting
var stringified = JSON.stringify( output, null, 2)
if( highlight ){
syntaxHighlight( stringified, "json", "console", function( data ){
console.log( data )
} )
} else {
console.log( stringified )
}
} else {
// Unrecognized type, so error out
console.log( "Unrecognized output type" )
}
}
readInput()