Skip to content

Commit 080c6eb

Browse files
committed
extend and refactor parser. all tests pass. npm test works
closes #33 closes #49 closes #45
1 parent 2861b95 commit 080c6eb

File tree

6 files changed

+230
-187
lines changed

6 files changed

+230
-187
lines changed

package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,8 @@
8282
"compile": "tsc",
8383
"docs": "typedoc --out doc --module UrlPattern index.ts",
8484
"prepublish": "npm run compile",
85-
"test": "tape test/*",
85+
"pretest": "npm run compile",
86+
"test": "tape -r esm test/*",
8687
"test-with-coverage": "istanbul cover coffeetape test/* && cat ./coverage/coverage.json | ./node_modules/codecov.io/bin/codecov.io.js",
8788
"test-in-browsers": "zuul test/*",
8889
"test-zuul-local": "zuul --local 8080 test/*"

src/parser.ts

Lines changed: 71 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -26,27 +26,72 @@ import {
2626
IOptions,
2727
} from "./options";
2828

29+
export function newEscapedCharParser(options: IOptions): Parser<Ast<any>> {
30+
return pick(1, string(options.escapeChar), regex(/^./));
31+
}
32+
33+
export function newWildcardParser(options: IOptions): Parser<Ast<any>> {
34+
return newAst("wildcard", string(options.wildcardChar));
35+
}
36+
2937
/*
30-
*
38+
* parses just the segment name in a named segment
3139
*/
32-
export function newUrlPatternParser(options: IOptions): Parser<Ast<any>> {
33-
const parseEscapedChar = pick(1, string(options.escapeChar), regex(/^./));
34-
35-
const parseSegmentName = regex(new RegExp(`^[${ options.segmentNameCharset }]+`));
40+
export function newSegmentNameParser(options: IOptions): Parser<string> {
41+
return regex(new RegExp(`^[${ options.segmentNameCharset }]+`));
42+
}
3643

37-
let parseNamedSegment = newAst("namedSegment", pick(1,
38-
string(options.segmentNameStartChar),
39-
parseSegmentName));
40-
if (options.segmentNameEndChar != null) {
41-
parseNamedSegment = newAst("namedSegment", pick(1,
44+
export function newNamedSegmentParser(options: IOptions): Parser<Ast<any>> {
45+
const parseSegmentName = newSegmentNameParser(options);
46+
if (options.segmentNameEndChar == null) {
47+
return newAst("namedSegment", pick(1,
48+
string(options.segmentNameStartChar),
49+
parseSegmentName));
50+
} else {
51+
return newAst("namedSegment", pick(1,
4252
string(options.segmentNameStartChar),
4353
parseSegmentName,
4454
string(options.segmentNameEndChar)));
4555
}
56+
}
57+
58+
export function newNamedWildcardParser(options: IOptions): Parser<Ast<any>> {
59+
if (options.segmentNameEndChar == null) {
60+
return newAst("namedWildcard", pick(2,
61+
string(options.wildcardChar),
62+
string(options.segmentNameStartChar),
63+
newSegmentNameParser(options),
64+
));
65+
} else {
66+
return newAst("namedWildcard", pick(2,
67+
string(options.wildcardChar),
68+
string(options.segmentNameStartChar),
69+
newSegmentNameParser(options),
70+
string(options.segmentNameEndChar),
71+
));
72+
}
73+
}
4674

47-
const parseWildcard = newAst("wildcard", string(options.wildcardChar));
75+
export function newStaticContentParser(options: IOptions): Parser<Ast<any>> {
76+
return newAst("staticContent", concatMany1Till(firstChoice(
77+
newEscapedCharParser(options),
78+
regex(/^./)),
79+
// parse any normal or escaped char until the following matches:
80+
firstChoice(
81+
string(options.segmentNameStartChar),
82+
string(options.optionalSegmentStartChar),
83+
string(options.optionalSegmentEndChar),
84+
newWildcardParser(options),
85+
newNamedWildcardParser(options),
86+
),
87+
));
88+
}
4889

49-
let pattern: Parser<any> = (input: string) => {
90+
/*
91+
*
92+
*/
93+
export function newUrlPatternParser(options: IOptions): Parser<Ast<any>> {
94+
let parsePattern: Parser<any> = (input: string) => {
5095
throw new Error(`
5196
this is just a temporary placeholder
5297
to make a circular dependency work.
@@ -56,27 +101,20 @@ export function newUrlPatternParser(options: IOptions): Parser<Ast<any>> {
56101

57102
const parseOptionalSegment = newAst("optionalSegment", pick(1,
58103
string(options.optionalSegmentStartChar),
59-
lazy(() => pattern),
104+
lazy(() => parsePattern),
60105
string(options.optionalSegmentEndChar)));
61106

62-
const parseStatic = newAst("static", concatMany1Till(firstChoice(
63-
parseEscapedChar,
64-
regex(/^./)),
65-
firstChoice(
66-
string(options.segmentNameStartChar),
67-
string(options.optionalSegmentStartChar),
68-
string(options.optionalSegmentEndChar),
69-
lazy(() => parseWildcard))));
70-
71-
const token = firstChoice(
72-
parseWildcard,
107+
const parseToken = firstChoice(
108+
newNamedWildcardParser(options),
109+
newWildcardParser(options),
73110
parseOptionalSegment,
74-
parseNamedSegment,
75-
parseStatic);
111+
newNamedSegmentParser(options),
112+
newStaticContentParser(options),
113+
);
76114

77-
pattern = many1(token);
115+
parsePattern = many1(parseToken);
78116

79-
return pattern;
117+
return parsePattern;
80118
}
81119

82120
// functions that further process ASTs returned as `.value` in parser results
@@ -91,7 +129,7 @@ function baseAstNodeToRegexString(astNode: Ast<any>, segmentValueCharset: string
91129
return "(.*?)";
92130
case "namedSegment":
93131
return `([${ segmentValueCharset }]+)`;
94-
case "static":
132+
case "staticContent":
95133
return escapeStringForRegex(astNode.value);
96134
case "optionalSegment":
97135
return `(?:${ baseAstNodeToRegexString(astNode.value, segmentValueCharset) })?`;
@@ -117,7 +155,7 @@ export function astNodeToNames(astNode: Ast<any> | Array<Ast<any>>): string[] {
117155
return ["_"];
118156
case "namedSegment":
119157
return [astNode.value];
120-
case "static":
158+
case "staticContent":
121159
return [];
122160
case "optionalSegment":
123161
return astNodeToNames(astNode.value);
@@ -184,7 +222,7 @@ function astNodeContainsSegmentsForProvidedParams(
184222
return getParam(params, "_", nextIndexes, false) != null;
185223
case "namedSegment":
186224
return getParam(params, astNode.value, nextIndexes, false) != null;
187-
case "static":
225+
case "staticContent":
188226
return false;
189227
case "optionalSegment":
190228
return astNodeContainsSegmentsForProvidedParams(astNode.value, params, nextIndexes);
@@ -199,7 +237,7 @@ function astNodeContainsSegmentsForProvidedParams(
199237
export function stringify(
200238
astNode: Ast<any> | Array<Ast<any>>,
201239
params: { [index: string]: any },
202-
nextIndexes: { [index: string]: number },
240+
nextIndexes: { [index: string]: number } = {},
203241
): string {
204242
if (Array.isArray(astNode)) {
205243
return stringConcatMap(astNode, (node) => stringify(node, params, nextIndexes));
@@ -210,7 +248,7 @@ export function stringify(
210248
return getParam(params, "_", nextIndexes, true);
211249
case "namedSegment":
212250
return getParam(params, astNode.value, nextIndexes, true);
213-
case "static":
251+
case "staticContent":
214252
return astNode.value;
215253
case "optionalSegment":
216254
if (astNodeContainsSegmentsForProvidedParams(astNode.value, params, nextIndexes)) {

src/url-pattern.ts

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -50,18 +50,18 @@ export default class UrlPattern {
5050
this.regex = pattern;
5151
if (optionsOrGroupNames != null) {
5252
if (!Array.isArray(optionsOrGroupNames)) {
53-
throw new TypeError(`
54-
if first argument is a RegExp the second argument
55-
may be an Array<String> of group names
56-
but you provided something else
57-
`);
53+
throw new TypeError([
54+
"if first argument is a RegExp the second argument",
55+
"may be an Array<String> of group names",
56+
"but you provided something else",
57+
].join(" "));
5858
}
5959
const groupCount = regexGroupCount(this.regex);
6060
if (optionsOrGroupNames.length !== groupCount) {
61-
throw new Error(`
62-
regex contains ${ groupCount } groups
63-
but array of group names contains ${ optionsOrGroupNames.length }
64-
`);
61+
throw new Error([
62+
`regex contains ${ groupCount } groups`,
63+
`but array of group names contains ${ optionsOrGroupNames.length }`,
64+
].join(" "));
6565
}
6666
this.names = optionsOrGroupNames;
6767
}
@@ -142,6 +142,6 @@ export default class UrlPattern {
142142
if (params !== Object(params)) {
143143
throw new Error("argument must be an object or undefined");
144144
}
145-
return stringify(this.ast, params, {});
145+
return stringify(this.ast, params);
146146
}
147147
}

test/match-fixtures.js

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -142,15 +142,16 @@ test('match', function(t) {
142142
});
143143

144144
pattern = new UrlPattern('/:foo_bar');
145-
t.equal(pattern.match('/_bar'), undefined);
145+
t.deepEqual(pattern.match('/_bar'),
146+
{foo_bar: '_bar'});
146147
t.deepEqual(pattern.match('/a_bar'),
147-
{foo: 'a'});
148+
{foo_bar: 'a_bar'});
148149
t.deepEqual(pattern.match('/a__bar'),
149-
{foo: 'a_'});
150+
{foo_bar: 'a__bar'});
150151
t.deepEqual(pattern.match('/a-b-c-d__bar'),
151-
{foo: 'a-b-c-d_'});
152+
{foo_bar: 'a-b-c-d__bar'});
152153
t.deepEqual(pattern.match('/a b%c-d__bar'),
153-
{foo: 'a b%c-d_'});
154+
{foo_bar: 'a b%c-d__bar'});
154155

155156
pattern = new UrlPattern('((((a)b)c)d)');
156157
t.deepEqual(pattern.match(''), {});

0 commit comments

Comments
 (0)