import fs from "fs-extra" ; import jju from "jju" ; import json5 from "json5-with-undefined" ; import { ReadonlyDeepT } from "type-ops" ; import { AnyObject } from "./common_types" ; import { InternalError } from "./internal_error" ; export { stringify as stringifyJson5 } from "json5-with-undefined" ; export type JsonValue = string | number | boolean | null | AnyObject | any [ ] ; export type JsonValueReadonly = string | number | boolean | null | ReadonlyDeepT < AnyObject > | ReadonlyArray < any > ; public export type JSONReviver = ( key : any , value : any ) => any ; /**
 * Options for  readJson5 .
 *  public export interface ReadOptions extends ParseOptions { encoding ? : string ; flag ? : string ; } remarks public export async function readJson5 ( file , options = { } ) { const contents = ( await fs . readFile ( file , options ) ) . toString ( ) ; return parseJson5 ( contents , options ) ; } /**
 * Options for  parseJson5 .
 *  public export interface ParseOptions { defaultValue prettyError ? : boolean ; reviver ? : JSONReviver ; defaultValue throws ? : boolean ; } const defaultParseOptions = { prettyError : true , throws : true , } ; remarks public export function parseJson5 ( text , options ? ) { const inOpts = options === undefined ? { } : typeof options === "function" ? { reviver : options } : options ; const { prettyError , reviver , throws } = { ... defaultParseOptions , ... inOpts } ; try { return json5 . parse ( text , reviver ) ; } catch ( err ) { if ( ! throws ) return null ; if ( prettyError ) err = prettifySyntaxError ( err , text ) ; throw err ; } } function prettifySyntaxError ( err , contents ) { if ( ! err || err . name !== "SyntaxError" ) return err ; const { columnNumber , lineNumber } = err ; if ( columnNumber == null || lineNumber == null ) return err ; const lines = contents . split ( /\r?\n/g , lineNumber ) ; if ( lines . length !== lineNumber ) { throw new InternalError ( `Attempted to display JSON5 error, but wrong number ` + `of lines found (expected=${ lineNumber } actual=${ lines . length }).\n` + `Original error: ${ err . message }` ) ; } err . originalMessage = err . message ; const errInfo = err . message . replace ( /^JSON5: / , "" ) . replace ( / at \d+:\d+/ , "" ) ; const msg = [ `Invalid JSON5 format: ${ errInfo } at line ${ lineNumber }, column ${ columnNumber }:` , lines [ lineNumber - 1 ] , " " . repeat ( columnNumber - 1 ) + "^-- error here" , ] ; err . message = msg . join ( "\n" ) ; return err ; } export interface Json5WriterReadOptions { encoding ? : string ; flag ? : string ; mustExist ? : boolean ; reviver ? : JSONReviver ; } const defaultReadOptions = { mustExist : true , } ; export interface Json5WriterOptions { /**
     * Attempt to match the style present in the existing file.
     */ matchStyle ? : boolean ; /**
     * If matchStyle is true, these options are only used if no file currently
     * exists. If matchStyle is false, these options are used on every update.
     */ style ? : jju . StringifyOptions ; } const defaultJson5WriterOptions = { matchStyle : true , style : { quote_keys : false , indent : 2 , } , } ; /**
 * Make updates to a JSON/JSON5 config file that might be updated by humans.
 * Retains comments and optionally attempts to preserve style already present
 * in the file.
 */ export class Json5Writer { private _options : Required < Json5WriterOptions > ; private _text ? : string ; private _value ? : any ; constructor ( readonly filename , options = { } ) { this . _options = { ... defaultJson5WriterOptions , ... options } ; } get text ( ) : string | undefined { return this . _text ; } get value ( ) : JsonValueReadonly | undefined { return this . _value ; } async read ( options = { } ) { const opts = { ... defaultReadOptions , ... options } ; try { this . _text = ( await fs . readFile ( this . filename , opts ) ) . toString ( ) ; } catch ( err ) { if ( err && err . code === "ENOENT" && ! opts . mustExist ) return undefined ; throw err ; } this . _value = jju . parse ( this . _text , opts ) ; return this . value ; } async update ( newValue ) { const orig = this . _text !== undefined ? this . _text : "null\n" ; const opts = this . _text === undefined || ! this . _options . matchStyle ? this . _options . style : undefined ; const newText = jju . update ( orig , newValue , opts ) ; await fs . writeFile ( this . filename , newText ) ; this . _text = newText ; this . _value = newValue ; } } export async function createJson5Writer ( filename , options = { } ) { const writer = new Json5Writer ( filename , options ) ; await writer . read ( options ) ; return writer ; }