bash-parser
Advanced tools
Comparing version 0.4.0 to 0.5.0
{ | ||
"name": "bash-parser", | ||
"version": "0.4.0", | ||
"version": "0.5.0", | ||
"main": "src/index.js", | ||
@@ -11,5 +11,5 @@ "description": "Standard compliant bash parser", | ||
"doc": "doctoc", | ||
"test": "ava --no-babel && xo --ignore **/built-grammar.js --ignore 'packages/**/*'", | ||
"build": "mgb ./src/modes posix && mgb ./src/modes bash", | ||
"cover-test": "nyc ava --no-babel && xo --ignore **/built-grammar.js --ignore 'packages/**/*'", | ||
"test": "ava && xo --ignore **/built-grammar.js", | ||
"build": "mgb ./src/modes posix && mgb ./src/modes bash && mgb ./src/modes word-expansion", | ||
"cover-test": "nyc ava && xo --ignore **/built-grammar.js", | ||
"cover-publish": "nyc report --reporter=text-lcov | coveralls" | ||
@@ -26,3 +26,3 @@ }, | ||
"json5": "^0.5.0", | ||
"mode-grammar-builder": "0.0.0", | ||
"mode-grammar-builder": "^0.6.0", | ||
"nyc": "^7.0.0", | ||
@@ -29,0 +29,0 @@ "xo": "^0.16.0" |
# bash-parser | ||
> See [repo root readme](https://github.com/vorpaljs/bash-parser) | ||
Parses bash source code to produce an AST | ||
[![Travis Build Status](https://img.shields.io/travis/vorpaljs/bash-parser/master.svg)](http://travis-ci.org/vorpaljs/bash-parser) | ||
[![Coveralls](https://img.shields.io/coveralls/vorpaljs/bash-parser.svg?maxAge=2592000)](https://coveralls.io/github/vorpaljs/bash-parser) | ||
[![NPM module](https://img.shields.io/npm/v/bash-parser.svg)](https://npmjs.org/package/bash-parser) | ||
[![NPM downloads](https://img.shields.io/npm/dt/bash-parser.svg)](https://npmjs.org/package/bash-parser) | ||
[![Try online](https://img.shields.io/badge/try_it-online!-yellow.svg)](https://vorpaljs.github.io/bash-parser-playground/) | ||
# Installation | ||
```bash | ||
npm install --save bash-parser | ||
``` | ||
# Usage | ||
```js | ||
const parse = require('bash-parser'); | ||
const ast = parse('echo ciao'); | ||
``` | ||
`ast` result is: | ||
```js | ||
{ | ||
type: "Script", | ||
commands: [ | ||
{ | ||
type: "SimpleCommand", | ||
name: { | ||
text: "echo", | ||
type: "Word" | ||
}, | ||
suffix: [ | ||
{ | ||
text: "ciao", | ||
type: "Word" | ||
} | ||
] | ||
} | ||
] | ||
} | ||
``` | ||
# Related projects | ||
* [cash](https://github.com/dthree/cash) - This parser should become the parser used by `cash` (and also [vorpal](https://github.com/dthree/vorpal)) | ||
* [nsh](https://github.com/piranna/nsh) - This parser should become the parser used by `nsh` | ||
* [js-shell-parse](https://github.com/grncdr/js-shell-parse) - bash-parser was born as a fork of `js-shell-parse`, but was rewritten to use a `jison` grammar | ||
* [jison](https://github.com/zaach/jison) - Bison in JavaScript. | ||
# Documentation | ||
Look in [documents folder](https://github.com/vorpaljs/bash-parser/tree/master/documents) | ||
# License | ||
The MIT License (MIT) | ||
Copyright (c) 2016 vorpaljs |
@@ -122,2 +122,5 @@ /* parser generated by jison 0.4.17 */ | ||
break; | ||
case 16: | ||
this.$ = yy.addRedirections($$[$0-1], $$[$0]) | ||
break; | ||
case 25: | ||
@@ -184,3 +187,3 @@ this.$ = yy.subshell($$[$0-1], $$[$0-2].loc, $$[$0].loc); | ||
case 55: | ||
this.$ = yy.patternAppend(pattern, $$[$0]); | ||
this.$ = yy.patternAppend($$[$0-2], $$[$0]); | ||
break; | ||
@@ -211,2 +214,8 @@ case 56: | ||
break; | ||
case 64: | ||
this.$ = [$$[$0], null]; | ||
break; | ||
case 65: | ||
this.$ = [$$[$0-1], $$[$0]]; | ||
break; | ||
case 67: | ||
@@ -255,3 +264,3 @@ this.$ = yy.braceGroup($$[$0-1], $$[$0-2].loc, $$[$0].loc); | ||
case 85: | ||
this.$ = [$$[$0-1].concat($$[$0])]; | ||
this.$ = $$[$0-1].concat($$[$0]); | ||
break; | ||
@@ -258,0 +267,0 @@ case 87: |
@@ -5,2 +5,121 @@ 'use strict'; | ||
const name = '[a-zA-Z_][a-zA-Z0-9_]*'; | ||
const parameterOperators = { | ||
// This is referred to as Substring Expansion. | ||
// It expands to up to length characters of the value | ||
// of parameter starting at the character specified by offset. | ||
[`^(${name}):([^:]*):?([^:]*)$`]: { | ||
op: 'substring', | ||
parameter: m => m[1], | ||
offset: m => parseInt(m[2], 10), | ||
length: m => parseInt(m[3], 10) || undefined | ||
}, | ||
// Expands to the names of variables whose names begin with prefix, | ||
// separated by the first character of the IFS special variable. | ||
// When ‘@’ is used and the expansion appears within double quotes, | ||
// each variable name expands to a separate word. | ||
// TODO: @ case may need some investigation, maybe it's not actually possible | ||
[`^!(${name})(\\*|@)$`]: { | ||
op: 'prefix', | ||
prefix: m => m[1], | ||
expandWords: m => m[2] === '@', | ||
parameter: () => undefined | ||
}, | ||
// If name is an array variable, expands to the list of array indices | ||
// (keys) assigned in name. If name is not an array, expands to 0 if | ||
// name is set and null otherwise. When ‘@’ is used and the expansion | ||
// appears within double quotes, each key expands to a separate word. | ||
// TODO: @ case may need some investigation, maybe it's not actually possible | ||
[`^!(${name})(\\[\\*\\]|\\[@\\])$`]: { | ||
op: 'arrayIndices', | ||
parameter: m => m[1], | ||
expandWords: m => m[2] === '[@]' | ||
}, | ||
// Parameter is expanded and the longest match of pattern against its | ||
// value is replaced with string. If pattern begins with ‘/’, all matches | ||
// of pattern are replaced with string. | ||
[`^(${name})\\/(\\/)?([^\\/])+\\/(.*)$`]: { | ||
op: 'stringReplace', | ||
parameter: m => m[1], | ||
substitute: m => m[3], | ||
replace: m => m[4], | ||
globally: m => m[2] === '/' | ||
}, | ||
// This expansion modifies the case of alphabetic characters in parameter. | ||
// The pattern is expanded to produce a pattern just as in filename expansion. | ||
// Each character in the expanded value of parameter is tested against pattern, | ||
// and, if it matches the pattern, its case is converted. The pattern should | ||
// not attempt to match more than one character. The ‘^’ operator converts | ||
// lowercase letters matching pattern to uppercase; the ‘,’ operator converts | ||
// matching uppercase letters to lowercase. The ‘^^’ and ‘,,’ expansions convert | ||
// each matched character in the expanded value; the ‘^’ and ‘,’ expansions match | ||
// and convert only the first character in the expanded value. If pattern is omitted, | ||
// it is treated like a ‘?’, which matches every character. If parameter is ‘@’ | ||
// or ‘*’, the case modification operation is applied to each positional parameter | ||
// in turn, and the expansion is the resultant list. If parameter is an array variable | ||
// subscripted with ‘@’ or ‘*’, the case modification operation is applied to each | ||
// member of the array in turn, and the expansion is the resultant list. | ||
[`^(${name})(\\^\\^|\\^|,,|,)(.*)$`]: { | ||
op: 'caseChange', | ||
parameter: m => m[1], | ||
pattern: m => m[3] || '?', | ||
case: m => m[2][0] === ',' ? 'lower' : 'upper', | ||
globally: m => m[2].length === 2 | ||
}, | ||
// The expansion is either a transformation of the value of parameter or information about | ||
// parameter itself, depending on the value of operator. Each operator is a single letter: | ||
// | ||
// Q - The expansion is a string that is the value of parameter quoted in a format that can | ||
// be reused as input. | ||
// E - The expansion is a string that is the value of parameter with backslash escape | ||
// sequences expanded as with the $'…' quoting mechansim. | ||
// P - The expansion is a string that is the result of expanding the value of parameter | ||
// as if it were a prompt string (see Controlling the Prompt). | ||
// A - The expansion is a string in the form of an assignment statement or declare command | ||
// that, if evaluated, will recreate parameter with its attributes and value. | ||
// a - The expansion is a string consisting of flag values representing parameter’s attributes. | ||
// | ||
// If parameter is ‘@’ or ‘*’, the operation is applied to each positional parameter in turn, | ||
// and the expansion is the resultant list. If parameter is an array variable subscripted | ||
// with ‘@’ or ‘*’, the operation is applied to each member of the array in turn, and the | ||
// expansion is the resultant list. | ||
// The result of the expansion is subject to word splitting and pathname expansion as | ||
// described below. | ||
[`^(${name})@([Q|E|P|A|a])$`]: { | ||
op: 'transformation', | ||
parameter: m => m[1], | ||
kind: m => { | ||
switch (m[2]) { | ||
case 'Q': return 'quoted'; | ||
case 'E': return 'escape'; | ||
case 'P': return 'prompt'; | ||
case 'A': return 'assignment'; | ||
case 'a': return 'flags'; | ||
default: return 'unknown'; | ||
} | ||
} | ||
}, | ||
// If the first character of parameter is an exclamation point (!), and parameter is not | ||
// a nameref, it introduces a level of variable indirection. Bash uses the value of the | ||
// variable formed from the rest of parameter as the name of the variable; this variable | ||
// is then expanded and that value is used in the rest of the substitution, rather than | ||
// the value of parameter itself. This is known as indirect expansion. If parameter is a | ||
// nameref, this expands to the name of the variable referenced by parameter instead of | ||
// performing the complete indirect expansion. The exceptions to this are the expansions | ||
// of ${!prefix*} and ${!name[@]} described below. The exclamation point must immediately | ||
// follow the left brace in order to introduce indirection. | ||
[`^!(.+)$`]: { | ||
op: 'indirection', | ||
word: m => m[1], | ||
parameter: () => undefined | ||
} | ||
}; | ||
module.exports = { | ||
@@ -21,4 +140,19 @@ inherits: 'posix', | ||
return Object.assign({}, posixMode, {phaseCatalog, lexerPhases}); | ||
const bashOperators = Object.assign( | ||
parameterOperators, | ||
posixMode.enums.parameterOperators | ||
); | ||
const enums = Object.assign( | ||
{}, | ||
posixMode.enums, | ||
{parameterOperators: bashOperators} | ||
); | ||
return Object.assign( | ||
{}, | ||
posixMode, | ||
{phaseCatalog, lexerPhases, enums} | ||
); | ||
} | ||
}; |
@@ -82,2 +82,11 @@ 'use strict'; | ||
builder.addRedirections = (compoundCommand, redirectList) => { | ||
compoundCommand.redirections = redirectList; | ||
if (options.insertLOC) { | ||
const lastRedirect = redirectList[redirectList.length - 1]; | ||
setLocEnd(compoundCommand.loc, lastRedirect.loc); | ||
} | ||
return compoundCommand; | ||
}; | ||
builder.term = logicalExpression => { | ||
@@ -172,5 +181,14 @@ const node = {type: 'CompoundList', commands: [logicalExpression]}; | ||
builder.functionDefinition = (name, body) => { | ||
const node = {type: 'Function', name, body}; | ||
const node = {type: 'Function', name}; | ||
node.body = body[0]; | ||
if (body[1]) { | ||
node.redirections = body[1]; | ||
} | ||
const endLoc = body[1] || body[0]; | ||
if (options.insertLOC) { | ||
node.loc = setLocEnd(setLocStart({}, name.loc), body.loc); | ||
node.loc = setLocEnd(setLocStart({}, name.loc), endLoc.loc); | ||
} | ||
@@ -224,7 +242,10 @@ return node; | ||
builder.commandAssignment = function commandAssignment(prefix) { | ||
return builder.command(prefix, {text: '', type: 'Word'}); | ||
return builder.command(prefix); | ||
}; | ||
builder.command = function command(prefix, command, suffix) { | ||
const node = {type: 'Command', name: command}; | ||
const node = {type: 'Command'}; | ||
if (command) { | ||
node.name = command; | ||
} | ||
@@ -243,4 +264,7 @@ if (options.insertLOC) { | ||
node.loc.end = lastSuffix.loc.end; | ||
} else if (command) { | ||
node.loc.end = command.loc.end; | ||
} else { | ||
node.loc.end = command.loc.end; | ||
const lastPrefix = prefix[prefix.length - 1]; | ||
node.loc.end = lastPrefix.loc.end; | ||
} | ||
@@ -247,0 +271,0 @@ } |
@@ -122,2 +122,5 @@ /* parser generated by jison 0.4.17 */ | ||
break; | ||
case 16: | ||
this.$ = yy.addRedirections($$[$0-1], $$[$0]) | ||
break; | ||
case 25: | ||
@@ -184,3 +187,3 @@ this.$ = yy.subshell($$[$0-1], $$[$0-2].loc, $$[$0].loc); | ||
case 55: | ||
this.$ = yy.patternAppend(pattern, $$[$0]); | ||
this.$ = yy.patternAppend($$[$0-2], $$[$0]); | ||
break; | ||
@@ -211,2 +214,8 @@ case 56: | ||
break; | ||
case 64: | ||
this.$ = [$$[$0], null]; | ||
break; | ||
case 65: | ||
this.$ = [$$[$0-1], $$[$0]]; | ||
break; | ||
case 67: | ||
@@ -255,3 +264,3 @@ this.$ = yy.braceGroup($$[$0-1], $$[$0-2].loc, $$[$0].loc); | ||
case 85: | ||
this.$ = [$$[$0-1].concat($$[$0])]; | ||
this.$ = $$[$0-1].concat($$[$0]); | ||
break; | ||
@@ -258,0 +267,0 @@ case 87: |
@@ -71,3 +71,6 @@ /* eslint-disable max-lines */ | ||
'compound_command', | ||
'compound_command redirect_list', | ||
[ | ||
'compound_command redirect_list', | ||
'$$ = yy.addRedirections($compound_command, $redirect_list)' | ||
], | ||
'function_definition' | ||
@@ -223,3 +226,3 @@ ], | ||
'pattern PIPE WORD', | ||
'$$ = yy.patternAppend(pattern, $WORD);' | ||
'$$ = yy.patternAppend($pattern, $WORD);' | ||
] | ||
@@ -270,4 +273,10 @@ ], | ||
function_body: [ | ||
'compound_command', | ||
'compound_command redirect_list' | ||
[ | ||
'compound_command', | ||
'$$ = [$compound_command, null];' | ||
], | ||
[ | ||
'compound_command redirect_list', | ||
'$$ = [$compound_command, $redirect_list];' | ||
] | ||
], | ||
@@ -366,3 +375,3 @@ fname: [ | ||
'redirect_list io_redirect', | ||
'$$ = [$redirect_list.concat($io_redirect)];' | ||
'$$ = $redirect_list.concat($io_redirect);' | ||
] | ||
@@ -369,0 +378,0 @@ ], |
'use strict'; | ||
const mapObj = require('map-obj'); | ||
const filter = require('filter-obj'); | ||
const map = require('map-iterable'); | ||
@@ -12,3 +13,4 @@ const pairs = require('object-pairs'); | ||
if (typeof v === 'function') { | ||
return [k, v(match)]; | ||
const val = v(match); | ||
return [k, val]; | ||
} | ||
@@ -47,6 +49,6 @@ | ||
return Object.assign( | ||
return filter(Object.assign( | ||
xp, | ||
opProps | ||
); | ||
), (k, v) => v !== undefined); | ||
} | ||
@@ -53,0 +55,0 @@ } |
@@ -7,3 +7,3 @@ 'use strict'; | ||
let replaced = false; | ||
let result = text.replace(/^~[^\/]*\//, (match, p1) => { | ||
let result = text.replace(/^~([^\/]*)\//, (match, p1) => { | ||
replaced = true; | ||
@@ -14,3 +14,3 @@ return resolveHomeUser(p1 || null) + '/'; | ||
if (!replaced) { | ||
result = text.replace(/^~.*$/, (match, p1) => { | ||
result = text.replace(/^~(.*)$/, (match, p1) => { | ||
return resolveHomeUser(p1 || null); | ||
@@ -17,0 +17,0 @@ }); |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
168074
62
4311
63
0
5