UNPKG

16 kBtext/coffeescriptView Raw
1# Imports
2fsUtil = require('safefs')
3pathUtil = require('path')
4extractOptsAndCallback = require('extract-opts')
5requireFresh = require('requirefresh')
6
7# Public: The exported CSON singleton
8class CSON
9 # ====================================
10 # Helpers
11
12 # Internal: Ensure Error Type
13 ensureErrorType: (err) ->
14 if err instanceof Error
15 return err
16 else
17 return @ensureErrorType(err)
18
19 # Internal: Complete with callback if it exists
20 complete: (result, next) ->
21 # Complete
22 if next
23 if result instanceof Error
24 next(result)
25 else
26 next(null, result)
27 return @
28 else
29 return result
30
31 # Internal: Fills in any missing options for use in our methods
32 #
33 # opts - {Object} The options to prepare
34 #
35 # Returns the same opts {Object} that we received
36 getOptions: (opts={}) ->
37 opts.format ?= null
38 opts.filename ?= null
39 if opts.filename
40 opts.filename = pathUtil.resolve(opts.filename)
41 opts.format ?= @getFormat(opts.filename)
42 if opts.filename is null
43 delete opts.filename
44
45 opts.json ?= true
46 opts.cson ?= true
47 opts.javascript ?= false
48 opts.coffeescript ?= false
49
50 return opts
51
52 # Internal: Gets the format for a file name or path
53 #
54 # file - {String} to get the format for
55 #
56 # Returns the determined format as a {String} ("json", "cson", "coffeescript", or "javascript", or null)
57 getFormat: (file) ->
58 return switch pathUtil.extname(file)
59 when '.json'
60 'json'
61 when '.cson'
62 'cson'
63 when '.coffee'
64 'coffeescript'
65 when '.js'
66 'javascript'
67 else
68 null
69
70 # Internal: Helper for {::createString}, {::parseString}, {::parseFile}, {::requireFile}
71 action: (args) ->
72 # Prepare
73 {action, prefix, suffix, data, opts, next} = args
74 suffix ?= ''
75 [opts, next] = extractOptsAndCallback(opts, next)
76
77 # Prepare options
78 switch action
79 when 'requireFile', 'parseFile'
80 opts.filename ?= data
81
82 # Add defaults
83 opts = @getOptions(opts)
84
85 # Default: CSON
86 if opts.format in [null, 'cson']
87 if opts.cson is true
88 result = @[prefix+'CSON'+suffix](data, opts)
89 else
90 result = new Error("CSON.#{action}: Desired format is CSON however CSON is disabled by an option")
91
92 # JSON
93 else if opts.format is 'json'
94 if opts.json is true
95 result = @[prefix+'JSON'+suffix](data, opts)
96 else
97 result = new Error("CSON.#{action}: Desired format is JSON however JSON is disabled by an option")
98
99 # JavaScript
100 else if opts.format is 'javascript'
101 if opts.javascript is true
102 result = @[prefix+'JS'+suffix](data, opts)
103 else
104 result = new Error("CSON.#{action}: Desired format is JavaScript however JavaScript is disabled by an option")
105
106 # CoffeeScript
107 else if opts.format is 'coffeescript'
108 if opts.coffeescript is true
109 result = @[prefix+'CS'+suffix](data, opts)
110 else
111 result = new Error("CSON.#{action}: Desired format is CoffeeScript however CoffeeScript is disabled by an option")
112
113 else
114 result = new Error("CSON.#{action}: Desired format is not supported")
115
116 # Complete
117 return @complete(result, next)
118
119
120
121 # ====================================
122 # Bundles
123
124 # Public: {Delegates to: .createString}
125 stringify: (data, replacer, indent) ->
126 opts = {}
127 opts.replacer = replacer
128 opts.indent = indent
129 return @createCSONString(data, opts)
130
131 # Public: {Delegates to: .parseCSONString}
132 parse: (data, opts, next) ->
133 return @parseCSONString(data, opts, next)
134
135 # Public: {Delegates to: .parseCSONFile}
136 load: (data, opts, next) ->
137 return @parseCSONFile(data, opts, next)
138
139 # Public: Converts an {Object} into a {String} of the desired format
140 #
141 # If the format option is not specified, we default to CSON
142 #
143 # data - {Object} The data to convert
144 # opts - {Object} The options (options may also be forwarded onto the parser library)
145 # :format - {String} The format to use: "cson" (default), "json", "coffeescript", or "javascript"
146 # :cson - {Boolean} Whether or not the CSON format should be allowed (defaults to `true`)
147 # :json - {Boolean} Whether or not the JSON format should be allowed (defaults to `true`)
148 #
149 # Returns {String} or {Error}
150 createString: (data, opts, next) ->
151 return @action({
152 action: 'createString'
153 prefix: 'create'
154 suffix: 'String'
155 data, opts, next
156 })
157
158 # Public: Converts a {String} of the desired format into an {Object}
159 #
160 # If the format option is not specified, we default to CSON
161 #
162 # data - {String} The string to parse
163 # opts - {Object} The options (options may also be forwarded onto the parser library)
164 # :format - {String} The format to use: "cson" (default), "json", "coffeescript", or "javascript"
165 # :cson - {Boolean} Whether or not the CSON format should be allowed (defaults to `true`)
166 # :json - {Boolean} Whether or not the JSON format should be allowed (defaults to `true`)
167 # :coffeescript - {Boolean} Whether or not the CoffeeScript format should be allowed (defaults to `false`)
168 # :json - {Boolean} Whether or not the CoffeeScript format should be allowed (defaults to `json`)
169 #
170 # Returns {Object} or {Error}
171 parseString: (data, opts, next) ->
172 return @action({
173 action: 'parseString'
174 prefix: 'parse'
175 suffix: 'String'
176 data, opts, next
177 })
178
179 # Public: Parses a file path of the desired format into an {Object}
180 #
181 # If the format option is not specified, we use the filename to detect what it should be, otherwise we default to CSON
182 #
183 # data - {String} The file path to parse
184 # opts - {Object} The options (options may also be forwarded onto the parser library)
185 # :format - {String} The format to use: "cson" (default), "json", "coffeescript", or "javascript"
186 # :cson - {Boolean} Whether or not the CSON format should be allowed (defaults to `true`)
187 # :json - {Boolean} Whether or not the JSON format should be allowed (defaults to `true`)
188 # :coffeescript - {Boolean} Whether or not the CoffeeScript format should be allowed (defaults to `false`)
189 # :json - {Boolean} Whether or not the CoffeeScript format should be allowed (defaults to `json`)
190 #
191 # Returns {Object} or {Error}
192 parseFile: (data, opts, next) ->
193 return @action({
194 action: 'parseFile'
195 prefix: 'parse'
196 suffix: 'File'
197 data, opts, next
198 })
199
200 # Public: Requires or parses a file path of the desired format into an {Object}
201 #
202 # If the format option is not specified, we use the filename to detect what it should be, otherwise we default to CSON
203 #
204 # data - {String} The file path to require or parse
205 # opts - {Object} The options (options may also be forwarded onto the parser library)
206 # :format - {String} The format to use: "cson" (default), "json", "coffeescript", or "javascript"
207 # :cson - {Boolean} Whether or not the CSON format should be allowed (defaults to `true`)
208 # :json - {Boolean} Whether or not the JSON format should be allowed (defaults to `true`)
209 # :coffeescript - {Boolean} Whether or not the CoffeeScript format should be allowed (defaults to `false`)
210 # :json - {Boolean} Whether or not the CoffeeScript format should be allowed (defaults to `json`)
211 #
212 # Returns {Object} or {Error}
213 requireFile: (data, opts, next) ->
214 return @action({
215 action: 'requireFile'
216 prefix: 'require'
217 suffix: 'File'
218 data, opts, next
219 })
220
221
222 # ====================================
223 # Creating Strings from Objects
224
225 # Public: Converts an {Object} into a JSON {String}
226 #
227 # data - {Object} The data to convert
228 # opts - {Object} The options (options may also be forwarded onto the parser library)
229 # :replacer - {Boolean} The replacer option for `JSON.stringify` (defaults to `null`)
230 # :indent - {Boolean} The indent option for `JSON.stringify` (defaults to two spaces ` `)
231 #
232 # Returns {String} or {Error}
233 createJSONString: (data, opts, next) ->
234 # Prepare
235 [opts, next] = extractOptsAndCallback(opts, next)
236 opts = @getOptions(opts)
237 opts.replacer ?= null
238 opts.indent ?= ' '
239
240 # Stringify
241 try
242 result = JSON.stringify(data, opts.replacer, opts.indent)
243 catch err
244 result = @ensureErrorType(err)
245
246 # Complete
247 return @complete(result, next)
248
249 # Public: Converts an {Object} into a CSON {String}
250 #
251 # data - {Object} The data to convert
252 # opts - {Object} The options (options may also be forwarded onto the parser library)
253 # :replacer - {Boolean} The replacer option for `require('cson-parser').stringify` (defaults to `null`)
254 # :indent - {Boolean} The indent option for `require('cson-parser').stringify` (defaults to a single tab `'\t'`)
255 #
256 # Returns {String} or {Error}
257 createCSONString: (data, opts, next) ->
258 # Prepare
259 [opts, next] = extractOptsAndCallback(opts, next)
260 opts = @getOptions(opts)
261 opts.replacer ?= null
262 opts.indent ?= '\t'
263
264 # Stringify
265 try
266 result = require('cson-parser').stringify(data, opts.replacer, opts.indent)
267 catch err
268 result = @ensureErrorType(err)
269
270 # Complete
271 return @complete(result, next)
272
273 # Private: Not yet supported
274 createCSString: (data, opts, next) ->
275 # Prepare
276 [opts, next] = extractOptsAndCallback(opts, next)
277
278 # Stringify
279 result = new Error('CSON.createCS: Creating CoffeeScript code is not yet supported')
280
281 # Complete
282 return @complete(result, next)
283
284 ###
285 Potentially we could use something like the following from CSON v1
286 However the JSON.stringify gets rid of functions...
287 which is the entire point of the coffeescript mode over the CSON mode...
288 So until we figure out how to toString() an object and keep the functions intact,
289 unsafe stringifying to CSON or CS or JS won't happen.
290
291 Perhaps https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/toSource
292 will be of use one day
293
294 src = "var result = #{JSON.stringify obj}"
295 result = require('js2coffee').build(src, opts).code
296 result = result.replace(/^\s*result\s*\=\s/, '')
297 if /^\s/.test(result) is false
298 result = result.trim()
299 if typeof obj is 'object'
300 unless Array.isArray(obj)
301 result = '{'+result+'}' unless result is '{}'
302 return result
303 ###
304
305
306 # Private: Not yet supported
307 createJSString: (data, opts, next) ->
308 # Prepare
309 [opts, next] = extractOptsAndCallback(opts, next)
310
311 # Stringify
312 result = new Error('CSON.createJS: Creating JavaScript code is not yet supported')
313
314 # Complete
315 return @complete(result, next)
316
317
318 # ====================================
319 # Parsing Strings to Objects
320
321 # Public: Parses a JSON {String} into an {Object}
322 #
323 # data - The JSON {String} to parse
324 # opts - {Object} The options, unused
325 #
326 # Returns {Object} or {Error}
327 parseJSONString: (data, opts, next) ->
328 # Prepare
329 [opts, next] = extractOptsAndCallback(opts, next)
330
331 # Parse
332 try
333 result = JSON.parse(data)
334 catch err
335 result = @ensureErrorType(err)
336
337 # Complete
338 return @complete(result, next)
339
340 # Public: Parses a CSON {String} into an {Object}
341 #
342 # data - The CSON {String} to parse
343 # opts - {Object} The options, unused
344 #
345 # Returns {Object} or {Error}
346 parseCSONString: (data, opts, next) ->
347 # Prepare
348 [opts, next] = extractOptsAndCallback(opts, next)
349
350 # Parse
351 try
352 result = require('cson-parser').parse(data)
353 catch err
354 result = @ensureErrorType(err)
355
356 # Complete
357 return @complete(result, next)
358
359 # Public: Parses a JavaScript {String} into an {Object}
360 #
361 # data - The JavaScript {String} to parse
362 # opts - {Object} The options (also passed to require('vm').runInNewContex)
363 # :context - {Object} The context option that is used in `require('vm').runInNewContext`, defaults to an empty object `{}`
364 #
365 # Returns {Object} or {Error}
366 parseJSString: (data, opts, next) ->
367 # Prepare
368 [opts, next] = extractOptsAndCallback(opts, next)
369 opts = @getOptions(opts)
370 opts.context ?= {}
371
372 # Parse
373 try
374 result = require('vm').runInNewContext(data, opts.context, opts)
375 catch err
376 result = @ensureErrorType(err)
377
378 # Complete
379 return @complete(result, next)
380
381 # Public: Parses a CoffeeScript {String} into an {Object}
382 #
383 # data - The CoffeeScript {String} to parse
384 # opts - {Object} The options, forwarded onto `require('coffeescript').eval`
385 #
386 # Returns {Object} or {Error}
387 parseCSString: (data, opts, next) ->
388 # Prepare
389 [opts, next] = extractOptsAndCallback(opts, next)
390 opts = @getOptions(opts)
391
392 # Parse
393 try
394 result = require('coffeescript').eval(data, opts)
395 catch err
396 result = @ensureErrorType(err)
397
398 # Complete
399 return @complete(result, next)
400
401
402 # ====================================
403 # Parsing Files to Objects
404
405 # Public: Parses a JSON file into an {Object}
406 #
407 # data - {String} The file path to parse
408 # opts - {Object} The options, forwarded onto {::parseJSONString}
409 #
410 # Returns {Object} or {Error}
411 parseJSONFile: (file, opts, next) ->
412 # Prepare
413 [opts, next] = extractOptsAndCallback(opts, next)
414
415 # Parse
416 result = fsUtil.readFileSync(file)
417 if result instanceof Error
418 result = result
419 else
420 result = @parseJSONString(result.toString(), opts)
421
422 # Complete
423 return @complete(result, next)
424
425 # Public: Parses a CSON file into an {Object}
426 #
427 # data - {String} The file path to parse
428 # opts - {Object} The options, forwarded onto {::parseCSONString}
429 #
430 # Returns {Object} or {Error}
431 parseCSONFile: (file, opts, next) ->
432 # Prepare
433 [opts, next] = extractOptsAndCallback(opts, next)
434
435 # Parse
436 result = fsUtil.readFileSync(file)
437 if result instanceof Error
438 result = result
439 else
440 result = @parseCSONString(result.toString(), opts)
441
442 # Complete
443 return @complete(result, next)
444
445 # Public: Parses a JAvaScript file into an {Object}
446 #
447 # data - {String} The file path to parse
448 # opts - {Object} The options, forwarded onto {::parseJSString}
449 #
450 # Returns {Object} or {Error}
451 parseJSFile: (file, opts, next) ->
452 # Prepare
453 [opts, next] = extractOptsAndCallback(opts, next)
454
455 # Parse
456 result = fsUtil.readFileSync(file)
457 if result instanceof Error
458 result = result
459 else
460 result = @parseJSString(result.toString(), opts)
461
462 # Complete
463 return @complete(result, next)
464
465 # Public: Parses a CoffeeScript file into an {Object}
466 #
467 # data - {String} The file path to parse
468 # opts - {Object} The options, forwarded onto {::parseCSString}
469 #
470 # Returns {Object} or {Error}
471 parseCSFile: (file, opts, next) ->
472 # Prepare
473 [opts, next] = extractOptsAndCallback(opts, next)
474
475 # Parse
476 result = fsUtil.readFileSync(file)
477 if result instanceof Error
478 result = result
479 else
480 result = @parseCSString(result.toString(), opts)
481
482 # Complete
483 return @complete(result, next)
484
485
486
487 # ====================================
488 # Requiring Files to Objects
489
490 # Public: {Delegates to: .parseJSONFile}
491 requireJSONFile: (file, opts, next) ->
492 # Prepare
493 [opts, next] = extractOptsAndCallback(opts, next)
494
495 # Require
496 result = @parseJSONFile(file, opts)
497
498 # Complete
499 return @complete(result, next)
500
501 # Public: {Delegates to: .parseCSONFile}
502 requireCSONFile: (file, opts, next) ->
503 # Prepare
504 [opts, next] = extractOptsAndCallback(opts, next)
505
506 # Require
507 result = @parseCSONFile(file, opts)
508
509 # Complete
510 return @complete(result, next)
511
512 # Public: Requires a JavaScript file and returns the result {Object}
513 #
514 # data - {String} The file path to require
515 # opts - {Object} The options, unused
516 #
517 # Returns {Object} or {Error}
518 requireJSFile: (file, opts, next) ->
519 # Prepare
520 [opts, next] = extractOptsAndCallback(opts, next)
521
522 # Require
523 try
524 result = requireFresh(file)
525 catch err
526 result = @ensureErrorType(err)
527
528 # Complete
529 return @complete(result, next)
530
531 # Public: Requires a CoffeeScript file and returns the result {Object}
532 #
533 # data - {String} The file path to require
534 # opts - {Object} The options, unused
535 #
536 # Returns {Object} or {Error}
537 requireCSFile: (file, opts, next) ->
538 # Prepare
539 [opts, next] = extractOptsAndCallback(opts, next)
540
541 # Require
542 require('coffeescript/register')
543 try
544 result = requireFresh(file)
545 catch err
546 result = @ensureErrorType(err)
547
548 # Complete
549 return @complete(result, next)
550
551# Export
552module.exports = new CSON()