Skip to content

Commit

Permalink
extend and refactor parser. all tests pass. npm test works
Browse files Browse the repository at this point in the history
closes #33
closes #49
closes #45
  • Loading branch information
snd committed Apr 30, 2019
1 parent 2861b95 commit 080c6eb
Show file tree
Hide file tree
Showing 6 changed files with 230 additions and 187 deletions.
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,8 @@
"compile": "tsc",
"docs": "typedoc --out doc --module UrlPattern index.ts",
"prepublish": "npm run compile",
"test": "tape test/*",
"pretest": "npm run compile",
"test": "tape -r esm test/*",
"test-with-coverage": "istanbul cover coffeetape test/* && cat ./coverage/coverage.json | ./node_modules/codecov.io/bin/codecov.io.js",
"test-in-browsers": "zuul test/*",
"test-zuul-local": "zuul --local 8080 test/*"
Expand Down
104 changes: 71 additions & 33 deletions src/parser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,27 +26,72 @@ import {
IOptions,
} from "./options";

export function newEscapedCharParser(options: IOptions): Parser<Ast<any>> {
return pick(1, string(options.escapeChar), regex(/^./));
}

export function newWildcardParser(options: IOptions): Parser<Ast<any>> {
return newAst("wildcard", string(options.wildcardChar));
}

