From e2000c04c5732a98036d4a9b042bc1d5fa34ea28 Mon Sep 17 00:00:00 2001 From: Koen Klaren Date: Wed, 14 Jun 2017 17:10:16 +0200 Subject: [PATCH 1/4] Add custom tags functionality --- src/index.es6 | 18 ++++++++++-- src/tokenize.es6 | 75 ++++++++++++++++++++++++++++++++---------------- 2 files changed, 67 insertions(+), 26 deletions(-) diff --git a/src/index.es6 b/src/index.es6 index f0d86f1..d10f6b3 100644 --- a/src/index.es6 +++ b/src/index.es6 @@ -9,6 +9,12 @@ import createFragment from 'react-addons-create-fragment'; */ import tokenize from './tokenize'; +const DEFAULT_TAGS = { + componentOpen: ['{{', '}}'], + componentClose: ['{{/', '}}'], + componentSelfClosing: ['{{', '/}}'], +}; + let currentMixedString; function getCloseIndex( openIndex, tokens ) { @@ -95,7 +101,7 @@ function buildChildren( tokens, components ) { } function interpolate( options ) { - const { mixedString, components, throwErrors } = options; + const { mixedString, components, throwErrors, tags = DEFAULT_TAGS } = options; currentMixedString = mixedString; @@ -111,7 +117,15 @@ function interpolate( options ) { return mixedString; } - let tokens = tokenize( mixedString ); + if ( typeof tags !== 'object' || ! tags.componentOpen || ! tags.componentClose || ! tags.componentSelfClosing ) { + if ( throwErrors ) { + throw new Error( `Interpolation Error: unable to process \`${ mixedString }\` because tags is invalid` ); + } + + return mixedString; + } + + let tokens = tokenize( mixedString, tags ); try { return buildChildren( tokens, components ); diff --git a/src/tokenize.es6 b/src/tokenize.es6 index 997531e..6d70449 100644 --- a/src/tokenize.es6 +++ b/src/tokenize.es6 @@ -1,32 +1,59 @@ -function identifyToken( item ) { - // {{/example}} - if ( item.match( /^\{\{\// ) ) { - return { - type: 'componentClose', - value: item.replace( /\W/g, '' ) - }; - } - // {{example /}} - if ( item.match( /\/\}\}$/ ) ) { - return { - type: 'componentSelfClosing', - value: item.replace( /\W/g, '' ) - }; - } - // {{example}} - if ( item.match( /^\{\{/ ) ) { - return { - type: 'componentOpen', - value: item.replace( /\W/g, '' ) - }; +const TOKEN_TYPES = [ 'componentClose', 'componentSelfClosing', 'componentOpen' ]; + +function identifyToken( item, regExps ) { + for ( let i = 0; i < TOKEN_TYPES.length; i++ ) { + const type = TOKEN_TYPES[ i ]; + const match = item.match( regExps[ type ] ); + + if ( match ) { + return { + type: type, + value: match[ 1 ] + }; + } } + return { type: 'string', value: item }; } -module.exports = function( mixedString ) { - const tokenStrings = mixedString.split( /(\{\{\/?\s*\w+\s*\/?\}\})/g ); // split to components and strings - return tokenStrings.map( identifyToken ); +/** + * @param {string} str + * @returns {string} + */ +function escapeRegExp( str ) { + return str.replace( /[-[\]{}()*+?.,\\^$|#\s]/g, '\\$&' ); +} + +/** + * @param {string[]} tag + * @param {boolean} match + * @returns {RegExp} + */ +function makeRegExp( tag, match ) { + const [ start, end ] = tag; + const inner = match ? '\\s*(\\w+)\\s*' : '\\s*\\w+\\s*'; + return new RegExp( `${escapeRegExp( start )}${inner}${escapeRegExp( end )}` ); +} + +module.exports = function( mixedString, tags ) { + // create regular expression that matches all components + const combinedRegExpString = TOKEN_TYPES + .map( type => tags[ type ] ) + .map( tag => makeRegExp( tag, false ).source ) + .join( '|' ); + const combinedRegExp = new RegExp( `(${combinedRegExpString})`, 'g' ); + + // split to components and strings + const tokenStrings = mixedString.split( combinedRegExp ); + + // create regular expressions for identifying tokens + const componentRegExps = {}; + TOKEN_TYPES.forEach( type => { + componentRegExps[ type ] = makeRegExp( tags[ type ], true ); + }); + + return tokenStrings.map( (tokenString) => identifyToken( tokenString, componentRegExps ) ); }; From 85f6e4bba27a0e8bafcb39f1f39c170b6762ec9f Mon Sep 17 00:00:00 2001 From: Koen Klaren Date: Wed, 14 Jun 2017 17:10:29 +0200 Subject: [PATCH 2/4] Add tests for custom tags --- test/test.jsx | 44 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) diff --git a/test/test.jsx b/test/test.jsx index 85324a7..71b0382 100644 --- a/test/test.jsx +++ b/test/test.jsx @@ -256,4 +256,48 @@ describe( 'interpolate-components', () => { assert.equal( expectedResultString, ReactDomServer.renderToStaticMarkup( instance ) ); } ); } ); + + describe( 'custom tags', () => { + it( 'should allow custom tags', () => { + const expectedResultString = '
test
'; + const interpolatedComponent = interpolateComponents( { + mixedString: '<
>test<
> <>', + components: { + div: div, + input: input + }, + tags: { + componentOpen: [ '<<', '>>' ], + componentClose: [ '<>' ], + componentSelfClosing: [ '<<', '/>>' ], + } + } ); + const instance = { interpolatedComponent }; + assert.equal( expectedResultString, ReactDomServer.renderToStaticMarkup( instance ) ); + } ); + + it( 'should throw if tags is not an object', () => { + assert.throws( () => { + interpolateComponents( { + mixedString: 'test', + components: {}, + tags: '{{', + throwErrors: true + } ); + } ); + } ); + + it( 'should throw if not all tags are provided', () => { + assert.throws( () => { + interpolateComponents( { + mixedString: 'test', + components: {}, + tags: { + componentOpen: [ '{{', '}}' ], + }, + throwErrors: true + } ); + } ); + } ); + } ); } ); From fea7574f901e720f7b1cac5402e4e392e2675b44 Mon Sep 17 00:00:00 2001 From: Koen Klaren Date: Thu, 15 Jun 2017 10:59:58 +0200 Subject: [PATCH 3/4] 1.2.0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 9b711e8..e7541d7 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "interpolate-components", - "version": "1.1.0", + "version": "1.2.0", "description": "Convert strings into structured React components.", "repository": { "type": "git", From 2f2bbf42830da8417badd103066429c15c32c9ae Mon Sep 17 00:00:00 2001 From: Koen Klaren Date: Thu, 15 Jun 2017 11:05:19 +0200 Subject: [PATCH 4/4] Update README.md --- README.md | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/README.md b/README.md index 2ee82ce..fdeef88 100644 --- a/README.md +++ b/README.md @@ -11,6 +11,7 @@ Interpolate-Components takes a single options object as an argument and returns - **mixedString** A string that contains component tokens to be interpolated - **components** An object with components assigned to named attributes +- **tags** (optional) An object with custom tag syntax to be used - **throwErrors** (optional) Whether errors should be thrown (as in pre-production environments) or we should more gracefully return the un-interpolated original string (as in production). This is optional and is false by default. ## Component tokens @@ -37,6 +38,20 @@ const jsxExample =

{ children }

; //

This is a fine example.

``` +## Custom tag syntax + +```js +interpolateComponents( { + mixedString: 'This uses <>custom<> syntax <>', + components: { em: , icon: }, + tags: { + componentOpen: [ '<<', '>>' ], + componentClose: [ '<>' ], + componentSelfClosing: [ '<<', '/>>' ], + } +} ); +``` + ## Testing ```sh # install dependencies