npm package: @pfeiferio/string-interpolate
Description: Lightweight string interpolation with named placeholders and filter pipelines
Author: Pascal Pfeifer
Latest version: 1.0.0

# `@pfeiferio/string-interpolate`

Lightweight string interpolation with named placeholders and filter pipelines.

* ✅ No dependencies
* ✅ No template engine
* ✅ No expressions, no loops
* ✅ Built-in and custom filters
* ✅ Safe, test-driven parser

---

## Installation

```bash
npm install @pfeiferio/string-interpolate
```

---

## Basic Usage

```js
import { interpolate } from '@pfeiferio/string-interpolate'

interpolate(
  'Hello {{ user.name }}',
  { user: { name: 'Pascal' } }
)
// → "Hello Pascal"
```

---

## Nested Placeholders (Dot Notation)

```js
interpolate(
  'ID: {{ order.customer.id }}',
  {
    order: {
      customer: { id: 42 }
    }
  }
)
// → "ID: 42"
```

---

## Selective Removal of Missing Placeholders

By default, unresolved placeholders are kept when `keep=true`.

With the `remove` option, you can **explicitly remove selected placeholders** when their value is missing, while keeping all others unchanged.

```js
interpolate(
  'Hello {{ user.name }} {{ user.email }}',
  {
    user: { name: 'Pascal' }
  },
  {
    keep: true,
    remove: ['user.email']
  }
)
// → "Hello Pascal "
```

### Behavior

* `remove` entries act as prefix paths
* All placeholders **under the given path**are removed if their value is `undefined`
* Existing values are **never removed**
* `remove` overrides `keep` for the specified paths

```js
interpolate(
  'Hello {{ user.email }}',
  {},
  {
    keep: true,
    remove: ['user.email']
  }
)
// → "Hello "
```

Partial matches **do** apply:

```js
interpolate(
  'Hello {{ user.email }}',
  {},
  {
    keep: true,
    remove: ['user']
  }
)
// → "Hello "
```

### Options Reference

```ts
interpolate(
  template: string,
  replacements: Record<string, unknown>,
  options?: {
    keep?: boolean
    remove?: string[]
    customFilters?: Record<string, FilterFunction>
  }
): string
```

---

## Filters

Filters are applied using a pipe syntax.

```js
interpolate(
  '{{ name | uppercase }}',
  { name: 'pascal' }
)
// → "PASCAL"
```

Multiple filters are applied left to right:

```js
interpolate(
  '{{ name | uppercase | truncate length=3 }}',
  { name: 'pascal' }
)
// → "PAS..."
```

---

## Filter Arguments

Filters receive **named arguments** using `key=value` syntax.

```js
interpolate(
  '{{ text | truncate length=4 ending="..." }}',
  { text: 'abcdef' }
)
// → "abcd..."
```

### Argument Rules

* Arguments are key-value based
* Values may be:

    * numbers (`length=10`)
    * booleans (`pretty=true`)
    * strings (`suffix=!!!`)
    * quoted strings with spaces (`ending="..."`)
* No escaping inside quoted strings
* Whitespace is used as argument separator

---

### Filter Composition
> Interpolation is single-pass.
> If a placeholder resolves to another placeholder expression, filters are not executed immediately.
> Instead, they are appended to the resulting placeholder expression.

---

## Built-in Filters

| Filter       | Description                     |
| ------------ | ------------------------------- |
| `uppercase`  | Convert string to upper case    |
| `lowercase`  | Convert string to lower case    |
| `capitalize` | Capitalize first character      |
| `append`     | Append a suffix                 |
| `concat`     | Concatenate text                |
| `replace`    | Replace all occurrences         |
| `ltrim`      | Trim characters from start      |
| `rtrim`      | Trim characters from end        |
| `trim`       | Trim characters from both sides |
| `truncate`   | Shorten string                  |
| `default`    | Fallback for `undefined`        |
| `padStart`   | Left-pad string                 |
| `padEnd`     | Right-pad string                |
| `json`       | JSON stringify                  |

Example:

```js
interpolate(
  '{{ value | replace search=" " replace="_" }}',
  { value: 'hello world' }
)
// → "hello_world"
```

---

## Custom Filters

You can provide custom filters via the options object.

```js
interpolate(
  '{{ name | reverse }}',
  { name: 'abc' },
  {
    customFilters: {
      reverse: (value) =>
        typeof value === 'string'
          ? value.split('').reverse().join('')
          : value
    }
  }
)
// → "cba"
```

Custom filters override built-in filters with the same name.

---

## Function Values

If a replacement value is a function, it will be executed:

```js
interpolate(
  'Now: {{ now }}',
  {
    now: () => new Date().toISOString()
  }
)
```

---

## API

### `interpolate(template, replacements, options?)`

```ts
interpolate(
  template: string,
  replacements: Record<string, unknown>,
  options?: {
    keep?: boolean,
    customFilters?: Record<string, FilterFunction>
  }
): string
```

---

## Design Goals

* Explicit syntax
* Predictable behavior
* No hidden magic
* Easy to extend
* Safe defaults

This package is **not** intended to be a full template engine.

---

## License

MIT

---