Skip to content

Commit

Permalink
write custom parser
Browse files Browse the repository at this point in the history
  • Loading branch information
Marcisbee committed Aug 27, 2022
1 parent 1ed6b95 commit 7f55670
Show file tree
Hide file tree
Showing 10 changed files with 420 additions and 172 deletions.
204 changes: 147 additions & 57 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,32 +2,33 @@

Formats message strings with number, date, plural, and select placeholders to create localized messages.

* **Small.** Between 464 bytes and 1 kilobytes (minified and gzipped).
* **Small.** Between 0.6 kilobytes and 1 kilobytes (minified and gzipped).
Zero dependencies.
* **Fast.** Does absolute minimum amount of computations necessary.
* **Tree Shakable.** Includes separate global transformers config that can be omitted.
* **Pipe syntax.** Transformer functions can customized and chained.
* **View framework support.** Use React/Preact etc. components as transformers.
* It has good **TypeScript** support.

```ts
import { format } from 'messagepipe'
import { MessagePipe } from 'messagepipe'

format('Hello {planet}!', { planet: 'Mars' })
const msg = MessagePipe().compile('Hello {planet}!')

msg({ planet: 'Mars' }) // => "Hello Mars!"
```

```ts
import { format } from 'messagepipe'

const transformers = {
reverse(value) {
return value.split('').reverse().join('')
},
capitalize(value) {
return value[0].toUpperCase() + value.slice(1).toLowerCase()
},
}
import { MessagePipe } from 'messagepipe'

const { compile } = MessagePipe({
reverse: (val) => val.split('').reverse().join(''),
capitalize: (val) => val[0].toUpperCase() + val.slice(1).toLowerCase(),
})

format('Hello {planet | reverse | capitalize}!', { planet: 'Mars' }, transformers)
const msg = compile('Hello {planet | reverse | capitalize}!')

msg({ planet: 'Mars' }) // => "Hello Sram!"
```

## Install
Expand All @@ -38,93 +39,182 @@ npm install messagepipe

## Guide

### @TODO: Core concepts
### Core concepts

```js
-transformer
|-argument name
| |-argument value
--┐ ├---┐ ├-

{name | json, space:101}

----------------------
|--┘ ├-------------
|| |-------
|| |-argument
||-pipe
|-selector
-message
```

In one `message` there can only be one `selector`, but there can be unlimited number of `pipes` with unlimited number of `arguments` in them. It is possible to build dynamic `selector` (meaning `message` can be inside it), but it is not possible to build dynamic `pipes` except for `argument values`.

So both of these are valid:
1. `"Hello {agents.{index}.fistName}"`;
2. `"{a} + {b} = {a | sum, sequence:{b}}"`.
_(Note: sum is a custom transformer in this case)_.

### `message`
Contains everything between `{` and `}` that in large includes 1 `selector` and n `pipes`.

### `selector`
String value that points to value from given props object e.g.:
- `"{name}"` + `{ name: 'john' }` => `"john"`;
- `"{agents[0].name}"` + `{ agents: [{ name: 'john' }] }` => `"john"`

### `pipe`
A combination of 1 `transformer` and n `arguments` e.g.:
- `"{name | capitalize}"`;
- `"{name | reverse | capitalize}"`;
- `"{a | sum, sequence:1, double}"` _(Note: argument "double" will pass `true` value to "sum" transformer)_.

### `transformer`
Function that can transform value that is being selected from given props.

Lets define "capitalize" transformer that would uppercase the first letter of any string:
```ts
function capitalize(value: string) {
return value[0].toUpperCase() + value.slice(1).toLowerCase();
}
```

### @TODO: Transformers
To use this transformer define it when initiating MessagePipe and then it will be available to pipes with name "capitalize":

```ts
const msgPipe = MessagePipe({
capitalize,
})
```

This would be valid use case for it: `"Greetings {name | capitalize}!"`.

### `argument`
To allow more functionality, we can use arguments, that are passed to transformer function.

```ts
function increment(value: number, { by = 1 }: Record<string, any> = {}) {
return value + by;
}
```

We can now use it like this:
- `"{count | increment}"` + `{ count: 1 }` => `2`;
- `"{count | increment | by:1}"` + `{ count: 1 }` => `2`;
- `"{count | increment | by:5}"` + `{ count: 1 }` => `6`.

We can stack any number of arguments separated by `,` (comma).

### Global transformers

#### `number`
### `defaultTransformers`

- #### select
@TODO: Write about this

#### `plural`
- #### json
@TODO: Write about this

#### `date`
### `intlTransformers`

- #### number
@TODO: Write about this

#### `time`
- #### plural
@TODO: Write about this

#### `relativeTime`
- #### date
@TODO: Write about this

#### `select`
- #### time
@TODO: Write about this

#### `json`
- #### relativeTime
@TODO: Write about this

## API

### @TODO: `format`
### @TODO: `MessagePipe`
#### @TODO: `compile`

### @TODO: `formatRaw`

### @TODO: `getGlobalTransformers`
#### @TODO: `compileRaw`

## Framework integration

Works with React and Preact out of the box. Just swap out `format` with `formatRaw` and good to go. This works because it returns raw array of values that was the output of selectors and transformers.
Works with React and Preact out of the box. Just swap out `compile` with `compileRaw` and good to go. This works because it returns raw array of values that was the output of selectors and transformers.

