diff --git a/Source/README.md b/Source/README.md index ebff050..bb816e7 100644 --- a/Source/README.md +++ b/Source/README.md @@ -1,22 +1,22 @@ # HJSON   [![Badge License]][License] -*A parser / stringifier for **[HJSON]**.* +A parser / stringifier for **[HJSON]**.
## Import -```JavaScript +```js import HJson from 'https://deno.land/x/hjson/mod.ts' ```

-## Parsing +## Parse -```JavaScript +```js import { parse } from 'https://deno.land/x/hjson/mod.ts' const { log } = console; @@ -48,9 +48,38 @@ log(parse(hjson));

-## Stringification +## Stringify -**Work In Progress** +```js +import { stringify } from 'https://deno.land/x/hjson/mod.ts' + +const { log } = console; + +const object = { + some : 3 , + variables : [ 'with' , 'a' , 'lot' ] , + inside : { them : 9 } +} + +log(stringify(hjson)); + + +/* + * { + * some : 3 + * + * variables : [ + * with + * a + * lot + * ] + * + * inside : { + * them : 9 + * } + * } + */ +```
diff --git a/Source/Stringify/Encode.js b/Source/Stringify/Encode.js new file mode 100644 index 0000000..770ad78 --- /dev/null +++ b/Source/Stringify/Encode.js @@ -0,0 +1,228 @@ + + +export default function stringify(object,options = {}){ + + options.style ??= 'modern'; + + if(options.style !== 'modern') + throw 'Unsupported style'; + + const base = modern(object); + + const pretty = base + .replace(/\[\s+\{/g,'[{') + .replace(/\}\s+\]/g,'}]') + .replace(/\}\s*,\s*\{/g,'},{'); + // .replace(//g,`\n$1:\n\n'''`); + // .replace(/\[\{\s*/g,'[{\n\n'); + + return [... indent(pretty) ].join('\n'); +} + +function * indent(pretty){ + + const lines = pretty.split('\n'); + + let level = 0; + let padding = ' '; + let open = false; + + + for(const line of lines){ + + const padded = () => + padding.repeat(Math.max(level,0)) + line; + + const trimmed = line.trim(); + + if(trimmed.length < 1){ + yield padded(); + continue; + } + + if(trimmed.startsWith(`'''`)){ + + if(!open) + level++ + + yield padded(); + + if(open) + level--; + + open = !open; + continue; + } + + if( + trimmed.startsWith('{') || + trimmed.endsWith('{') || + trimmed.endsWith('[') + ){ + yield padded(); + level++; + continue; + } + + if( + trimmed.startsWith('}') || + trimmed.endsWith('}') || + trimmed.endsWith(']') + ){ + level--; + yield padded(); + continue; + } + + yield padded(); + } +} + + +function modern(object){ + + let lines = toObject(object); + + return lines.join('\n'); +} + +function toItem(item){ + + switch(typeof item){ + case 'number' : + return [ `${ item }` ]; + case 'boolean' : + return [ (item) ? 'true' : 'false' ]; + case 'string' : + + if(item.includes('\n')) + return [ `'''` , item , `'''` ]; + + if( + item.startsWith('#') || + /[,:\[\]\{\}]/g.test(item) + ) return [ `'${ item }'`]; + + return [ `${ item }` ]; + case 'array' : + return toArray(item); + case 'object': + return toObject(item); + } +} +// +// function indent(string){ +// return ' ' + string; +// } + +function toArray(array){ + + const lines = [ '[' ]; + + for(const item of array) + lines.push(...toItem(item));//.map(indent) + + lines.push(']'); + + return (lines.length === 2) + ? [ '[]' ] + : lines ; +} + +function toObject(object){ + + const longestFirst = ([ a ],[ b ]) => b.length - a.length; + + const + entries = Object.entries(object) , + simple = entries + .filter(([ _ , value ]) => [ 'string' , 'number' , 'boolean' ].includes(typeof value)) + .sort(longestFirst) + .map(formString) + .flat(); + // .map((string) => ' ' + string); + + const complex = entries + .filter(([ _ , value ]) => [ 'object' , 'array' ].includes(typeof value)) + .sort(longestFirst) + .map(toComplex) + .flat(); + // .map((string) => ' ' + string); + + complex.shift(); + + const lines = [ '{' ]; + + if(simple.length && complex.length) + lines.push(''); + + if(simple.length) + lines.push(...simple); + + if(simple.length && complex.length) + lines.push(''); + + if(complex.length) + lines.push(...complex); + + lines.push('}'); + + return (lines.length === 2) + ? [ '{}' ] + : lines ; +} + +function toComplex([ key , value ]){ + + if(Array.isArray(value)){ + + const [ first , ...rest ] = toArray(value); + + return [ + '' , + `${ toString(key) } : ${ first }`, + ...rest + ]; + } else { + const [ first , ...rest ] = toObject(value); + + return [ + '' , + `${ toString(key) } : ${ first }`, + ...rest + ]; + } +} + + +function formString([ key , value ]){ + switch(typeof value){ + case 'number' : + return [ `${ toString(key) } : ${ value }` ]; + case 'boolean' : + return [ `${ toString(key) } : ${ (value) + ? ['true'] + : ['false'] }` ] + case 'string' : + + if(value.includes('\n')) + return [ '' , `${ toString(key) }:` , '' , `'''` , value , `'''` ]; + + if( + value.startsWith('#') || + /[,:\[\]\{\}]/g.test(value) + ) return [ `${ toString(key) } : '${ value }'`]; + + return [ `${ toString(key) } : ${ value }` ]; + } +} + +function toString(value){ + + if( + value.startsWith('#') || + /[,:\[\]\{\}]/g.test(value) + ) return `'${ value }'`; + + return value; +} \ No newline at end of file diff --git a/Source/Stringify/mod.ts b/Source/Stringify/mod.ts new file mode 100644 index 0000000..f12f1e8 --- /dev/null +++ b/Source/Stringify/mod.ts @@ -0,0 +1,7 @@ + +import encode from './Encode.js' + + +export default function stringify ( value : object , options : object ) : string { + return encode(value,options); +} \ No newline at end of file diff --git a/Source/mod.ts b/Source/mod.ts index 886e73d..d775451 100644 --- a/Source/mod.ts +++ b/Source/mod.ts @@ -1,9 +1,16 @@ import decode from './Decode.js' - export function parse ( text : string ) : object { return decode(text); } -export default { parse } \ No newline at end of file + +import encode from './Stringify/mod.ts' + +export function stringify ( value : object , options : object ) : string { + return encode(value,options); +} + + +export default { parse , stringify } \ No newline at end of file diff --git a/Tests/Stringify/Basic.test.js b/Tests/Stringify/Basic.test.js new file mode 100644 index 0000000..ea38c67 --- /dev/null +++ b/Tests/Stringify/Basic.test.js @@ -0,0 +1,34 @@ + +import { stringify } from '../../Source/mod.ts' + +const { log } = console; + + +const object = { + difficulty : 3 , + name : 'lab' , + planet : 'pluto' , + sector : 3 , + captureWave : 10 , + + research : { + + name : '\ntesting \'wow\'\n' , + + parent : 'nuclearFacility' , + + objectives: [{ + preset : 'nuclearFacility' , + type : 'SectorComplete' + }] + } +} + + + +Deno.test('Basic Stringification Test',() => { + + const hjson = stringify(object); + + log(hjson); +}); \ No newline at end of file