/*
*
* parses just the segment name in a named segment
*/
export function newUrlPatternParser(options: IOptions): Parser<Ast<any>> {
const parseEscapedChar = pick(1, string(options.escapeChar), regex(/^./));

const parseSegmentName = regex(new RegExp(`^[${ options.segmentNameCharset }]+`));
export function newSegmentNameParser(options: IOptions): Parser<string> {
return regex(new RegExp(`^[${ options.segmentNameCharset }]+`));
}

let parseNamedSegment = newAst("namedSegment", pick(1,
string(options.segmentNameStartChar),
parseSegmentName));
if (options.segmentNameEndChar != null) {
parseNamedSegment = newAst("namedSegment", pick(1,
export function newNamedSegmentParser(options: IOptions): Parser<Ast<any>> {
const parseSegmentName = newSegmentNameParser(options);
if (options.segmentNameEndChar == null) {
return newAst("namedSegment", pick(1,
string(options.segmentNameStartChar),
parseSegmentName));
} else {
return newAst("namedSegment", pick(1,
string(options.segmentNameStartChar),
parseSegmentName,
string(options.segmentNameEndChar)));
}
}

export function newNamedWildcardParser(options: IOptions): Parser<Ast<any>> {
if (options.segmentNameEndChar == null) {
return newAst("namedWildcard", pick(2,
string(options.wildcardChar),
string(options.segmentNameStartChar),
newSegmentNameParser(options),
));
} else {
return newAst("namedWildcard", pick(2,
string(options.wildcardChar),
string(options.segmentNameStartChar),
newSegmentNameParser(options),
string(options.segmentNameEndChar),
));
}
}

const parseWildcard = newAst("wildcard", string(options.wildcardChar));
export function newStaticContentParser(options: IOptions): Parser<Ast<any>> {
return newAst("staticContent", concatMany1Till(firstChoice(
newEscapedCharParser(options),
regex(/^./)),
// parse any normal or escaped char until the following matches:
firstChoice(
string(options.segmentNameStartChar),
string(options.optionalSegmentStartChar),
string(options.optionalSegmentEndChar),
newWildcardParser(options),
newNamedWildcardParser(options),
),
));
}

let pattern: Parser<any> = (input: string) => {
/*
*
*/
export function newUrlPatternParser(options: IOptions): Parser<Ast<any>> {
let parsePattern: Parser<any> = (input: string) => {
throw new Error(`
this is just a temporary placeholder
to make a circular dependency work.
Expand All @@ -56,27 +101,20 @@ export function newUrlPatternParser(options: IOptions): Parser<Ast<any>> {

const parseOptionalSegment = newAst("optionalSegment", pick(1,
string(options.optionalSegmentStartChar),
lazy(() => pattern),
lazy(() => parsePattern),
string(options.optionalSegmentEndChar)));

const parseStatic = newAst("static", concatMany1Till(firstChoice(
parseEscapedChar,
regex(/^./)),
firstChoice(
string(options.segmentNameStartChar),
string(options.optionalSegmentStartChar),
string(options.optionalSegmentEndChar),
lazy(() => parseWildcard))));

const token = firstChoice(
parseWildcard,
const parseToken = firstChoice(
newNamedWildcardParser(options),
newWildcardParser(options),
parseOptionalSegment,
parseNamedSegment,
parseStatic);
newNamedSegmentParser(options),
newStaticContentParser(options),
);

pattern = many1(token);
parsePattern = many1(parseToken);

return pattern;
return parsePattern;
}

// functions that further process ASTs returned as `.value` in parser results
Expand All @@ -91,7 +129,7 @@ function baseAstNodeToRegexString(astNode: Ast<any>, segmentValueCharset: string
return "(.*?)";
case "namedSegment":
return `([${ segmentValueCharset }]+)`;
case "static":
case "staticContent":
return escapeStringForRegex(astNode.value);
case "optionalSegment":
return `(?:${ baseAstNodeToRegexString(astNode.value, segmentValueCharset) })?`;
Expand All @@ -117,7 +155,7 @@ export function astNodeToNames(astNode: Ast<any> | Array<Ast<any>>): string[] {
return ["_"];
case "namedSegment":
return [astNode.value];
case "static":
case "staticContent":
return [];
case "optionalSegment":
return astNodeToNames(astNode.value);
Expand Down Expand Up @@ -184,7 +222,7 @@ function astNodeContainsSegmentsForProvidedParams(
return getParam(params, "_", nextIndexes, false) != null;
case "namedSegment":
return getParam(params, astNode.value, nextIndexes, false) != null;
case "static":
case "staticContent":
return false;
case "optionalSegment":
return astNodeContainsSegmentsForProvidedParams(astNode.value, params, nextIndexes);
Expand All @@ -199,7 +237,7 @@ function astNodeContainsSegmentsForProvidedParams(
export function stringify(
astNode: Ast<any> | Array<Ast<any>>,
params: { [index: string]: any },
nextIndexes: { [index: string]: number },
nextIndexes: { [index: string]: number } = {},
): string {
if (Array.isArray(astNode)) {
return stringConcatMap(astNode, (node) => stringify(node, params, nextIndexes));
Expand All @@ -210,7 +248,7 @@ export function stringify(
return getParam(params, "_", nextIndexes, true);
case "namedSegment":
return getParam(params, astNode.value, nextIndexes, true);
case "static":
case "staticContent":
return astNode.value;
case "optionalSegment":
if (astNodeContainsSegmentsForProvidedParams(astNode.value, params, nextIndexes)) {
Expand Down
20 changes: 10 additions & 10 deletions src/url-pattern.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,18 +50,18 @@ export default class UrlPattern {
this.regex = pattern;
if (optionsOrGroupNames != null) {
if (!Array.isArray(optionsOrGroupNames)) {
throw new TypeError(`
if first argument is a RegExp the second argument
may be an Array<String> of group names
but you provided something else
`);
throw new TypeError([
"if first argument is a RegExp the second argument",
"may be an Array<String> of group names",
"but you provided something else",
].join(" "));
}
const groupCount = regexGroupCount(this.regex);
if (optionsOrGroupNames.length !== groupCount) {
throw new Error(`
regex contains ${ groupCount } groups
but array of group names contains ${ optionsOrGroupNames.length }
`);
throw new Error([
`regex contains ${ groupCount } groups`,
`but array of group names contains ${ optionsOrGroupNames.length }`,
].join(" "));
}
this.names = optionsOrGroupNames;
}
Expand Down Expand Up @@ -142,6 +142,6 @@ export default class UrlPattern {
if (params !== Object(params)) {
throw new Error("argument must be an object or undefined");
}
return stringify(this.ast, params, {});
return stringify(this.ast, params);
}
}
11 changes: 6 additions & 5 deletions test/match-fixtures.js
Original file line number Diff line number Diff line change
Expand Up @@ -142,15 +142,16 @@ test('match', function(t) {
});

pattern = new UrlPattern('/:foo_bar');
t.equal(pattern.match('/_bar'), undefined);
t.deepEqual(pattern.match('/_bar'),
{foo_bar: '_bar'});
t.deepEqual(pattern.match('/a_bar'),
{foo: 'a'});
{foo_bar: 'a_bar'});
t.deepEqual(pattern.match('/a__bar'),
{foo: 'a_'});
{foo_bar: 'a__bar'});
t.deepEqual(pattern.match('/a-b-c-d__bar'),
{foo: 'a-b-c-d_'});
{foo_bar: 'a-b-c-d__bar'});
t.deepEqual(pattern.match('/a b%c-d__bar'),
{foo: 'a b%c-d_'});
{foo_bar: 'a b%c-d__bar'});

pattern = new UrlPattern('((((a)b)c)d)');
t.deepEqual(pattern.match(''), {});
Expand Down
Loading

0 comments on commit 080c6eb

Please sign in to comment.