├── src ├── source │ └── index.js ├── codeBlock │ └── index.js ├── utils │ ├── readFromFixture │ │ ├── fixtures │ │ │ └── contents.txt │ │ ├── index.js │ │ ├── readFromFixture.test.js │ │ └── readFromFixture.js │ ├── index.js │ └── index.test.js ├── html │ ├── index.js │ ├── fixtures │ │ ├── html.txt │ │ └── newline-conversion.txt │ ├── html.js │ └── html.test.js ├── oneLine │ ├── index.js │ ├── fixtures │ │ ├── oneLine-sentence.txt │ │ └── oneLine.txt │ ├── oneLine.js │ └── oneLine.test.js ├── safeHtml │ ├── index.js │ ├── fixtures │ │ ├── normal-html.txt │ │ ├── newline-conversion.txt │ │ └── escaped-html.txt │ ├── safeHtml.js │ └── safeHtml.test.js ├── stripIndent │ ├── fixtures │ │ ├── stripIndent.txt │ │ └── maintainIndent.txt │ ├── index.js │ ├── stripIndent.js │ └── stripIndent.test.js ├── TemplateTag │ ├── index.js │ ├── TemplateTag.test.js │ └── TemplateTag.js ├── commaLists │ ├── index.js │ ├── fixtures │ │ └── commaLists.txt │ ├── commaLists.js │ └── commaLists.test.js ├── commaListsOr │ ├── index.js │ ├── fixtures │ │ └── commaListsOr.txt │ ├── commaListsOr.js │ └── commaListsOr.test.js ├── inlineLists │ ├── index.js │ ├── fixtures │ │ └── inlineLists.txt │ ├── inlineLists.js │ └── inlineLists.test.js ├── oneLineTrim │ ├── index.js │ ├── fixtures │ │ └── oneLineTrim.txt │ ├── oneLineTrim.js │ └── oneLineTrim.test.js ├── stripIndents │ ├── index.js │ ├── fixtures │ │ └── stripIndents.txt │ ├── stripIndents.js │ └── stripIndents.test.js ├── commaListsAnd │ ├── index.js │ ├── fixtures │ │ └── commaListsAnd.txt │ ├── commaListsAnd.js │ └── commaListsAnd.test.js ├── oneLineCommaLists │ ├── index.js │ ├── fixtures │ │ └── oneLineCommaLists.txt │ ├── oneLineCommaLists.js │ └── oneLineCommaLists.test.js ├── oneLineCommaListsOr │ ├── index.js │ ├── fixtures │ │ └── oneLineCommaListsOr.txt │ ├── oneLineCommaListsOr.js │ └── oneLineCommaListsOr.test.js ├── oneLineInlineLists │ ├── index.js │ ├── fixtures │ │ └── oneLineInlineLists.txt │ ├── oneLineInlineLists.js │ └── oneLineInlineLists.test.js ├── inlineArrayTransformer │ ├── index.js │ ├── inlineArrayTransformer.test.js │ └── inlineArrayTransformer.js ├── oneLineCommaListsAnd │ ├── index.js │ ├── fixtures │ │ └── oneLineCommaListsAnd.txt │ ├── oneLineCommaListsAnd.js │ └── oneLineCommaListsAnd.test.js ├── splitStringTransformer │ ├── index.js │ ├── splitStringTransformer.js │ └── splitStringTransformer.test.js ├── stripIndentTransformer │ ├── index.js │ ├── fixtures │ │ ├── stripIndents.txt │ │ └── stripIndent.txt │ ├── stripIndentTransformer.js │ └── stripIndentTransformer.test.js ├── trimResultTransformer │ ├── index.js │ ├── trimResultTransformer.js │ └── trimResultTransformer.test.js ├── replaceResultTransformer │ ├── index.js │ ├── replaceResultTransformer.js │ └── replaceResultTransformer.test.js ├── replaceSubstitutionTransformer │ ├── index.js │ ├── replaceSubstitutionTransformer.js │ └── replaceSubstitutionTransformer.test.js ├── removeNonPrintingValuesTransformer │ ├── index.js │ ├── removeNonPrintingValuesTransformer.js │ └── removeNonPrintingValuesTransformer.test.js ├── index.test.js └── index.js ├── .npmignore ├── .travis.yml ├── .bithoundrc ├── .editorconfig ├── wallaby.js ├── .babelrc ├── contributing.md ├── appveyor.yml ├── .gitignore ├── license.md ├── package.json └── readme.md /src/source/index.js: -------------------------------------------------------------------------------- 1 | export default from '../html' 2 | -------------------------------------------------------------------------------- /src/codeBlock/index.js: -------------------------------------------------------------------------------- 1 | export default from '../html' 2 | -------------------------------------------------------------------------------- /src/utils/readFromFixture/fixtures/contents.txt: -------------------------------------------------------------------------------- 1 | wow such doge 2 | -------------------------------------------------------------------------------- /src/html/index.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | export default from './html' 4 | -------------------------------------------------------------------------------- /src/oneLine/index.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | export default from './oneLine' 4 | -------------------------------------------------------------------------------- /src/safeHtml/index.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | export default from './safeHtml' 4 | -------------------------------------------------------------------------------- /src/stripIndent/fixtures/stripIndent.txt: -------------------------------------------------------------------------------- 1 | wow such indent gone 2 | very amaze 3 | -------------------------------------------------------------------------------- /src/TemplateTag/index.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | export default from './TemplateTag' 4 | -------------------------------------------------------------------------------- /src/commaLists/index.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | export default from './commaLists' 4 | -------------------------------------------------------------------------------- /src/commaListsOr/index.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | export default from './commaListsOr' 4 | -------------------------------------------------------------------------------- /src/inlineLists/index.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | export default from './inlineLists' 4 | -------------------------------------------------------------------------------- /src/oneLineTrim/index.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | export default from './oneLineTrim' 4 | -------------------------------------------------------------------------------- /src/stripIndent/fixtures/maintainIndent.txt: -------------------------------------------------------------------------------- 1 | wow such indent gone 2 | very amaze 3 | -------------------------------------------------------------------------------- /src/stripIndent/index.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | export default from './stripIndent' 4 | -------------------------------------------------------------------------------- /src/stripIndents/index.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | export default from './stripIndents' 4 | -------------------------------------------------------------------------------- /src/commaListsAnd/index.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | export default from './commaListsAnd' 4 | -------------------------------------------------------------------------------- /src/utils/index.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | export readFromFixture from './readFromFixture' 4 | -------------------------------------------------------------------------------- /src/oneLine/fixtures/oneLine-sentence.txt: -------------------------------------------------------------------------------- 1 | Sentences also work. Double spacing is preserved. 2 | -------------------------------------------------------------------------------- /src/oneLine/fixtures/oneLine.txt: -------------------------------------------------------------------------------- 1 | wow such doge is very amaze at one line neat from multiline 2 | -------------------------------------------------------------------------------- /src/oneLineCommaLists/index.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | export default from './oneLineCommaLists' 4 | -------------------------------------------------------------------------------- /src/oneLineTrim/fixtures/oneLineTrim.txt: -------------------------------------------------------------------------------- 1 | wow such reductionvery absence of spacemuch amaze 2 | -------------------------------------------------------------------------------- /src/stripIndents/fixtures/stripIndents.txt: -------------------------------------------------------------------------------- 1 | wow such indent gone 2 | very amaze 3 | foo bar baz 4 | -------------------------------------------------------------------------------- /src/oneLineCommaListsOr/index.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | export default from './oneLineCommaListsOr' 4 | -------------------------------------------------------------------------------- /src/oneLineInlineLists/index.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | export default from './oneLineInlineLists' 4 | -------------------------------------------------------------------------------- /src/utils/readFromFixture/index.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | export default from './readFromFixture' 4 | -------------------------------------------------------------------------------- /src/commaLists/fixtures/commaLists.txt: -------------------------------------------------------------------------------- 1 | Doge <3's these fruits: apple, banana, kiwi 2 | they are amaze 3 | -------------------------------------------------------------------------------- /src/inlineArrayTransformer/index.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | export default from './inlineArrayTransformer' 4 | -------------------------------------------------------------------------------- /src/inlineLists/fixtures/inlineLists.txt: -------------------------------------------------------------------------------- 1 | Doge <3's these fruits: apple banana kiwi 2 | they are amaze 3 | -------------------------------------------------------------------------------- /src/oneLineCommaListsAnd/index.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | export default from './oneLineCommaListsAnd' 4 | -------------------------------------------------------------------------------- /src/splitStringTransformer/index.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | export default from './splitStringTransformer' 4 | -------------------------------------------------------------------------------- /src/stripIndentTransformer/index.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | export default from './stripIndentTransformer' 4 | -------------------------------------------------------------------------------- /src/trimResultTransformer/index.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | export default from './trimResultTransformer' 4 | -------------------------------------------------------------------------------- /src/commaListsOr/fixtures/commaListsOr.txt: -------------------------------------------------------------------------------- 1 | Doge <3's these fruits: apple, banana or kiwi 2 | they are amaze 3 | -------------------------------------------------------------------------------- /src/replaceResultTransformer/index.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | export default from './replaceResultTransformer' 4 | -------------------------------------------------------------------------------- /src/safeHtml/fixtures/normal-html.txt: -------------------------------------------------------------------------------- 1 |

amaze

2 | 7 | -------------------------------------------------------------------------------- /src/commaListsAnd/fixtures/commaListsAnd.txt: -------------------------------------------------------------------------------- 1 | Doge <3's these fruits: apple, banana and kiwi 2 | they are amaze 3 | -------------------------------------------------------------------------------- /src/oneLineCommaLists/fixtures/oneLineCommaLists.txt: -------------------------------------------------------------------------------- 1 | Doge <3's these fruits: apple, banana, kiwi they are amaze 2 | -------------------------------------------------------------------------------- /src/oneLineInlineLists/fixtures/oneLineInlineLists.txt: -------------------------------------------------------------------------------- 1 | Doge <3's these fruits: apple banana kiwi they are amaze 2 | -------------------------------------------------------------------------------- /src/stripIndentTransformer/fixtures/stripIndents.txt: -------------------------------------------------------------------------------- 1 | foo bar baz 2 | bar baz foo 3 | baz foo bar 4 | wow such doge 5 | -------------------------------------------------------------------------------- /src/oneLineCommaListsOr/fixtures/oneLineCommaListsOr.txt: -------------------------------------------------------------------------------- 1 | Doge <3's these fruits: apple, banana or kiwi they are amaze 2 | -------------------------------------------------------------------------------- /src/safeHtml/fixtures/newline-conversion.txt: -------------------------------------------------------------------------------- 1 |

amaze

2 | 7 | -------------------------------------------------------------------------------- /src/stripIndentTransformer/fixtures/stripIndent.txt: -------------------------------------------------------------------------------- 1 | foo bar baz 2 | bar baz foo 3 | baz foo bar 4 | wow such doge 5 | -------------------------------------------------------------------------------- /src/html/fixtures/html.txt: -------------------------------------------------------------------------------- 1 |

amaze

2 | 7 | -------------------------------------------------------------------------------- /src/oneLineCommaListsAnd/fixtures/oneLineCommaListsAnd.txt: -------------------------------------------------------------------------------- 1 | Doge <3's these fruits: apple, banana and kiwi they are amaze 2 | -------------------------------------------------------------------------------- /src/replaceSubstitutionTransformer/index.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | export default from './replaceSubstitutionTransformer' 4 | -------------------------------------------------------------------------------- /src/removeNonPrintingValuesTransformer/index.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | export default from './removeNonPrintingValuesTransformer' 4 | -------------------------------------------------------------------------------- /src/html/fixtures/newline-conversion.txt: -------------------------------------------------------------------------------- 1 |

amaze

2 | 7 | -------------------------------------------------------------------------------- /src/safeHtml/fixtures/escaped-html.txt: -------------------------------------------------------------------------------- 1 |

amaze

