Skip to content

Commit 7f55670

Browse files
committed
write custom parser
1 parent 1ed6b95 commit 7f55670

File tree

10 files changed

+420
-172
lines changed

10 files changed

+420
-172
lines changed

README.md

Lines changed: 147 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -2,32 +2,33 @@
22

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

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

1213
```ts
13-
import { format } from 'messagepipe'
14+
import { MessagePipe } from 'messagepipe'
1415

15-
format('Hello {planet}!', { planet: 'Mars' })
16+
const msg = MessagePipe().compile('Hello {planet}!')
17+
18+
msg({ planet: 'Mars' }) // => "Hello Mars!"
1619
```
1720

1821
```ts
19-
import { format } from 'messagepipe'
20-
21-
const transformers = {
22-
reverse(value) {
23-
return value.split('').reverse().join('')
24-
},
25-
capitalize(value) {
26-
return value[0].toUpperCase() + value.slice(1).toLowerCase()
27-
},
28-
}
22+
import { MessagePipe } from 'messagepipe'
23+
24+
const { compile } = MessagePipe({
25+
reverse: (val) => val.split('').reverse().join(''),
26+
capitalize: (val) => val[0].toUpperCase() + val.slice(1).toLowerCase(),
27+
})
2928

30-
format('Hello {planet | reverse | capitalize}!', { planet: 'Mars' }, transformers)
29+
const msg = compile('Hello {planet | reverse | capitalize}!')
30+
31+
msg({ planet: 'Mars' }) // => "Hello Sram!"
3132
```
3233

3334
## Install
@@ -38,93 +39,182 @@ npm install messagepipe
3839

3940
## Guide
4041

41-
### @TODO: Core concepts
42+
### Core concepts
43+
44+
```js
45+
-transformer
46+
|-argument name
47+
| |-argument value
48+
--┐ ├---┐ ├-
49+
50+
{name | json, space:101}
51+
52+
----------------------
53+
|--┘ ├-------------
54+
|| |-------
55+
|| |-argument
56+
||-pipe
57+
|-selector
58+
-message
59+
```
60+
61+
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`.
62+
63+
So both of these are valid:
64+
1. `"Hello {agents.{index}.fistName}"`;
65+
2. `"{a} + {b} = {a | sum, sequence:{b}}"`.
66+
_(Note: sum is a custom transformer in this case)_.
67+
68+
### `message`
69+
Contains everything between `{` and `}` that in large includes 1 `selector` and n `pipes`.
70+
71+
### `selector`
72+
String value that points to value from given props object e.g.:
73+
- `"{name}"` + `{ name: 'john' }` => `"john"`;
74+
- `"{agents[0].name}"` + `{ agents: [{ name: 'john' }] }` => `"john"`
75+
76+
### `pipe`
77+
A combination of 1 `transformer` and n `arguments` e.g.:
78+
- `"{name | capitalize}"`;
79+
- `"{name | reverse | capitalize}"`;
80+
- `"{a | sum, sequence:1, double}"` _(Note: argument "double" will pass `true` value to "sum" transformer)_.
81+
82+
### `transformer`
83+
Function that can transform value that is being selected from given props.
84+
85+
Lets define "capitalize" transformer that would uppercase the first letter of any string:
86+
```ts
87+
function capitalize(value: string) {
88+
return value[0].toUpperCase() + value.slice(1).toLowerCase();
89+
}
90+
```
4291

43-
### @TODO: Transformers
92+
To use this transformer define it when initiating MessagePipe and then it will be available to pipes with name "capitalize":
93+
94+
```ts
95+
const msgPipe = MessagePipe({
96+
capitalize,
97+
})
98+
```
99+
100+
This would be valid use case for it: `"Greetings {name | capitalize}!"`.
101+
102+
### `argument`
103+
To allow more functionality, we can use arguments, that are passed to transformer function.
104+
105+
```ts
106+
function increment(value: number, { by = 1 }: Record<string, any> = {}) {
107+
return value + by;
108+
}
109+
```
110+
111+
We can now use it like this:
112+
- `"{count | increment}"` + `{ count: 1 }` => `2`;
113+
- `"{count | increment | by:1}"` + `{ count: 1 }` => `2`;
114+
- `"{count | increment | by:5}"` + `{ count: 1 }` => `6`.
115+
116+
We can stack any number of arguments separated by `,` (comma).
44117

45118
### Global transformers
46119

47-
#### `number`
120+
### `defaultTransformers`
121+
122+
- #### select
48123
@TODO: Write about this
49124

50-
#### `plural`
125+
- #### json
51126
@TODO: Write about this
52127

53-
#### `date`
128+
### `intlTransformers`
129+
130+
- #### number
54131
@TODO: Write about this
55132

56-
#### `time`
133+
- #### plural
57134
@TODO: Write about this
58135

59-
#### `relativeTime`
136+
- #### date
60137
@TODO: Write about this
61138

62-
#### `select`
139+
- #### time
63140
@TODO: Write about this
64141

65-
#### `json`
142+
- #### relativeTime
66143
@TODO: Write about this
67144

68145
## API
69146

70-
### @TODO: `format`
147+
### @TODO: `MessagePipe`
148+
#### @TODO: `compile`
71149

72-
### @TODO: `formatRaw`
73-
74-
### @TODO: `getGlobalTransformers`
150+
#### @TODO: `compileRaw`
75151

76152
## Framework integration
77153

78-
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.
154+
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.
79155

80156
```tsx
81-
import { formatRaw } from 'messagepipe'
157+
import { MessagePipe } from 'messagepipe'
82158

83159
function Mention(username) {
84160
const {href} = useUser(username)
85161

86162
return <a href={href}>{username}</a>
87163
}
88164

