pax_global_header00006660000000000000000000000064123614677250014527gustar00rootroot0000000000000052 comment=32d4b1bb66ccb865fb1a0eb118cb8a8ad2cf48b3 character-parser-1.2.1/000077500000000000000000000000001236146772500147565ustar00rootroot00000000000000character-parser-1.2.1/.gitignore000066400000000000000000000001421236146772500167430ustar00rootroot00000000000000lib-cov *.seed *.log *.csv *.dat *.out *.pid *.gz pids logs results npm-debug.log node_modules character-parser-1.2.1/.npmignore000066400000000000000000000000211236146772500167460ustar00rootroot00000000000000test/ .travis.ymlcharacter-parser-1.2.1/.travis.yml000066400000000000000000000000571236146772500170710ustar00rootroot00000000000000language: node_js node_js: - "0.10" - "0.8"character-parser-1.2.1/LICENSE000066400000000000000000000020431236146772500157620ustar00rootroot00000000000000Copyright (c) 2013 Forbes Lindesay 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: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 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. character-parser-1.2.1/README.md000066400000000000000000000106551236146772500162440ustar00rootroot00000000000000# character-parser Parse JavaScript one character at a time to look for snippets in Templates. This is not a validator, it's just designed to allow you to have sections of JavaScript delimited by brackets robustly. [![Build Status](https://img.shields.io/travis/ForbesLindesay/character-parser/master.svg)](https://travis-ci.org/ForbesLindesay/character-parser) ## Installation npm install character-parser ## Usage Work out how much depth changes: ```js var state = parse('foo(arg1, arg2, {\n foo: [a, b\n'); assert(state.roundDepth === 1); assert(state.curlyDepth === 1); assert(state.squareDepth === 1); parse(' c, d]\n })', state); assert(state.squareDepth === 0); assert(state.curlyDepth === 0); assert(state.roundDepth === 0); ``` ### Bracketed Expressions Find all the contents of a bracketed expression: ```js var section = parser.parseMax('foo="(", bar="}") bing bong'); assert(section.start === 0); assert(section.end === 16);//exclusive end of string assert(section.src = 'foo="(", bar="}"'); var section = parser.parseMax('{foo="(", bar="}"} bing bong', {start: 1}); assert(section.start === 1); assert(section.end === 17);//exclusive end of string assert(section.src = 'foo="(", bar="}"'); ``` The bracketed expression parsing simply parses up to but excluding the first unmatched closed bracket (`)`, `}`, `]`). It is clever enough to ignore brackets in comments or strings. ### Custom Delimited Expressions Find code up to a custom delimiter: ```js var section = parser.parseUntil('foo.bar("%>").baz%> bing bong', '%>'); assert(section.start === 0); assert(section.end === 17);//exclusive end of string assert(section.src = 'foo.bar("%>").baz'); var section = parser.parseUntil('<%foo.bar("%>").baz%> bing bong', '%>', {start: 2}); assert(section.start === 2); assert(section.end === 19);//exclusive end of string assert(section.src = 'foo.bar("%>").baz'); ``` Delimiters are ignored if they are inside strings or comments. ## API ### parse(str, state = defaultState(), options = {start: 0, end: src.length}) Parse a string starting at the index start, and return the state after parsing that string. If you want to parse one string in multiple sections you should keep passing the resulting state to the next parse operation. Returns a `State` object. ### parseMax(src, options = {start: 0}) Parses the source until the first unmatched close bracket (any of `)`, `}`, `]`). It returns an object with the structure: ```js { start: 0,//index of first character of string end: 13,//index of first character after the end of string src: 'source string' } ``` ### parseUntil(src, delimiter, options = {start: 0, includeLineComment: false}) Parses the source until the first occurence of `delimiter` which is not in a string or a comment. If `includeLineComment` is `true`, it will still count if the delimiter occurs in a line comment, but not in a block comment. It returns an object with the structure: ```js { start: 0,//index of first character of string end: 13,//index of first character after the end of string src: 'source string' } ``` ### parseChar(character, state = defaultState()) Parses the single character and returns the state. See `parse` for the structure of the returned state object. N.B. character must be a single character not a multi character string. ### defaultState() Get a default starting state. ### isPunctuator(character) Returns `true` if `character` represents punctuation in JavaScript. ### isKeyword(name) Returns `true` if `name` is a keyword in JavaScript. ## State A state is an object with the following structure ```js { lineComment: false, //true if inside a line comment blockComment: false, //true if inside a block comment singleQuote: false, //true if inside a single quoted string doubleQuote: false, //true if inside a double quoted string regexp: false, //true if inside a regular expression escaped: false, //true if in a string and the last character was an escape character roundDepth: 0, //number of un-closed open `(` brackets curlyDepth: 0, //number of un-closed open `{` brackets squareDepth: 0 //number of un-closed open `[` brackets } ``` It also has the following useful methods: - `.isString()` returns `true` if the current location is inside a string. - `.isComment()` returns `true` if the current location is inside a comment. - `isNesting()` returns `true` if the current location is anything but at the top level, i.e. with no nesting. ## License MITcharacter-parser-1.2.1/index.js000066400000000000000000000164671236146772500164410ustar00rootroot00000000000000exports = (module.exports = parse); exports.parse = parse; function parse(src, state, options) { options = options || {}; state = state || exports.defaultState(); var start = options.start || 0; var end = options.end || src.length; var index = start; while (index < end) { if (state.roundDepth < 0 || state.curlyDepth < 0 || state.squareDepth < 0) { throw new SyntaxError('Mismatched Bracket: ' + src[index - 1]); } exports.parseChar(src[index++], state); } return state; } exports.parseMax = parseMax; function parseMax(src, options) { options = options || {}; var start = options.start || 0; var index = start; var state = exports.defaultState(); while (state.roundDepth >= 0 && state.curlyDepth >= 0 && state.squareDepth >= 0) { if (index >= src.length) { throw new Error('The end of the string was reached with no closing bracket found.'); } exports.parseChar(src[index++], state); } var end = index - 1; return { start: start, end: end, src: src.substring(start, end) }; } exports.parseUntil = parseUntil; function parseUntil(src, delimiter, options) { options = options || {}; var includeLineComment = options.includeLineComment || false; var start = options.start || 0; var index = start; var state = exports.defaultState(); while (state.isString() || state.regexp || state.blockComment || (!includeLineComment && state.lineComment) || !startsWith(src, delimiter, index)) { exports.parseChar(src[index++], state); } var end = index; return { start: start, end: end, src: src.substring(start, end) }; } exports.parseChar = parseChar; function parseChar(character, state) { if (character.length !== 1) throw new Error('Character must be a string of length 1'); state = state || exports.defaultState(); state.src = state.src || ''; state.src += character; var wasComment = state.blockComment || state.lineComment; var lastChar = state.history ? state.history[0] : ''; if (state.regexpStart) { if (character === '/' || character == '*') { state.regexp = false; } state.regexpStart = false; } if (state.lineComment) { if (character === '\n') { state.lineComment = false; } } else if (state.blockComment) { if (state.lastChar === '*' && character === '/') { state.blockComment = false; } } else if (state.singleQuote) { if (character === '\'' && !state.escaped) { state.singleQuote = false; } else if (character === '\\' && !state.escaped) { state.escaped = true; } else { state.escaped = false; } } else if (state.doubleQuote) { if (character === '"' && !state.escaped) { state.doubleQuote = false; } else if (character === '\\' && !state.escaped) { state.escaped = true; } else { state.escaped = false; } } else if (state.regexp) { if (character === '/' && !state.escaped) { state.regexp = false; } else if (character === '\\' && !state.escaped) { state.escaped = true; } else { state.escaped = false; } } else if (lastChar === '/' && character === '/') { state.history = state.history.substr(1); state.lineComment = true; } else if (lastChar === '/' && character === '*') { state.history = state.history.substr(1); state.blockComment = true; } else if (character === '/' && isRegexp(state.history)) { state.regexp = true; state.regexpStart = true; } else if (character === '\'') { state.singleQuote = true; } else if (character === '"') { state.doubleQuote = true; } else if (character === '(') { state.roundDepth++; } else if (character === ')') { state.roundDepth--; } else if (character === '{') { state.curlyDepth++; } else if (character === '}') { state.curlyDepth--; } else if (character === '[') { state.squareDepth++; } else if (character === ']') { state.squareDepth--; } if (!state.blockComment && !state.lineComment && !wasComment) state.history = character + state.history; state.lastChar = character; // store last character for ending block comments return state; } exports.defaultState = function () { return new State() }; function State() { this.lineComment = false; this.blockComment = false; this.singleQuote = false; this.doubleQuote = false; this.regexp = false; this.escaped = false; this.roundDepth = 0; this.curlyDepth = 0; this.squareDepth = 0; this.history = '' this.lastChar = '' } State.prototype.isString = function () { return this.singleQuote || this.doubleQuote; } State.prototype.isComment = function () { return this.lineComment || this.blockComment; } State.prototype.isNesting = function () { return this.isString() || this.isComment() || this.regexp || this.roundDepth > 0 || this.curlyDepth > 0 || this.squareDepth > 0 } function startsWith(str, start, i) { return str.substr(i || 0, start.length) === start; } exports.isPunctuator = isPunctuator function isPunctuator(c) { if (!c) return true; // the start of a string is a punctuator var code = c.charCodeAt(0) switch (code) { case 46: // . dot case 40: // ( open bracket case 41: // ) close bracket case 59: // ; semicolon case 44: // , comma case 123: // { open curly brace case 125: // } close curly brace case 91: // [ case 93: // ] case 58: // : case 63: // ? case 126: // ~ case 37: // % case 38: // & case 42: // *: case 43: // + case 45: // - case 47: // / case 60: // < case 62: // > case 94: // ^ case 124: // | case 33: // ! case 61: // = return true; default: return false; } } exports.isKeyword = isKeyword function isKeyword(id) { return (id === 'if') || (id === 'in') || (id === 'do') || (id === 'var') || (id === 'for') || (id === 'new') || (id === 'try') || (id === 'let') || (id === 'this') || (id === 'else') || (id === 'case') || (id === 'void') || (id === 'with') || (id === 'enum') || (id === 'while') || (id === 'break') || (id === 'catch') || (id === 'throw') || (id === 'const') || (id === 'yield') || (id === 'class') || (id === 'super') || (id === 'return') || (id === 'typeof') || (id === 'delete') || (id === 'switch') || (id === 'export') || (id === 'import') || (id === 'default') || (id === 'finally') || (id === 'extends') || (id === 'function') || (id === 'continue') || (id === 'debugger') || (id === 'package') || (id === 'private') || (id === 'interface') || (id === 'instanceof') || (id === 'implements') || (id === 'protected') || (id === 'public') || (id === 'static') || (id === 'yield') || (id === 'let'); } function isRegexp(history) { //could be start of regexp or divide sign history = history.replace(/^\s*/, ''); //unless its an `if`, `while`, `for` or `with` it's a divide, so we assume it's a divide if (history[0] === ')') return false; //unless it's a function expression, it's a regexp, so we assume it's a regexp if (history[0] === '}') return true; //any punctuation means it's a regexp if (isPunctuator(history[0])) return true; //if the last thing was a keyword then it must be a regexp (e.g. `typeof /foo/`) if (/^\w+\b/.test(history) && isKeyword(/^\w+\b/.exec(history)[0].split('').reverse().join(''))) return true; return false; } character-parser-1.2.1/package.json000066400000000000000000000013231236146772500172430ustar00rootroot00000000000000{ "name": "character-parser", "version": "1.2.1", "description": "Parse JavaScript one character at a time to look for snippets in Templates. This is not a validator, it's just designed to allow you to have sections of JavaScript delimited by brackets robustly.", "main": "index.js", "scripts": { "test": "mocha -R spec" }, "repository": { "type": "git", "url": "https://github.com/ForbesLindesay/character-parser.git" }, "keywords": [ "parser", "JavaScript", "bracket", "nesting", "comment", "string", "escape", "escaping" ], "author": "ForbesLindesay", "license": "MIT", "devDependencies": { "better-assert": "~1.0.0", "mocha": "~1.9.0" } }character-parser-1.2.1/test/000077500000000000000000000000001236146772500157355ustar00rootroot00000000000000character-parser-1.2.1/test/index.js000066400000000000000000000046361236146772500174130ustar00rootroot00000000000000var assert = require('better-assert'); var parser = require('../'); var parse = parser; it('works out how much depth changes', function () { var state = parse('foo(arg1, arg2, {\n foo: [a, b\n'); assert(state.roundDepth === 1); assert(state.curlyDepth === 1); assert(state.squareDepth === 1); parse(' c, d]\n })', state); assert(state.squareDepth === 0); assert(state.curlyDepth === 0); assert(state.roundDepth === 0); }); it('finds contents of bracketed expressions', function () { var section = parser.parseMax('foo="(", bar="}") bing bong'); assert(section.start === 0); assert(section.end === 16);//exclusive end of string assert(section.src = 'foo="(", bar="}"'); var section = parser.parseMax('{foo="(", bar="}"} bing bong', {start: 1}); assert(section.start === 1); assert(section.end === 17);//exclusive end of string assert(section.src = 'foo="(", bar="}"'); }); it('finds code up to a custom delimiter', function () { var section = parser.parseUntil('foo.bar("%>").baz%> bing bong', '%>'); assert(section.start === 0); assert(section.end === 17);//exclusive end of string assert(section.src = 'foo.bar("%>").baz'); var section = parser.parseUntil('<%foo.bar("%>").baz%> bing bong', '%>', {start: 2}); assert(section.start === 2); assert(section.end === 19);//exclusive end of string assert(section.src = 'foo.bar("%>").baz'); }); describe('regressions', function () { describe('#1', function () { it('parses regular expressions', function () { var section = parser.parseMax('foo=/\\//g, bar="}") bing bong'); assert(section.start === 0); assert(section.end === 18);//exclusive end of string assert(section.src = 'foo=/\\//g, bar="}"'); var section = parser.parseMax('foo = typeof /\\//g, bar="}") bing bong'); assert(section.start === 0); //assert(section.end === 18);//exclusive end of string assert(section.src = 'foo = typeof /\\//g, bar="}"'); }) }) describe('#6', function () { it('parses block comments', function () { var section = parser.parseMax('/* ) */) bing bong'); assert(section.start === 0); assert(section.end === 7);//exclusive end of string assert(section.src = '/* ) */)'); var section = parser.parseMax('/* /) */) bing bong'); assert(section.start === 0); assert(section.end === 8);//exclusive end of string assert(section.src = '/* ) */)'); }) }) })