2 | 8 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | src 2 | .babelrc 3 | .editorconfig 4 | .gitignore 5 | .travis.yml 6 | .appveyor.yml 7 | contributing.md 8 | wallaby.js 9 | .nyc_output 10 | coverage 11 | lib/**/*.test.js 12 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: false 2 | language: node_js 3 | cache: 4 | directories: 5 | - node_modules 6 | notifications: 7 | email: true 8 | node_js: 9 | - "6" 10 | - "5" 11 | script: 12 | - npm run test-ci 13 | after_script: 14 | - npm run codecov 15 | -------------------------------------------------------------------------------- /.bithoundrc: -------------------------------------------------------------------------------- 1 | { 2 | "test": [ 3 | "src/**/*.test.js" 4 | ], 5 | "critics": { 6 | "lint": { 7 | "engine": "standard" 8 | } 9 | }, 10 | "dependencies": { 11 | "unused-ignores": [ 12 | "babel-runtime", 13 | "regenerator-runtime" 14 | ] 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/stripIndent/stripIndent.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | import TemplateTag from '../TemplateTag' 4 | import stripIndentTransformer from '../stripIndentTransformer' 5 | import trimResultTransformer from '../trimResultTransformer' 6 | 7 | const stripIndent = new TemplateTag( 8 | stripIndentTransformer, 9 | trimResultTransformer 10 | ) 11 | 12 | export default stripIndent 13 | -------------------------------------------------------------------------------- /src/oneLine/oneLine.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | import TemplateTag from '../TemplateTag' 4 | import trimResultTransformer from '../trimResultTransformer' 5 | import replaceResultTransformer from '../replaceResultTransformer' 6 | 7 | const oneLine = new TemplateTag( 8 | replaceResultTransformer(/(?:\n(?:\s*))+/g, ' '), 9 | trimResultTransformer 10 | ) 11 | 12 | export default oneLine 13 | -------------------------------------------------------------------------------- /src/stripIndents/stripIndents.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | import TemplateTag from '../TemplateTag' 4 | import stripIndentTransformer from '../stripIndentTransformer' 5 | import trimResultTransformer from '../trimResultTransformer' 6 | 7 | const stripIndents = new TemplateTag( 8 | stripIndentTransformer('all'), 9 | trimResultTransformer 10 | ) 11 | 12 | export default stripIndents 13 | -------------------------------------------------------------------------------- /src/oneLineTrim/oneLineTrim.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | import TemplateTag from '../TemplateTag' 4 | import trimResultTransformer from '../trimResultTransformer' 5 | import replaceResultTransformer from '../replaceResultTransformer' 6 | 7 | const oneLineTrim = new TemplateTag( 8 | replaceResultTransformer(/(?:\n\s+)/g, ''), 9 | trimResultTransformer 10 | ) 11 | 12 | export default oneLineTrim 13 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # This file is for unifying the coding style for different editors and IDEs. 2 | # More information at http://EditorConfig.org 3 | 4 | root = true 5 | 6 | [*] 7 | indent_style = space 8 | end_of_line = lf 9 | charset = utf-8 10 | trim_trailing_whitespace = true 11 | insert_final_newline = true 12 | indent_size = 2 13 | 14 | [*.md] 15 | indent_size = 4 16 | trim_trailing_whitespace = false 17 | -------------------------------------------------------------------------------- /wallaby.js: -------------------------------------------------------------------------------- 1 | module.exports = function (wallaby) { 2 | return { 3 | files: [ 4 | 'src/**/*.js' 5 | ], 6 | 7 | tests: [ 8 | 'src/**/*.test.js' 9 | ], 10 | 11 | compilers: { 12 | '**/*.js': wallaby.compilers.babel() 13 | }, 14 | 15 | env: { 16 | type: 'node', 17 | runner: 'node' 18 | }, 19 | 20 | testFramework: 'ava', 21 | 22 | debug: true 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/utils/readFromFixture/readFromFixture.test.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | import test from 'ava' 4 | import readFromFixture from './readFromFixture' 5 | 6 | test('reads the correct fixture contents', async (t) => { 7 | const actual = await readFromFixture('contents') 8 | const expected = 'wow such doge' 9 | t.is(actual, expected) 10 | }) 11 | 12 | test('should reject if no file was found', (t) => { 13 | t.throws(readFromFixture('nothing')) 14 | }) 15 | -------------------------------------------------------------------------------- /src/inlineLists/inlineLists.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | import TemplateTag from '../TemplateTag' 4 | import stripIndentTransformer from '../stripIndentTransformer' 5 | import inlineArrayTransformer from '../inlineArrayTransformer' 6 | import trimResultTransformer from '../trimResultTransformer' 7 | 8 | const inlineLists = new TemplateTag( 9 | inlineArrayTransformer, 10 | stripIndentTransformer, 11 | trimResultTransformer 12 | ) 13 | 14 | export default inlineLists 15 | -------------------------------------------------------------------------------- /src/stripIndents/stripIndents.test.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | import test from 'ava' 4 | import stripIndents from './stripIndents' 5 | import {readFromFixture} from '../utils' 6 | 7 | const val = 'amaze' 8 | 9 | test('strips all indentation', async (t) => { 10 | const expected = await readFromFixture('stripIndents') 11 | const actual = stripIndents` 12 | wow such indent gone 13 | very ${val} 14 | foo bar baz 15 | ` 16 | t.is(actual, expected) 17 | }) 18 | -------------------------------------------------------------------------------- /src/commaLists/commaLists.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | import TemplateTag from '../TemplateTag' 4 | import stripIndentTransformer from '../stripIndentTransformer' 5 | import inlineArrayTransformer from '../inlineArrayTransformer' 6 | import trimResultTransformer from '../trimResultTransformer' 7 | 8 | const commaLists = new TemplateTag( 9 | inlineArrayTransformer({ separator: ',' }), 10 | stripIndentTransformer, 11 | trimResultTransformer 12 | ) 13 | 14 | export default commaLists 15 | -------------------------------------------------------------------------------- /src/oneLineTrim/oneLineTrim.test.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | import test from 'ava' 4 | import oneLineTrim from './oneLineTrim' 5 | import {readFromFixture} from '../utils' 6 | 7 | const val = 'amaze' 8 | 9 | test('reduces to one line while trimming newlines', async (t) => { 10 | const expected = await readFromFixture('oneLineTrim') 11 | const actual = oneLineTrim` 12 | wow such reduction 13 | very absence of space 14 | much ${val} 15 | ` 16 | t.is(actual, expected) 17 | }) 18 | -------------------------------------------------------------------------------- /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | "stage-0" 4 | ], 5 | "plugins": [ 6 | "transform-runtime" 7 | ], 8 | "sourceMaps": "inline", 9 | "env": { 10 | "cjs": { 11 | "presets": [ 12 | "latest" 13 | ], 14 | "plugins": [ 15 | "add-module-exports" 16 | ] 17 | }, 18 | "es": { 19 | "presets": [ 20 | ["latest", { 21 | "es2015": { 22 | "modules": false 23 | } 24 | }] 25 | ] 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/commaListsOr/commaListsOr.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | import TemplateTag from '../TemplateTag' 4 | import stripIndentTransformer from '../stripIndentTransformer' 5 | import inlineArrayTransformer from '../inlineArrayTransformer' 6 | import trimResultTransformer from '../trimResultTransformer' 7 | 8 | const commaListsOr = new TemplateTag( 9 | inlineArrayTransformer({ separator: ',', conjunction: 'or' }), 10 | stripIndentTransformer, 11 | trimResultTransformer 12 | ) 13 | 14 | export default commaListsOr 15 | -------------------------------------------------------------------------------- /src/commaListsAnd/commaListsAnd.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | import TemplateTag from '../TemplateTag' 4 | import stripIndentTransformer from '../stripIndentTransformer' 5 | import inlineArrayTransformer from '../inlineArrayTransformer' 6 | import trimResultTransformer from '../trimResultTransformer' 7 | 8 | const commaListsAnd = new TemplateTag( 9 | inlineArrayTransformer({ separator: ',', conjunction: 'and' }), 10 | stripIndentTransformer, 11 | trimResultTransformer 12 | ) 13 | 14 | export default commaListsAnd 15 | -------------------------------------------------------------------------------- /src/oneLineInlineLists/oneLineInlineLists.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | import TemplateTag from '../TemplateTag' 4 | import inlineArrayTransformer from '../inlineArrayTransformer' 5 | import trimResultTransformer from '../trimResultTransformer' 6 | import replaceResultTransformer from '../replaceResultTransformer' 7 | 8 | const oneLineInlineLists = new TemplateTag( 9 | inlineArrayTransformer, 10 | replaceResultTransformer(/(?:\s+)/g, ' '), 11 | trimResultTransformer 12 | ) 13 | 14 | export default oneLineInlineLists 15 | -------------------------------------------------------------------------------- /src/commaLists/commaLists.test.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | import test from 'ava' 4 | import commaLists from './commaLists' 5 | import {readFromFixture} from '../utils' 6 | 7 | const val = 'amaze' 8 | 9 | test('includes arrays as comma-separated list', async (t) => { 10 | const fruits = ['apple', 'banana', 'kiwi'] 11 | const expected = await readFromFixture('commaLists') 12 | const actual = commaLists` 13 | Doge <3's these fruits: ${fruits} 14 | they are ${val} 15 | ` 16 | t.is(actual, expected) 17 | }) 18 | -------------------------------------------------------------------------------- /src/inlineLists/inlineLists.test.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | import test from 'ava' 3 | import inlineLists from './inlineLists' 4 | import {readFromFixture} from '../utils' 5 | 6 | const val = 'amaze' 7 | 8 | test('includes arrays as space-separated list', async (t) => { 9 | const fruits = ['apple', 'banana', 'kiwi'] 10 | const expected = await readFromFixture('inlineLists') 11 | const actual = inlineLists` 12 | Doge <3's these fruits: ${fruits} 13 | they are ${val} 14 | ` 15 | t.is(actual, expected) 16 | }) 17 | -------------------------------------------------------------------------------- /src/oneLineCommaLists/oneLineCommaLists.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | import TemplateTag from '../TemplateTag' 4 | import inlineArrayTransformer from '../inlineArrayTransformer' 5 | import trimResultTransformer from '../trimResultTransformer' 6 | import replaceResultTransformer from '../replaceResultTransformer' 7 | 8 | const oneLineCommaLists = new TemplateTag( 9 | inlineArrayTransformer({ separator: ',' }), 10 | replaceResultTransformer(/(?:\s+)/g, ' '), 11 | trimResultTransformer 12 | ) 13 | 14 | export default oneLineCommaLists 15 | -------------------------------------------------------------------------------- /src/commaListsOr/commaListsOr.test.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | import test from 'ava' 4 | import commaListsOr from './commaListsOr' 5 | import {readFromFixture} from '../utils' 6 | 7 | const val = 'amaze' 8 | 9 | test('includes arrays as comma-separated list with "or"', async (t) => { 10 | const fruits = ['apple', 'banana', 'kiwi'] 11 | const expected = await readFromFixture('commaListsOr') 12 | const actual = commaListsOr` 13 | Doge <3's these fruits: ${fruits} 14 | they are ${val} 15 | ` 16 | t.is(actual, expected) 17 | }) 18 | -------------------------------------------------------------------------------- /src/commaListsAnd/commaListsAnd.test.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | import test from 'ava' 4 | import commaListsAnd from './commaListsAnd' 5 | import {readFromFixture} from '../utils' 6 | 7 | const val = 'amaze' 8 | 9 | test('includes arrays as comma-separated list with "and"', async (t) => { 10 | const fruits = ['apple', 'banana', 'kiwi'] 11 | const expected = await readFromFixture('commaListsAnd') 12 | const actual = commaListsAnd` 13 | Doge <3's these fruits: ${fruits} 14 | they are ${val} 15 | ` 16 | t.is(actual, expected) 17 | }) 18 | -------------------------------------------------------------------------------- /src/oneLineCommaListsOr/oneLineCommaListsOr.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | import TemplateTag from '../TemplateTag' 4 | import inlineArrayTransformer from '../inlineArrayTransformer' 5 | import trimResultTransformer from '../trimResultTransformer' 6 | import replaceResultTransformer from '../replaceResultTransformer' 7 | 8 | const oneLineCommaListsOr = new TemplateTag( 9 | inlineArrayTransformer({ separator: ',', conjunction: 'or' }), 10 | replaceResultTransformer(/(?:\s+)/g, ' '), 11 | trimResultTransformer 12 | ) 13 | 14 | export default oneLineCommaListsOr 15 | -------------------------------------------------------------------------------- /src/oneLineCommaListsAnd/oneLineCommaListsAnd.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | import TemplateTag from '../TemplateTag' 4 | import inlineArrayTransformer from '../inlineArrayTransformer' 5 | import trimResultTransformer from '../trimResultTransformer' 6 | import replaceResultTransformer from '../replaceResultTransformer' 7 | 8 | const oneLineCommaListsAnd = new TemplateTag( 9 | inlineArrayTransformer({ separator: ',', conjunction: 'and' }), 10 | replaceResultTransformer(/(?:\s+)/g, ' '), 11 | trimResultTransformer 12 | ) 13 | 14 | export default oneLineCommaListsAnd 15 | -------------------------------------------------------------------------------- /src/oneLineCommaLists/oneLineCommaLists.test.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | import test from 'ava' 4 | import oneLineCommaLists from './oneLineCommaLists' 5 | import {readFromFixture} from '../utils' 6 | 7 | const val = 'amaze' 8 | 9 | test('includes arrays as comma-separated list on one line', async (t) => { 10 | const fruits = ['apple', 'banana', 'kiwi'] 11 | const expected = await readFromFixture('oneLineCommaLists') 12 | const actual = oneLineCommaLists` 13 | Doge <3's these fruits: ${fruits} 14 | they are ${val} 15 | ` 16 | t.is(actual, expected) 17 | }) 18 | -------------------------------------------------------------------------------- /src/oneLineInlineLists/oneLineInlineLists.test.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | import test from 'ava' 4 | import oneLineInlineLists from './oneLineInlineLists' 5 | import {readFromFixture} from '../utils' 6 | 7 | const val = 'amaze' 8 | 9 | test('includes arrays as inline list on one line', async (t) => { 10 | const fruits = ['apple', 'banana', 'kiwi'] 11 | const expected = await readFromFixture('oneLineInlineLists') 12 | const actual = oneLineInlineLists` 13 | Doge <3's these fruits: ${fruits} 14 | they are ${val} 15 | ` 16 | t.is(actual, expected) 17 | }) 18 | -------------------------------------------------------------------------------- /src/removeNonPrintingValuesTransformer/removeNonPrintingValuesTransformer.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const isValidValue = (x) => 4 | x != null && 5 | !Number.isNaN(x) && 6 | typeof x !== 'boolean' 7 | 8 | const removeNonPrintingValuesTransformer = () => ({ 9 | onSubstitution (substitution) { 10 | if (Array.isArray(substitution)) { 11 | return substitution.filter(isValidValue) 12 | } 13 | if (isValidValue(substitution)) { 14 | return substitution 15 | } 16 | return '' 17 | } 18 | }) 19 | 20 | export default removeNonPrintingValuesTransformer 21 | -------------------------------------------------------------------------------- /src/splitStringTransformer/splitStringTransformer.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const splitStringTransformer = (splitBy) => ({ 4 | onSubstitution (substitution, resultSoFar) { 5 | if (splitBy != null && typeof splitBy === 'string') { 6 | if (typeof substitution === 'string' && substitution.includes(splitBy)) { 7 | substitution = substitution.split(splitBy) 8 | } 9 | } else { 10 | throw new Error('You need to specify a string character to split by.') 11 | } 12 | return substitution 13 | } 14 | }) 15 | 16 | export default splitStringTransformer 17 | -------------------------------------------------------------------------------- /src/oneLineCommaListsOr/oneLineCommaListsOr.test.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | import test from 'ava' 3 | import oneLineCommaListsOr from './oneLineCommaListsOr' 4 | import {readFromFixture} from '../utils' 5 | 6 | const val = 'amaze' 7 | 8 | test('includes arrays as comma-separated list on one line with "or"', async (t) => { 9 | const fruits = ['apple', 'banana', 'kiwi'] 10 | const expected = await readFromFixture('oneLineCommaListsOr') 11 | const actual = oneLineCommaListsOr` 12 | Doge <3's these fruits: ${fruits} 13 | they are ${val} 14 | ` 15 | t.is(actual, expected) 16 | }) 17 | -------------------------------------------------------------------------------- /src/oneLineCommaListsAnd/oneLineCommaListsAnd.test.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | import test from 'ava' 4 | import oneLineCommaListsAnd from './oneLineCommaListsAnd' 5 | import {readFromFixture} from '../utils' 6 | 7 | const val = 'amaze' 8 | 9 | test('includes arrays as comma-separated list on one line with "and"', async (t) => { 10 | const fruits = ['apple', 'banana', 'kiwi'] 11 | const expected = await readFromFixture('oneLineCommaListsAnd') 12 | const actual = oneLineCommaListsAnd` 13 | Doge <3's these fruits: ${fruits} 14 | they are ${val} 15 | ` 16 | t.is(actual, expected) 17 | }) 18 | -------------------------------------------------------------------------------- /src/utils/readFromFixture/readFromFixture.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | import fs from 'fs' 4 | import node from 'when/node' 5 | 6 | /** 7 | * reads the text contents of .txt in the fixtures folder 8 | * relative to the caller module's test file 9 | * @param {String} name - the name of the fixture you want to read 10 | * @return {Promise} - the retrieved fixture's file contents 11 | */ 12 | export default function readFromFixture (name) { 13 | return node.call(fs.readFile, `./fixtures/${name}.txt`, 'utf8') 14 | .then((contents) => contents.replace(/\r\n/g, '\n').trim()) 15 | } 16 | -------------------------------------------------------------------------------- /contributing.md: -------------------------------------------------------------------------------- 1 | # Contribution Guidelines 2 | 3 | ## Setup 4 | 5 | 1. Fork this repository 6 | 2. `git clone` your fork down to your local machine 7 | 3. `cd` into the directory for your fork 8 | 4. run `npm install` 9 | 10 | ## Tests 11 | 12 | Please include _passing_ tests for any new logic or features you include in your contribution. This project uses [AVA](/sindresorhus/ava) to run tests and is setup to look for files that end with `.test.js` in the `src` directory. 13 | 14 | Furthermore, this project uses the [StandardJS](/feross/standard) code style, 15 | and tests will _fail_ if your code does not adhere to this style. 16 | -------------------------------------------------------------------------------- /src/replaceSubstitutionTransformer/replaceSubstitutionTransformer.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const replaceSubstitutionTransformer = (replaceWhat, replaceWith) => ({ 4 | onSubstitution (substitution, resultSoFar) { 5 | if (replaceWhat == null || replaceWith == null) { 6 | throw new Error('replaceSubstitutionTransformer requires at least 2 arguments.') 7 | } 8 | 9 | // Do not touch if null or undefined 10 | if (substitution == null) { 11 | return substitution 12 | } else { 13 | return substitution.toString().replace(replaceWhat, replaceWith) 14 | } 15 | } 16 | }) 17 | 18 | export default replaceSubstitutionTransformer 19 | -------------------------------------------------------------------------------- /src/html/html.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | import TemplateTag from '../TemplateTag' 4 | import stripIndentTransformer from '../stripIndentTransformer' 5 | import inlineArrayTransformer from '../inlineArrayTransformer' 6 | import trimResultTransformer from '../trimResultTransformer' 7 | import splitStringTransformer from '../splitStringTransformer' 8 | import removeNonPrintingValuesTransformer from '../removeNonPrintingValuesTransformer' 9 | 10 | const html = new TemplateTag( 11 | splitStringTransformer('\n'), 12 | removeNonPrintingValuesTransformer, 13 | inlineArrayTransformer, 14 | stripIndentTransformer, 15 | trimResultTransformer 16 | ) 17 | 18 | export default html 19 | -------------------------------------------------------------------------------- /appveyor.yml: -------------------------------------------------------------------------------- 1 | version: "{build}" 2 | environment: 3 | matrix: 4 | - nodejs_version: "6" 5 | - nodejs_version: "5" 6 | platform: 7 | - x86 8 | - x64 9 | install: 10 | - ps: Install-Product node $env:nodejs_version $env:platform 11 | - set CI=true 12 | - set AVA_APPVEYOR=true 13 | - npm install -g npm@latest || (timeout 30 && npm install -g npm@latest) 14 | - set PATH=%APPDATA%\npm;%PATH% 15 | - npm install || (timeout 30 && npm install) 16 | test_script: 17 | # Output useful info for debugging. 18 | - node --version && npm --version 19 | - cmd: npm run test-ci 20 | build: off 21 | shallow_clone: true 22 | clone_depth: 1 23 | matrix: 24 | fast_finish: true 25 | cache: 26 | - node_modules -> package.json # local npm modules 27 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Created by https://www.gitignore.io/api/node 2 | 3 | ### Node ### 4 | # Logs 5 | logs 6 | *.log 7 | npm-debug.log* 8 | 9 | lib 10 | es 11 | .DS_Store 12 | 13 | # Runtime data 14 | pids 15 | *.pid 16 | *.seed 17 | 18 | # Directory for instrumented libs generated by jscoverage/JSCover 19 | lib-cov 20 | 21 | # Coverage directory used by tools like istanbul 22 | coverage 23 | .nyc_output 24 | 25 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 26 | .grunt 27 | 28 | # node-waf configuration 29 | .lock-wscript 30 | 31 | # Compiled binary addons (http://nodejs.org/api/addons.html) 32 | build/Release 33 | 34 | # Dependency directory 35 | # https://docs.npmjs.com/misc/faq#should-i-check-my-node-modules-folder-into-git 36 | node_modules 37 | -------------------------------------------------------------------------------- /src/replaceResultTransformer/replaceResultTransformer.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | /** 4 | * Replaces tabs, newlines and spaces with the chosen value when they occur in sequences 5 | * @param {(String|RegExp)} replaceWhat - the value or pattern that should be replaced 6 | * @param {*} replaceWith - the replacement value 7 | * @return {Object} - a TemplateTag transformer 8 | */ 9 | const replaceResultTransformer = (replaceWhat, replaceWith) => ({ 10 | onEndResult (endResult) { 11 | if (replaceWhat == null || replaceWith == null) { 12 | throw new Error('replaceResultTransformer requires at least 2 arguments.') 13 | } 14 | return endResult.replace(replaceWhat, replaceWith) 15 | } 16 | }) 17 | 18 | export default replaceResultTransformer 19 | -------------------------------------------------------------------------------- /src/oneLine/oneLine.test.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | import test from 'ava' 4 | import oneLine from './oneLine' 5 | import {readFromFixture} from '../utils' 6 | 7 | const val = 'amaze' 8 | 9 | test('reduces text to one line, replacing newlines with spaces', async (t) => { 10 | const expected = await readFromFixture('oneLine') 11 | const actual = oneLine` 12 | wow such doge 13 | is very ${val} 14 | at one line neat 15 | from multiline 16 | ` 17 | t.is(actual, expected) 18 | }) 19 | 20 | test('preserves whitespace within input lines, replacing only newlines', async (t) => { 21 | const expected = await readFromFixture('oneLine-sentence') 22 | const actual = oneLine` 23 | Sentences also work. Double 24 | spacing is preserved. 25 | ` 26 | t.is(actual, expected) 27 | }) 28 | -------------------------------------------------------------------------------- /src/trimResultTransformer/trimResultTransformer.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | /** 4 | * TemplateTag transformer that trims whitespace on the end result of a tagged template 5 | * @param {String} side = '' - The side of the string to trim. Can be 'left' or 'right' 6 | * @return {Object} - a TemplateTag transformer 7 | */ 8 | const trimResultTransformer = (side = '') => ({ 9 | onEndResult (endResult) { 10 | side = side.toLowerCase() 11 | // uppercase the first letter of side value 12 | if (side === 'left' || side === 'right') { 13 | side = side.charAt(0).toUpperCase() + side.slice(1) 14 | } else if (side !== '') { 15 | throw new Error(`Side not supported: ${side}`) 16 | } 17 | return endResult[`trim${side}`]() 18 | } 19 | }) 20 | 21 | export default trimResultTransformer 22 | -------------------------------------------------------------------------------- /src/html/html.test.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | import test from 'ava' 4 | import html from './html' 5 | import {readFromFixture} from '../utils' 6 | 7 | const val = 'amaze' 8 | const nil = null 9 | 10 | test('renders HTML, including arrays', async (t) => { 11 | const fruits = ['apple', 'banana', 'kiwi'] 12 | const expected = await readFromFixture('html') 13 | const actual = html` 14 |

${val}${nil}

15 | 18 | ` 19 | t.is(actual, expected) 20 | }) 21 | 22 | test('converts strings containing newlines into proper indented output', async (t) => { 23 | const newlines = '
  • one
  • \n
  • two
  • ' 24 | const expected = await readFromFixture('newline-conversion') 25 | const actual = html` 26 |

    ${val}${nil}

    27 | 31 | ` 32 | t.is(actual, expected) 33 | }) 34 | -------------------------------------------------------------------------------- /src/safeHtml/safeHtml.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | import TemplateTag from '../TemplateTag' 4 | import stripIndentTransformer from '../stripIndentTransformer' 5 | import inlineArrayTransformer from '../inlineArrayTransformer' 6 | import trimResultTransformer from '../trimResultTransformer' 7 | import splitStringTransformer from '../splitStringTransformer' 8 | import replaceSubstitutionTransformer from '../replaceSubstitutionTransformer' 9 | 10 | const safeHtml = new TemplateTag( 11 | splitStringTransformer('\n'), 12 | inlineArrayTransformer, 13 | stripIndentTransformer, 14 | trimResultTransformer, 15 | replaceSubstitutionTransformer(/&/g, '&'), 16 | replaceSubstitutionTransformer(//g, '>'), 18 | replaceSubstitutionTransformer(/"/g, '"'), 19 | replaceSubstitutionTransformer(/'/g, '''), 20 | replaceSubstitutionTransformer(/`/g, '`') 21 | ) 22 | 23 | export default safeHtml 24 | -------------------------------------------------------------------------------- /src/utils/index.test.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | import fs from 'fs' 4 | import path from 'path' 5 | import test from 'ava' 6 | import mm from 'micromatch' 7 | import node from 'when/node' 8 | 9 | const observe = [ 10 | '*', 11 | '!index.js', 12 | '!index.test.js' 13 | ] 14 | 15 | test.beforeEach(async (t) => { 16 | t.context.modules = mm(await node.call(fs.readdir, __dirname), observe) 17 | }) 18 | 19 | test('utils exports all the right modules directly', async (t) => { 20 | const modules = t.context.modules 21 | t.plan(modules.length) 22 | modules.forEach((module) => { 23 | const _path = path.join(__dirname, module) 24 | t.true(require(_path) != null, `${module} is not exported properly`) 25 | }) 26 | }) 27 | 28 | test('utils exports all the right modules as props', async (t) => { 29 | const modules = t.context.modules 30 | t.plan(modules.length) 31 | modules.forEach((module) => { 32 | t.true(require('./index')[module] != null, `${module} is not exported properly`) 33 | }) 34 | }) 35 | -------------------------------------------------------------------------------- /src/index.test.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | import fs from 'fs' 4 | import path from 'path' 5 | import test from 'ava' 6 | import mm from 'micromatch' 7 | import node from 'when/node' 8 | 9 | const observe = [ 10 | '*', 11 | '!utils', 12 | '!index.js', 13 | '!index.test.js' 14 | ] 15 | 16 | test.beforeEach(async (t) => { 17 | t.context.modules = mm(await node.call(fs.readdir, __dirname), observe) 18 | }) 19 | 20 | test('common-tags exports all the right modules directly', async (t) => { 21 | const modules = t.context.modules 22 | t.plan(modules.length) 23 | modules.forEach((module) => { 24 | const _path = path.join(__dirname, module) 25 | t.true(require(_path) != null, `${module} is not exported properly`) 26 | }) 27 | }) 28 | 29 | test('common-tags exports all the right modules as props', async (t) => { 30 | const modules = t.context.modules 31 | t.plan(modules.length) 32 | modules.forEach((module) => { 33 | t.true(require('./index')[module] != null, `${module} is not exported properly`) 34 | }) 35 | }) 36 | -------------------------------------------------------------------------------- /license.md: -------------------------------------------------------------------------------- 1 | License (MIT) 2 | ------------- 3 | 4 | Copyright © Declan de Wet 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 7 | 8 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 9 | 10 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 11 | -------------------------------------------------------------------------------- /src/stripIndent/stripIndent.test.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | import test from 'ava' 4 | import stripIndent from './stripIndent' 5 | import {readFromFixture} from '../utils' 6 | 7 | const val = 'amaze' 8 | 9 | test('strips indentation', async (t) => { 10 | const expected = await readFromFixture('stripIndent') 11 | const actual = stripIndent` 12 | wow such indent gone 13 | very ${val} 14 | ` 15 | t.is(actual, expected) 16 | }) 17 | 18 | test('strips larger indentation', async (t) => { 19 | const expected = await readFromFixture('stripIndent') 20 | const actual = stripIndent` 21 | wow such indent gone 22 | very ${val} 23 | ` 24 | t.is(actual, expected) 25 | }) 26 | 27 | test('maintains deeper indentation', async (t) => { 28 | const expected = await readFromFixture('maintainIndent') 29 | const actual = stripIndent` 30 | wow such indent gone 31 | very ${val} 32 | ` 33 | t.is(actual, expected) 34 | }) 35 | 36 | test('does nothing if there are no indents', async (t) => { 37 | const expected = 'wow such doge' 38 | const actual = stripIndent`wow such doge` 39 | t.is(actual, expected) 40 | }) 41 | -------------------------------------------------------------------------------- /src/trimResultTransformer/trimResultTransformer.test.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | import test from 'ava' 4 | import TemplateTag from '../TemplateTag' 5 | import stripIndentTransformer from '../stripIndentTransformer' 6 | import trimResultTransformer from './trimResultTransformer' 7 | 8 | test('trims outer padding', (t) => { 9 | const trim = new TemplateTag(trimResultTransformer) 10 | t.is(trim` foo `, 'foo') 11 | }) 12 | 13 | test('trims left padding', (t) => { 14 | const trimLeft = new TemplateTag(trimResultTransformer('left')) 15 | t.is(trimLeft` foo `, 'foo ') 16 | }) 17 | 18 | test('trims right padding', (t) => { 19 | const trimRight = new TemplateTag(trimResultTransformer('right')) 20 | t.is(trimRight` foo `, ' foo') 21 | }) 22 | 23 | test('throws an error if invalid side supplied', (t) => { 24 | const trimUp = new TemplateTag(trimResultTransformer('up')) 25 | t.throws(() => trimUp`foo`) 26 | }) 27 | 28 | test('can be used sequentially', (t) => { 29 | const trimLeft = new TemplateTag(stripIndentTransformer, trimResultTransformer('left')) 30 | t.is(trimLeft` foo `, 'foo ') 31 | t.is(trimLeft` bar `, 'bar ') 32 | }) 33 | -------------------------------------------------------------------------------- /src/removeNonPrintingValuesTransformer/removeNonPrintingValuesTransformer.test.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | import test from 'ava' 4 | import TemplateTag from '../TemplateTag' 5 | import inlineArrayTransformer from '../inlineArrayTransformer' 6 | import removeNonPrintingValuesTransformer from '../removeNonPrintingValuesTransformer' 7 | 8 | test('removes null', (t) => { 9 | const remove = new TemplateTag(removeNonPrintingValuesTransformer) 10 | const nil = null 11 | t.is(remove`a${nil}z`, 'az') 12 | }) 13 | 14 | test('removes bool', (t) => { 15 | const remove = new TemplateTag(removeNonPrintingValuesTransformer) 16 | const yep = true 17 | const nope = false 18 | t.is(remove`a${yep}${nope}z`, 'az') 19 | }) 20 | 21 | test('removes NaN', (t) => { 22 | const remove = new TemplateTag(removeNonPrintingValuesTransformer) 23 | const nan = 0 / 0 24 | t.is(remove`a${nan}z`, 'az') 25 | }) 26 | 27 | test('removes non-printing array values', (t) => { 28 | const remove = new TemplateTag( 29 | removeNonPrintingValuesTransformer, 30 | inlineArrayTransformer 31 | ) 32 | const val = ['foo', undefined, 'bar', null] 33 | t.is(remove`a ${val} z`, 'a foo bar z') 34 | }) 35 | -------------------------------------------------------------------------------- /src/splitStringTransformer/splitStringTransformer.test.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | import test from 'ava' 4 | import TemplateTag from '../TemplateTag' 5 | import inlineArrayTransformer from '../inlineArrayTransformer' 6 | import splitStringTransformer from './splitStringTransformer' 7 | 8 | test('splits a string substitution into an array by the specified character', (t) => { 9 | const tag = new TemplateTag( 10 | splitStringTransformer('\n'), 11 | inlineArrayTransformer 12 | ) 13 | t.is(tag`foo ${'bar\nbaz'}`, 'foo bar baz') 14 | }) 15 | 16 | test('ignores string if splitBy character is not found', (t) => { 17 | const tag = new TemplateTag(splitStringTransformer('.')) 18 | t.is(tag`foo ${'bar,baz'}`, 'foo bar,baz') 19 | }) 20 | 21 | test('ignores substitution if it is not a string', (t) => { 22 | const tag = new TemplateTag(splitStringTransformer('')) 23 | t.is(tag`foo ${5}`, 'foo 5') 24 | }) 25 | 26 | test('throws an error if splitBy param is undefined or not a string', (t) => { 27 | const tag1 = new TemplateTag(splitStringTransformer) 28 | const tag2 = new TemplateTag(splitStringTransformer(5)) 29 | t.throws(() => tag1`foo ${'bar'}`) 30 | t.throws(() => tag2`foo ${'bar'}`) 31 | }) 32 | -------------------------------------------------------------------------------- /src/stripIndentTransformer/stripIndentTransformer.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | /** 4 | * strips indentation from a template literal 5 | * @param {String} type = 'initial' - whether to remove all indentation or just leading indentation. can be 'all' or 'initial' 6 | * @return {Object} - a TemplateTag transformer 7 | */ 8 | const stripIndentTransformer = (type = 'initial') => ({ 9 | onEndResult (endResult) { 10 | if (type === 'initial') { 11 | // remove the shortest leading indentation from each line 12 | const match = endResult.match(/^[ \t]*(?=\S)/gm) 13 | // return early if there's nothing to strip 14 | if (match === null) { 15 | return endResult 16 | } 17 | const indent = Math.min(...match.map(el => el.length)) 18 | const regexp = new RegExp('^[ \\t]{' + indent + '}', 'gm') 19 | endResult = indent > 0 ? endResult.replace(regexp, '') : endResult 20 | } else if (type === 'all') { 21 | // remove all indentation from each line 22 | endResult = endResult.split('\n').map(line => line.trimLeft()).join('\n') 23 | } else { 24 | throw new Error(`Unknown type: ${type}`) 25 | } 26 | return endResult 27 | } 28 | }) 29 | 30 | export default stripIndentTransformer 31 | -------------------------------------------------------------------------------- /src/replaceResultTransformer/replaceResultTransformer.test.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | import test from 'ava' 4 | import TemplateTag from '../TemplateTag' 5 | import replaceResultTransformer from './replaceResultTransformer' 6 | import trimResultTransformer from '../trimResultTransformer' 7 | 8 | test('replaces sequential whitespace with a single space', (t) => { 9 | const oneLine = new TemplateTag( 10 | replaceResultTransformer(/(?:\s+)/g, ' '), 11 | trimResultTransformer 12 | ) 13 | const expected = 'foo bar baz' 14 | const actual = oneLine` 15 | foo 16 | bar 17 | baz 18 | ` 19 | t.is(actual, expected) 20 | }) 21 | 22 | test('can be set so sequence requires a newline at the beginning before triggering replacement', (t) => { 23 | const oneLineTrim = new TemplateTag( 24 | replaceResultTransformer(/(?:\n\s+)/g, ''), 25 | trimResultTransformer 26 | ) 27 | const expected = 'https://google.com?utm_source=common-tags' 28 | const actual = oneLineTrim` 29 | https:// 30 | google.com 31 | ?utm_source=common-tags 32 | ` 33 | t.is(actual, expected) 34 | }) 35 | 36 | test('throws error if no arguments are supplied', (t) => { 37 | const tag = new TemplateTag(replaceResultTransformer) 38 | t.throws(() => tag`foo`) 39 | }) 40 | -------------------------------------------------------------------------------- /src/safeHtml/safeHtml.test.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | import test from 'ava' 4 | import safeHtml from './safeHtml' 5 | import {readFromFixture} from '../utils' 6 | 7 | const val = 'amaze' 8 | 9 | test('renders HTML, including arrays', async (t) => { 10 | const fruits = ['apple', 'banana', 'kiwi'] 11 | const expected = await readFromFixture('normal-html') 12 | const actual = safeHtml` 13 |

    ${val}

    14 | 17 | ` 18 | t.is(actual, expected) 19 | }) 20 | 21 | test('converts strings containing newlines into proper indented output', async (t) => { 22 | const newlines = 'one\ntwo' 23 | const expected = await readFromFixture('newline-conversion') 24 | const actual = safeHtml` 25 |

    ${val}

    26 | 30 | ` 31 | t.is(actual, expected) 32 | }) 33 | 34 | test('correctly escapes HTML tags on substitution', async (t) => { 35 | const fruits = ['apple', 'banana', 'kiwi', '

    dangerous fruit

    '] 36 | const expected = await readFromFixture('escaped-html') 37 | const actual = safeHtml` 38 |

    ${val}

    39 | 42 | ` 43 | t.is(actual, expected) 44 | }) 45 | -------------------------------------------------------------------------------- /src/replaceSubstitutionTransformer/replaceSubstitutionTransformer.test.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | import test from 'ava' 4 | import replaceSubstitutionTransformer from './replaceSubstitutionTransformer' 5 | import TemplateTag from '../TemplateTag' 6 | 7 | test('only operates on substitutions', (t) => { 8 | const tag = new TemplateTag( 9 | replaceSubstitutionTransformer(//g, '>') 11 | ) 12 | t.is(tag`

    foo${''}

    `, '

    foo<bar></bar>

    ') 13 | }) 14 | 15 | test('does not touch undefined and null substitutions', (t) => { 16 | const tag = new TemplateTag(replaceSubstitutionTransformer(/u/g, '')) 17 | t.is(tag`foo ${undefined} bar ${null}`, 'foo undefined bar null') 18 | }) 19 | 20 | test('works on numbers', (t) => { 21 | const tag = new TemplateTag(replaceSubstitutionTransformer(/2/g, '3')) 22 | t.is(tag`foo ${2} bar ${43.12}`, 'foo 3 bar 43.13') 23 | }) 24 | 25 | test('works on arrays', (t) => { 26 | const tag = new TemplateTag(replaceSubstitutionTransformer(/foo/g, 'bar')) 27 | t.is(tag`${['foo', 'bar', 'foo']}`, 'bar,bar,bar') 28 | }) 29 | 30 | test('throws error if no arguments are supplied when used', (t) => { 31 | const tag = new TemplateTag(replaceSubstitutionTransformer()) 32 | t.throws(() => tag`${'foo'}`) 33 | }) 34 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | // core 4 | export TemplateTag from './TemplateTag' 5 | 6 | // transformers 7 | export trimResultTransformer from './trimResultTransformer' 8 | export stripIndentTransformer from './stripIndentTransformer' 9 | export replaceResultTransformer from './replaceResultTransformer' 10 | export replaceSubstitutionTransformer from './replaceSubstitutionTransformer' 11 | export inlineArrayTransformer from './inlineArrayTransformer' 12 | export splitStringTransformer from './splitStringTransformer' 13 | export removeNonPrintingValuesTransformer from './removeNonPrintingValuesTransformer' 14 | 15 | // tags 16 | export commaLists from './commaLists' 17 | export commaListsAnd from './commaListsAnd' 18 | export commaListsOr from './commaListsOr' 19 | export html from './html' 20 | export codeBlock from './codeBlock' 21 | export source from './source' 22 | export safeHtml from './safeHtml' 23 | export oneLine from './oneLine' 24 | export oneLineTrim from './oneLineTrim' 25 | export oneLineCommaLists from './oneLineCommaLists' 26 | export oneLineCommaListsOr from './oneLineCommaListsOr' 27 | export oneLineCommaListsAnd from './oneLineCommaListsAnd' 28 | export inlineLists from './inlineLists' 29 | export oneLineInlineLists from './oneLineInlineLists' 30 | export stripIndent from './stripIndent' 31 | export stripIndents from './stripIndents' 32 | -------------------------------------------------------------------------------- /src/inlineArrayTransformer/inlineArrayTransformer.test.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | import test from 'ava' 4 | import inlineArrayTransformer from './inlineArrayTransformer' 5 | import TemplateTag from '../TemplateTag' 6 | 7 | test('only operates on arrays', (t) => { 8 | const tag = new TemplateTag(inlineArrayTransformer) 9 | t.is(tag`foo ${5} ${'bar'}`, 'foo 5 bar') 10 | }) 11 | 12 | test('includes an array as a comma-separated list', (t) => { 13 | const tag = new TemplateTag(inlineArrayTransformer({ separator: ',' })) 14 | t.is(tag`I like ${['apple', 'banana', 'kiwi']}`, 'I like apple, banana, kiwi') 15 | }) 16 | 17 | test('replaces last separator with a conjunction', (t) => { 18 | const tag = new TemplateTag( 19 | inlineArrayTransformer({ separator: ',', conjunction: 'and' }) 20 | ) 21 | t.is(tag`I like ${['apple', 'banana', 'kiwi']}`, 'I like apple, banana and kiwi') 22 | }) 23 | 24 | test('does not require preceded whitespace', (t) => { 25 | const tag = new TemplateTag( 26 | inlineArrayTransformer({ separator: ',' }) 27 | ) 28 | t.is(tag`My friends are (${['bob', 'sally', 'jim']})`, 'My friends are (bob, sally, jim)') 29 | }) 30 | 31 | test('supports serial/oxford separators', (t) => { 32 | const tag = new TemplateTag( 33 | inlineArrayTransformer({ separator: ',', conjunction: 'or', serial: true }) 34 | ) 35 | t.is(tag`My friends are always ${['dramatic', 'emotional', 'needy']}`, 'My friends are always dramatic, emotional, or needy') 36 | }) 37 | -------------------------------------------------------------------------------- /src/stripIndentTransformer/stripIndentTransformer.test.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | import test from 'ava' 4 | import TemplateTag from '../TemplateTag' 5 | import stripIndentTransformer from './stripIndentTransformer' 6 | import trimResultTransformer from '../trimResultTransformer' 7 | import {readFromFixture} from '../utils' 8 | 9 | test('default behaviour removes the leading indent, but preserves the rest', async (t) => { 10 | const stripIndent = new TemplateTag( 11 | stripIndentTransformer, 12 | trimResultTransformer 13 | ) 14 | const expected = await readFromFixture('stripIndent') 15 | const actual = stripIndent` 16 | foo bar baz 17 | bar baz foo 18 | baz foo bar 19 | wow such doge 20 | ` 21 | t.is(actual, expected) 22 | }) 23 | 24 | test('type "initial" does not remove indents if there is no need to do so', (t) => { 25 | const stripIndent = new TemplateTag( 26 | stripIndentTransformer, 27 | trimResultTransformer 28 | ) 29 | t.is(stripIndent``, '') 30 | t.is(stripIndent`foo`, 'foo') 31 | t.is(stripIndent`foo\nbar`, 'foo\nbar') 32 | }) 33 | 34 | test('removes all indents if type is "all"', async (t) => { 35 | const stripIndents = new TemplateTag( 36 | stripIndentTransformer('all'), 37 | trimResultTransformer 38 | ) 39 | const expected = await readFromFixture('stripIndents') 40 | const actual = stripIndents` 41 | foo bar baz 42 | bar baz foo 43 | baz foo bar 44 | wow such doge 45 | ` 46 | t.is(actual, expected) 47 | }) 48 | 49 | test('throws an error if encounters invalid type', (t) => { 50 | const stripBlueIndents = new TemplateTag(stripIndentTransformer('blue')) 51 | t.throws(() => stripBlueIndents`foo`) 52 | }) 53 | -------------------------------------------------------------------------------- /src/inlineArrayTransformer/inlineArrayTransformer.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const defaults = { 4 | separator: '', 5 | conjunction: '', 6 | serial: false 7 | } 8 | 9 | /** 10 | * Converts an array substitution to a string containing a list 11 | * @param {String} [opts.separator = ''] - the character that separates each item 12 | * @param {String} [opts.conjunction = ''] - replace the last separator with this 13 | * @param {Boolean} [opts.serial = false] - include the separator before the conjunction? (Oxford comma use-case) 14 | * 15 | * @return {Object} - a TemplateTag transformer 16 | */ 17 | const inlineArrayTransformer = (opts = defaults) => ({ 18 | onSubstitution (substitution, resultSoFar) { 19 | // only operate on arrays 20 | if (Array.isArray(substitution)) { 21 | const separator = opts.separator 22 | const conjunction = opts.conjunction 23 | const serial = opts.serial 24 | // join each item in the array into a string where each item is separated by separator 25 | // be sure to maintain indentation 26 | const indent = resultSoFar.match(/(\s+)$/) 27 | if (indent) { 28 | substitution = substitution.join(separator + indent[1]) 29 | } else { 30 | substitution = substitution.join(separator + ' ') 31 | } 32 | // if conjunction is set, replace the last separator with conjunction 33 | if (conjunction) { 34 | const separatorIndex = substitution.lastIndexOf(separator) 35 | substitution = substitution 36 | .substr(0, separatorIndex) + (serial ? separator : '') + ' ' + 37 | conjunction + substitution.substr(separatorIndex + 1) 38 | } 39 | } 40 | return substitution 41 | } 42 | }) 43 | 44 | export default inlineArrayTransformer 45 | -------------------------------------------------------------------------------- /src/TemplateTag/TemplateTag.test.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | import test from 'ava' 4 | import TemplateTag from '../TemplateTag' 5 | 6 | test('does no processing by default', (t) => { 7 | const tag = new TemplateTag() 8 | t.is(tag`foo`, 'foo') 9 | }) 10 | 11 | test('transformer methods are optional', (t) => { 12 | const noMethods = new TemplateTag({}) 13 | const noSub = new TemplateTag({ 14 | onEndResult (endResult) { 15 | return endResult.toUpperCase() 16 | } 17 | }) 18 | const noEnd = new TemplateTag({ 19 | onSubstitution (sub) { 20 | return sub.split('').reverse().join('') 21 | } 22 | }) 23 | t.is(noMethods`foo`, 'foo') 24 | t.is(noSub`bar`, 'BAR') 25 | t.is(noEnd`foo ${'bar'}`, 'foo rab') 26 | }) 27 | 28 | test('performs a transformation & provides correct values to transform methods', (t) => { 29 | const tag = new TemplateTag({ 30 | onSubstitution (substitution, resultSoFar) { 31 | this.ctx = this.ctx || { subs: [] } 32 | this.ctx.subs.push({ substitution, resultSoFar }) 33 | return substitution 34 | }, 35 | onEndResult (endResult) { 36 | this.ctx.endResult = endResult.toUpperCase() 37 | return this.ctx 38 | } 39 | }) 40 | const data = tag`foo ${'bar'} baz ${'fizz'}` 41 | t.deepEqual(data, { 42 | endResult: 'FOO BAR BAZ FIZZ', 43 | subs: [{ 44 | substitution: 'bar', 45 | resultSoFar: 'foo ' 46 | }, { 47 | substitution: 'fizz', 48 | resultSoFar: 'foo bar baz ' 49 | }] 50 | }) 51 | }) 52 | 53 | test('automatically initiates a transformer if passed as a function', (t) => { 54 | const plugin = () => ({ 55 | onEndResult (endResult) { 56 | return endResult.toUpperCase() 57 | } 58 | }) 59 | const tag = new TemplateTag(plugin) 60 | t.is(tag`foo bar`, 'FOO BAR') 61 | }) 62 | 63 | test('supports pipeline of transformers as both argument list and as array', (t) => { 64 | const transform1 = { 65 | onSubstitution (substitution) { 66 | return substitution.replace('foo', 'doge') 67 | } 68 | } 69 | const transform2 = { 70 | onEndResult (endResult) { 71 | return endResult.toUpperCase() 72 | } 73 | } 74 | const argumentListTag = new TemplateTag(transform1, transform2) 75 | const arrayTag = new TemplateTag([transform1, transform2]) 76 | t.is(argumentListTag`wow ${'foo'}`, 'WOW DOGE') 77 | t.is(arrayTag`bow ${'foo'}`, 'BOW DOGE') 78 | }) 79 | 80 | test('supports tail processing of another tag if first argument to tag is a tag', (t) => { 81 | const tag = new TemplateTag({ 82 | onEndResult (endResult) { 83 | return endResult.toUpperCase().trim() 84 | } 85 | }) 86 | const raw = tag(String.raw)` 87 | foo bar 88 | ${500} 89 | ` 90 | t.is(raw, 'FOO BAR\n 500') 91 | }) 92 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "common-tags", 3 | "description": "a few common utility template tags for ES2015", 4 | "version": "1.4.0", 5 | "author": "Declan de Wet ", 6 | "ava": { 7 | "verbose": true, 8 | "babel": "inherit", 9 | "failFast": true, 10 | "require": [ 11 | "babel-register" 12 | ], 13 | "files": [ 14 | "src/**/*.test.js" 15 | ] 16 | }, 17 | "bugs": { 18 | "url": "http://github.com/declandewet/common-tags/issues" 19 | }, 20 | "contributors": [ 21 | "Declan de Wet (https://github.com/declandewet)", 22 | "Jason Killian (https://github.com/JKillian)", 23 | "Laurent Goudet (https://github.com/laurentgoudet)", 24 | "Kamil Ogórek (https://github.com/kamilogorek)" 25 | ], 26 | "dependencies": { 27 | "babel-runtime": "^6.18.0" 28 | }, 29 | "devDependencies": { 30 | "ava": "^0.16.0", 31 | "babel-cli": "^6.18.0", 32 | "babel-eslint": "^7.1.0", 33 | "babel-plugin-add-module-exports": "^0.2.1", 34 | "babel-plugin-transform-runtime": "^6.8.0", 35 | "babel-preset-latest": "^6.16.0", 36 | "babel-preset-stage-0": "^6.16.0", 37 | "babel-register": "^6.18.0", 38 | "codecov": "^1.0.1", 39 | "cross-env": "3.1.4", 40 | "doctoc": "^1.0.0", 41 | "micromatch": "^2.3.8", 42 | "nyc": "^8.3.2", 43 | "regenerator-runtime": "^0.10.0", 44 | "rimraf": "^2.5.2", 45 | "snazzy": "^6.0.0", 46 | "when": "^3.7.7", 47 | "which": "^1.2.11" 48 | }, 49 | "directories": { 50 | "lib": "lib" 51 | }, 52 | "engines": { 53 | "node": ">=4.0.0" 54 | }, 55 | "homepage": "https://github.com/declandewet/common-tags", 56 | "keywords": [ 57 | "array", 58 | "babel", 59 | "es2015", 60 | "es2015-tag", 61 | "es6", 62 | "es6-tag", 63 | "heredoc", 64 | "html", 65 | "indent", 66 | "indents", 67 | "line", 68 | "literal", 69 | "multi", 70 | "multiline", 71 | "normalize", 72 | "one", 73 | "oneline", 74 | "single", 75 | "singleline", 76 | "string", 77 | "strings", 78 | "strip", 79 | "tag", 80 | "tagged", 81 | "template" 82 | ], 83 | "license": "MIT", 84 | "main": "lib", 85 | "jsnext:main": "es", 86 | "module": "es", 87 | "repository": { 88 | "type": "git", 89 | "url": "https://github.com/declandewet/common-tags" 90 | }, 91 | "scripts": { 92 | "clear": "rimraf lib && rimraf es", 93 | "build": "npm run clear && npm run build:cjs && npm run build:es", 94 | "build:cjs": "cross-env BABEL_ENV=cjs babel src -d lib --ignore *.test.js", 95 | "build:es": "cross-env BABEL_ENV=es babel src -d es --ignore *.test.js", 96 | "codecov": "npm run coverage && codecov", 97 | "coverage": "nyc report --reporter=lcov", 98 | "lint": "snazzy", 99 | "precoveralls": "npm run coverage", 100 | "prerelease": "npm run build", 101 | "preversion": "doctoc readme.md --title '# :books: Table of Contents' && npm test", 102 | "release": "npm publish", 103 | "test": "npm run lint && cross-env BABEL_ENV=cjs nyc ava", 104 | "test-ci": "npm run lint && cross-env BABEL_ENV=cjs nyc ava --serial --fail-fast" 105 | }, 106 | "standard": { 107 | "parser": "babel-eslint", 108 | "ignore": [ 109 | "readme.md", 110 | "es" 111 | ] 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /src/TemplateTag/TemplateTag.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | /** 4 | * @class TemplateTag 5 | * @classdesc Consumes a pipeline of composeable transformer plugins and produces a template tag. 6 | */ 7 | export default class TemplateTag { 8 | /** 9 | * constructs a template tag 10 | * @constructs TemplateTag 11 | * @param {...Object} [...transformers] - an array or arguments list of transformers 12 | * @return {Function} - a template tag 13 | */ 14 | constructor (...transformers) { 15 | // if first argument is an array, extrude it as a list of transformers 16 | if (transformers.length && Array.isArray(transformers[0])) { 17 | transformers = transformers[0] 18 | } 19 | 20 | // if any transformers are functions, this means they are not initiated - automatically initiate them 21 | this.transformers = transformers.map((transformer) => { 22 | return typeof transformer === 'function' 23 | ? transformer() 24 | : transformer 25 | }) 26 | 27 | // return an ES2015 template tag 28 | return ::this.tag 29 | } 30 | 31 | /** 32 | * Applies all transformers to a template literal tagged with this method. 33 | * If a function is passed as the first argument, assumes the function is a template tag 34 | * and applies it to the template, returning a template tag. 35 | * @param {(Function|Array)} args[0] - Either a template tag or an array containing template strings separated by identifier 36 | * @param {...*} [args[1]] - Optional list of substitution values. 37 | * @return {(String|Function)} - Either an intermediary tag function or the results of processing the template. 38 | */ 39 | tag (...args) { 40 | // if the first argument passed is a function, assume it is a template tag and return 41 | // an intermediary tag that processes the template using the aforementioned tag, passing the 42 | // result to our tag 43 | if (typeof args[0] === 'function') { 44 | return this.interimTag.bind(this, args.shift()) 45 | } 46 | 47 | // else, return a transformed end result of processing the template with our tag 48 | return this.transformEndResult( 49 | args.shift().reduce(this.processSubstitutions.bind(this, args)) 50 | ) 51 | } 52 | 53 | /** 54 | * An intermediary template tag that receives a template tag and passes the result of calling the template with the received 55 | * template tag to our own template tag. 56 | * @param {Function} nextTag - the received template tag 57 | * @param {Array} template - the template to process 58 | * @param {...*} ...substitutions - `substitutions` is an array of all substitutions in the template 59 | * @return {*} - the final processed value 60 | */ 61 | interimTag (previousTag, template, ...substitutions) { 62 | return this.tag`${previousTag(template, ...substitutions)}` 63 | } 64 | 65 | /** 66 | * Performs bulk processing on the tagged template, transforming each substitution and then 67 | * concatenating the resulting values into a string. 68 | * @param {Array<*>} substitutions - an array of all remaining substitutions present in this template 69 | * @param {String} resultSoFar - this iteration's result string so far 70 | * @param {String} remainingPart - the template chunk after the current substitution 71 | * @return {String} - the result of joining this iteration's processed substitution with the result 72 | */ 73 | processSubstitutions (substitutions, resultSoFar, remainingPart) { 74 | const substitution = this.transformSubstitution( 75 | substitutions.shift(), 76 | resultSoFar 77 | ) 78 | return resultSoFar + substitution + remainingPart 79 | } 80 | 81 | /** 82 | * When a substitution is encountered, iterates through each transformer and applies the transformer's 83 | * `onSubstitution` method to the substitution. 84 | * @param {*} substitution - The current substitution 85 | * @param {String} resultSoFar - The result up to and excluding this substitution. 86 | * @return {*} - The final result of applying all substitution transformations. 87 | */ 88 | transformSubstitution (substitution, resultSoFar) { 89 | const cb = (res, transform) => transform.onSubstitution 90 | ? transform.onSubstitution(res, resultSoFar) 91 | : res 92 | return this.transformers.reduce(cb, substitution) 93 | } 94 | 95 | /** 96 | * Iterates through each transformer, applying the transformer's `onEndResult` method to the 97 | * template literal after all substitutions have finished processing. 98 | * @param {String} endResult - The processed template, just before it is returned from the tag 99 | * @return {String} - The final results of processing each transformer 100 | */ 101 | transformEndResult (endResult) { 102 | const cb = (res, transform) => transform.onEndResult 103 | ? transform.onEndResult(res) 104 | : res 105 | return this.transformers.reduce(cb, endResult) 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | ![](http://imgh.us/common-tags_5.png) 2 | 3 | > :bookmark: A set of **well-tested**, commonly used template literal tag functions for use in ES2015+. 4 | > 5 | > :star2: Plus some extra goodies for easily making your own tags. 6 | 7 | 8 | 9 | # :battery: Project Status 10 | 11 | | Info | Badges | 12 | | ---------- | ---------------------------------------- | 13 | | Version | [![github release](https://img.shields.io/github/release/declandewet/common-tags.svg?style=flat-square)](https://github.com/declandewet/common-tags/releases/latest) [![npm version](https://img.shields.io/npm/v/common-tags.svg?style=flat-square)](http://npmjs.org/package/common-tags) | 14 | | License | [![npm license](https://img.shields.io/npm/l/common-tags.svg?style=flat-square)](https://github.com/declandewet/common-tags/blob/master/license.md) | 15 | | Popularity | [![npm downloads](https://img.shields.io/npm/dm/common-tags.svg?style=flat-square)](http://npm-stat.com/charts.html?package=common-tags) | 16 | | Testing | [![Build status](https://ci.appveyor.com/api/projects/status/75eiommx0llt3sgd?svg=true)](https://ci.appveyor.com/project/declandewet/common-tags) [![build status](https://img.shields.io/travis/declandewet/common-tags.svg?style=flat-square)](https://travis-ci.org/declandewet/common-tags) [![codecov.io](https://img.shields.io/codecov/c/gh/declandewet/common-tags.svg?style=flat-square)](https://codecov.io/gh/declandewet/common-tags?branch=master) | 17 | | Quality | [![bitHound Overall Score](https://www.bithound.io/github/declandewet/common-tags/badges/score.svg)](https://www.bithound.io/github/declandewet/common-tags) [![dependency status](https://img.shields.io/david/declandewet/common-tags.svg?style=flat-square)](https://david-dm.org/declandewet/common-tags) [![dev dependency status](https://img.shields.io/david/dev/declandewet/common-tags.svg?style=flat-square)](https://david-dm.org/declandewet/common-tags#info=devDependencies) | 18 | | Style | [![js-standard-style](https://cdn.rawgit.com/feross/standard/master/badge.svg)](https://github.com/feross/standard) | 19 | 20 | 21 | 22 | 23 | 24 | # :books: Table of Contents 25 | 26 | - [:wave: Introduction](#wave-introduction) 27 | - [:revolving_hearts: Why should you care?](#revolving_hearts-why-should-you-care) 28 | - [:arrow_double_down: Installation](#arrow_double_down-installation) 29 | - [Requirements](#requirements) 30 | - [Instructions](#instructions) 31 | - [:books: Usage](#books-usage) 32 | - [Imports](#imports) 33 | - [Available Tags](#available-tags) 34 | - [`html`](#html) 35 | - [Aliases: `source`, `codeBlock`](#aliases-source-codeblock) 36 | - [`safeHtml`](#safehtml) 37 | - [`oneLine`](#oneline) 38 | - [`oneLineTrim`](#onelinetrim) 39 | - [`stripIndent`](#stripindent) 40 | - [`stripIndents`](#stripindents) 41 | - [`inlineLists`](#inlinelists) 42 | - [`oneLineInlineLists`](#onelineinlinelists) 43 | - [`commaLists`](#commalists) 44 | - [`commaListsOr`](#commalistsor) 45 | - [`commaListsAnd`](#commalistsand) 46 | - [`oneLineCommaLists`](#onelinecommalists) 47 | - [`oneLineCommaListsOr`](#onelinecommalistsor) 48 | - [`oneLineCommaListsAnd`](#onelinecommalistsand) 49 | - [:wrench: Advanced Usage](#wrench-advanced-usage) 50 | - [Tail Processing](#tail-processing) 51 | - [Make Your Own Template Tag](#make-your-own-template-tag) 52 | - [Class is in Session: TemplateTag](#class-is-in-session-templatetag) 53 | - [The Anatomy of a Transformer](#the-anatomy-of-a-transformer) 54 | - [Plugin Transformers](#plugin-transformers) 55 | - [Plugin Pipeline](#plugin-pipeline) 56 | - [Returning Other Values from a Transformer](#returning-other-values-from-a-transformer) 57 | - [List of Built-in Transformers](#list-of-built-in-transformers) 58 | - [`trimResultTransformer([side])`](#trimresulttransformerside) 59 | - [`stripIndentTransformer([type='initial'])`](#stripindenttransformertypeinitial) 60 | - [`replaceResultTransformer(replaceWhat, replaceWith)`](#replaceresulttransformerreplacewhat-replacewith) 61 | - [`replaceSubstitutionTransformer(replaceWhat, replaceWith)`](#replacesubstitutiontransformerreplacewhat-replacewith) 62 | - [`inlineArrayTransformer(opts)`](#inlinearraytransformeropts) 63 | - [`splitStringTransformer(splitBy)`](#splitstringtransformersplitby) 64 | - [How to Contribute](#how-to-contribute) 65 | - [License](#license) 66 | - [:stars: Other ES2015 Template Tag Modules](#stars-other-es2015-template-tag-modules) 67 | 68 | 69 | 70 | 71 | 72 | # :wave: Introduction 73 | 74 | `common-tags` initially started out as two template tags I'd always find myself writing - one for stripping indents, and one for trimming multiline strings down to a single line. In it's prime, I was an avid user of [CoffeeScript](http://coffeescript.org), which had this behaviour by default as part of it's block strings feature. I also started out programming in Ruby, which has a similar mechanism called Heredocs. 75 | 76 | Over time, I found myself needing a few more template tags to cover edge cases - ones that supported including arrays, or ones that helped to render out tiny bits of HTML not large enough to deserve their own file or an entire template engine. So I packaged all of these up into this module. 77 | 78 | As more features were proposed, and I found myself needing a way to override the default settings to cover even more edge cases, I realized that my initial implementation wouldn't be easy to scale. 79 | 80 | So I re-wrote this module on top of a core architecture that makes use of transformer plugins which can be composed, imported independently and re-used. 81 | 82 | Have a read of the next section to find out why you should care. :smile: 83 | 84 | 85 | 86 | # :revolving_hearts: Why should you care? 87 | 88 | Tagged templates in ES2015 are a welcome feature. But, they have their downsides. One such downside is that they preserve all whitespace by default - which makes multiline strings in source code look terrible. 89 | 90 | Source code is not just for computers to interpret. Humans have to read it too :grin:. If you care at all about how neat your source code is, or come from a [CoffeeScript](http://coffeescript.org/) background and miss the [block string syntax](http://coffeescript.org/#strings), then you will love `common-tags`, as it was initially intended to bring this feature "back" to JS since it's [initial commit](https://github.com/declandewet/common-tags/commit/2595288d6c276439d98d1bcbbb0aa113f4f7cd86). 91 | 92 | `common-tags` also [exposes a means of composing pipelines of dynamic transformer plugins](#plugin-transformers). As someone with a little experience writing tagged templates, I can admit that it is often the case that one tag might need to do the same thing as another tag before doing any further processing; for example - a typical tag that renders out HTML could strip initial indents first, then worry about handling character escapes. Both steps could easily be useful as their own separate template tags, but there isn't an immediately obvious way of composing the two together for maximum re-use. `common-tags` offers not [one](#tail-processing), but [two](#plugin-pipeline) ways of doing this. 93 | 94 | Furthermore, I try to keep this project as transparently stable and updated as frequently as I possibly can. As you may have already seen by the [project status table](#battery-project-status), `common-tags` is linted, well tested, tests are well covered, tests pass on both Unix and Windows operating systems, the popularity bandwidth is easily referenced and dependency health is in plain sight :smile:. `common-tags` is also already used in production on a number of proprietary sites and dependent projects, and [contributions are always welcome](#how-to-contribute), as are [suggestions](issues). 95 | 96 | 97 | 98 | 99 | 100 | # :arrow_double_down: Installation 101 | 102 | ### Requirements 103 | 104 | The official recommendation for running `common-tags` is as follows: 105 | 106 | - [Node.js](https://nodejs.org/en/download/) v5.0.0 or higher 107 | - In order to use `common-tags`, your environment will also need to support ES2015 tagged templates ([pssst… check Babel out](http://babeljs.io)). 108 | 109 | It might work with below versions of Node, but this is not a gaurantee. 110 | 111 | 112 | ### Instructions 113 | 114 | `common-tags` is a [Node](https://nodejs.org/) module. So, as long as you have Node.js and NPM installed, installing `common-tags` is as simple as running this in a terminal at the root of your project: 115 | 116 | ```sh 117 | $ npm install common-tags --save 118 | ``` 119 | 120 | 121 | 122 | 123 | 124 | # :books: Usage 125 | 126 | ### Imports 127 | 128 | Like all modules, `common-tags` begins with an `import`. In fact, `common-tags` supports two styles of import: 129 | 130 | **Named imports:** 131 | 132 | ```js 133 | import {stripIndent} from 'common-tags' 134 | ``` 135 | 136 | **Direct module imports:** 137 | 138 | *(Useful if your bundler doesn't support [tree shaking](https://medium.com/@roman01la/dead-code-elimination-and-tree-shaking-in-javascript-build-systems-fb8512c86edf#.p30lbjm94) but you still want to only include modules you need).* 139 | 140 | ```js 141 | import stripIndent from 'common-tags/lib/stripIndent' 142 | ``` 143 | 144 | 145 | 146 | ### Available Tags 147 | 148 | `common-tags` exports a bunch of wonderful pre-cooked template tags for your eager consumption. They are as follows: 149 | 150 | 151 | 152 | #### `html` 153 | ##### Aliases: `source`, `codeBlock` 154 | 155 | You'll often find that you might want to include an array in a template. Typically, doing something like `${array.join(', ')}` would work - but what if you're printing a list of items in an HTML template and want to maintain the indentation? You'd have to count the spaces manually and include them in the `.join()` call - which is a bit *ugly* for my taste. This tag properly indents arrays, as well as newline characters in string substitutions, by converting them to an array split by newline and re-using the same array inclusion logic: 156 | 157 | ```js 158 | import {html} from 'common-tags' 159 | let fruits = ['apple', 'orange', 'watermelon'] 160 | html` 161 |
    162 |
      163 | ${fruits.map(fruit => `
    • ${fruit}
    • `)} 164 | ${'
    • kiwi
    • \n
    • guava
    • '} 165 |
    166 |
    167 | `); 168 | ``` 169 | 170 | Outputs: 171 | 172 | ```html 173 |
    174 |
      175 |
    • apple
    • 176 |
    • orange
    • 177 |
    • watermelon
    • 178 |
    • kiwi
    • 179 |
    • guava
    • 180 |
    181 |
    182 | ``` 183 | 184 | 185 | 186 | 187 | 188 | #### `safeHtml` 189 | 190 | A tag very similar to `html` but it does safe HTML escaping for strings coming from substitutions. When combined with regular `html` tag, you can do basic HTML templating that is safe from XSS (Cross-Site Scripting) attacks. 191 | 192 | ```js 193 | import {html, safeHtml} from 'common-tags' 194 | let userMessages = ['hi', 'what are you up to?', ''] 195 | html` 196 |
    197 |
      198 | ${userMessages.map(message => safeHtml`
    • ${message}
    • `)} 199 |
    200 |
    201 | ` 202 | ``` 203 | 204 | Outputs: 205 | 206 | ```html 207 |
    208 |
      209 |
    • hi
    • 210 |
    • what are you up to?
    • 211 |
    • <script>alert("something evil")</script>
    • 212 |
    213 |
    214 | ``` 215 | 216 | 217 | 218 | 219 | 220 | #### `oneLine` 221 | 222 | Allows you to keep your single-line strings under 80 characters without resorting to crazy string concatenation. 223 | ```js 224 | import {oneLine} from 'common-tags' 225 | 226 | oneLine` 227 | foo 228 | bar 229 | baz 230 | `) 231 | // "foo bar baz" 232 | ``` 233 | 234 | 235 | 236 | 237 | 238 | #### `oneLineTrim` 239 | 240 | Allows you to keep your single-line strings under 80 characters while trimming the new lines: 241 | 242 | ```js 243 | import {oneLineTrim} from 'common-tags' 244 | 245 | oneLineTrim` 246 | https://news.com/article 247 | ?utm_source=designernews.co 248 | `) 249 | // https://news.com/article?utm_source=designernews.co 250 | ``` 251 | 252 | 253 | 254 | 255 | 256 | #### `stripIndent` 257 | 258 | If you want to strip the initial indentation from the beginning of each line in a multiline string: 259 | 260 | ```js 261 | import {stripIndent} from 'common-tags' 262 | 263 | stripIndent` 264 | This is a multi-line string. 265 | You'll ${verb} that it is indented. 266 | We don't want to output this indentation. 267 | But we do want to keep this line indented. 268 | ` 269 | // This is a multi-line string. 270 | // You'll notice that it is indented. 271 | // We don't want to output this indentation. 272 | // But we do want to keep this line indented. 273 | ``` 274 | 275 | 276 | 277 | 278 | 279 | #### `stripIndents` 280 | 281 | If you want to strip *all* of the indentation from the beginning of each line in a multiline string: 282 | 283 | ```js 284 | import {stripIndents} from 'common-tags' 285 | 286 | stripIndents` 287 | This is a multi-line string. 288 | You'll ${verb} that it is indented. 289 | We don't want to output this indentation. 290 | We don't want to keep this line indented either. 291 | ` 292 | // This is a multi-line string. 293 | // You'll notice that it is indented. 294 | // We don't want to output this indentation. 295 | // We don't want to keep this line indented either. 296 | ``` 297 | 298 | 299 | 300 | 301 | 302 | #### `inlineLists` 303 | 304 | Allows you to inline an array substitution as a list: 305 | 306 | ```js 307 | import {inlineLists} from 'common-tags' 308 | 309 | inlineLists` 310 | I like ${['apples', 'bananas', 'watermelons']} 311 | They're good! 312 | ` 313 | // I like apples bananas watermelons 314 | // They're good! 315 | ``` 316 | 317 | 318 | 319 | 320 | 321 | #### `oneLineInlineLists` 322 | 323 | Allows you to inline an array substitution as a list, rendered out on a single line: 324 | 325 | ```js 326 | import {oneLineInlineLists} from 'common-tags' 327 | 328 | oneLineInlineLists` 329 | I like ${['apples', 'bananas', 'watermelons']} 330 | They're good! 331 | ` 332 | // I like apples bananas watermelons They're good! 333 | ``` 334 | 335 | 336 | 337 | 338 | 339 | #### `commaLists` 340 | 341 | Allows you to inline an array substitution as a comma-separated list: 342 | 343 | ```js 344 | import {commaLists} from 'common-tags' 345 | 346 | commaLists` 347 | I like ${['apples', 'bananas', 'watermelons']} 348 | They're good! 349 | ` 350 | // I like apples, bananas, watermelons 351 | // They're good! 352 | ``` 353 | 354 | 355 | 356 | 357 | 358 | #### `commaListsOr` 359 | 360 | Allows you to inline an array substitution as a comma-separated list, the last of which is preceded by the word "or": 361 | 362 | ```js 363 | import {commaListsOr} from 'common-tags' 364 | 365 | commaListsOr` 366 | I like ${['apples', 'bananas', 'watermelons']} 367 | They're good! 368 | ` 369 | // I like apples, bananas or watermelons 370 | // They're good! 371 | ``` 372 | 373 | 374 | 375 | 376 | 377 | #### `commaListsAnd` 378 | 379 | Allows you to inline an array substitution as a comma-separated list, the last of which is preceded by the word "and": 380 | 381 | ```js 382 | import {commaListsAnd} from 'common-tags' 383 | 384 | commaListsAnd` 385 | I like ${['apples', 'bananas', 'watermelons']} 386 | They're good! 387 | ` 388 | // I like apples, bananas and watermelons 389 | // They're good! 390 | ``` 391 | 392 | 393 | 394 | 395 | 396 | #### `oneLineCommaLists` 397 | 398 | Allows you to inline an array substitution as a comma-separated list, and is rendered out on to a single line: 399 | 400 | ```js 401 | import {oneLineCommaLists} from 'common-tags' 402 | 403 | oneLineCommaLists` 404 | I like ${['apples', 'bananas', 'watermelons']} 405 | They're good! 406 | ` 407 | // I like apples, bananas or watermelons They're good! 408 | ``` 409 | 410 | 411 | 412 | 413 | 414 | #### `oneLineCommaListsOr` 415 | 416 | Allows you to inline an array substitution as a comma-separated list, the last of which is preceded by the word "or", and is rendered out on to a single line: 417 | 418 | ```js 419 | import {oneLineCommaListsOr} from 'common-tags' 420 | 421 | oneLineCommaListsOr` 422 | I like ${['apples', 'bananas', 'watermelons']} 423 | They're good! 424 | ` 425 | // I like apples, bananas or watermelons They're good! 426 | ``` 427 | 428 | 429 | 430 | 431 | 432 | #### `oneLineCommaListsAnd` 433 | 434 | Allows you to inline an array substitution as a comma-separated list, the last of which is preceded by the word "and", and is rendered out on to a single line: 435 | 436 | ```js 437 | import {oneLineCommaListsAnd} from 'common-tags' 438 | 439 | oneLineCommaListsAnd` 440 | I like ${['apples', 'bananas', 'watermelons']} 441 | They're good! 442 | ` 443 | // I like apples, bananas and watermelons They're good! 444 | ``` 445 | 446 | 447 | 448 | # :wrench: Advanced Usage 449 | 450 | ### Tail Processing 451 | 452 | It's possible to pass the output of a tagged template to another template tag in pure ES2015+: 453 | 454 | ```js 455 | import {oneLine} from 'common-tags' 456 | 457 | oneLine` 458 | ${String.raw` 459 | foo 460 | bar\nbaz 461 | `} 462 | ` 463 | // "foo bar\nbaz" 464 | ``` 465 | 466 | 467 | 468 | We can make this neater. Every tag `common-tags` exports can delay execution if it receives a function as it's first argument. This function is assumed to be a template tag, and is called via an intermediary tagging process before the result is passed back to our tag. Use it like so (this code is equivalent to the previous code block): 469 | 470 | ```js 471 | import {oneLine} from 'common-tags' 472 | 473 | oneLine(String.raw)` 474 | foo 475 | bar\nbaz 476 | ` 477 | // "foo bar\nbaz" 478 | ``` 479 | 480 | 481 | 482 | ### Make Your Own Template Tag 483 | 484 | `common-tags` exposes an interface that allows you to painlessly create your own template tags. 485 | 486 | 487 | 488 | #### Class is in Session: TemplateTag 489 | 490 | `common-tags` exports a `TemplateTag` class. This class is the foundation of `common-tags`. The concept of the class works on the premise that transformations occur on a template either when the template is finished being processed (`onEndResult`), or when the tag encounters a substitution (`onSubstitution`). Any tag produced by this class supports [tail processing](#tail-processing). 491 | 492 | The easiest tag to create is a tag that does nothing: 493 | 494 | ```js 495 | import {TemplateTag} from 'common-tags' 496 | 497 | const doNothing = new TemplateTag() 498 | 499 | doNothing`foo bar` 500 | // 'foo bar' 501 | ``` 502 | 503 | 504 | 505 | 506 | 507 | #### The Anatomy of a Transformer 508 | 509 | `TemplateTag` receives either an array or argument list of `transformers`. A `transformer` is just a plain object with two optional methods - `onSubstitution` and `onEndResult` - it looks like this: 510 | 511 | ```js 512 | { 513 | onSubstitution (substitution, resultSoFar) { 514 | // optional. Called when the tag encounters a substitution. 515 | // (a substitution is whatever's inside "${}" in your template literal) 516 | // `substitution` is the value of the current substitution 517 | // `resultSoFar` is the end result up to the point of this substitution 518 | }, 519 | onEndResult (endResult) { 520 | // optional. Called when all substitutions have been parsed 521 | // `endResult` is the final value. 522 | } 523 | } 524 | ``` 525 | 526 | 527 | 528 | 529 | 530 | #### Plugin Transformers 531 | 532 | You can wrap a transformer in a function that receives arguments in order to create a dynamic plugin: 533 | 534 | ```js 535 | const substitutionReplacer = (oldValue, newValue) => ({ 536 | onSubstitution(substitution, resultSoFar) { 537 | if (substitution === oldValue) { 538 | return newValue 539 | } 540 | return substitution 541 | } 542 | }) 543 | 544 | const replaceFizzWithBuzz = new TemplateTag(substitutionReplacer('fizz', 'buzz')) 545 | 546 | replaceFizzWithBuzz`foo bar ${"fizz"}` 547 | // "foo bar buzz" 548 | ``` 549 | 550 | > **note** - if you call `new TemplateTag(substitutionReplacer)`, `substitutionReplacer` will automatically be initiated with no arguments. 551 | 552 | 553 | 554 | #### Plugin Pipeline 555 | 556 | You can pass a list of transformers, and `TemplateTag` will call them on your tag in the order they are specified: 557 | 558 | ```js 559 | // note: passing these as an array also works 560 | const replace = new TemplateTag( 561 | substitutionReplacer('fizz', 'buzz'), 562 | substitutionReplacer('foo', 'bar') 563 | ) 564 | 565 | replace`${"foo"} ${"fizz"}` 566 | // "bar buzz" 567 | ``` 568 | 569 | 570 | 571 | When multiple transformers are passed to `TemplateTag`, they will be iterated twice - first, all transformer `onSubstitution` methods will be called. Once they are done processing, all transformer `onEndResult` methods will be called. 572 | 573 | 574 | 575 | #### Returning Other Values from a Transformer 576 | 577 | This is super easy. Transformers are just objects, after all. They have full access to `this`: 578 | 579 | ```js 580 | const listSubs = { 581 | onSubstitution(sub, res) { 582 | this.ctx = this.ctx || { subs: [] } 583 | this.ctx.subs.push({ sub, precededBy: res }) 584 | return sub 585 | }, 586 | onEndResult(res) { 587 | return this.ctx 588 | } 589 | } 590 | 591 | const toJSON = { 592 | onEndResult(res) { 593 | return JSON.stringify(res, null, 2) 594 | } 595 | } 596 | 597 | const log = { 598 | onEndResult(res) { 599 | console.log(res) 600 | return res 601 | } 602 | } 603 | 604 | const process = new TemplateTag([listSubs, toJSON, log]) 605 | 606 | process` 607 | foo ${'bar'} 608 | fizz ${'buzz'} 609 | ` 610 | // { 611 | // "subs": [ 612 | // { 613 | // "sub": "bar", 614 | // "precededBy": "\n foo " 615 | // }, 616 | // { 617 | // "sub": "buzz", 618 | // "precededBy": "\n foo bar\n fizz " 619 | // } 620 | // ] 621 | // } 622 | ``` 623 | 624 | 625 | 626 | #### List of Built-in Transformers 627 | 628 | Since `common-tags` is built on the foundation of this TemplateTag class, it comes with its own set of built-in transformers: 629 | 630 | 631 | 632 | ##### `trimResultTransformer([side])` 633 | 634 | Trims the whitespace surrounding the end result. Accepts an optional `side` (can be `"left"` or `"right"`) that when supplied, will only trim whitespace from that side of the string. 635 | 636 | 637 | 638 | ##### `stripIndentTransformer([type='initial'])` 639 | 640 | Strips the indents from the end result. Offers two types: `all`, which removes all indentation from each line, and `initial`, which removes the shortest indent level from each line. Defaults to `initial`. 641 | 642 | 643 | 644 | ##### `replaceResultTransformer(replaceWhat, replaceWith)` 645 | 646 | Replaces a value or pattern in the end result with a new value. `replaceWhat` can be a string or a regular expression, `replaceWith` is the new value. 647 | 648 | 649 | 650 | ##### `replaceSubstitutionTransformer(replaceWhat, replaceWith)` 651 | 652 | Replaces the result of all substitutions (results of calling `${ ... }`) with a new value. Same as for `replaceResultTransformer`, `replaceWhat` can be a string or regular expression and `replaceWith` is the new value. 653 | 654 | 655 | 656 | ##### `inlineArrayTransformer(opts)` 657 | 658 | Converts any array substitutions into a string that represents a list. Accepts an options object: 659 | 660 | ```js 661 | opts = { 662 | separator: ',', // what to separate each item with (always followed by a space) 663 | conjunction: 'and', // replace the last separator with this value 664 | serial: true // should the separator be included before the conjunction? As in the case of serial/oxford commas 665 | } 666 | ``` 667 | 668 | 669 | 670 | ##### `splitStringTransformer(splitBy)` 671 | 672 | Splits a string substitution into an array by the provided `splitBy` substring, **only** if the string contains the `splitBy` substring. 673 | 674 | 675 | 676 | # How to Contribute 677 | 678 | Please see the [Contribution Guidelines](contributing.md). 679 | 680 | 681 | 682 | # License 683 | 684 | MIT. See [license.md](license.md). 685 | 686 | 687 | 688 | 689 | 690 | # :stars: Other ES2015 Template Tag Modules 691 | 692 | If `common-tags` doesn't quite fit your bill, and you just can't seem to find what you're looking for - perhaps these might be of use to you? 693 | 694 | 695 | 696 | - [tage](https://www.npmjs.com/package/tage) - make functions work as template tags too 697 | - [is-tagged](https://www.npmjs.com/package/is-tagged) - Check whether a function call is initiated by a tagged template string or invoked in a regular way 698 | - [es6-template-strings](https://www.npmjs.com/package/es6-template-strings) - Compile and resolve template strings notation as specified in ES6 699 | - [t7](https://github.com/trueadm/t7) - A light-weight virtual-dom template library 700 | - [html-template-tag](https://www.npmjs.com/package/html-template-tag) - ES6 Tagged Template for compiling HTML template strings. 701 | - [clean-tagged-string](https://www.npmjs.com/package/clean-tagged-string) - A simple utility function to clean ES6 template strings. 702 | - [multiline-tag](https://www.npmjs.com/package/multiline-tag) - Tags for template strings making them behave like coffee multiline strings 703 | - [deindent](https://www.npmjs.com/package/deindent) - ES6 template string helper for deindentation. 704 | - [heredoc-tag](https://www.npmjs.com/package/heredoc-tag) - Heredoc helpers for ES2015 template strings 705 | - [regx](https://www.npmjs.com/package/regx) - Tagged template string regular expression compiler. 706 | - [regexr](https://www.npmjs.org/package/regexr) - Provides an ES6 template tag function that makes it easy to compose regexes out of template strings without double-escaped hell. 707 | - [url-escape-tag](https://www.npmjs.com/package/url-escape-tag) - A template tag for escaping url parameters based on ES2015 tagged templates. 708 | - [shell-escape-tag](https://www.npmjs.com/package/shell-escape-tag) - An ES6+ template tag which escapes parameters for interpolation into shell commands. 709 | - [sql-tags](https://www.npmjs.com/package/sql-tags) - ES6 tagged template string functions for SQL statements. 710 | - [sql-tag](https://www.npmjs.com/package/sql-tag) - A template tag for writing elegant sql strings. 711 | - [sequelize-sql-tag](https://www.npmjs.com/package/sequelize-sql-tag) - A sequelize plugin for sql-tag 712 | - [pg-sql-tag](https://www.npmjs.com/package/pg-sql-tag) - A pg plugin for sql-tag 713 | - [sql-template-strings](https://www.npmjs.com/package/sql-template-strings) - ES6 tagged template strings for prepared statements with mysql and postgres 714 | - [sql-composer](https://www.npmjs.com/package/sql-composer) - Composable SQL template strings for Node.js 715 | - [pg-template-tag](https://www.npmjs.com/package/pg-template-tag) - ECMAScript 6 (2015) template tag function to write queries for node-postgres. 716 | - [digraph-tag](https://www.npmjs.com/package/digraph-tag) - ES6 string template tag for quickly generating directed graph data 717 | - [es2015-i18n-tag](https://www.npmjs.com/package/es2015-i18n-tag) - ES2015 template literal tag for i18n and l10n translation and localization 718 | --------------------------------------------------------------------------------