89-
const transformers = { Mention }
165+
// We use React/Preact component as a transformer
166+
const { compileRaw } = MessagePipe({ Mention })
167+
const msg = compileRaw('Hello {name | Mention}!')
90168

91169
function App() {
92-
const message = 'Hello {name | Mention}'
93-
return <div>{formatRaw(message, {name: 'john'}, transformers)}</div>
94-
} // => "<div>Hello <a href="...">john</a></div>"
170+
return <div>{msg({name: 'john'})}</div>
171+
} // => "<div>Hello <a href="...">john</a>!</div>"
95172
```
96173

174+
Since we used `compileRaw`, library would output something like this: `['Hello ', [ReactElement], '!']`.
175+
176+
This will work with any kind of framework or custom library.
177+
97178
# Motivation
98179
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.
99180

100-
One immediate flat that messagepipe solves is ability to select nested properties.
181+
One immediate flaw that messagepipe solves is ability to select nested values.
101182

102183
## @TODO:
103184

104185
- [ ] Write up spec
105-
- [x] General idea
106-
- `Hello {name}!` => `"Hello John!"`
107-
- [x] Selecting shallow values
108-
- `{name}` => `"John"`
109-
- [x] Selecting nested values
110-
- `{agent.name}` => `"John"`
111-
- [x] Selecting single value from array
112-
- `{agent.0.name}` => `"John"`
113-
- [x] Selecting multiple values from array
114-
- `{agent.*.name}` => `["John", "Lars"]`
115-
- [x] Using transformers
116-
- `{name | uppercase}` => `uppercase("John")`
117-
- [x] Using chained transformers
118-
- `{name | reverse | uppercase}` => `uppercase(reverse("John"))`
119-
- [x] Passing variables to transformer
120-
- `{name | json, space:2}` => `json("John", {space:2})`
121-
- [x] Passing variables to transformer
122-
- `{name | remove, text:"name", caseSensitive:true}` => `remove("John", {text:"name", caseSensitive:true})`
123-
- [ ] Escape `{` & `}`
124-
- `Escaped: \\{ \\}` => `"Escaped: { }"`
125-
- [ ] Using selector inside selector
126-
- `{agent.{index}.name}` => `"Lars"`
127-
186+
- [x] General idea
187+
- `Hello {name}!` => `"Hello John!"`
188+
- [x] Selecting shallow values
189+
- `{name}` => `"John"`
190+
- [x] Selecting nested values
191+
- `{agent.name}` => `"John"`
192+
- [x] Selecting single value from array
193+
- `{agent.0.name}` => `"John"`
194+
- [x] Selecting multiple values from array
195+
- `{agent.*.name}` => `["John", "Lars"]`
196+
- [x] Using transformers
197+
- `{name | uppercase}` => `uppercase("John")`
198+
- [x] Using chained transformers
199+
- `{name | reverse | uppercase}` => `uppercase(reverse("John"))`
200+
- [x] Passing parameters to transformer
201+
- `{name | json, space:2}` => `json("John", {space:2})`
202+
- `{name | remove, text:"name", caseSensitive:true}` => `remove("John", {text:"name", caseSensitive:true})`
203+
- [ ] Escaping `"`, "|", `,` and `:` in argument names
204+
- `{name | select, "a\"2":2}`
205+
- `{name | select, "a:2":2}`
206+
- `{name | select, "a|2":2}`
207+
- `{name | select, "a,2":2}`
208+
- [ ] Escape `{` & `}`
209+
- `Escaped: \\{ \\}` => `"Escaped: { }"`
210+
- [x] Using selector inside selector
211+
- Nested selector in selector can only be used to define path of the selector
212+
- `{agent.{index}.name}` = `{agent[2].name}` => `"Lars"`
213+
- `{agent.{index}}` = `{agent[2]}` => `{"name":"Lars"}`
214+
- `{{index}}` = `{2}` => `undefined`
215+
- [x] Using selector inside pipes
216+
- Nested selector in pipes can only be used to construct argument values
217+
- `{name | json, space:{space}}` => `json("John", {space:2})`
128218

129219
# License
130220
[MIT](LICENCE) &copy; [Marcis Bergmanis](https://twitter.com/marcisbee)

dev.ts

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,24 @@
1-
import { format } from './src'
1+
import { MessagePipe } from './src'
22

3-
const transformers = {
3+
const mp1 = MessagePipe({
44
reverse(value) {
55
return value.split('').reverse().join('')
66
},
77
capitalize(value) {
88
return value[0].toUpperCase() + value.slice(1).toLowerCase()
99
},
10-
}
10+
})
1111

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

14-
const transformers2 = {
14+
const mp2 = MessagePipe({
1515
remove(value, { text, replacer = '' }) {
1616
if (!text) {
1717
return '';
1818
}
1919

2020
return value.replace(text, replacer);
2121
},
22-
}
22+
})
2323

24-
console.log(format('Hello {planet | remove, text:"a", replacer: "4"}!', { planet: 'Mars' }, transformers2))
24+
console.log(mp2.compile('Hello {planet | remove, "text":"a", replacer: "4"}!')({ planet: 'Mars' }))

package-lock.json

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "messagepipe",
3-
"version": "0.1.1",
3+
"version": "0.2.0",
44
"description": "Formats message strings with number, date, plural, and select placeholders to create localized messages",
55
"main": "dist/index.js",
66
"module": "dist/index.esm.js",

src/index.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,2 @@
1-
export { getGlobalTransformers } from './transformers';
2-
export { format, formatRaw } from './messagepipe';
1+
export { defaultTransformers, intlTransformers } from './transformers';
2+
export { MessagePipe } from './messagepipe';

0 commit comments

Comments
 (0)