init
This commit is contained in:
128
README.md
Normal file
128
README.md
Normal file
@@ -0,0 +1,128 @@
|
||||
# tdir
|
||||
|
||||
Treat a directory as a template. File paths and contents support conditionals (`@if`/`@elseif`/`@else`) and variable substitution (`@var`). Provide a Zod schema and tdir validates it matches the template at setup time, then validates context at render time.
|
||||
|
||||
## Install
|
||||
|
||||
```sh
|
||||
bun install tdir zod
|
||||
```
|
||||
|
||||
## Quick start
|
||||
|
||||
Given a template directory:
|
||||
|
||||
```
|
||||
templates/
|
||||
<@if(context.web)>web/
|
||||
index.html
|
||||
```
|
||||
|
||||
Where `index.html` contains:
|
||||
|
||||
```html
|
||||
<html>
|
||||
<@if(context.header.show)>
|
||||
<head><@var(context.header.title)></head>
|
||||
<@endif>
|
||||
<body></body>
|
||||
</html>
|
||||
```
|
||||
|
||||
Render it:
|
||||
|
||||
```ts
|
||||
import { initRenderer } from "tdir"
|
||||
import { z } from "zod"
|
||||
|
||||
const createRenderer = initRenderer("./templates")
|
||||
|
||||
const render = createRenderer(z.object({
|
||||
web: z.boolean(),
|
||||
header: z.object({
|
||||
show: z.boolean(),
|
||||
title: z.string()
|
||||
})
|
||||
}))
|
||||
|
||||
render("./output", {
|
||||
web: true,
|
||||
header: { show: true, title: "Hello" }
|
||||
})
|
||||
// Creates: output/web/index.html with <head>Hello</head>
|
||||
```
|
||||
|
||||
## Template directives
|
||||
|
||||
### In file contents
|
||||
|
||||
| Directive | Description |
|
||||
|---|---|
|
||||
| `<@if(context.x)>` | Conditional block (must end with `<@endif>`) |
|
||||
| `<@elseif(context.y)>` | Else-if branch |
|
||||
| `<@else>` | Else branch |
|
||||
| `<@endif>` | End conditional block |
|
||||
| `<@var(context.x)>` | Substitute with context value (default type: `string`) |
|
||||
| `<@var(context.x:number)>` | Substitute with explicit type |
|
||||
|
||||
### In directory/file names
|
||||
|
||||
| Directive | Description |
|
||||
|---|---|
|
||||
| `<@if(context.x)>dirname` | Conditionally include directory/file |
|
||||
| `<@var(context.x)>` | Dynamic directory/file name |
|
||||
|
||||
These can be combined: `<@if(context.web.create)><@var(context.web.dir)>` creates a directory named by `context.web.dir` only if `context.web.create` is true.
|
||||
|
||||
## Schema validation
|
||||
|
||||
`createRenderer` validates that your Zod schema matches the template variables. Mismatches throw `SchemaMismatchError`:
|
||||
|
||||
```ts
|
||||
import { initRenderer, SchemaMismatchError } from "tdir"
|
||||
import { z } from "zod"
|
||||
|
||||
const createRenderer = initRenderer("./templates")
|
||||
|
||||
// Template uses <@if(context.web)> which requires a boolean,
|
||||
// but schema declares string -- throws SchemaMismatchError
|
||||
createRenderer(z.object({
|
||||
web: z.string(), // wrong type
|
||||
header: z.object({ show: z.boolean(), title: z.string() })
|
||||
}))
|
||||
// SchemaMismatchError: Shema doesnt match used template variables: web: expected z.boolean() but schema has z.string()
|
||||
|
||||
// Schema is missing fields used in templates -- throws SchemaMismatchError
|
||||
createRenderer(z.object({
|
||||
web: z.boolean()
|
||||
// missing header
|
||||
}))
|
||||
// SchemaMismatchError: Shema doesnt match used template variables: header: missing in schema
|
||||
```
|
||||
|
||||
## Context validation
|
||||
|
||||
At render time, the context is validated by Zod. Invalid context throws `z.ZodError`:
|
||||
|
||||
```ts
|
||||
const render = createRenderer(z.object({
|
||||
web: z.boolean(),
|
||||
header: z.object({ show: z.boolean(), title: z.string() })
|
||||
}))
|
||||
|
||||
render("./output", {})
|
||||
// ZodError: required at "web", required at "header"
|
||||
|
||||
render("./output", { web: "not a boolean", header: { show: true, title: "Hi" } })
|
||||
// ZodError: expected boolean, received string at "web"
|
||||
```
|
||||
|
||||
## Unmatched directives
|
||||
|
||||
A `<@if>` without a matching `<@endif>` throws at render time:
|
||||
|
||||
```ts
|
||||
// If a template file contains <@if(context.x)> with no <@endif>
|
||||
render("./output", { x: true })
|
||||
// Error: Unmatched <@if> without <@endif>
|
||||
```
|
||||
Reference in New Issue
Block a user