# @m4rsh/cones

tools to roll js

[![NPM Version](https://img.shields.io/npm/v/%40m4rsh%2Fcones)](https://npmjs.org/package/@m4rsh/cones) [![npm bundle size](https://img.shields.io/bundlephobia/minzip/%40m4rsh%2Fcones)](https://bundlephobia.com/package/@m4rsh/cones)

---

> 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](https://github.com/m4r-sh/cones/tree/master/examples#automatic-barrel-files)

### [ID Compressor](https://github.com/m4r-sh/cones/tree/master/examples#id-compressor)

### [Generate Markdown](https://github.com/m4r-sh/cones/tree/master/examples#generate-markdown)

### [Custom naming scheme for jstag](https://github.com/m4r-sh/cones/tree/master/examples#custom-naming-scheme-for-jstag)

### [Annotating Log Statements](https://github.com/m4r-sh/cones/tree/master/examples#annotating-log-statements)


---

## API

### `jstag`

function to create a custom `js` tagged template function to compose javascript code with automatic imports

```js
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**

```js
import { styles as $__0, render as $__1, handlers as $__2 } from "@ui/Button"

let x = $__0;
let y = $__1;

let fn = () => $__2;

```

<details>
<summary><strong>Details on using Ref</strong></summary>

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

</details>

---

### `fmt`

```js
import { fmt } from '@m4rsh/cones'

let code = fmt`
    <h1>left-aligned</h1>
    <div>
        ${fmt`
            <h2>preserved indent</h2>
        `}
    </div>
`
```

**Output**

```html
<h1>left-aligned</h1>
<div>
    <h2>preserved indent</h2>
<div>
```

**(without fmt)**

```html

    <h1>left-aligned</h1>
    <div>
        
            <h2>preserved indent</h2>

    </div>

```

<details>
<summary><strong>How indents work</strong></summary>

- 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

</details>

---

### `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.

```js
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](https://npmjs.org/stringify-object)

```js
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](https://npmjs.org/json-stringify-pretty-compact)

```js
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**

```json
[                                     
  {"id": 1, "$": ["home", "about"]},  
  {"id": 2, "$": ["faq", "contact"]}, 
  {"id": 3, "$": ["test", "app"]}     
]                                     
```

**With JSON.stringify()**

```json
[
 {
  "id": 1,
  "$": [
   "home",
   "about"
  ]
 },
 {
  "id": 2,
  "$": [
   "faq",
   "contact"
  ]
 },
 {
  "id": 3,
  "$": [
   "test",
   "app"
  ]
 }
]
```

---

## References

- concept of rendering files in other languages inspired by [`uhtml`](https://github.com/WebReflection/uhtml) by [@WebReflection](https://github.com/WebReflection/uhtml)
- [`stringify-object`](https://www.npmjs.com/package/stringify-object) by [@sindresorhus](https://github.com/sindresorhus)
- [`json-stringify-pretty-compact`](https://www.npmjs.com/package/json-stringify-pretty-compact) by [@lydell](https://github.com/lydell)



