├── .gitignore ├── LICENSE ├── README.md ├── package-lock.json ├── package.json ├── src └── index.js └── test ├── test.spec.js └── unit └── index.spec.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016-present Jay Phelps, and contributors. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # multiline-template 2 | 3 | Multiline tagged templates using a pipe `|` to signal line start. No more crazy indent hacks. 4 | 5 | ## Install 6 | 7 | ``` 8 | npm install --save multiline-template 9 | ``` 10 | 11 | ## Usage 12 | 13 | Using [Tagged Template Literals](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Template_literals#Tagged_template_literals), you use the **pipe** `|` to signal where you want to line to actually start in the resulting string. 14 | 15 | ```js 16 | import multiline from 'multiline-template'; 17 | // or 18 | const multiline = require('multiline-template'); 19 | 20 | const msg = multiline` 21 | |first 22 | |second 23 | |third 24 | |fourth 25 | `; 26 | 27 | console.log(msg); 28 | ``` 29 | 30 | ``` 31 | first 32 | second 33 | third 34 | fourth 35 | ``` 36 | 37 | It also indents interpolated values to the provided indention level 38 | 39 | ```js 40 | import multiline from 'multiline-template'; 41 | 42 | const part = multiline` 43 | |second 44 | |third 45 | `; 46 | 47 | const msg = multiline` 48 | |first 49 | | ${part} 50 | |fourth 51 | `; 52 | 53 | console.log(msg); 54 | ``` 55 | 56 | ``` 57 | first 58 | second 59 | third 60 | fourth 61 | ``` 62 | 63 | The line will always start where you say, no matter how much indention comes before the pipe. 64 | 65 | ```js 66 | import multiline from 'multiline-template'; 67 | 68 | (function () { 69 | (function () { 70 | 71 | // there is actually a lot of excess indention 72 | // before the pipes, but it is ignored! 73 | const part = multiline` 74 | |second 75 | |third 76 | `; 77 | 78 | const msg = multiline` 79 | |first 80 | | ${part} 81 | |fourth 82 | `; 83 | 84 | console.log(msg); 85 | 86 | })(); 87 | })(); 88 | ``` 89 | 90 | ``` 91 | first 92 | second 93 | third 94 | fourth 95 | ``` 96 | 97 | ## Credit 98 | This was heavily inspired by [Scala's multiline string pipe markers](https://docs.scala-lang.org/overviews/scala-book/two-notes-about-strings.html#multiline-strings), though there are some differences e.g. how nesting works. 99 | 100 | :shipit: 101 | -------------------------------------------------------------------------------- /package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "multiline-template", 3 | "version": "0.1.2", 4 | "lockfileVersion": 1, 5 | "requires": true, 6 | "dependencies": { 7 | "assertion-error": { 8 | "version": "1.1.0", 9 | "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.1.0.tgz", 10 | "integrity": "sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==", 11 | "dev": true 12 | }, 13 | "chai": { 14 | "version": "3.5.0", 15 | "resolved": "https://registry.npmjs.org/chai/-/chai-3.5.0.tgz", 16 | "integrity": "sha1-TQJjewZ/6Vi9v906QOxW/vc3Mkc=", 17 | "dev": true, 18 | "requires": { 19 | "assertion-error": "1.1.0", 20 | "deep-eql": "0.1.3", 21 | "type-detect": "1.0.0" 22 | } 23 | }, 24 | "commander": { 25 | "version": "2.3.0", 26 | "resolved": "https://registry.npmjs.org/commander/-/commander-2.3.0.tgz", 27 | "integrity": "sha1-/UMOiJgy7DU7ms0d4hfBHLPu+HM=", 28 | "dev": true 29 | }, 30 | "debug": { 31 | "version": "2.2.0", 32 | "resolved": "https://registry.npmjs.org/debug/-/debug-2.2.0.tgz", 33 | "integrity": "sha1-+HBX6ZWxofauaklgZkE3vFbwOdo=", 34 | "dev": true, 35 | "requires": { 36 | "ms": "0.7.1" 37 | } 38 | }, 39 | "deep-eql": { 40 | "version": "0.1.3", 41 | "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-0.1.3.tgz", 42 | "integrity": "sha1-71WKyrjeJSBs1xOQbXTlaTDrafI=", 43 | "dev": true, 44 | "requires": { 45 | "type-detect": "0.1.1" 46 | }, 47 | "dependencies": { 48 | "type-detect": { 49 | "version": "0.1.1", 50 | "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-0.1.1.tgz", 51 | "integrity": "sha1-C6XsKohWQORw6k6FBZcZANrFiCI=", 52 | "dev": true 53 | } 54 | } 55 | }, 56 | "diff": { 57 | "version": "1.4.0", 58 | "resolved": "https://registry.npmjs.org/diff/-/diff-1.4.0.tgz", 59 | "integrity": "sha1-fyjS657nsVqX79ic5j3P2qPMur8=", 60 | "dev": true 61 | }, 62 | "escape-string-regexp": { 63 | "version": "1.0.2", 64 | "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.2.tgz", 65 | "integrity": "sha1-Tbwv5nTnGUnK8/smlc5/LcHZqNE=", 66 | "dev": true 67 | }, 68 | "formatio": { 69 | "version": "1.1.1", 70 | "resolved": "https://registry.npmjs.org/formatio/-/formatio-1.1.1.tgz", 71 | "integrity": "sha1-XtPM1jZVEJc4NGXZlhmRAOhhYek=", 72 | "dev": true, 73 | "requires": { 74 | "samsam": "1.1.2" 75 | } 76 | }, 77 | "glob": { 78 | "version": "3.2.11", 79 | "resolved": "https://registry.npmjs.org/glob/-/glob-3.2.11.tgz", 80 | "integrity": "sha1-Spc/Y1uRkPcV0QmH1cAP0oFevj0=", 81 | "dev": true, 82 | "requires": { 83 | "inherits": "2.0.4", 84 | "minimatch": "0.3.0" 85 | } 86 | }, 87 | "growl": { 88 | "version": "1.9.2", 89 | "resolved": "https://registry.npmjs.org/growl/-/growl-1.9.2.tgz", 90 | "integrity": "sha1-Dqd0NxXbjY3ixe3hd14bRayFwC8=", 91 | "dev": true 92 | }, 93 | "inherits": { 94 | "version": "2.0.4", 95 | "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", 96 | "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", 97 | "dev": true 98 | }, 99 | "is-arguments": { 100 | "version": "1.0.4", 101 | "resolved": "https://registry.npmjs.org/is-arguments/-/is-arguments-1.0.4.tgz", 102 | "integrity": "sha512-xPh0Rmt8NE65sNzvyUmWgI1tz3mKq74lGA0mL8LYZcoIzKOzDh6HmrYm3d18k60nHerC8A9Km8kYu87zfSFnLA==", 103 | "dev": true 104 | }, 105 | "is-generator-function": { 106 | "version": "1.0.7", 107 | "resolved": "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.0.7.tgz", 108 | "integrity": "sha512-YZc5EwyO4f2kWCax7oegfuSr9mFz1ZvieNYBEjmukLxgXfBUbxAWGVF7GZf0zidYtoBl3WvC07YK0wT76a+Rtw==", 109 | "dev": true 110 | }, 111 | "jade": { 112 | "version": "0.26.3", 113 | "resolved": "https://registry.npmjs.org/jade/-/jade-0.26.3.tgz", 114 | "integrity": "sha1-jxDXl32NefL2/4YqgbBRPMslaGw=", 115 | "dev": true, 116 | "requires": { 117 | "commander": "0.6.1", 118 | "mkdirp": "0.3.0" 119 | }, 120 | "dependencies": { 121 | "commander": { 122 | "version": "0.6.1", 123 | "resolved": "https://registry.npmjs.org/commander/-/commander-0.6.1.tgz", 124 | "integrity": "sha1-+mihT2qUXVTbvlDYzbMyDp47GgY=", 125 | "dev": true 126 | }, 127 | "mkdirp": { 128 | "version": "0.3.0", 129 | "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.3.0.tgz", 130 | "integrity": "sha1-G79asbqCevI1dRQ0kEJkVfSB/h4=", 131 | "dev": true 132 | } 133 | } 134 | }, 135 | "lolex": { 136 | "version": "1.3.2", 137 | "resolved": "https://registry.npmjs.org/lolex/-/lolex-1.3.2.tgz", 138 | "integrity": "sha1-fD2mL/yzDw9agKJWbKJORdigHzE=", 139 | "dev": true 140 | }, 141 | "lru-cache": { 142 | "version": "2.7.3", 143 | "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-2.7.3.tgz", 144 | "integrity": "sha1-bUUk6LlV+V1PW1iFHOId1y+06VI=", 145 | "dev": true 146 | }, 147 | "minimatch": { 148 | "version": "0.3.0", 149 | "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-0.3.0.tgz", 150 | "integrity": "sha1-J12O2qxPG7MyZHIInnlJyDlGmd0=", 151 | "dev": true, 152 | "requires": { 153 | "lru-cache": "2.7.3", 154 | "sigmund": "1.0.1" 155 | } 156 | }, 157 | "minimist": { 158 | "version": "0.0.8", 159 | "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", 160 | "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=", 161 | "dev": true 162 | }, 163 | "mkdirp": { 164 | "version": "0.5.1", 165 | "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", 166 | "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", 167 | "dev": true, 168 | "requires": { 169 | "minimist": "0.0.8" 170 | } 171 | }, 172 | "mocha": { 173 | "version": "2.5.3", 174 | "resolved": "https://registry.npmjs.org/mocha/-/mocha-2.5.3.tgz", 175 | "integrity": "sha1-FhvlvetJZ3HrmzV0UFC2IrWu/Fg=", 176 | "dev": true, 177 | "requires": { 178 | "commander": "2.3.0", 179 | "debug": "2.2.0", 180 | "diff": "1.4.0", 181 | "escape-string-regexp": "1.0.2", 182 | "glob": "3.2.11", 183 | "growl": "1.9.2", 184 | "jade": "0.26.3", 185 | "mkdirp": "0.5.1", 186 | "supports-color": "1.2.0", 187 | "to-iso-string": "0.0.2" 188 | } 189 | }, 190 | "ms": { 191 | "version": "0.7.1", 192 | "resolved": "https://registry.npmjs.org/ms/-/ms-0.7.1.tgz", 193 | "integrity": "sha1-nNE8A62/8ltl7/3nzoZO6VIBcJg=", 194 | "dev": true 195 | }, 196 | "safe-buffer": { 197 | "version": "5.2.0", 198 | "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.0.tgz", 199 | "integrity": "sha512-fZEwUGbVl7kouZs1jCdMLdt95hdIv0ZeHg6L7qPeciMZhZ+/gdesW4wgTARkrFWEpspjEATAzUGPG8N2jJiwbg==", 200 | "dev": true 201 | }, 202 | "samsam": { 203 | "version": "1.1.2", 204 | "resolved": "https://registry.npmjs.org/samsam/-/samsam-1.1.2.tgz", 205 | "integrity": "sha1-vsEf3IOp/aBjQBIQ5AF2wwJNFWc=", 206 | "dev": true 207 | }, 208 | "sigmund": { 209 | "version": "1.0.1", 210 | "resolved": "https://registry.npmjs.org/sigmund/-/sigmund-1.0.1.tgz", 211 | "integrity": "sha1-P/IfGYytIXX587eBhT/ZTQ0ZtZA=", 212 | "dev": true 213 | }, 214 | "sinon": { 215 | "version": "1.17.7", 216 | "resolved": "https://registry.npmjs.org/sinon/-/sinon-1.17.7.tgz", 217 | "integrity": "sha1-RUKk9JugxFwF6y6d2dID4rjv4L8=", 218 | "dev": true, 219 | "requires": { 220 | "formatio": "1.1.1", 221 | "lolex": "1.3.2", 222 | "samsam": "1.1.2", 223 | "util": "0.12.2" 224 | } 225 | }, 226 | "sinon-chai": { 227 | "version": "2.14.0", 228 | "resolved": "https://registry.npmjs.org/sinon-chai/-/sinon-chai-2.14.0.tgz", 229 | "integrity": "sha512-9stIF1utB0ywNHNT7RgiXbdmen8QDCRsrTjw+G9TgKt1Yexjiv8TOWZ6WHsTPz57Yky3DIswZvEqX8fpuHNDtQ==", 230 | "dev": true 231 | }, 232 | "supports-color": { 233 | "version": "1.2.0", 234 | "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-1.2.0.tgz", 235 | "integrity": "sha1-/x7R5hFp0Gs88tWI4YixjYhH4X4=", 236 | "dev": true 237 | }, 238 | "to-iso-string": { 239 | "version": "0.0.2", 240 | "resolved": "https://registry.npmjs.org/to-iso-string/-/to-iso-string-0.0.2.tgz", 241 | "integrity": "sha1-TcGeZk38y+Jb2NtQiwDG2hWCVdE=", 242 | "dev": true 243 | }, 244 | "type-detect": { 245 | "version": "1.0.0", 246 | "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-1.0.0.tgz", 247 | "integrity": "sha1-diIXzAbbJY7EiQihKY6LlRIejqI=", 248 | "dev": true 249 | }, 250 | "util": { 251 | "version": "0.12.2", 252 | "resolved": "https://registry.npmjs.org/util/-/util-0.12.2.tgz", 253 | "integrity": "sha512-XE+MkWQvglYa+IOfBt5UFG93EmncEMP23UqpgDvVZVFBPxwmkK10QRp6pgU4xICPnWRf/t0zPv4noYSUq9gqUQ==", 254 | "dev": true, 255 | "requires": { 256 | "inherits": "2.0.4", 257 | "is-arguments": "1.0.4", 258 | "is-generator-function": "1.0.7", 259 | "safe-buffer": "5.2.0" 260 | } 261 | } 262 | } 263 | } 264 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "multiline-template", 3 | "version": "1.1.0", 4 | "description": "Multiline tagged templates using pipes | to signal line start, no more crazy indent hacks.", 5 | "main": "src/index.js", 6 | "scripts": { 7 | "test": "mocha \"test/**/*.spec.js\"" 8 | }, 9 | "keywords": [ 10 | "multiline", 11 | "indent", 12 | "template", 13 | "pipe", 14 | "line", 15 | "newline", 16 | "string" 17 | ], 18 | "author": "Jay Phelps ", 19 | "license": "MIT", 20 | "repository": { 21 | "type": "git", 22 | "url": "https://github.com/jayphelps/multiline-template.git" 23 | }, 24 | "bugs": { 25 | "url": "https://github.com/jayphelps/multiline-template/issues" 26 | }, 27 | "homepage": "https://github.com/jayphelps/multiline-template", 28 | "devDependencies": { 29 | "chai": "^3.2.0", 30 | "mocha": "^2.2.5", 31 | "sinon": "^1.15.4", 32 | "sinon-chai": "^2.8.0" 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | var slice = Array.prototype.slice; 3 | 4 | function multiline(strings) { 5 | var args = slice.call(arguments, 1); 6 | 7 | return ( 8 | strings 9 | // add interpolated arguments 10 | .reduce(function (out, part, i) { 11 | if (args.hasOwnProperty(i)) { 12 | var lines = part.split('\n'); 13 | // find indention of the current line 14 | var indent = lines[lines.length - 1].replace( 15 | /[ \t\r]*\|([ \t\r]*).*$/, 16 | '$1' 17 | ); 18 | // indent interpolated lines to match 19 | var tail = String(args[i]) 20 | .split('\n') 21 | .join('\n' + indent); 22 | return out + part + tail; 23 | } else { 24 | return out + part; 25 | } 26 | }, '') 27 | // remove whitespace from start/end 28 | .replace(/^\s*|\n\s*$/g, '') 29 | // indent as instructed, relative to pipe position 30 | // plus a single space after 31 | .replace(/^[ \t\r]*\|(.*)$/gm, '$1') 32 | ); 33 | } 34 | 35 | module.exports = multiline; 36 | module.exports.multiline = multiline; 37 | module.exports['default'] = multiline; 38 | 39 | Object.defineProperty(module.exports, '__esModule', { 40 | value: true, 41 | }); 42 | -------------------------------------------------------------------------------- /test/test.spec.js: -------------------------------------------------------------------------------- 1 | const chai = require('chai'); 2 | require('sinon'); 3 | const sinonChai = require('sinon-chai'); 4 | 5 | chai.should(); 6 | chai.use(sinonChai); 7 | -------------------------------------------------------------------------------- /test/unit/index.spec.js: -------------------------------------------------------------------------------- 1 | const multiline = require('../../'); 2 | 3 | describe('multiline', () => { 4 | it('handles simple lines', () => { 5 | multiline` 6 | | 7 | |first 8 | | 9 | |second 10 | | 11 | `.should.equal('\nfirst\n\nsecond\n'); 12 | }); 13 | 14 | it('handles simple interpolation', () => { 15 | const part = multiline` 16 | |second 17 | |third 18 | `; 19 | 20 | multiline` 21 | |first 22 | |${part} 23 | |fourth 24 | `.should.equal('first\nsecond\nthird\nfourth'); 25 | }); 26 | 27 | it('handles non-string values', () => { 28 | multiline` 29 | |first 30 | | ${undefined} 31 | | ${null} 32 | | ${false} 33 | | ${1} 34 | | ${{}} 35 | | ${[1, 2]} 36 | | ${Symbol('test')} 37 | |fourth 38 | `.should.equal('first\n undefined\n null\n false\n 1\n [object Object]\n 1,2\n Symbol(test)\nfourth'); 39 | }); 40 | 41 | it('indents interpolated values', () => { 42 | const part = multiline` 43 | |second 44 | |third 45 | `; 46 | 47 | multiline` 48 | |first 49 | | ${part} 50 | |fourth 51 | `.should.equal('first\n second\n third\nfourth'); 52 | }); 53 | 54 | it('allows extra intentional whitespace', () => { 55 | const part = multiline` 56 | |second\n 57 | |third 58 | `; 59 | 60 | // Have to use ${''} hack because vscode removes trailing whitespace 61 | // event inside template literals! It's not context/JS aware. 62 | // https://github.com/Microsoft/vscode/issues/52711 63 | multiline` 64 | |first \n 65 | |${part} 66 | |fourth ${''} 67 | `.should.equal('first \n\nsecond\n\nthird\nfourth '); 68 | }); 69 | }); 70 | --------------------------------------------------------------------------------