```tsx
import { formatRaw } from 'messagepipe'
import { MessagePipe } from 'messagepipe'

function Mention(username) {
const {href} = useUser(username)

return <a href={href}>{username}</a>
}

const transformers = { Mention }
// We use React/Preact component as a transformer
const { compileRaw } = MessagePipe({ Mention })
const msg = compileRaw('Hello {name | Mention}!')

function App() {
const message = 'Hello {name | Mention}'
return <div>{formatRaw(message, {name: 'john'}, transformers)}</div>
} // => "<div>Hello <a href="...">john</a></div>"
return <div>{msg({name: 'john'})}</div>
} // => "<div>Hello <a href="...">john</a>!</div>"
```

Since we used `compileRaw`, library would output something like this: `['Hello ', [ReactElement], '!']`.

This will work with any kind of framework or custom library.

# Motivation
I was used to messageformat being the go to for this sort of stuff, but it has big flaws in the spec and library maintainers obviously wouldn't want to deviate from it. So the goal for messagepipe was to create NEW spec that solves all of the issues with it + must be faster & smaller.

One immediate flat that messagepipe solves is ability to select nested properties.
One immediate flaw that messagepipe solves is ability to select nested values.

## @TODO:

- [ ] Write up spec
- [x] General idea
- `Hello {name}!` => `"Hello John!"`
- [x] Selecting shallow values
- `{name}` => `"John"`
- [x] Selecting nested values
- `{agent.name}` => `"John"`
- [x] Selecting single value from array
- `{agent.0.name}` => `"John"`
- [x] Selecting multiple values from array
- `{agent.*.name}` => `["John", "Lars"]`
- [x] Using transformers
- `{name | uppercase}` => `uppercase("John")`
- [x] Using chained transformers
- `{name | reverse | uppercase}` => `uppercase(reverse("John"))`
- [x] Passing variables to transformer
- `{name | json, space:2}` => `json("John", {space:2})`
- [x] Passing variables to transformer
- `{name | remove, text:"name", caseSensitive:true}` => `remove("John", {text:"name", caseSensitive:true})`
- [ ] Escape `{` & `}`
- `Escaped: \\{ \\}` => `"Escaped: { }"`
- [ ] Using selector inside selector
- `{agent.{index}.name}` => `"Lars"`

- [x] General idea
- `Hello {name}!` => `"Hello John!"`
- [x] Selecting shallow values
- `{name}` => `"John"`
- [x] Selecting nested values
- `{agent.name}` => `"John"`
- [x] Selecting single value from array
- `{agent.0.name}` => `"John"`
- [x] Selecting multiple values from array
- `{agent.*.name}` => `["John", "Lars"]`
- [x] Using transformers
- `{name | uppercase}` => `uppercase("John")`
- [x] Using chained transformers
- `{name | reverse | uppercase}` => `uppercase(reverse("John"))`
- [x] Passing parameters to transformer
- `{name | json, space:2}` => `json("John", {space:2})`
- `{name | remove, text:"name", caseSensitive:true}` => `remove("John", {text:"name", caseSensitive:true})`
- [ ] Escaping `"`, "|", `,` and `:` in argument names
- `{name | select, "a\"2":2}`
- `{name | select, "a:2":2}`
- `{name | select, "a|2":2}`
- `{name | select, "a,2":2}`
- [ ] Escape `{` & `}`
- `Escaped: \\{ \\}` => `"Escaped: { }"`
- [x] Using selector inside selector
- Nested selector in selector can only be used to define path of the selector
- `{agent.{index}.name}` = `{agent[2].name}` => `"Lars"`
- `{agent.{index}}` = `{agent[2]}` => `{"name":"Lars"}`
- `{{index}}` = `{2}` => `undefined`
- [x] Using selector inside pipes
- Nested selector in pipes can only be used to construct argument values
- `{name | json, space:{space}}` => `json("John", {space:2})`

# License
[MIT](LICENCE) &copy; [Marcis Bergmanis](https://twitter.com/marcisbee)
14 changes: 7 additions & 7 deletions dev.ts
Original file line number Diff line number Diff line change
@@ -1,24 +1,24 @@
import { format } from './src'
import { MessagePipe } from './src'

const transformers = {
const mp1 = MessagePipe({
reverse(value) {
return value.split('').reverse().join('')
},
capitalize(value) {
return value[0].toUpperCase() + value.slice(1).toLowerCase()
},
}
})

console.log(format('Hello {planet | reverse | capitalize}!', { planet: 'Mars' }, transformers))
console.log(mp1.compile('Hello {planet | reverse | capitalize}!')({ planet: 'Mars' }))

const transformers2 = {
const mp2 = MessagePipe({
remove(value, { text, replacer = '' }) {
if (!text) {
return '';
}

return value.replace(text, replacer);
},
}
})

console.log(format('Hello {planet | remove, text:"a", replacer: "4"}!', { planet: 'Mars' }, transformers2))
console.log(mp2.compile('Hello {planet | remove, "text":"a", replacer: "4"}!')({ planet: 'Mars' }))
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "messagepipe",
"version": "0.1.1",
"version": "0.2.0",
"description": "Formats message strings with number, date, plural, and select placeholders to create localized messages",
"main": "dist/index.js",
"module": "dist/index.esm.js",
Expand Down
4 changes: 2 additions & 2 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
export { getGlobalTransformers } from './transformers';
export { format, formatRaw } from './messagepipe';
export { defaultTransformers, intlTransformers } from './transformers';
export { MessagePipe } from './messagepipe';
Loading

0 comments on commit 7f55670

Please sign in to comment.