UNPKG

split-string

Version:

Easy way to split a string on a given character unless it's quoted or escaped.

149 lines (124 loc) 3.78 kB
'use strict'; module.exports = (input, options = {}, fn) => { if (typeof input !== 'string') throw new TypeError('expected a string'); if (typeof options === 'function') { fn = options; options = {}; } let separator = options.separator || '.'; let ast = { type: 'root', nodes: [], stash: [''] }; let stack = [ast]; let state = { input, separator, stack }; let string = input; let value, node; let i = -1; state.bos = () => i === 0; state.eos = () => i === string.length; state.prev = () => string[i - 1]; state.next = () => string[i + 1]; let quotes = options.quotes || []; let openers = options.brackets || {}; if (options.brackets === true) { openers = { '[': ']', '(': ')', '{': '}', '<': '>' }; } if (options.quotes === true) { quotes = ['"', '\'', '`']; } let closers = invert(openers); let keep = options.keep || (value => value !== '\\'); const block = () => (state.block = stack[stack.length - 1]); const peek = () => string[i + 1]; const next = () => string[++i]; const append = value => { state.value = value; if (value && keep(value, state) !== false) { state.block.stash[state.block.stash.length - 1] += value; } }; const closeIndex = (value, startIdx) => { let idx = string.indexOf(value, startIdx); if (idx > -1 && string[idx - 1] === '\\') { idx = closeIndex(value, idx + 1); } return idx; }; for (; i < string.length - 1;) { state.value = value = next(); state.index = i; block(); // handle escaped characters if (value === '\\') { if (peek() === '\\') { append(value + next()); } else { // if the next char is not '\\', allow the "append" function // to determine if the backslashes should be added append(value); append(next()); } continue; } // handle quoted strings if (quotes.includes(value)) { let pos = i + 1; let idx = closeIndex(value, pos); if (idx > -1) { append(value); // append opening quote append(string.slice(pos, idx)); // append quoted string append(string[idx]); // append closing quote i = idx; continue; } append(value); continue; } // handle opening brackets, if not disabled if (options.brackets !== false && openers[value]) { node = { type: 'bracket', nodes: [] }; node.stash = keep(value) !== false ? [value] : ['']; node.parent = state.block; state.block.nodes.push(node); stack.push(node); continue; } // handle closing brackets, if not disabled if (options.brackets !== false && closers[value]) { if (stack.length === 1) { append(value); continue; } append(value); node = stack.pop(); block(); append(node.stash.join('')); continue; } // push separator onto stash if (value === separator && state.block.type === 'root') { if (typeof fn === 'function' && fn(state) === false) { append(value); continue; } state.block.stash.push(''); continue; } // append value onto the last string on the stash append(value); } node = stack.pop(); while (node !== ast) { if (options.strict === true) { let column = i - node.stash.length + 1; throw new SyntaxError(`Unmatched: "${node.stash[0]}", at column ${column}`); } value = (node.parent.stash.pop() + node.stash.join('.')); node.parent.stash = node.parent.stash.concat(value.split('.')); node = stack.pop(); } return node.stash; }; function invert(obj) { let inverted = {}; for (const key of Object.keys(obj)) inverted[obj[key]] = key; return inverted; }