@m4rsh/cones
tools to roll js
using tagged templates to generate files with ease
- jstag: template string with indents & object strings & automatic imports
- fmt: template string with indents & object strings
- ranges: find matching ranges with regex
- changes: apply edited ranges
- objstr: print javascript objects (like JSON but less verbose)
- jsonstr: object to JSON with better spacing
Examples
Automatic barrel files
ID Compressor
Generate Markdown
Custom naming scheme for jstag
Annotating Log Statements
API
jstag
function to create a custom js tagged template function to compose javascript code with automatic imports
import { jstag, Ref } from '@m4rsh/cones'
const js = jstag()
let code = js`
let x = ${ Ref("@ui/Button", ['styles']) };
let y = ${ Ref("@ui/Button", ['render']) };
${js`
let fn = () => ${ Ref("@ui/Button", ["handlers"]) };
`}
`
Output
import { styles as $__0, render as $__1, handlers as $__2 } from "@ui/Button"
let x = $__0;
let y = $__1;
let fn = () => $__2;
Details on using Ref
Ref objects are basically pointers to an imported resource - defined by path + exports. They are converted to strings at the very end, once all Refs in the template are known. This allows for generating optimized import statements when the template is stringified.
Imports are generated with the following heuristics:
- References to the same path + export will reuse the same identifier
- Importing * alongside named exports will generate 2 different imports to allow for tree-shaking
- Not specifying an export will default to the 'default' export
- Refs within nested jstag templates will automatically merge with the 'root' template that gets stringifed.
- Paths are treated as raw strings, not resolved or changed
fmt
import { fmt } from '@m4rsh/cones'
let code = fmt`
<h1>left-aligned</h1>
<div>
${fmt`
<h2>preserved indent</h2>
`}
</div>
`
Output
<h1>left-aligned</h1>
<div>
<h2>preserved indent</h2>
<div>
(without fmt)
<h1>left-aligned</h1>
<div>
<h2>preserved indent</h2>
</div>
How indents work
- When parsing a template string, if a Hole is on a line that's preceded by tabs or spaces, save that as 'padding'
- If the value for that Hole contains newlines, insert that 'padding' at the beginning of every newline (after the first)
- Trim the empty lines before the first non-whitespace character
- On the first line with content, count the number of tabs/spaces on that line
- For every subsequent line, remove up to that much whitespace on subsequent lines
If everything goes smoothly:
- Indenting templates with a newline at top/bottom are nicely trimmed away for the output
- Inserting values within a template preserve the indentation of the opening
${ - Nested indentation is consistent
ranges
Find selections within a source text that match the regex pattern. Returns ideal format for running changes
(For serious file manipulation, I recommend using tree-sitter instead of this regex method)
changes
Apply changes (in reverse order to preserve correctness) to source text at given range indices, returning the new string.
import { fmt, ranges, changes } from '@m4rsh/cones'
let src = fmt`
/*<context>*/
function a(){}
function b(){}
/*</context>*/
function c(){}
/*<context>*/
function d(){}
/*</context>*/
`
let zones = ranges(src,/^\/\*<\/?context>\*\/$/gm)
console.log(zones)
// [ 0, 13, "/*<context>*/" ],
// [ 44, 58, "/*</context>*/" ],
// [ 76, 89, "/*<context>*/" ],
// [ 105, 119, "/*</context>*/" ]
let group_i = 1
let modified = changes(src, zones.map(([b,e,str]) => {
if(str.includes("</")){
return [b,e,"// --- CLOSE " + (group_i++)]
}
return [b,e,"// --- OPEN " + (group_i)]
}))
console.log(modified == fmt`
// --- OPEN 1
function a(){}
function b(){}
// --- CLOSE 1
function c(){}
// --- OPEN 2
function d(){}
// --- CLOSE 2
`) // true
objstr
Pulled from stringify-object
import { objstr } from '@m4rsh/cones'
let obj = {
x: 30,
y: "test",
z: {
"int-er/es*ting": [9.2291]
}
}
let str = objstr(obj, {
indent:' ', // two spaces is default
singleQuotes:false, // ' vs "
inlineCharacterLimit:80, // line wrap limit
pad:'', // used internally to preserve indents
valueTransform: (v)=>v, // undefined by default
})
console.log(str)
// {x: 30, y: "test", z: {"int-er/es*ting": [9.2291]}}
jsonstr
Pulled from json-stringify-pretty-compact
import { jsonstr } from '@m4rsh/cones'
let list = [
{ id: 1, $: ["home", "about"] },
{ id: 2, $: ["faq", "contact"] },
{ id: 3, $: ["test", "app"] },
]
let str = jsonstr(obj, {
indent:' ', // two spaces is default
inlineCharacterLimit:80, // line wrap limit
pad:'', // used internally to preserve indents
})
console.log(str)
Output
[
{"id": 1, "$": ["home", "about"]},
{"id": 2, "$": ["faq", "contact"]},
{"id": 3, "$": ["test", "app"]}
]
With JSON.stringify()
[
{
"id": 1,
"$": [
"home",
"about"
]
},
{
"id": 2,
"$": [
"faq",
"contact"
]
},
{
"id": 3,
"$": [
"test",
"app"
]
}
]
References
- concept of rendering files in other languages inspired by
uhtmlby @WebReflection stringify-objectby @sindresorhusjson-stringify-pretty-compactby @lydell