@doist/react-interpolate 0.0.2 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/.eslintrc.js CHANGED
@@ -3,7 +3,6 @@ const config = {
3
3
  "eslint:recommended",
4
4
  "plugin:react/recommended",
5
5
  "prettier",
6
- "prettier/react",
7
6
  "plugin:compat/recommended"
8
7
  ],
9
8
  env: {
@@ -18,7 +17,7 @@ const config = {
18
17
  sourceType: "module"
19
18
  },
20
19
  plugins: ["react", "prettier", "react-hooks", "jest"],
21
- parser: "babel-eslint",
20
+ parser: "@babel/eslint-parser",
22
21
  rules: {
23
22
  semi: ["error", "never"],
24
23
  "arrow-parens": ["error", "as-needed"],
@@ -1,17 +1,9 @@
1
- # This workflow will do a clean install of node dependencies, build the source code and run tests across different versions of node
2
- # For more information see: https://help.github.com/actions/language-and-framework-guides/using-nodejs-with-github-actions
3
-
4
1
  name: CI
5
2
 
6
- on:
7
- push:
8
- branches: [ master ]
9
- pull_request:
10
- branches: [ master ]
3
+ on: [push, pull_request]
11
4
 
12
5
  jobs:
13
6
  build:
14
-
15
7
  runs-on: ubuntu-latest
16
8
 
17
9
  strategy:
package/README.md CHANGED
@@ -10,13 +10,15 @@ A string interpolation component that formats and interpolates a template string
10
10
  import Interpolate from "@doist/react-interpolate"
11
11
 
12
12
  function Greeting() {
13
- return <Interpolate
14
- string="<h1>Hello {name}. Here is <a>your order info</a></h1>"
15
- mapping={
16
- a={child => <a href="https://orderinfo.com">{child}</a>)
17
- name="William"
18
- }
19
- />
13
+ return (
14
+ <Interpolate
15
+ string="<h1>Hello {name}. Here is <a>your order info</a></h1>"
16
+ mapping={{
17
+ name: "William",
18
+ a: <a href="https://orderinfo.com" />
19
+ }}
20
+ />
21
+ )
20
22
  }
21
23
  ```
22
24
 
@@ -39,20 +41,26 @@ Please see the [Interpolation syntax](./#interpolation-syntax) section below for
39
41
  #### `mapping`
40
42
  An object that defines the values to be injected for placeholder and tags defined in the template string. Optional.
41
43
 
42
- - You can map placeholder and self-closing tag to any [valid element value](https://reactjs.org/docs/react-api.html#isvalidelement)
43
- - For open & close tag, you need to supply a renderer function that defines how the the enclosed children should be rendered.
44
+ - For placeholder or self-closing tag, the mapping value could be any valid element value
45
+ - For open & close tag, the mapping value could be either renderer function or an element.
44
46
 
45
47
  ```jsx
46
48
  <Interpolate
47
- string="Hello {name}. Here is <a>your order info</a><hr/>"
48
- mapping={
49
- // you can map placholder and self-closing tag to any valid element value
50
- name="William"
51
- hr={<hr className="break"/>}
52
-
53
- // mapping value for open & close tag must be a function
54
- a={children => <a href="https://orderinfo.com">{children}</a>)
55
- }
49
+ string="Hello {name}. Here is <orderLink>your order info</orderLink><hr/>. \
50
+ Please contact <supportLink>support</supportLink> for help"
51
+ mapping={{
52
+ // you can map placholder and self-closing tag to any valid element value
53
+ name: "William",
54
+ hr: <hr className="break" />,
55
+
56
+ // you can map open & close tag to a rendering function
57
+ orderLink: text => (
58
+ <a href="https://orderinfo.com">{text}</a>
59
+ ),
60
+
61
+ // or you can map open & close tag to an element
62
+ supportLink: <a href="https://orderinfo.com" />
63
+ }}
56
64
  />
57
65
  ```
58
66
 
@@ -71,6 +79,10 @@ Optional. `true` by default.
71
79
  />
72
80
  ```
73
81
 
82
+ #### `syntax`
83
+
84
+ Optional. `syntax` props allow use of react-Interpolate with different string formatting syntax. Please see the ["Custom syntax support"](#custom-syntax-support) section for more detail.
85
+
74
86
 
75
87
  ## Interpolation syntax
76
88
 
@@ -96,17 +108,51 @@ Placeholder name should be alphanumeric (`[A-Za-z0-9_]`). Placeholders could be
96
108
  "Here is <a><b>you order info {name}</b></a>"
97
109
  ```
98
110
 
99
- Tag name should be alphanumeric (`[A-Za-z0-9_]`). Open & close tag could only be mapped to a renderer function.
111
+ Tag name should be alphanumeric (`[A-Za-z0-9_]`).
112
+
113
+ Open & close tag could be mapped to an element value.
114
+
115
+ ```jsx
116
+ <Interpolate
117
+ string="Here is <a>your order info</a>"
118
+ mapping={{
119
+ a: <a href="https://orderinfo.com" />
120
+ }}
121
+ />
122
+
123
+
124
+ // Invalid; the mapping value element should not contain children
125
+ <Interpolate
126
+ string="Here is <a>your order info</a>"
127
+ mapping={{
128
+ a: (
129
+ <a href="https://orderinfo.com">
130
+ <b />
131
+ <br />
132
+ </a>
133
+ )
134
+ }}
135
+ />
136
+ ```
137
+
138
+ Open & close tag could be mapped to a rendering function, which would take a single argument that contains the enclosing text.
100
139
 
101
140
  ```jsx
102
141
  <Interpolate
103
- string="Here is <a>your order info</a>"
104
- mapping={
105
- a={children => <a href="https://orderinfo.com">{children}</a>)
106
- }
142
+ string="Here is <a>your order info</a>"
143
+ mapping={{
144
+ a: text => (
145
+ <a href="https://orderinfo.com">
146
+ <b>{text}</b>
147
+ <br />
148
+ </a>
149
+ )
150
+ }}
107
151
  />
108
152
  ```
109
153
 
154
+
155
+
110
156
  Unclosed tag or incorrect nesting of tag would result in syntax error.
111
157
 
112
158
  ```js
@@ -134,3 +180,48 @@ When tags are used the string but there are no correponding mapped value, it wou
134
180
  // would render: <h1>Hellow</h1><br/>World
135
181
  <Interpolate string="<h1>Hello</h1><br/>world"/>
136
182
  ```
183
+
184
+
185
+
186
+ ## Custom syntax support
187
+
188
+ You may already be using a formatting syntax in your string that is different than the built-in syntax support from Interpolate. You can configure Interpolate so that it could recognize the formatting syntax that you use.
189
+
190
+ For instance, you may be using [i18next](https://www.i18next.com/) which has a slightly different placeholder syntax.
191
+ ```
192
+ hello {{name}}
193
+ ```
194
+
195
+ You can define the formatting syntax of your string via `syntax` props.
196
+
197
+ ```jsx
198
+ import Interpolate, { TOKEN_PLACEHOLDER } from "react-interpolate"
199
+
200
+ const i18nNextSyntax = [
201
+ {
202
+ type: TOKEN_PLACEHOLDER,
203
+ regex: /{{\s*(\w+)\s*}}/g
204
+ }
205
+ ]
206
+
207
+ // will output "hi steven"
208
+ <Interpolate
209
+ syntax={i18nNextSyntax}
210
+ string="hi {{name}}"
211
+ mapping={{
212
+ name: "steven"
213
+ }}
214
+ />
215
+ ```
216
+
217
+ react-interpolate comes with i18next syntax support, and you can enable it via
218
+
219
+ ```jsx
220
+ import { SYNTAX_I18NEXT } from "react-interpolate"
221
+
222
+ <Interpolate
223
+ syntax={SYNTAX_I18NEXT}
224
+ ...
225
+ />
226
+ ```
227
+
@@ -1,7 +1,7 @@
1
1
  /* eslint-disable react/display-name */
2
2
  import React from "react"
3
3
  import { render } from "@testing-library/react"
4
- import Interpolate from "../src/interpolate"
4
+ import Interpolate, { SYNTAX_I18NEXT } from "../src"
5
5
 
6
6
  Interpolate.defaultProps = {
7
7
  graceful: false
@@ -19,10 +19,8 @@ const surpressConsole = () => {
19
19
 
20
20
  describe("Interpolate", () => {
21
21
  function renderTest({ expected, ...props }) {
22
- return () => {
23
- const { container } = render(<Interpolate {...props} />)
24
- expect(container.innerHTML).toEqual(expected)
25
- }
22
+ const { container } = render(<Interpolate {...props} />)
23
+ expect(container.innerHTML).toEqual(expected)
26
24
  }
27
25
 
28
26
  test("when no mapping is provide", () => {
@@ -36,8 +34,7 @@ describe("Interpolate", () => {
36
34
  restore()
37
35
  })
38
36
 
39
- test(
40
- "tag mapping",
37
+ test("tag mapping", () =>
41
38
  renderTest({
42
39
  string: "<h1>hello <b>steven</b></h1>. welcome to todoist",
43
40
  mapping: {
@@ -45,11 +42,19 @@ describe("Interpolate", () => {
45
42
  h1: child => <h2>{child}</h2>
46
43
  },
47
44
  expected: "<h2>hello <i>steven</i></h2>. welcome to todoist"
48
- })
49
- )
45
+ }))
50
46
 
51
- test(
52
- "placholder mapping",
47
+ test("tag mapping: should accept element directly", () =>
48
+ renderTest({
49
+ string: "<h1>hello <b>steven</b></h1>. welcome to todoist",
50
+ mapping: {
51
+ b: <i />,
52
+ h1: <h2 />
53
+ },
54
+ expected: "<h2>hello <i>steven</i></h2>. welcome to todoist"
55
+ }))
56
+
57
+ test("placholder mapping", () =>
53
58
  renderTest({
54
59
  string: "{greeting} <b>{name}</b>. welcome to todoist",
55
60
  mapping: {
@@ -57,22 +62,18 @@ describe("Interpolate", () => {
57
62
  name: () => <i>steven</i>
58
63
  },
59
64
  expected: "hi <b><i>steven</i></b>. welcome to todoist"
60
- })
61
- )
65
+ }))
62
66
 
63
- test(
64
- "void tag mapping",
67
+ test("void tag mapping", () =>
65
68
  renderTest({
66
69
  string: "hello <br/>",
67
70
  mapping: {
68
71
  br: <hr />
69
72
  },
70
73
  expected: "hello <hr>"
71
- })
72
- )
74
+ }))
73
75
 
74
- test(
75
- "combination of mapping",
76
+ test("combination of mapping", () =>
76
77
  renderTest({
77
78
  string: "<h1>hello <b>{name}</b></h1>.<br/> welcome to todoist",
78
79
  mapping: {
@@ -82,8 +83,7 @@ describe("Interpolate", () => {
82
83
  br: <hr />
83
84
  },
84
85
  expected: "<h2>hello <i>steven</i></h2>.<hr> welcome to todoist"
85
- })
86
- )
86
+ }))
87
87
 
88
88
  test("combination of mapping with function component", () => {
89
89
  // eslint-disable-next-line
@@ -104,8 +104,7 @@ describe("Interpolate", () => {
104
104
  })
105
105
  })
106
106
 
107
- test(
108
- "spacing in the void tag and placeholder should be allowed",
107
+ test("spacing in the void tag and placeholder should be allowed", () =>
109
108
  renderTest({
110
109
  string: "hello { name }<br /> welcome to todoist",
111
110
  mapping: {
@@ -113,8 +112,7 @@ describe("Interpolate", () => {
113
112
  br: <hr />
114
113
  },
115
114
  expected: "hello steven<hr> welcome to todoist"
116
- })
117
- )
115
+ }))
118
116
 
119
117
  test("the mapping value should be interpolate corrected with proper html escape", () => {
120
118
  renderTest({
@@ -125,7 +123,7 @@ describe("Interpolate", () => {
125
123
  },
126
124
  expected:
127
125
  "hello &lt;script&gt;window.xss = 1&lt;/script&gt;&lt;script&gt;window.xss = 1&lt;/script&gt; welcome to todoist"
128
- })()
126
+ })
129
127
 
130
128
  expect(window.css).toBeUndefined()
131
129
  })
@@ -137,13 +135,28 @@ describe("Interpolate", () => {
137
135
  string: "</h1>",
138
136
  expected: "&lt;/h1&gt;",
139
137
  graceful: true
140
- })()
138
+ })
141
139
 
142
140
  restore()
143
141
  })
142
+
143
+ test("using SYNTAX_I18NEXT", () => {
144
+ renderTest({
145
+ syntax: SYNTAX_I18NEXT,
146
+ string: "<0>hello <b>{{name}}</b></0>.<br/> welcome to todoist",
147
+ mapping: {
148
+ 0: child => <h2 className="subheader">{child}</h2>,
149
+ b: child => <i>{child}</i>,
150
+ name: "steven",
151
+ br: <hr />
152
+ },
153
+ expected:
154
+ '<h2 class="subheader">hello <i>steven</i></h2>.<hr> welcome to todoist'
155
+ })
156
+ })
144
157
  })
145
158
 
146
- describe("Interpolate: error", () => {
159
+ describe("Interpolate: error cases", () => {
147
160
  let restore
148
161
  beforeAll(() => {
149
162
  restore = surpressConsole()
@@ -152,19 +165,39 @@ describe("Interpolate: error", () => {
152
165
  restore()
153
166
  })
154
167
 
155
- function renderTest({ props }) {
168
+ function renderTest({ expectedError, ...props }) {
156
169
  return () => {
157
- expect(() => render(<Interpolate {...props} />)).toThrow()
170
+ expect(() => render(<Interpolate {...props} />)).toThrow(
171
+ expectedError
172
+ )
158
173
  }
159
174
  }
160
175
 
161
- test("non-closing tag", renderTest({ string: "<b>" }))
176
+ test(
177
+ "non-closing tag",
178
+ renderTest({
179
+ string: "<b>",
180
+ expectedError:
181
+ "Syntax error. Please check if each open tag is closed correctly"
182
+ })
183
+ )
162
184
 
163
185
  test(
164
186
  "mapping value for tag should always be a function",
165
187
  renderTest({
166
188
  string: "<h1>hello</h1>. welcome to todoist",
167
- mapping: { h1: "hi" }
189
+ mapping: { h1: "hi" },
190
+ expectedError: "Invalid mapping value for"
191
+ })
192
+ )
193
+
194
+ test(
195
+ "when passing element as value but the element contains children, error should be thrown",
196
+ renderTest({
197
+ string: "<h1>hello</h1>. welcome to todoist",
198
+ mapping: { h1: <h1>hi</h1> },
199
+ expectedError:
200
+ "when passing an element as value, the element should not contains children"
168
201
  })
169
202
  )
170
203
  })
@@ -37,7 +37,9 @@ describe("parser: error handling", () => {
37
37
  test(`invalid syntax: ${string}`, () => {
38
38
  expect(() => {
39
39
  parser(string)
40
- }).toThrow()
40
+ }).toThrow(
41
+ "Syntax error. Please check if each open tag is closed correctly"
42
+ )
41
43
  })
42
44
  })
43
45
  })
@@ -1,13 +1,20 @@
1
1
  'use strict';
2
2
 
3
- function _interopDefault (ex) { return (ex && (typeof ex === 'object') && 'default' in ex) ? ex['default'] : ex; }
3
+ Object.defineProperty(exports, '__esModule', { value: true });
4
4
 
5
- var React = _interopDefault(require('react'));
6
- var PropTypes = _interopDefault(require('prop-types'));
7
- var _classCallCheck = _interopDefault(require('@babel/runtime/helpers/classCallCheck'));
8
- var _createClass = _interopDefault(require('@babel/runtime/helpers/createClass'));
9
- var _objectWithoutProperties = _interopDefault(require('@babel/runtime/helpers/objectWithoutProperties'));
10
- var _defineProperty = _interopDefault(require('@babel/runtime/helpers/defineProperty'));
5
+ var React = require('react');
6
+ var PropTypes = require('prop-types');
7
+ var _classCallCheck = require('@babel/runtime/helpers/classCallCheck');
8
+ var _createClass = require('@babel/runtime/helpers/createClass');
9
+ var _objectWithoutProperties = require('@babel/runtime/helpers/objectWithoutProperties');
10
+
11
+ function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; }
12
+
13
+ var React__default = /*#__PURE__*/_interopDefaultLegacy(React);
14
+ var PropTypes__default = /*#__PURE__*/_interopDefaultLegacy(PropTypes);
15
+ var _classCallCheck__default = /*#__PURE__*/_interopDefaultLegacy(_classCallCheck);
16
+ var _createClass__default = /*#__PURE__*/_interopDefaultLegacy(_createClass);
17
+ var _objectWithoutProperties__default = /*#__PURE__*/_interopDefaultLegacy(_objectWithoutProperties);
11
18
 
12
19
  var TOKEN_PLACEHOLDER = "TOKEN_PLACEHOLDER";
13
20
  var TOKEN_OPEN_TAG = "TOKEN_OPEN_TAG";
@@ -20,13 +27,15 @@ var NODE_VOID_ELEMENT = "NODE_VOID_ELEMENT";
20
27
  var NODE_PLACEHOLDER = "NODE_PLACEHOLDER";
21
28
  var NODE_TEXT = "NODE_TEXT";
22
29
 
30
+ var _excluded = ["type", "children"];
31
+
23
32
  var Node = /*#__PURE__*/function () {
24
33
  function Node(_ref) {
25
34
  var type = _ref.type,
26
35
  children = _ref.children,
27
- fields = _objectWithoutProperties(_ref, ["type", "children"]);
36
+ fields = _objectWithoutProperties__default["default"](_ref, _excluded);
28
37
 
29
- _classCallCheck(this, Node);
38
+ _classCallCheck__default["default"](this, Node);
30
39
 
31
40
  this.type = type;
32
41
  this.children = children || [];
@@ -40,7 +49,7 @@ var Node = /*#__PURE__*/function () {
40
49
  }
41
50
  }
42
51
 
43
- _createClass(Node, [{
52
+ _createClass__default["default"](Node, [{
44
53
  key: "appendChild",
45
54
  value: function appendChild(child) {
46
55
  this.children.push(child);
@@ -95,29 +104,69 @@ Node.createPlaceholderNode = function (token) {
95
104
  });
96
105
  };
97
106
 
107
+ var SYNTAX_BUILT_IN = [{
108
+ type: TOKEN_PLACEHOLDER,
109
+ regex: /{\s*(\w+)\s*}/g
110
+ }, {
111
+ type: TOKEN_OPEN_TAG,
112
+ regex: /<(\w+)>/g
113
+ }, {
114
+ type: TOKEN_CLOSE_TAG,
115
+ regex: /<\/(\w+)>/g
116
+ }, {
117
+ type: TOKEN_SELF_TAG,
118
+ regex: /<(\w+)\s*\/>/g
119
+ }];
120
+ var SYNTAX_I18NEXT = [{
121
+ type: TOKEN_PLACEHOLDER,
122
+ regex: /{{\s*(\w+)\s*}}/g
123
+ }, {
124
+ type: TOKEN_OPEN_TAG,
125
+ regex: /<(\w+)>/g
126
+ }, {
127
+ type: TOKEN_CLOSE_TAG,
128
+ regex: /<\/(\w+)>/g
129
+ }, {
130
+ type: TOKEN_SELF_TAG,
131
+ regex: /<(\w+)\s*\/>/g
132
+ }];
133
+
134
+ function _createForOfIteratorHelper(o, allowArrayLike) { var it = typeof Symbol !== "undefined" && o[Symbol.iterator] || o["@@iterator"]; if (!it) { if (Array.isArray(o) || (it = _unsupportedIterableToArray(o)) || allowArrayLike && o && typeof o.length === "number") { if (it) o = it; var i = 0; var F = function F() {}; return { s: F, n: function n() { if (i >= o.length) return { done: true }; return { done: false, value: o[i++] }; }, e: function e(_e) { throw _e; }, f: F }; } throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); } var normalCompletion = true, didErr = false, err; return { s: function s() { it = it.call(o); }, n: function n() { var step = it.next(); normalCompletion = step.done; return step; }, e: function e(_e2) { didErr = true; err = _e2; }, f: function f() { try { if (!normalCompletion && it.return != null) it.return(); } finally { if (didErr) throw err; } } }; }
135
+
136
+ function _unsupportedIterableToArray(o, minLen) { if (!o) return; if (typeof o === "string") return _arrayLikeToArray(o, minLen); var n = Object.prototype.toString.call(o).slice(8, -1); if (n === "Object" && o.constructor) n = o.constructor.name; if (n === "Map" || n === "Set") return Array.from(o); if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _arrayLikeToArray(o, minLen); }
137
+
138
+ function _arrayLikeToArray(arr, len) { if (len == null || len > arr.length) len = arr.length; for (var i = 0, arr2 = new Array(len); i < len; i++) { arr2[i] = arr[i]; } return arr2; }
98
139
  /*
99
140
  * return a array of token object
100
141
  */
101
142
 
102
- function lexer(string) {
103
- var _regxMap;
104
-
105
- var regxMap = (_regxMap = {}, _defineProperty(_regxMap, TOKEN_PLACEHOLDER, /{\s*(\w+)\s*}/g), _defineProperty(_regxMap, TOKEN_OPEN_TAG, /<(\w+)>/g), _defineProperty(_regxMap, TOKEN_CLOSE_TAG, /<\/(\w+)>/g), _defineProperty(_regxMap, TOKEN_SELF_TAG, /<(\w+)\s*\/>/g), _regxMap);
143
+ function lexer(string, syntax) {
106
144
  var matches = [];
107
145
 
108
- for (var tokenType in regxMap) {
109
- var regx = regxMap[tokenType];
110
- var res;
111
-
112
- while ((res = regx.exec(string)) !== null) {
113
- matches.push({
114
- type: tokenType,
115
- string: res[0],
116
- name: res[1],
117
- start: res.index,
118
- end: res.index + res[0].length
119
- });
146
+ var _iterator = _createForOfIteratorHelper(syntax),
147
+ _step;
148
+
149
+ try {
150
+ for (_iterator.s(); !(_step = _iterator.n()).done;) {
151
+ var rule = _step.value;
152
+ var type = rule.type,
153
+ regex = rule.regex;
154
+ var res;
155
+
156
+ while ((res = regex.exec(string)) !== null) {
157
+ matches.push({
158
+ type: type,
159
+ string: res[0],
160
+ name: res[1],
161
+ start: res.index,
162
+ end: res.index + res[0].length
163
+ });
164
+ }
120
165
  }
166
+ } catch (err) {
167
+ _iterator.e(err);
168
+ } finally {
169
+ _iterator.f();
121
170
  }
122
171
 
123
172
  matches.sort(function (a, b) {
@@ -159,12 +208,16 @@ function lexer(string) {
159
208
  return tokens;
160
209
  }
161
210
 
162
- function parser(string) {
163
- var tokens = lexer(string);
211
+ function parser(string, syntax) {
212
+ if (!syntax) {
213
+ syntax = SYNTAX_BUILT_IN;
214
+ }
215
+
216
+ var tokens = lexer(string, syntax);
164
217
  var p = new Parser(tokens);
165
218
  return p.parse();
166
219
  }
167
- var SYNTAX_ERROR = "syntax error"; // A special token representing end of the tream
220
+ var SYNTAX_ERROR = "Syntax error. Please check if each open tag is closed correctly"; // A special token representing end of the tream
168
221
 
169
222
  var EPSILON = {
170
223
  type: "EPSILON"
@@ -198,13 +251,13 @@ var EPSILON = {
198
251
 
199
252
  var Parser = /*#__PURE__*/function () {
200
253
  function Parser(tokens) {
201
- _classCallCheck(this, Parser);
254
+ _classCallCheck__default["default"](this, Parser);
202
255
 
203
256
  this.tokens = [].concat(tokens);
204
257
  this.tags = [];
205
258
  }
206
259
 
207
- _createClass(Parser, [{
260
+ _createClass__default["default"](Parser, [{
208
261
  key: "parse",
209
262
  value: function parse() {
210
263
  var tree = this.document();
@@ -304,6 +357,11 @@ var Parser = /*#__PURE__*/function () {
304
357
 
305
358
  this.match(TOKEN_CLOSE_TAG);
306
359
  }
360
+ }, {
361
+ key: "lookahead",
362
+ get: function get() {
363
+ return this.tokens.length === 0 ? EPSILON : this.tokens[0];
364
+ }
307
365
  }, {
308
366
  key: "predict",
309
367
  value: function predict() {
@@ -328,11 +386,6 @@ var Parser = /*#__PURE__*/function () {
328
386
  value: function pushTag(token) {
329
387
  this.tags.push(token);
330
388
  }
331
- }, {
332
- key: "lookahead",
333
- get: function get() {
334
- return this.tokens.length === 0 ? EPSILON : this.tokens[0];
335
- }
336
389
  }]);
337
390
 
338
391
  return Parser;
@@ -340,7 +393,7 @@ var Parser = /*#__PURE__*/function () {
340
393
 
341
394
  var createElement = function createElement(node, mapping, keyPrefix) {
342
395
  var children = node.children.map(function (c, i) {
343
- return /*#__PURE__*/React.createElement(React.Fragment, {
396
+ return /*#__PURE__*/React__default["default"].createElement(React__default["default"].Fragment, {
344
397
  key: keyPrefix + i
345
398
  }, createElement(c, mapping, keyPrefix));
346
399
  });
@@ -353,7 +406,7 @@ var createElement = function createElement(node, mapping, keyPrefix) {
353
406
 
354
407
  case NODE_FRAGMENT:
355
408
  {
356
- return React.createElement(React.Fragment, null, children);
409
+ return /*#__PURE__*/React__default["default"].createElement(React__default["default"].Fragment, null, children);
357
410
  }
358
411
 
359
412
  case NODE_VOID_ELEMENT:
@@ -364,22 +417,32 @@ var createElement = function createElement(node, mapping, keyPrefix) {
364
417
  return val();
365
418
  }
366
419
 
367
- return val || React.createElement(node.name, null);
420
+ return val || /*#__PURE__*/React__default["default"].createElement(node.name, null);
368
421
  }
369
422
 
370
423
  case NODE_TAG_ELEMENT:
371
424
  {
372
425
  var _val = mapping[node.name];
373
426
 
427
+ if (_val === undefined) {
428
+ return /*#__PURE__*/React__default["default"].createElement(node.name, null, children);
429
+ }
430
+
374
431
  if (typeof _val === "function") {
375
432
  return _val(children);
376
433
  }
377
434
 
378
- if (_val !== undefined) {
379
- throw new Error("tag \"".concat(node.name, "\" should be mapped to a renderer function"));
435
+ if ( /*#__PURE__*/React__default["default"].isValidElement(_val)) {
436
+ if (React__default["default"].Children.count(_val.props.children) !== 0) {
437
+ throw new Error("when passing an element as value, the element should not contains children");
438
+ }
439
+
440
+ return /*#__PURE__*/React__default["default"].cloneElement(_val, {
441
+ children: children
442
+ });
380
443
  }
381
444
 
382
- return React.createElement(node.name, null, children);
445
+ throw new Error("Invalid mapping value for \"".concat(node.name, "\". Only element or render function are accepted"));
383
446
  }
384
447
 
385
448
  case NODE_PLACEHOLDER:
@@ -405,13 +468,14 @@ var createElement = function createElement(node, mapping, keyPrefix) {
405
468
 
406
469
  function Interpolate(_ref) {
407
470
  var string = _ref.string,
471
+ syntax = _ref.syntax,
408
472
  _ref$mapping = _ref.mapping,
409
473
  mapping = _ref$mapping === void 0 ? {} : _ref$mapping,
410
474
  _ref$graceful = _ref.graceful,
411
475
  graceful = _ref$graceful === void 0 ? true : _ref$graceful;
412
476
 
413
477
  try {
414
- var tree = parser(string);
478
+ var tree = parser(string, syntax);
415
479
  return createElement(tree, mapping, string);
416
480
  } catch (e) {
417
481
  if (graceful) {
@@ -423,9 +487,15 @@ function Interpolate(_ref) {
423
487
  }
424
488
  }
425
489
  Interpolate.propTypes = {
426
- string: PropTypes.string.isRequired,
427
- mapping: PropTypes.object,
428
- graceful: PropTypes.bool
490
+ string: PropTypes__default["default"].string.isRequired,
491
+ mapping: PropTypes__default["default"].object,
492
+ graceful: PropTypes__default["default"].bool
429
493
  };
430
494
 
431
- module.exports = Interpolate;
495
+ exports.SYNTAX_BUILT_IN = SYNTAX_BUILT_IN;
496
+ exports.SYNTAX_I18NEXT = SYNTAX_I18NEXT;
497
+ exports.TOKEN_CLOSE_TAG = TOKEN_CLOSE_TAG;
498
+ exports.TOKEN_OPEN_TAG = TOKEN_OPEN_TAG;
499
+ exports.TOKEN_PLACEHOLDER = TOKEN_PLACEHOLDER;
500
+ exports.TOKEN_SELF_TAG = TOKEN_SELF_TAG;
501
+ exports["default"] = Interpolate;
@@ -1 +1 @@
1
- "use strict";function _interopDefault(e){return e&&"object"==typeof e&&"default"in e?e.default:e}var React=_interopDefault(require("react")),PropTypes=_interopDefault(require("prop-types")),_classCallCheck=_interopDefault(require("@babel/runtime/helpers/classCallCheck")),_createClass=_interopDefault(require("@babel/runtime/helpers/createClass")),_objectWithoutProperties=_interopDefault(require("@babel/runtime/helpers/objectWithoutProperties")),_defineProperty=_interopDefault(require("@babel/runtime/helpers/defineProperty")),TOKEN_PLACEHOLDER="TOKEN_PLACEHOLDER",TOKEN_OPEN_TAG="TOKEN_OPEN_TAG",TOKEN_CLOSE_TAG="TOKEN_CLOSE_TAG",TOKEN_SELF_TAG="TOKEN_SELF_TAG",TOKEN_TEXT="TOKEN_TEXT",NODE_FRAGMENT="NODE_FRAGMENT",NODE_TAG_ELEMENT="NODE_TAG_ELEMENT",NODE_VOID_ELEMENT="NODE_VOID_ELEMENT",NODE_PLACEHOLDER="NODE_PLACEHOLDER",NODE_TEXT="NODE_TEXT",Node=function(){function e(t){var r=t.type,n=t.children,a=_objectWithoutProperties(t,["type","children"]);for(var o in _classCallCheck(this,e),this.type=r,this.children=n||[],a)this[o]=a[o];a.token&&(this.string=a.token.string)}return _createClass(e,[{key:"appendChild",value:function(e){this.children.push(e)}},{key:"isLeaf",get:function(){return 0===this.children.length}}]),e}();function lexer(e){var t,r=(_defineProperty(t={},TOKEN_PLACEHOLDER,/{\s*(\w+)\s*}/g),_defineProperty(t,TOKEN_OPEN_TAG,/<(\w+)>/g),_defineProperty(t,TOKEN_CLOSE_TAG,/<\/(\w+)>/g),_defineProperty(t,TOKEN_SELF_TAG,/<(\w+)\s*\/>/g),t),n=[];for(var a in r)for(var o,i=r[a];null!==(o=i.exec(e));)n.push({type:a,string:o[0],name:o[1],start:o.index,end:o.index+o[0].length});n.sort(function(e,t){return e.start-t.start});var E=[],s=0;n.forEach(function(t){if(t.start!==s){var r=t.start,n=e.substring(s,r);E.push({type:TOKEN_TEXT,string:n,text:n,start:s,end:r}),s=t.end}else s=t.end});var c=e.substring(s);return""!==c&&E.push({type:TOKEN_TEXT,string:c,start:s,end:e.length}),[].concat(E,n).sort(function(e,t){return e.start-t.start})}function parser(e){var t=lexer(e);return new Parser(t).parse()}Node.createTagNode=function(e,t){return new Node({type:NODE_TAG_ELEMENT,children:t,name:e.name,token:e})},Node.createFragmentNode=function(e){return new Node({type:NODE_FRAGMENT,children:e})},Node.createVoidNode=function(e){return new Node({type:NODE_VOID_ELEMENT,name:e.name,token:e})},Node.createTextNode=function(e){return new Node({type:NODE_TEXT,text:e.string,token:e})},Node.createPlaceholderNode=function(e){return new Node({type:NODE_PLACEHOLDER,name:e.name,token:e})};var SYNTAX_ERROR="syntax error",EPSILON={type:"EPSILON"},Parser=function(){function e(t){_classCallCheck(this,e),this.tokens=[].concat(t),this.tags=[]}return _createClass(e,[{key:"parse",value:function(){var e=this.document();if(!this.predict(EPSILON.type))throw new Error(SYNTAX_ERROR);return e}},{key:"document",value:function(){var e=this.elementOrData();return Node.createFragmentNode(e)}},{key:"element",value:function(){var e=this.openTag(),t=this.elementOrData();return this.endTag(),Node.createTagNode(e,t)}},{key:"openTag",value:function(){if(!this.predict(TOKEN_OPEN_TAG))throw new Error(SYNTAX_ERROR);return this.pushTag(this.lookahead),this.match(TOKEN_OPEN_TAG)}},{key:"elementOrData",value:function(){var e=[];if(this.predict(TOKEN_OPEN_TAG)&&(e.push(this.element()),e=e.concat(this.elementOrData())),this.predict(TOKEN_SELF_TAG,TOKEN_TEXT,TOKEN_PLACEHOLDER)){var t,r=this.match(this.lookahead.type);switch(r.type){case TOKEN_SELF_TAG:t=Node.createVoidNode(r);break;case TOKEN_TEXT:t=Node.createTextNode(r);break;case TOKEN_PLACEHOLDER:t=Node.createPlaceholderNode(r)}e.push(t),e=e.concat(this.elementOrData())}return e}},{key:"endTag",value:function(){if(!this.predict(TOKEN_CLOSE_TAG))throw new Error(SYNTAX_ERROR);if(this.tags.pop().name!==this.lookahead.name)throw new Error(SYNTAX_ERROR);this.match(TOKEN_CLOSE_TAG)}},{key:"predict",value:function(){for(var e=arguments.length,t=new Array(e),r=0;r<e;r++)t[r]=arguments[r];return t.includes(this.lookahead.type)}},{key:"match",value:function(e){if(!this.predict(e))throw new Error(SYNTAX_ERROR);return this.tokens.shift()}},{key:"pushTag",value:function(e){this.tags.push(e)}},{key:"lookahead",get:function(){return 0===this.tokens.length?EPSILON:this.tokens[0]}}]),e}(),createElement=function e(t,r,n){var a=t.children.map(function(t,a){return React.createElement(React.Fragment,{key:n+a},e(t,r,n))});switch(t.type){case NODE_TEXT:return t.text;case NODE_FRAGMENT:return React.createElement(React.Fragment,null,a);case NODE_VOID_ELEMENT:var o=r[t.name];return"function"==typeof o?o():o||React.createElement(t.name,null);case NODE_TAG_ELEMENT:var i=r[t.name];if("function"==typeof i)return i(a);if(void 0!==i)throw new Error('tag "'.concat(t.name,'" should be mapped to a renderer function'));return React.createElement(t.name,null,a);case NODE_PLACEHOLDER:var E=r[t.name];return void 0===E?(console.warn('missing "'.concat(t.name,'" in mapping')),t.string):"function"==typeof E?E():E;default:return}};function Interpolate(e){var t=e.string,r=e.mapping,n=void 0===r?{}:r,a=e.graceful,o=void 0===a||a;try{var i=parser(t);return createElement(i,n,t)}catch(e){if(o)return console.error(e),t;throw e}}Interpolate.propTypes={string:PropTypes.string.isRequired,mapping:PropTypes.object,graceful:PropTypes.bool},module.exports=Interpolate;
1
+ "use strict";Object.defineProperty(exports,"__esModule",{value:!0});var React=require("react"),PropTypes=require("prop-types"),_classCallCheck=require("@babel/runtime/helpers/classCallCheck"),_createClass=require("@babel/runtime/helpers/createClass"),_objectWithoutProperties=require("@babel/runtime/helpers/objectWithoutProperties");function _interopDefaultLegacy(e){return e&&"object"==typeof e&&"default"in e?e:{default:e}}var React__default=_interopDefaultLegacy(React),PropTypes__default=_interopDefaultLegacy(PropTypes),_classCallCheck__default=_interopDefaultLegacy(_classCallCheck),_createClass__default=_interopDefaultLegacy(_createClass),_objectWithoutProperties__default=_interopDefaultLegacy(_objectWithoutProperties),TOKEN_PLACEHOLDER="TOKEN_PLACEHOLDER",TOKEN_OPEN_TAG="TOKEN_OPEN_TAG",TOKEN_CLOSE_TAG="TOKEN_CLOSE_TAG",TOKEN_SELF_TAG="TOKEN_SELF_TAG",TOKEN_TEXT="TOKEN_TEXT",NODE_FRAGMENT="NODE_FRAGMENT",NODE_TAG_ELEMENT="NODE_TAG_ELEMENT",NODE_VOID_ELEMENT="NODE_VOID_ELEMENT",NODE_PLACEHOLDER="NODE_PLACEHOLDER",NODE_TEXT="NODE_TEXT",_excluded=["type","children"],Node=function(){function e(t){var r=t.type,n=t.children,a=_objectWithoutProperties__default.default(t,_excluded);for(var o in _classCallCheck__default.default(this,e),this.type=r,this.children=n||[],a)this[o]=a[o];a.token&&(this.string=a.token.string)}return _createClass__default.default(e,[{key:"appendChild",value:function(e){this.children.push(e)}},{key:"isLeaf",get:function(){return 0===this.children.length}}]),e}();Node.createTagNode=function(e,t){return new Node({type:NODE_TAG_ELEMENT,children:t,name:e.name,token:e})},Node.createFragmentNode=function(e){return new Node({type:NODE_FRAGMENT,children:e})},Node.createVoidNode=function(e){return new Node({type:NODE_VOID_ELEMENT,name:e.name,token:e})},Node.createTextNode=function(e){return new Node({type:NODE_TEXT,text:e.string,token:e})},Node.createPlaceholderNode=function(e){return new Node({type:NODE_PLACEHOLDER,name:e.name,token:e})};var SYNTAX_BUILT_IN=[{type:TOKEN_PLACEHOLDER,regex:/{\s*(\w+)\s*}/g},{type:TOKEN_OPEN_TAG,regex:/<(\w+)>/g},{type:TOKEN_CLOSE_TAG,regex:/<\/(\w+)>/g},{type:TOKEN_SELF_TAG,regex:/<(\w+)\s*\/>/g}],SYNTAX_I18NEXT=[{type:TOKEN_PLACEHOLDER,regex:/{{\s*(\w+)\s*}}/g},{type:TOKEN_OPEN_TAG,regex:/<(\w+)>/g},{type:TOKEN_CLOSE_TAG,regex:/<\/(\w+)>/g},{type:TOKEN_SELF_TAG,regex:/<(\w+)\s*\/>/g}];function _createForOfIteratorHelper(e,t){var r="undefined"!=typeof Symbol&&e[Symbol.iterator]||e["@@iterator"];if(!r){if(Array.isArray(e)||(r=_unsupportedIterableToArray(e))||t&&e&&"number"==typeof e.length){r&&(e=r);var n=0,a=function(){};return{s:a,n:function(){return n>=e.length?{done:!0}:{done:!1,value:e[n++]}},e:function(e){throw e},f:a}}throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.")}var o,i=!0,u=!1;return{s:function(){r=r.call(e)},n:function(){var e=r.next();return i=e.done,e},e:function(e){u=!0,o=e},f:function(){try{i||null==r.return||r.return()}finally{if(u)throw o}}}}function _unsupportedIterableToArray(e,t){if(e){if("string"==typeof e)return _arrayLikeToArray(e,t);var r=Object.prototype.toString.call(e).slice(8,-1);return"Object"===r&&e.constructor&&(r=e.constructor.name),"Map"===r||"Set"===r?Array.from(e):"Arguments"===r||/^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(r)?_arrayLikeToArray(e,t):void 0}}function _arrayLikeToArray(e,t){(null==t||t>e.length)&&(t=e.length);for(var r=0,n=new Array(t);r<t;r++)n[r]=e[r];return n}function lexer(e,t){var r,n=[],a=_createForOfIteratorHelper(t);try{for(a.s();!(r=a.n()).done;)for(var o,i=r.value,u=i.type,l=i.regex;null!==(o=l.exec(e));)n.push({type:u,string:o[0],name:o[1],start:o.index,end:o.index+o[0].length})}catch(e){a.e(e)}finally{a.f()}n.sort(function(e,t){return e.start-t.start});var c=[],s=0;n.forEach(function(t){if(t.start!==s){var r=t.start,n=e.substring(s,r);c.push({type:TOKEN_TEXT,string:n,text:n,start:s,end:r}),s=t.end}else s=t.end});var _=e.substring(s);return""!==_&&c.push({type:TOKEN_TEXT,string:_,start:s,end:e.length}),[].concat(c,n).sort(function(e,t){return e.start-t.start})}function parser(e,t){t||(t=SYNTAX_BUILT_IN);var r=lexer(e,t);return new Parser(r).parse()}var SYNTAX_ERROR="Syntax error. Please check if each open tag is closed correctly",EPSILON={type:"EPSILON"},Parser=function(){function e(t){_classCallCheck__default.default(this,e),this.tokens=[].concat(t),this.tags=[]}return _createClass__default.default(e,[{key:"parse",value:function(){var e=this.document();if(!this.predict(EPSILON.type))throw new Error(SYNTAX_ERROR);return e}},{key:"document",value:function(){var e=this.elementOrData();return Node.createFragmentNode(e)}},{key:"element",value:function(){var e=this.openTag(),t=this.elementOrData();return this.endTag(),Node.createTagNode(e,t)}},{key:"openTag",value:function(){if(!this.predict(TOKEN_OPEN_TAG))throw new Error(SYNTAX_ERROR);return this.pushTag(this.lookahead),this.match(TOKEN_OPEN_TAG)}},{key:"elementOrData",value:function(){var e=[];if(this.predict(TOKEN_OPEN_TAG)&&(e.push(this.element()),e=e.concat(this.elementOrData())),this.predict(TOKEN_SELF_TAG,TOKEN_TEXT,TOKEN_PLACEHOLDER)){var t,r=this.match(this.lookahead.type);switch(r.type){case TOKEN_SELF_TAG:t=Node.createVoidNode(r);break;case TOKEN_TEXT:t=Node.createTextNode(r);break;case TOKEN_PLACEHOLDER:t=Node.createPlaceholderNode(r)}e.push(t),e=e.concat(this.elementOrData())}return e}},{key:"endTag",value:function(){if(!this.predict(TOKEN_CLOSE_TAG))throw new Error(SYNTAX_ERROR);if(this.tags.pop().name!==this.lookahead.name)throw new Error(SYNTAX_ERROR);this.match(TOKEN_CLOSE_TAG)}},{key:"lookahead",get:function(){return 0===this.tokens.length?EPSILON:this.tokens[0]}},{key:"predict",value:function(){for(var e=arguments.length,t=new Array(e),r=0;r<e;r++)t[r]=arguments[r];return t.includes(this.lookahead.type)}},{key:"match",value:function(e){if(!this.predict(e))throw new Error(SYNTAX_ERROR);return this.tokens.shift()}},{key:"pushTag",value:function(e){this.tags.push(e)}}]),e}(),createElement=function e(t,r,n){var a=t.children.map(function(t,a){return React__default.default.createElement(React__default.default.Fragment,{key:n+a},e(t,r,n))});switch(t.type){case NODE_TEXT:return t.text;case NODE_FRAGMENT:return React__default.default.createElement(React__default.default.Fragment,null,a);case NODE_VOID_ELEMENT:var o=r[t.name];return"function"==typeof o?o():o||React__default.default.createElement(t.name,null);case NODE_TAG_ELEMENT:var i=r[t.name];if(void 0===i)return React__default.default.createElement(t.name,null,a);if("function"==typeof i)return i(a);if(React__default.default.isValidElement(i)){if(0!==React__default.default.Children.count(i.props.children))throw new Error("when passing an element as value, the element should not contains children");return React__default.default.cloneElement(i,{children:a})}throw new Error('Invalid mapping value for "'.concat(t.name,'". Only element or render function are accepted'));case NODE_PLACEHOLDER:var u=r[t.name];return void 0===u?(console.warn('missing "'.concat(t.name,'" in mapping')),t.string):"function"==typeof u?u():u;default:return}};function Interpolate(e){var t=e.string,r=e.syntax,n=e.mapping,a=void 0===n?{}:n,o=e.graceful,i=void 0===o||o;try{var u=parser(t,r);return createElement(u,a,t)}catch(e){if(i)return console.error(e),t;throw e}}Interpolate.propTypes={string:PropTypes__default.default.string.isRequired,mapping:PropTypes__default.default.object,graceful:PropTypes__default.default.bool},exports.SYNTAX_BUILT_IN=SYNTAX_BUILT_IN,exports.SYNTAX_I18NEXT=SYNTAX_I18NEXT,exports.TOKEN_CLOSE_TAG=TOKEN_CLOSE_TAG,exports.TOKEN_OPEN_TAG=TOKEN_OPEN_TAG,exports.TOKEN_PLACEHOLDER=TOKEN_PLACEHOLDER,exports.TOKEN_SELF_TAG=TOKEN_SELF_TAG,exports.default=Interpolate;
Binary file
@@ -1 +1 @@
1
- import React from"react";import PropTypes from"prop-types";import _classCallCheck from"@babel/runtime/helpers/classCallCheck";import _createClass from"@babel/runtime/helpers/createClass";import _objectWithoutProperties from"@babel/runtime/helpers/objectWithoutProperties";import _defineProperty from"@babel/runtime/helpers/defineProperty";var TOKEN_PLACEHOLDER="TOKEN_PLACEHOLDER",TOKEN_OPEN_TAG="TOKEN_OPEN_TAG",TOKEN_CLOSE_TAG="TOKEN_CLOSE_TAG",TOKEN_SELF_TAG="TOKEN_SELF_TAG",TOKEN_TEXT="TOKEN_TEXT",NODE_FRAGMENT="NODE_FRAGMENT",NODE_TAG_ELEMENT="NODE_TAG_ELEMENT",NODE_VOID_ELEMENT="NODE_VOID_ELEMENT",NODE_PLACEHOLDER="NODE_PLACEHOLDER",NODE_TEXT="NODE_TEXT",Node=function(){function e(t){var r=t.type,n=t.children,a=_objectWithoutProperties(t,["type","children"]);for(var o in _classCallCheck(this,e),this.type=r,this.children=n||[],a)this[o]=a[o];a.token&&(this.string=a.token.string)}return _createClass(e,[{key:"appendChild",value:function(e){this.children.push(e)}},{key:"isLeaf",get:function(){return 0===this.children.length}}]),e}();function lexer(e){var t,r=(_defineProperty(t={},TOKEN_PLACEHOLDER,/{\s*(\w+)\s*}/g),_defineProperty(t,TOKEN_OPEN_TAG,/<(\w+)>/g),_defineProperty(t,TOKEN_CLOSE_TAG,/<\/(\w+)>/g),_defineProperty(t,TOKEN_SELF_TAG,/<(\w+)\s*\/>/g),t),n=[];for(var a in r)for(var o,i=r[a];null!==(o=i.exec(e));)n.push({type:a,string:o[0],name:o[1],start:o.index,end:o.index+o[0].length});n.sort(function(e,t){return e.start-t.start});var E=[],s=0;n.forEach(function(t){if(t.start!==s){var r=t.start,n=e.substring(s,r);E.push({type:TOKEN_TEXT,string:n,text:n,start:s,end:r}),s=t.end}else s=t.end});var c=e.substring(s);return""!==c&&E.push({type:TOKEN_TEXT,string:c,start:s,end:e.length}),[].concat(E,n).sort(function(e,t){return e.start-t.start})}function parser(e){var t=lexer(e);return new Parser(t).parse()}Node.createTagNode=function(e,t){return new Node({type:NODE_TAG_ELEMENT,children:t,name:e.name,token:e})},Node.createFragmentNode=function(e){return new Node({type:NODE_FRAGMENT,children:e})},Node.createVoidNode=function(e){return new Node({type:NODE_VOID_ELEMENT,name:e.name,token:e})},Node.createTextNode=function(e){return new Node({type:NODE_TEXT,text:e.string,token:e})},Node.createPlaceholderNode=function(e){return new Node({type:NODE_PLACEHOLDER,name:e.name,token:e})};var SYNTAX_ERROR="syntax error",EPSILON={type:"EPSILON"},Parser=function(){function e(t){_classCallCheck(this,e),this.tokens=[].concat(t),this.tags=[]}return _createClass(e,[{key:"parse",value:function(){var e=this.document();if(!this.predict(EPSILON.type))throw new Error(SYNTAX_ERROR);return e}},{key:"document",value:function(){var e=this.elementOrData();return Node.createFragmentNode(e)}},{key:"element",value:function(){var e=this.openTag(),t=this.elementOrData();return this.endTag(),Node.createTagNode(e,t)}},{key:"openTag",value:function(){if(!this.predict(TOKEN_OPEN_TAG))throw new Error(SYNTAX_ERROR);return this.pushTag(this.lookahead),this.match(TOKEN_OPEN_TAG)}},{key:"elementOrData",value:function(){var e=[];if(this.predict(TOKEN_OPEN_TAG)&&(e.push(this.element()),e=e.concat(this.elementOrData())),this.predict(TOKEN_SELF_TAG,TOKEN_TEXT,TOKEN_PLACEHOLDER)){var t,r=this.match(this.lookahead.type);switch(r.type){case TOKEN_SELF_TAG:t=Node.createVoidNode(r);break;case TOKEN_TEXT:t=Node.createTextNode(r);break;case TOKEN_PLACEHOLDER:t=Node.createPlaceholderNode(r)}e.push(t),e=e.concat(this.elementOrData())}return e}},{key:"endTag",value:function(){if(!this.predict(TOKEN_CLOSE_TAG))throw new Error(SYNTAX_ERROR);if(this.tags.pop().name!==this.lookahead.name)throw new Error(SYNTAX_ERROR);this.match(TOKEN_CLOSE_TAG)}},{key:"predict",value:function(){for(var e=arguments.length,t=new Array(e),r=0;r<e;r++)t[r]=arguments[r];return t.includes(this.lookahead.type)}},{key:"match",value:function(e){if(!this.predict(e))throw new Error(SYNTAX_ERROR);return this.tokens.shift()}},{key:"pushTag",value:function(e){this.tags.push(e)}},{key:"lookahead",get:function(){return 0===this.tokens.length?EPSILON:this.tokens[0]}}]),e}(),createElement=function e(t,r,n){var a=t.children.map(function(t,a){return React.createElement(React.Fragment,{key:n+a},e(t,r,n))});switch(t.type){case NODE_TEXT:return t.text;case NODE_FRAGMENT:return React.createElement(React.Fragment,null,a);case NODE_VOID_ELEMENT:var o=r[t.name];return"function"==typeof o?o():o||React.createElement(t.name,null);case NODE_TAG_ELEMENT:var i=r[t.name];if("function"==typeof i)return i(a);if(void 0!==i)throw new Error('tag "'.concat(t.name,'" should be mapped to a renderer function'));return React.createElement(t.name,null,a);case NODE_PLACEHOLDER:var E=r[t.name];return void 0===E?(console.warn('missing "'.concat(t.name,'" in mapping')),t.string):"function"==typeof E?E():E;default:return}};function Interpolate(e){var t=e.string,r=e.mapping,n=void 0===r?{}:r,a=e.graceful,o=void 0===a||a;try{var i=parser(t);return createElement(i,n,t)}catch(e){if(o)return console.error(e),t;throw e}}Interpolate.propTypes={string:PropTypes.string.isRequired,mapping:PropTypes.object,graceful:PropTypes.bool};export default Interpolate;
1
+ import React from"react";import PropTypes from"prop-types";import _classCallCheck from"@babel/runtime/helpers/classCallCheck";import _createClass from"@babel/runtime/helpers/createClass";import _objectWithoutProperties from"@babel/runtime/helpers/objectWithoutProperties";var TOKEN_PLACEHOLDER="TOKEN_PLACEHOLDER",TOKEN_OPEN_TAG="TOKEN_OPEN_TAG",TOKEN_CLOSE_TAG="TOKEN_CLOSE_TAG",TOKEN_SELF_TAG="TOKEN_SELF_TAG",TOKEN_TEXT="TOKEN_TEXT",NODE_FRAGMENT="NODE_FRAGMENT",NODE_TAG_ELEMENT="NODE_TAG_ELEMENT",NODE_VOID_ELEMENT="NODE_VOID_ELEMENT",NODE_PLACEHOLDER="NODE_PLACEHOLDER",NODE_TEXT="NODE_TEXT",_excluded=["type","children"],Node=function(){function e(t){var r=t.type,n=t.children,a=_objectWithoutProperties(t,_excluded);for(var o in _classCallCheck(this,e),this.type=r,this.children=n||[],a)this[o]=a[o];a.token&&(this.string=a.token.string)}return _createClass(e,[{key:"appendChild",value:function(e){this.children.push(e)}},{key:"isLeaf",get:function(){return 0===this.children.length}}]),e}();Node.createTagNode=function(e,t){return new Node({type:NODE_TAG_ELEMENT,children:t,name:e.name,token:e})},Node.createFragmentNode=function(e){return new Node({type:NODE_FRAGMENT,children:e})},Node.createVoidNode=function(e){return new Node({type:NODE_VOID_ELEMENT,name:e.name,token:e})},Node.createTextNode=function(e){return new Node({type:NODE_TEXT,text:e.string,token:e})},Node.createPlaceholderNode=function(e){return new Node({type:NODE_PLACEHOLDER,name:e.name,token:e})};var SYNTAX_BUILT_IN=[{type:TOKEN_PLACEHOLDER,regex:/{\s*(\w+)\s*}/g},{type:TOKEN_OPEN_TAG,regex:/<(\w+)>/g},{type:TOKEN_CLOSE_TAG,regex:/<\/(\w+)>/g},{type:TOKEN_SELF_TAG,regex:/<(\w+)\s*\/>/g}],SYNTAX_I18NEXT=[{type:TOKEN_PLACEHOLDER,regex:/{{\s*(\w+)\s*}}/g},{type:TOKEN_OPEN_TAG,regex:/<(\w+)>/g},{type:TOKEN_CLOSE_TAG,regex:/<\/(\w+)>/g},{type:TOKEN_SELF_TAG,regex:/<(\w+)\s*\/>/g}];function _createForOfIteratorHelper(e,t){var r="undefined"!=typeof Symbol&&e[Symbol.iterator]||e["@@iterator"];if(!r){if(Array.isArray(e)||(r=_unsupportedIterableToArray(e))||t&&e&&"number"==typeof e.length){r&&(e=r);var n=0,a=function(){};return{s:a,n:function(){return n>=e.length?{done:!0}:{done:!1,value:e[n++]}},e:function(e){throw e},f:a}}throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.")}var o,i=!0,c=!1;return{s:function(){r=r.call(e)},n:function(){var e=r.next();return i=e.done,e},e:function(e){c=!0,o=e},f:function(){try{i||null==r.return||r.return()}finally{if(c)throw o}}}}function _unsupportedIterableToArray(e,t){if(e){if("string"==typeof e)return _arrayLikeToArray(e,t);var r=Object.prototype.toString.call(e).slice(8,-1);return"Object"===r&&e.constructor&&(r=e.constructor.name),"Map"===r||"Set"===r?Array.from(e):"Arguments"===r||/^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(r)?_arrayLikeToArray(e,t):void 0}}function _arrayLikeToArray(e,t){(null==t||t>e.length)&&(t=e.length);for(var r=0,n=new Array(t);r<t;r++)n[r]=e[r];return n}function lexer(e,t){var r,n=[],a=_createForOfIteratorHelper(t);try{for(a.s();!(r=a.n()).done;)for(var o,i=r.value,c=i.type,E=i.regex;null!==(o=E.exec(e));)n.push({type:c,string:o[0],name:o[1],start:o.index,end:o.index+o[0].length})}catch(e){a.e(e)}finally{a.f()}n.sort(function(e,t){return e.start-t.start});var s=[],u=0;n.forEach(function(t){if(t.start!==u){var r=t.start,n=e.substring(u,r);s.push({type:TOKEN_TEXT,string:n,text:n,start:u,end:r}),u=t.end}else u=t.end});var l=e.substring(u);return""!==l&&s.push({type:TOKEN_TEXT,string:l,start:u,end:e.length}),[].concat(s,n).sort(function(e,t){return e.start-t.start})}function parser(e,t){t||(t=SYNTAX_BUILT_IN);var r=lexer(e,t);return new Parser(r).parse()}var SYNTAX_ERROR="Syntax error. Please check if each open tag is closed correctly",EPSILON={type:"EPSILON"},Parser=function(){function e(t){_classCallCheck(this,e),this.tokens=[].concat(t),this.tags=[]}return _createClass(e,[{key:"parse",value:function(){var e=this.document();if(!this.predict(EPSILON.type))throw new Error(SYNTAX_ERROR);return e}},{key:"document",value:function(){var e=this.elementOrData();return Node.createFragmentNode(e)}},{key:"element",value:function(){var e=this.openTag(),t=this.elementOrData();return this.endTag(),Node.createTagNode(e,t)}},{key:"openTag",value:function(){if(!this.predict(TOKEN_OPEN_TAG))throw new Error(SYNTAX_ERROR);return this.pushTag(this.lookahead),this.match(TOKEN_OPEN_TAG)}},{key:"elementOrData",value:function(){var e=[];if(this.predict(TOKEN_OPEN_TAG)&&(e.push(this.element()),e=e.concat(this.elementOrData())),this.predict(TOKEN_SELF_TAG,TOKEN_TEXT,TOKEN_PLACEHOLDER)){var t,r=this.match(this.lookahead.type);switch(r.type){case TOKEN_SELF_TAG:t=Node.createVoidNode(r);break;case TOKEN_TEXT:t=Node.createTextNode(r);break;case TOKEN_PLACEHOLDER:t=Node.createPlaceholderNode(r)}e.push(t),e=e.concat(this.elementOrData())}return e}},{key:"endTag",value:function(){if(!this.predict(TOKEN_CLOSE_TAG))throw new Error(SYNTAX_ERROR);if(this.tags.pop().name!==this.lookahead.name)throw new Error(SYNTAX_ERROR);this.match(TOKEN_CLOSE_TAG)}},{key:"lookahead",get:function(){return 0===this.tokens.length?EPSILON:this.tokens[0]}},{key:"predict",value:function(){for(var e=arguments.length,t=new Array(e),r=0;r<e;r++)t[r]=arguments[r];return t.includes(this.lookahead.type)}},{key:"match",value:function(e){if(!this.predict(e))throw new Error(SYNTAX_ERROR);return this.tokens.shift()}},{key:"pushTag",value:function(e){this.tags.push(e)}}]),e}(),createElement=function e(t,r,n){var a=t.children.map(function(t,a){return React.createElement(React.Fragment,{key:n+a},e(t,r,n))});switch(t.type){case NODE_TEXT:return t.text;case NODE_FRAGMENT:return React.createElement(React.Fragment,null,a);case NODE_VOID_ELEMENT:var o=r[t.name];return"function"==typeof o?o():o||React.createElement(t.name,null);case NODE_TAG_ELEMENT:var i=r[t.name];if(void 0===i)return React.createElement(t.name,null,a);if("function"==typeof i)return i(a);if(React.isValidElement(i)){if(0!==React.Children.count(i.props.children))throw new Error("when passing an element as value, the element should not contains children");return React.cloneElement(i,{children:a})}throw new Error('Invalid mapping value for "'.concat(t.name,'". Only element or render function are accepted'));case NODE_PLACEHOLDER:var c=r[t.name];return void 0===c?(console.warn('missing "'.concat(t.name,'" in mapping')),t.string):"function"==typeof c?c():c;default:return}};function Interpolate(e){var t=e.string,r=e.syntax,n=e.mapping,a=void 0===n?{}:n,o=e.graceful,i=void 0===o||o;try{var c=parser(t,r);return createElement(c,a,t)}catch(e){if(i)return console.error(e),t;throw e}}Interpolate.propTypes={string:PropTypes.string.isRequired,mapping:PropTypes.object,graceful:PropTypes.bool};export{SYNTAX_BUILT_IN,SYNTAX_I18NEXT,TOKEN_CLOSE_TAG,TOKEN_OPEN_TAG,TOKEN_PLACEHOLDER,TOKEN_SELF_TAG,Interpolate as default};
Binary file
@@ -3,7 +3,6 @@ import PropTypes from 'prop-types';
3
3
  import _classCallCheck from '@babel/runtime/helpers/classCallCheck';
4
4
  import _createClass from '@babel/runtime/helpers/createClass';
5
5
  import _objectWithoutProperties from '@babel/runtime/helpers/objectWithoutProperties';
6
- import _defineProperty from '@babel/runtime/helpers/defineProperty';
7
6
 
8
7
  var TOKEN_PLACEHOLDER = "TOKEN_PLACEHOLDER";
9
8
  var TOKEN_OPEN_TAG = "TOKEN_OPEN_TAG";
@@ -16,11 +15,13 @@ var NODE_VOID_ELEMENT = "NODE_VOID_ELEMENT";
16
15
  var NODE_PLACEHOLDER = "NODE_PLACEHOLDER";
17
16
  var NODE_TEXT = "NODE_TEXT";
18
17
 
18
+ var _excluded = ["type", "children"];
19
+
19
20
  var Node = /*#__PURE__*/function () {
20
21
  function Node(_ref) {
21
22
  var type = _ref.type,
22
23
  children = _ref.children,
23
- fields = _objectWithoutProperties(_ref, ["type", "children"]);
24
+ fields = _objectWithoutProperties(_ref, _excluded);
24
25
 
25
26
  _classCallCheck(this, Node);
26
27
 
@@ -91,29 +92,69 @@ Node.createPlaceholderNode = function (token) {
91
92
  });
92
93
  };
93
94
 
95
+ var SYNTAX_BUILT_IN = [{
96
+ type: TOKEN_PLACEHOLDER,
97
+ regex: /{\s*(\w+)\s*}/g
98
+ }, {
99
+ type: TOKEN_OPEN_TAG,
100
+ regex: /<(\w+)>/g
101
+ }, {
102
+ type: TOKEN_CLOSE_TAG,
103
+ regex: /<\/(\w+)>/g
104
+ }, {
105
+ type: TOKEN_SELF_TAG,
106
+ regex: /<(\w+)\s*\/>/g
107
+ }];
108
+ var SYNTAX_I18NEXT = [{
109
+ type: TOKEN_PLACEHOLDER,
110
+ regex: /{{\s*(\w+)\s*}}/g
111
+ }, {
112
+ type: TOKEN_OPEN_TAG,
113
+ regex: /<(\w+)>/g
114
+ }, {
115
+ type: TOKEN_CLOSE_TAG,
116
+ regex: /<\/(\w+)>/g
117
+ }, {
118
+ type: TOKEN_SELF_TAG,
119
+ regex: /<(\w+)\s*\/>/g
120
+ }];
121
+
122
+ function _createForOfIteratorHelper(o, allowArrayLike) { var it = typeof Symbol !== "undefined" && o[Symbol.iterator] || o["@@iterator"]; if (!it) { if (Array.isArray(o) || (it = _unsupportedIterableToArray(o)) || allowArrayLike && o && typeof o.length === "number") { if (it) o = it; var i = 0; var F = function F() {}; return { s: F, n: function n() { if (i >= o.length) return { done: true }; return { done: false, value: o[i++] }; }, e: function e(_e) { throw _e; }, f: F }; } throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); } var normalCompletion = true, didErr = false, err; return { s: function s() { it = it.call(o); }, n: function n() { var step = it.next(); normalCompletion = step.done; return step; }, e: function e(_e2) { didErr = true; err = _e2; }, f: function f() { try { if (!normalCompletion && it.return != null) it.return(); } finally { if (didErr) throw err; } } }; }
123
+
124
+ function _unsupportedIterableToArray(o, minLen) { if (!o) return; if (typeof o === "string") return _arrayLikeToArray(o, minLen); var n = Object.prototype.toString.call(o).slice(8, -1); if (n === "Object" && o.constructor) n = o.constructor.name; if (n === "Map" || n === "Set") return Array.from(o); if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _arrayLikeToArray(o, minLen); }
125
+
126
+ function _arrayLikeToArray(arr, len) { if (len == null || len > arr.length) len = arr.length; for (var i = 0, arr2 = new Array(len); i < len; i++) { arr2[i] = arr[i]; } return arr2; }
94
127
  /*
95
128
  * return a array of token object
96
129
  */
97
130
 
98
- function lexer(string) {
99
- var _regxMap;
100
-
101
- var regxMap = (_regxMap = {}, _defineProperty(_regxMap, TOKEN_PLACEHOLDER, /{\s*(\w+)\s*}/g), _defineProperty(_regxMap, TOKEN_OPEN_TAG, /<(\w+)>/g), _defineProperty(_regxMap, TOKEN_CLOSE_TAG, /<\/(\w+)>/g), _defineProperty(_regxMap, TOKEN_SELF_TAG, /<(\w+)\s*\/>/g), _regxMap);
131
+ function lexer(string, syntax) {
102
132
  var matches = [];
103
133
 
104
- for (var tokenType in regxMap) {
105
- var regx = regxMap[tokenType];
106
- var res;
107
-
108
- while ((res = regx.exec(string)) !== null) {
109
- matches.push({
110
- type: tokenType,
111
- string: res[0],
112
- name: res[1],
113
- start: res.index,
114
- end: res.index + res[0].length
115
- });
134
+ var _iterator = _createForOfIteratorHelper(syntax),
135
+ _step;
136
+
137
+ try {
138
+ for (_iterator.s(); !(_step = _iterator.n()).done;) {
139
+ var rule = _step.value;
140
+ var type = rule.type,
141
+ regex = rule.regex;
142
+ var res;
143
+
144
+ while ((res = regex.exec(string)) !== null) {
145
+ matches.push({
146
+ type: type,
147
+ string: res[0],
148
+ name: res[1],
149
+ start: res.index,
150
+ end: res.index + res[0].length
151
+ });
152
+ }
116
153
  }
154
+ } catch (err) {
155
+ _iterator.e(err);
156
+ } finally {
157
+ _iterator.f();
117
158
  }
118
159
 
119
160
  matches.sort(function (a, b) {
@@ -155,12 +196,16 @@ function lexer(string) {
155
196
  return tokens;
156
197
  }
157
198
 
158
- function parser(string) {
159
- var tokens = lexer(string);
199
+ function parser(string, syntax) {
200
+ if (!syntax) {
201
+ syntax = SYNTAX_BUILT_IN;
202
+ }
203
+
204
+ var tokens = lexer(string, syntax);
160
205
  var p = new Parser(tokens);
161
206
  return p.parse();
162
207
  }
163
- var SYNTAX_ERROR = "syntax error"; // A special token representing end of the tream
208
+ var SYNTAX_ERROR = "Syntax error. Please check if each open tag is closed correctly"; // A special token representing end of the tream
164
209
 
165
210
  var EPSILON = {
166
211
  type: "EPSILON"
@@ -300,6 +345,11 @@ var Parser = /*#__PURE__*/function () {
300
345
 
301
346
  this.match(TOKEN_CLOSE_TAG);
302
347
  }
348
+ }, {
349
+ key: "lookahead",
350
+ get: function get() {
351
+ return this.tokens.length === 0 ? EPSILON : this.tokens[0];
352
+ }
303
353
  }, {
304
354
  key: "predict",
305
355
  value: function predict() {
@@ -324,11 +374,6 @@ var Parser = /*#__PURE__*/function () {
324
374
  value: function pushTag(token) {
325
375
  this.tags.push(token);
326
376
  }
327
- }, {
328
- key: "lookahead",
329
- get: function get() {
330
- return this.tokens.length === 0 ? EPSILON : this.tokens[0];
331
- }
332
377
  }]);
333
378
 
334
379
  return Parser;
@@ -349,7 +394,7 @@ var createElement = function createElement(node, mapping, keyPrefix) {
349
394
 
350
395
  case NODE_FRAGMENT:
351
396
  {
352
- return React.createElement(React.Fragment, null, children);
397
+ return /*#__PURE__*/React.createElement(React.Fragment, null, children);
353
398
  }
354
399
 
355
400
  case NODE_VOID_ELEMENT:
@@ -360,22 +405,32 @@ var createElement = function createElement(node, mapping, keyPrefix) {
360
405
  return val();
361
406
  }
362
407
 
363
- return val || React.createElement(node.name, null);
408
+ return val || /*#__PURE__*/React.createElement(node.name, null);
364
409
  }
365
410
 
366
411
  case NODE_TAG_ELEMENT:
367
412
  {
368
413
  var _val = mapping[node.name];
369
414
 
415
+ if (_val === undefined) {
416
+ return /*#__PURE__*/React.createElement(node.name, null, children);
417
+ }
418
+
370
419
  if (typeof _val === "function") {
371
420
  return _val(children);
372
421
  }
373
422
 
374
- if (_val !== undefined) {
375
- throw new Error("tag \"".concat(node.name, "\" should be mapped to a renderer function"));
423
+ if ( /*#__PURE__*/React.isValidElement(_val)) {
424
+ if (React.Children.count(_val.props.children) !== 0) {
425
+ throw new Error("when passing an element as value, the element should not contains children");
426
+ }
427
+
428
+ return /*#__PURE__*/React.cloneElement(_val, {
429
+ children: children
430
+ });
376
431
  }
377
432
 
378
- return React.createElement(node.name, null, children);
433
+ throw new Error("Invalid mapping value for \"".concat(node.name, "\". Only element or render function are accepted"));
379
434
  }
380
435
 
381
436
  case NODE_PLACEHOLDER:
@@ -401,13 +456,14 @@ var createElement = function createElement(node, mapping, keyPrefix) {
401
456
 
402
457
  function Interpolate(_ref) {
403
458
  var string = _ref.string,
459
+ syntax = _ref.syntax,
404
460
  _ref$mapping = _ref.mapping,
405
461
  mapping = _ref$mapping === void 0 ? {} : _ref$mapping,
406
462
  _ref$graceful = _ref.graceful,
407
463
  graceful = _ref$graceful === void 0 ? true : _ref$graceful;
408
464
 
409
465
  try {
410
- var tree = parser(string);
466
+ var tree = parser(string, syntax);
411
467
  return createElement(tree, mapping, string);
412
468
  } catch (e) {
413
469
  if (graceful) {
@@ -424,4 +480,4 @@ Interpolate.propTypes = {
424
480
  graceful: PropTypes.bool
425
481
  };
426
482
 
427
- export default Interpolate;
483
+ export { SYNTAX_BUILT_IN, SYNTAX_I18NEXT, TOKEN_CLOSE_TAG, TOKEN_OPEN_TAG, TOKEN_PLACEHOLDER, TOKEN_SELF_TAG, Interpolate as default };
package/jest.config.js ADDED
@@ -0,0 +1,3 @@
1
+ module.exports = {
2
+ testEnvironment: "jsdom"
3
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@doist/react-interpolate",
3
- "version": "0.0.2",
3
+ "version": "0.3.0",
4
4
  "description": "A string interpolation component that formats and interpolates a template string in a safe way",
5
5
  "main": "dist/react-interpolate.cjs",
6
6
  "module": "dist/react-interpolate.mjs",
@@ -10,7 +10,10 @@
10
10
  "build": "del dist && rollup -c && npm run mini-cjs && npm run mini-mjs && npm run gzip",
11
11
  "mini-cjs": "uglifyjs dist/react-interpolate.cjs --compress --mangle --enclose --output dist/react-interpolate.min.cjs",
12
12
  "mini-mjs": "uglifyjs dist/react-interpolate.mjs --compress --mangle --enclose --output dist/react-interpolate.min.mjs",
13
- "gzip": "gzip dist/**/*.min.*js --extension=gz"
13
+ "gzip": "gzip dist/**/*.min.*js --extension=gz",
14
+ "publish": "npm run build && npm publish",
15
+ "release": "git checkout master && git pull && release-it",
16
+ "release:auto": "git checkout master && git pull && release-it --ci"
14
17
  },
15
18
  "repository": {
16
19
  "type": "git",
@@ -27,37 +30,38 @@
27
30
  "author": "Steven Kao",
28
31
  "license": "ISC",
29
32
  "peerDependencies": {
30
- "react": "^16.13.1",
31
- "react-dom": "^16.13.1"
33
+ "react": "^17.0.2",
34
+ "react-dom": "^17.0.2"
32
35
  },
33
36
  "devDependencies": {
34
- "@babel/cli": "^7.8.4",
35
- "@babel/core": "^7.9.0",
36
- "@babel/plugin-transform-runtime": "^7.9.0",
37
- "@babel/preset-env": "^7.9.0",
38
- "@babel/preset-react": "^7.9.4",
39
- "@testing-library/react": "^10.0.1",
40
- "babel-eslint": "^10.1.0",
41
- "babel-jest": "^25.2.3",
42
- "core-js": "^3.6.4",
43
- "del-cli": "^3.0.0",
44
- "eslint": "^6.8.0",
45
- "eslint-config-prettier": "^6.10.1",
46
- "eslint-plugin-compat": "^3.5.1",
47
- "eslint-plugin-jest": "^23.8.2",
48
- "eslint-plugin-prettier": "^3.1.2",
49
- "eslint-plugin-react": "^7.19.0",
50
- "eslint-plugin-react-hooks": "^3.0.0",
51
- "gzip-cli": "^1.1.0",
52
- "jest": "^25.2.3",
53
- "prettier": "^2.0.2",
54
- "react": "^16.13.1",
55
- "react-dom": "^16.13.1",
56
- "rollup": "^2.3.3",
37
+ "@babel/cli": "^7.16.0",
38
+ "@babel/core": "^7.16.0",
39
+ "@babel/eslint-parser": "^7.16.3",
40
+ "@babel/plugin-transform-runtime": "^7.16.4",
41
+ "@babel/preset-env": "^7.16.4",
42
+ "@babel/preset-react": "^7.16.0",
43
+ "@testing-library/react": "^12.1.2",
44
+ "babel-jest": "^27.3.1",
45
+ "core-js": "^3.19.1",
46
+ "del-cli": "^4.0.1",
47
+ "eslint": "^8.2.0",
48
+ "eslint-config-prettier": "^8.3.0",
49
+ "eslint-plugin-compat": "^4.0.0",
50
+ "eslint-plugin-jest": "^25.2.4",
51
+ "eslint-plugin-prettier": "^4.0.0",
52
+ "eslint-plugin-react": "^7.27.0",
53
+ "eslint-plugin-react-hooks": "^4.3.0",
54
+ "gzip-cli": "^1.2.0",
55
+ "jest": "^27.3.1",
56
+ "prettier": "^2.4.1",
57
+ "react": "^17.0.2",
58
+ "react-dom": "^17.0.2",
59
+ "release-it": "^14.11.7",
60
+ "rollup": "^2.60.0",
57
61
  "rollup-plugin-babel": "^4.4.0",
58
62
  "uglify-es": "^3.3.9"
59
63
  },
60
64
  "dependencies": {
61
- "@babel/runtime": "^7.9.2"
65
+ "@babel/runtime": "^7.16.3"
62
66
  }
63
67
  }
package/src/index.js CHANGED
@@ -1,2 +1,10 @@
1
1
  import Interpolate from "./interpolate"
2
2
  export default Interpolate
3
+
4
+ export { SYNTAX_I18NEXT, SYNTAX_BUILT_IN } from "./syntax"
5
+ export {
6
+ TOKEN_PLACEHOLDER,
7
+ TOKEN_OPEN_TAG,
8
+ TOKEN_CLOSE_TAG,
9
+ TOKEN_SELF_TAG
10
+ } from "./constants"
@@ -33,17 +33,28 @@ const createElement = (node, mapping, keyPrefix) => {
33
33
  }
34
34
  case NODE_TAG_ELEMENT: {
35
35
  const val = mapping[node.name]
36
+
37
+ if (val === undefined) {
38
+ return React.createElement(node.name, null, children)
39
+ }
40
+
36
41
  if (typeof val === "function") {
37
42
  return val(children)
38
43
  }
39
44
 
40
- if (val !== undefined) {
41
- throw new Error(
42
- `tag "${node.name}" should be mapped to a renderer function`
43
- )
45
+ if (React.isValidElement(val)) {
46
+ if (React.Children.count(val.props.children) !== 0) {
47
+ throw new Error(
48
+ "when passing an element as value, the element should not contains children"
49
+ )
50
+ }
51
+
52
+ return React.cloneElement(val, { children })
44
53
  }
45
54
 
46
- return React.createElement(node.name, null, children)
55
+ throw new Error(
56
+ `Invalid mapping value for "${node.name}". Only element or render function are accepted`
57
+ )
47
58
  }
48
59
  case NODE_PLACEHOLDER: {
49
60
  const val = mapping[node.name]
@@ -63,9 +74,14 @@ const createElement = (node, mapping, keyPrefix) => {
63
74
  }
64
75
  }
65
76
 
66
- export default function Interpolate({ string, mapping = {}, graceful = true }) {
77
+ export default function Interpolate({
78
+ string,
79
+ syntax,
80
+ mapping = {},
81
+ graceful = true
82
+ }) {
67
83
  try {
68
- const tree = parser(string)
84
+ const tree = parser(string, syntax)
69
85
  return createElement(tree, mapping, string)
70
86
  } catch (e) {
71
87
  if (graceful) {
package/src/lexer.js CHANGED
@@ -1,31 +1,19 @@
1
- import {
2
- TOKEN_PLACEHOLDER,
3
- TOKEN_OPEN_TAG,
4
- TOKEN_CLOSE_TAG,
5
- TOKEN_SELF_TAG,
6
- TOKEN_TEXT
7
- } from "./constants"
1
+ import { TOKEN_TEXT } from "./constants"
2
+ export { SYNTAX_I18NEXT, SYNTAX_BUILT_IN } from "./syntax"
8
3
 
9
4
  /*
10
5
  * return a array of token object
11
6
  */
12
- export default function lexer(string) {
13
- const regxMap = {
14
- [TOKEN_PLACEHOLDER]: /{\s*(\w+)\s*}/g,
15
- [TOKEN_OPEN_TAG]: /<(\w+)>/g,
16
- [TOKEN_CLOSE_TAG]: /<\/(\w+)>/g,
17
- [TOKEN_SELF_TAG]: /<(\w+)\s*\/>/g
18
- }
19
-
7
+ export default function lexer(string, syntax) {
20
8
  const matches = []
21
9
 
22
- for (const tokenType in regxMap) {
23
- const regx = regxMap[tokenType]
10
+ for (const rule of syntax) {
11
+ const { type, regex } = rule
24
12
 
25
13
  var res
26
- while ((res = regx.exec(string)) !== null) {
14
+ while ((res = regex.exec(string)) !== null) {
27
15
  matches.push({
28
- type: tokenType,
16
+ type,
29
17
  string: res[0],
30
18
  name: res[1],
31
19
  start: res.index,
package/src/parser.js CHANGED
@@ -7,14 +7,20 @@ import {
7
7
  } from "./constants"
8
8
  import Node from "./node.js"
9
9
  import lexer from "./lexer"
10
+ import { SYNTAX_BUILT_IN } from "./syntax"
10
11
 
11
- export default function parser(string) {
12
- const tokens = lexer(string)
12
+ export default function parser(string, syntax) {
13
+ if (!syntax) {
14
+ syntax = SYNTAX_BUILT_IN
15
+ }
16
+
17
+ const tokens = lexer(string, syntax)
13
18
  const p = new Parser(tokens)
14
19
  return p.parse()
15
20
  }
16
21
 
17
- const SYNTAX_ERROR = "syntax error"
22
+ const SYNTAX_ERROR =
23
+ "Syntax error. Please check if each open tag is closed correctly"
18
24
 
19
25
  // A special token representing end of the tream
20
26
  const EPSILON = {
package/src/syntax.js ADDED
@@ -0,0 +1,38 @@
1
+ import {
2
+ TOKEN_PLACEHOLDER,
3
+ TOKEN_OPEN_TAG,
4
+ TOKEN_CLOSE_TAG,
5
+ TOKEN_SELF_TAG
6
+ } from "./constants"
7
+
8
+ export const SYNTAX_BUILT_IN = [
9
+ {
10
+ type: TOKEN_PLACEHOLDER,
11
+ regex: /{\s*(\w+)\s*}/g
12
+ },
13
+ {
14
+ type: TOKEN_OPEN_TAG,
15
+ regex: /<(\w+)>/g
16
+ },
17
+ {
18
+ type: TOKEN_CLOSE_TAG,
19
+ regex: /<\/(\w+)>/g
20
+ },
21
+ { type: TOKEN_SELF_TAG, regex: /<(\w+)\s*\/>/g }
22
+ ]
23
+
24
+ export const SYNTAX_I18NEXT = [
25
+ {
26
+ type: TOKEN_PLACEHOLDER,
27
+ regex: /{{\s*(\w+)\s*}}/g
28
+ },
29
+ {
30
+ type: TOKEN_OPEN_TAG,
31
+ regex: /<(\w+)>/g
32
+ },
33
+ {
34
+ type: TOKEN_CLOSE_TAG,
35
+ regex: /<\/(\w+)>/g
36
+ },
37
+ { type: TOKEN_SELF_TAG, regex: /<(\w+)\s*\/>/g }
38
+ ]