@peter.naydenov/url-pattern 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,131 @@
1
+ /**
2
+ * Options for customizing the pattern syntax
3
+ */
4
+ export interface UrlPatternOptions {
5
+ /** Character used for escaping special characters (default: '\\') */
6
+ escapeChar?: string;
7
+ /** Character that starts a named segment (default: ':') */
8
+ segmentNameStartChar?: string;
9
+ /** Characters allowed in segment names (default: 'a-zA-Z0-9') */
10
+ segmentNameCharset?: string;
11
+ /** Characters allowed in segment values (default: 'a-zA-Z0-9-_~ %') */
12
+ segmentValueCharset?: string;
13
+ /** Character that starts an optional segment (default: '(') */
14
+ optionalSegmentStartChar?: string;
15
+ /** Character that ends an optional segment (default: ')') */
16
+ optionalSegmentEndChar?: string;
17
+ /** Character that denotes a wildcard (default: '*') */
18
+ wildcardChar?: string;
19
+ }
20
+
21
+ /**
22
+ * Result of matching a pattern against a string
23
+ */
24
+ export interface MatchResult {
25
+ [key: string]: string | string[] | null;
26
+ }
27
+
28
+ /**
29
+ * Compiled pattern object
30
+ */
31
+ export interface CompiledPattern {
32
+ regex: string;
33
+ regexObj: RegExp;
34
+ segments: ParsedSegment[];
35
+ segmentNames: SegmentName[];
36
+ options: UrlPatternOptions;
37
+ isRegex: boolean;
38
+ pattern?: string;
39
+ keys?: string[];
40
+ }
41
+
42
+ /**
43
+ * Parsed segment
44
+ */
45
+ export interface ParsedSegment {
46
+ name: string;
47
+ type: 'named' | 'wildcard' | 'literal';
48
+ optional?: boolean;
49
+ regex: string;
50
+ }
51
+
52
+ /**
53
+ * Segment name mapping
54
+ */
55
+ export interface SegmentName {
56
+ name: string;
57
+ index: number;
58
+ type: 'named' | 'wildcard';
59
+ }
60
+
61
+ /**
62
+ * UrlPattern class for matching and generating URLs
63
+ */
64
+ export class UrlPattern {
65
+ /**
66
+ * @param pattern - Pattern string or RegExp
67
+ * @param options - Options object or keys array (for regex patterns)
68
+ */
69
+ constructor(pattern: string | RegExp, options?: UrlPatternOptions | string[]);
70
+
71
+ /**
72
+ * Match a string against the pattern
73
+ * @param str - String to match
74
+ * @returns Extracted values or null if no match
75
+ */
76
+ match(str: string): MatchResult | null;
77
+
78
+ /**
79
+ * Generate a string from the pattern
80
+ * @param values - Values to stringify
81
+ * @returns Generated string
82
+ */
83
+ stringify(values?: Record<string, any>): string;
84
+ }
85
+
86
+ /**
87
+ * Creates a new UrlPattern instance (functional API)
88
+ * @param pattern - Pattern string or RegExp
89
+ * @param options - Options object or keys array (for regex patterns)
90
+ * @returns UrlPattern instance
91
+ */
92
+ export function urlPattern(pattern: string | RegExp, options?: UrlPatternOptions | string[]): UrlPattern;
93
+
94
+ /**
95
+ * Creates a compiled pattern from a string
96
+ * @param pattern - Pattern string
97
+ * @param options - Options
98
+ * @returns Compiled pattern
99
+ */
100
+ export function makePattern(pattern: string, options?: UrlPatternOptions): CompiledPattern;
101
+
102
+ /**
103
+ * Creates a compiled pattern from a regex
104
+ * @param regex - Regex pattern
105
+ * @param keys - Array of key names for captured groups
106
+ * @returns Compiled pattern
107
+ */
108
+ export function makePatternFromRegex(regex: RegExp, keys?: string[]): CompiledPattern;
109
+
110
+ /**
111
+ * Matches a string against a compiled pattern
112
+ * @param compiled - Compiled pattern
113
+ * @param str - String to match
114
+ * @returns Extracted values or null
115
+ */
116
+ export function match(compiled: CompiledPattern, str: string): MatchResult | null;
117
+
118
+ /**
119
+ * Stringifies a pattern with given values
120
+ * @param compiled - Compiled pattern
121
+ * @param values - Values to stringify
122
+ * @returns Generated string
123
+ */
124
+ export function stringify(compiled: CompiledPattern, values?: Record<string, any>): string;
125
+
126
+ /**
127
+ * Default options
128
+ */
129
+ export const DEFAULT_OPTIONS: UrlPatternOptions;
130
+
131
+ export default UrlPattern;
package/dist/index.js ADDED
@@ -0,0 +1,493 @@
1
+ /**
2
+ * @fileoverview URL pattern matching library
3
+ * @module url-pattern
4
+ */
5
+
6
+ /**
7
+ * @typedef {Object} UrlPatternOptions
8
+ * @property {string} [escapeChar='\\'] - Character used for escaping special characters
9
+ * @property {string} [segmentNameStartChar=':'] - Character that starts a named segment
10
+ * @property {string} [segmentNameCharset='a-zA-Z0-9'] - Characters allowed in segment names
11
+ * @property {string} [segmentValueCharset='a-zA-Z0-9-_~ %'] - Characters allowed in segment values
12
+ * @property {string} [optionalSegmentStartChar='('] - Character that starts an optional segment
13
+ * @property {string} [optionalSegmentEndChar=')'] - Character that ends an optional segment
14
+ * @property {string} [wildcardChar='*'] - Character that denotes a wildcard
15
+ */
16
+
17
+ /**
18
+ * @typedef {Object} ParsedSegment
19
+ * @property {string} name - Segment name
20
+ * @property {string} type - Segment type ('named' | 'wildcard' | 'literal')
21
+ * @property {boolean} [optional=false] - Whether the segment is optional
22
+ * @property {string} regex - Compiled regex string
23
+ */
24
+
25
+ /**
26
+ * @typedef {Object} SegmentName
27
+ * @property {string} name - Segment name
28
+ * @property {number} index - Capture group index
29
+ * @property {string} type - Segment type ('named' | 'wildcard')
30
+ */
31
+
32
+ /**
33
+ * @typedef {Object} CompiledPattern
34
+ * @property {string} regex - Compiled regex string
35
+ * @property {RegExp} regexObj - Compiled regex object
36
+ * @property {Array<ParsedSegment>} segments - Parsed segments
37
+ * @property {Array<SegmentName>} segmentNames - Segment name mappings
38
+ * @property {UrlPatternOptions} options - Options used
39
+ * @property {boolean} isRegex - Whether pattern was created from regex
40
+ * @property {string} [pattern] - Original pattern string
41
+ * @property {Array<string>} [keys] - Keys for regex patterns
42
+ */
43
+
44
+ /**
45
+ * Default options for URL pattern matching
46
+ * @type {UrlPatternOptions}
47
+ */
48
+ const DEFAULT_OPTIONS = {
49
+ escapeChar: '\\',
50
+ segmentNameStartChar: ':',
51
+ segmentNameCharset: 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789',
52
+ segmentValueCharset: 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_~ %',
53
+ optionalSegmentStartChar: '(',
54
+ optionalSegmentEndChar: ')',
55
+ wildcardChar: '*'
56
+ };
57
+
58
+ /**
59
+ * Escapes special regex characters in a string
60
+ * @param {string} str - String to escape
61
+ * @returns {string} Escaped string
62
+ */
63
+ const escapeRegex = (str) => str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
64
+
65
+ /**
66
+ * Merges default options with user provided options
67
+ * @param {UrlPatternOptions} [userOptions={}] - User provided options
68
+ * @returns {UrlPatternOptions} Merged options
69
+ */
70
+ const mergeOptions = (userOptions = {}) => ({
71
+ ...DEFAULT_OPTIONS,
72
+ ...userOptions
73
+ });
74
+
75
+ /**
76
+ * Finds the position of the next special character in the pattern
77
+ * @param {string} pattern - Pattern string
78
+ * @param {number} start - Starting position
79
+ * @param {UrlPatternOptions} options - Parsing options
80
+ * @returns {number} Position of next special character
81
+ */
82
+ const findNextSpecialChar = (pattern, start, options) => {
83
+ const chars = [
84
+ options.escapeChar,
85
+ options.optionalSegmentStartChar,
86
+ options.optionalSegmentEndChar,
87
+ options.wildcardChar,
88
+ options.segmentNameStartChar
89
+ ];
90
+
91
+ let minPos = pattern.length;
92
+
93
+ for (let i = 0; i < chars.length; i++) {
94
+ const char = chars[i];
95
+ if (!char) continue;
96
+ const pos = pattern.indexOf(char, start);
97
+ if (pos !== -1 && pos < minPos) {
98
+ minPos = pos;
99
+ }
100
+ }
101
+
102
+ return minPos;
103
+ };
104
+
105
+ /**
106
+ * Parses a pattern string into segments
107
+ * @param {string} pattern - Pattern string to parse
108
+ * @param {UrlPatternOptions} options - Parsing options
109
+ * @returns {Array<ParsedSegment>} Parsed segments
110
+ */
111
+ const parsePattern = (pattern, options) => {
112
+ const segments = [];
113
+ let i = 0;
114
+ let inOptional = false;
115
+
116
+ while (i < pattern.length) {
117
+ const char = pattern[i];
118
+
119
+ if (char === options.escapeChar && i + 1 < pattern.length) {
120
+ segments.push({
121
+ type: 'literal',
122
+ name: pattern[i + 1],
123
+ regex: escapeRegex(pattern[i + 1]),
124
+ optional: inOptional
125
+ });
126
+ i += 2;
127
+ continue;
128
+ }
129
+
130
+ if (char === options.optionalSegmentStartChar) {
131
+ inOptional = true;
132
+ i++;
133
+ continue;
134
+ }
135
+
136
+ if (char === options.optionalSegmentEndChar) {
137
+ inOptional = false;
138
+ i++;
139
+ continue;
140
+ }
141
+
142
+ if (char === options.wildcardChar) {
143
+ segments.push({
144
+ type: 'wildcard',
145
+ name: '_',
146
+ regex: '.*',
147
+ optional: inOptional
148
+ });
149
+ i++;
150
+ continue;
151
+ }
152
+
153
+ if (char === options.segmentNameStartChar && i + 1 < pattern.length) {
154
+ const remaining = pattern.slice(i + 1);
155
+ let nameEnd = 0;
156
+ const charset = options.segmentNameCharset || '';
157
+
158
+ for (let j = 0; j < remaining.length; j++) {
159
+ if (!charset.includes(remaining[j])) {
160
+ break;
161
+ }
162
+ nameEnd = j + 1;
163
+ }
164
+
165
+ const name = remaining.slice(0, nameEnd);
166
+
167
+ if (name.length > 0) {
168
+ let valueCharset = options.segmentValueCharset || '';
169
+ if (valueCharset.includes('-') && valueCharset.indexOf('-') > 0 && valueCharset.indexOf('-') < valueCharset.length - 1) {
170
+ valueCharset = valueCharset.replace(/-/g, '');
171
+ valueCharset += '-';
172
+ }
173
+ const escapedValueCharset = valueCharset.replace(/\]/g, '\\]');
174
+ const valueRegex = `([${escapedValueCharset}]+)`;
175
+
176
+ segments.push({
177
+ type: 'named',
178
+ name,
179
+ regex: valueRegex,
180
+ optional: inOptional
181
+ });
182
+ i += 1 + nameEnd;
183
+ continue;
184
+ }
185
+ }
186
+
187
+ const literalEnd = findNextSpecialChar(pattern, i, options);
188
+
189
+ if (literalEnd > i) {
190
+ const literal = pattern.slice(i, literalEnd);
191
+ segments.push({
192
+ type: 'literal',
193
+ name: literal,
194
+ regex: escapeRegex(literal),
195
+ optional: inOptional
196
+ });
197
+ i = literalEnd;
198
+ continue;
199
+ }
200
+
201
+ i++;
202
+ }
203
+
204
+ return segments;
205
+ };
206
+
207
+ /**
208
+ * Compiles segments into a regex pattern
209
+ * @param {Array<ParsedSegment>} segments - Parsed segments
210
+ * @param {UrlPatternOptions} options - Options
211
+ * @returns {{regex: string, segmentNames: Array<SegmentName>}} Compiled regex and segment names
212
+ */
213
+ const compileRegex = (segments, options) => {
214
+ let regex = '^';
215
+ let groupIndex = 0;
216
+ /** @type {Array<SegmentName>} */
217
+ const segmentNames = [];
218
+ let i = 0;
219
+
220
+ while (i < segments.length) {
221
+ const segment = segments[i];
222
+
223
+ if (segment.optional) {
224
+ let optionalPart = '';
225
+ let j = i;
226
+
227
+ while (j < segments.length && segments[j].optional) {
228
+ const seg = segments[j];
229
+
230
+ if (seg.type === 'wildcard') {
231
+ optionalPart += '(.*)';
232
+ segmentNames.push({ name: '_', index: groupIndex, type: 'wildcard' });
233
+ groupIndex++;
234
+ } else if (seg.type === 'named') {
235
+ optionalPart += seg.regex;
236
+ segmentNames.push({ name: seg.name, index: groupIndex, type: 'named' });
237
+ groupIndex++;
238
+ } else {
239
+ optionalPart += seg.regex;
240
+ }
241
+
242
+ j++;
243
+ }
244
+
245
+ regex += `(?:${optionalPart})?`;
246
+ i = j;
247
+ continue;
248
+ }
249
+
250
+ if (segment.type === 'wildcard') {
251
+ regex += '(.*)';
252
+ segmentNames.push({ name: '_', index: groupIndex, type: 'wildcard' });
253
+ groupIndex++;
254
+ } else if (segment.type === 'named') {
255
+ regex += segment.regex;
256
+ segmentNames.push({ name: segment.name, index: groupIndex, type: 'named' });
257
+ groupIndex++;
258
+ } else {
259
+ regex += segment.regex;
260
+ }
261
+
262
+ i++;
263
+ }
264
+
265
+ regex += '$';
266
+
267
+ return { regex, segmentNames };
268
+ };
269
+
270
+ /**
271
+ * Creates a compiled pattern from a string
272
+ * @param {string} pattern - Pattern string
273
+ * @param {UrlPatternOptions} [options={}] - Options
274
+ * @returns {CompiledPattern} Compiled pattern
275
+ */
276
+ const makePattern = (pattern, options = {}) => {
277
+ const mergedOptions = mergeOptions(options);
278
+ const segments = parsePattern(pattern, mergedOptions);
279
+ const { regex, segmentNames } = compileRegex(segments, mergedOptions);
280
+
281
+ return {
282
+ regex,
283
+ regexObj: new RegExp(regex),
284
+ segments,
285
+ segmentNames,
286
+ options: mergedOptions,
287
+ isRegex: false,
288
+ pattern
289
+ };
290
+ };
291
+
292
+ /**
293
+ * Creates a compiled pattern from a regex
294
+ * @param {RegExp} regex - Regex pattern
295
+ * @param {Array<string>} [keys=[]] - Array of key names for captured groups
296
+ * @returns {CompiledPattern} Compiled pattern
297
+ */
298
+ const makePatternFromRegex = (regex, keys = []) => {
299
+ return {
300
+ regex: regex.source,
301
+ regexObj: regex,
302
+ segments: [],
303
+ segmentNames: keys.map((name, index) => ({ name, index, type: 'named' })),
304
+ options: DEFAULT_OPTIONS,
305
+ isRegex: true,
306
+ keys
307
+ };
308
+ };
309
+
310
+ /**
311
+ * Matches a string against a compiled pattern
312
+ * @param {CompiledPattern} compiled - Compiled pattern
313
+ * @param {string} str - String to match
314
+ * @returns {Object|null} Extracted values or null if no match
315
+ */
316
+ const match = (compiled, str) => {
317
+ const matchResult = compiled.regexObj.exec(str);
318
+
319
+ if (!matchResult) {
320
+ return null;
321
+ }
322
+
323
+ if (compiled.isRegex) {
324
+ if (compiled.keys && compiled.keys.length > 0) {
325
+ const result = {};
326
+ compiled.keys.forEach((key, index) => {
327
+ const val = matchResult[index + 1];
328
+ result[key] = val !== undefined ? val : null;
329
+ });
330
+ return result;
331
+ }
332
+ return matchResult.slice(1);
333
+ }
334
+
335
+ const result = {};
336
+ const usedNames = new Set();
337
+
338
+ for (let i = 0; i < compiled.segmentNames.length; i++) {
339
+ const segInfo = compiled.segmentNames[i];
340
+ const value = matchResult[segInfo.index + 1] || '';
341
+
342
+ if (usedNames.has(segInfo.name)) {
343
+ if (!Array.isArray(result[segInfo.name])) {
344
+ result[segInfo.name] = [result[segInfo.name]];
345
+ }
346
+ result[segInfo.name].push(value);
347
+ } else {
348
+ usedNames.add(segInfo.name);
349
+ result[segInfo.name] = value;
350
+ }
351
+ }
352
+
353
+ for (const key in result) {
354
+ if (result[key] === '') {
355
+ delete result[key];
356
+ }
357
+ }
358
+
359
+ return result;
360
+ };
361
+
362
+ /**
363
+ * Stringifies a pattern with given values
364
+ * @param {CompiledPattern} compiled - Compiled pattern
365
+ * @param {Object} [values={}] - Values to stringify
366
+ * @returns {string} Generated string
367
+ * @throws {Error} If required values are missing
368
+ */
369
+ const stringify = (compiled, values = {}) => {
370
+ if (compiled.isRegex) {
371
+ throw new Error('Cannot stringify a pattern created from regex');
372
+ }
373
+
374
+ let result = '';
375
+ let i = 0;
376
+
377
+ while (i < compiled.segments.length) {
378
+ const segment = compiled.segments[i];
379
+
380
+ if (segment.optional) {
381
+ let optionalPart = '';
382
+ let j = i;
383
+
384
+ while (j < compiled.segments.length && compiled.segments[j].optional) {
385
+ const seg = compiled.segments[j];
386
+
387
+ if (seg.type === 'literal') {
388
+ optionalPart += seg.name;
389
+ } else if (seg.type === 'named') {
390
+ const val = values[seg.name];
391
+ if (val !== undefined && val !== null && val !== '') {
392
+ optionalPart += Array.isArray(val) ? val.join('/') : val;
393
+ } else {
394
+ optionalPart = '';
395
+ break;
396
+ }
397
+ } else if (seg.type === 'wildcard') {
398
+ const val = values._;
399
+ if (val !== undefined && val !== null && val !== '') {
400
+ optionalPart += Array.isArray(val) ? val.join('/') : val;
401
+ } else {
402
+ optionalPart = '';
403
+ break;
404
+ }
405
+ }
406
+
407
+ j++;
408
+ }
409
+
410
+ if (optionalPart !== '') {
411
+ result += optionalPart;
412
+ }
413
+
414
+ if (i === j) {
415
+ i++;
416
+ } else {
417
+ i = j;
418
+ }
419
+ continue;
420
+ }
421
+
422
+ if (segment.type === 'literal') {
423
+ result += segment.name;
424
+ } else if (segment.type === 'named') {
425
+ const value = values[segment.name];
426
+ if (value === undefined || value === null || value === '') {
427
+ throw new Error(`Missing required value for segment: ${segment.name}`);
428
+ }
429
+ result += Array.isArray(value) ? value.join('/') : value;
430
+ } else if (segment.type === 'wildcard') {
431
+ const value = values._;
432
+ if (value === undefined || value === null || value === '') {
433
+ throw new Error('Missing required wildcard value');
434
+ }
435
+ result += Array.isArray(value) ? value.join('/') : value;
436
+ }
437
+
438
+ i++;
439
+ }
440
+
441
+ return result;
442
+ };
443
+
444
+ /**
445
+ * UrlPattern class for matching and generating URLs
446
+ */
447
+ class UrlPattern {
448
+ /**
449
+ * @param {string|RegExp} pattern - Pattern string or regex
450
+ * @param {UrlPatternOptions|Array<string>} [options={}] - Options or keys (for regex)
451
+ */
452
+ constructor(pattern, options = {}) {
453
+ if (pattern instanceof RegExp) {
454
+ const keys = Array.isArray(options) ? options : [];
455
+ /** @type {CompiledPattern} */
456
+ this.compiled = makePatternFromRegex(pattern, keys);
457
+ } else {
458
+ /** @type {CompiledPattern} */
459
+ this.compiled = makePattern(pattern, /** @type {UrlPatternOptions} */ (options));
460
+ }
461
+ }
462
+
463
+ /**
464
+ * Match a string against the pattern
465
+ * @param {string} str - String to match
466
+ * @returns {Object|null} Extracted values or null if no match
467
+ */
468
+ match(str) {
469
+ return match(this.compiled, str);
470
+ }
471
+
472
+ /**
473
+ * Generate a string from the pattern
474
+ * @param {Object} [values={}] - Values to stringify
475
+ * @returns {string} Generated string
476
+ */
477
+ stringify(values) {
478
+ return stringify(this.compiled, values);
479
+ }
480
+ }
481
+
482
+ /**
483
+ * Creates a new UrlPattern instance (functional API)
484
+ * @param {string|RegExp} pattern - Pattern string or regex
485
+ * @param {UrlPatternOptions|Array<string>} [options={}] - Options or keys
486
+ * @returns {UrlPattern} UrlPattern instance
487
+ */
488
+ const urlPattern = (pattern, options = {}) => {
489
+ return new UrlPattern(pattern, options);
490
+ };
491
+
492
+ export { UrlPattern, urlPattern, makePattern, makePatternFromRegex, match, stringify, DEFAULT_OPTIONS };
493
+ export default UrlPattern;
package/package.json ADDED
@@ -0,0 +1,34 @@
1
+ {
2
+ "name": "@peter.naydenov/url-pattern",
3
+ "version": "1.0.0",
4
+ "description": "Matching patterns for urls and other strings. Turn strings into data or data into strings.",
5
+ "main": "dist/index.js",
6
+ "module": "dist/index.esm.js",
7
+ "types": "dist/index.d.ts",
8
+ "files": [
9
+ "dist"
10
+ ],
11
+ "scripts": {
12
+ "build": "cp src/index.js dist/index.js",
13
+ "test": "vitest run",
14
+ "test:watch": "vitest",
15
+ "cover": "vitest run --coverage",
16
+ "lint": "eslint src --ext .js",
17
+ "prepublishOnly": "npm run build"
18
+ },
19
+ "keywords": [
20
+ "url",
21
+ "pattern",
22
+ "match",
23
+ "routing",
24
+ "regex"
25
+ ],
26
+ "author": "Peter Naydenov",
27
+ "license": "MIT",
28
+ "devDependencies": {
29
+ "@vitest/coverage-v8": "^4.1.5",
30
+ "eslint": "^10.3.0",
31
+ "typescript": "^6.0.3",
32
+ "vitest": "^4.1.5"
33
+ }
34
+ }
package/readme.md ADDED
@@ -0,0 +1,136 @@
1
+ # URL Pattern (@peter.naydenov/url-pattern)
2
+
3
+ [![version](https://img.shields.io/npm/v/@peter.naydenov/url-pattern.svg)](https://www.npmjs.com/package/@peter.naydenov/url-pattern)
4
+ [![license](https://img.shields.io/github/license/PeterNaydenov/git-url-pattern.svg)](https://github.com/PeterNaydenov/git-url-pattern/blob/main/LICENSE)
5
+ [![GitHub issues](https://img.shields.io/github/issues-raw/PeterNaydenov/git-url-pattern.svg)](https://github.com/PeterNaydenov/git-url-pattern/issues)
6
+ [![GitHub top language](https://img.shields.io/github/languages/top/PeterNaydenov/git-url-pattern.svg)](https://github.com/PeterNaydenov/git-url-pattern)
7
+ [![npm bundle size](https://img.shields.io/bundlejs/size/@peter.naydenov/url-pattern.svg)](https://www.npmjs.com/package/@peter.naydenov/url-pattern)
8
+
9
+ Easier than regex string matching patterns for urls and other strings. Turn strings into data or data into strings.
10
+
11
+ ## Install
12
+
13
+ ```
14
+ npm install @peter.naydenov/url-pattern
15
+ ```
16
+
17
+ Once it has been installed, it can be used by writing this line of JavaScript:
18
+
19
+ ```js
20
+ // if you are using ES6:
21
+ import urlPattern from '@peter.naydenov/url-pattern'
22
+
23
+ // if you are using commonJS:
24
+ const urlPattern = require ( '@peter.naydenov/url-pattern' )
25
+ ```
26
+
27
+ ## How to use it
28
+
29
+ ### Parse a pattern and match a string
30
+
31
+ ```js
32
+ import urlPattern from '@peter.naydenov/url-pattern'
33
+
34
+ const pattern = urlPattern ( '/user/:username/post/:postId' )
35
+ const result = pattern.match ( '/user/john/post/123' )
36
+
37
+ console.log ( result )
38
+ // Output: { username: 'john', postId: '123' }
39
+ ```
40
+
41
+ ### Match a URL with optional segments
42
+
43
+ ```js
44
+ import urlPattern from '@peter.naydenov/url-pattern'
45
+
46
+ const pattern = urlPattern ( '/api/(v1)/users/:id' )
47
+ const result = pattern.match ( '/api/v1/users/456' )
48
+
49
+ console.log ( result )
50
+ // Output: { id: '456' }
51
+ ```
52
+
53
+ ### Use wildcard
54
+
55
+ ```js
56
+ import urlPattern from '@peter.naydenov/url-pattern'
57
+
58
+ const pattern = urlPattern ( '/files/*' )
59
+ const result = pattern.match ( '/files/images/photo.jpg' )
60
+
61
+ console.log ( result )
62
+ // Output: { '*': 'images/photo.jpg' }
63
+ ```
64
+
65
+ ### Generate URL from data
66
+
67
+ ```js
68
+ import urlPattern from '@peter.naydenov/url-pattern'
69
+
70
+ const pattern = urlPattern ( '/user/:username/post/:postId' )
71
+ const url = pattern.stringify ( { username: 'john', postId: '123' } )
72
+
73
+ console.log ( url )
74
+ // Output: '/user/john/post/123'
75
+ ```
76
+
77
+ ### Configure pattern options
78
+
79
+ ```js
80
+ import urlPattern from '@peter.naydenov/url-pattern'
81
+
82
+ const pattern = urlPattern ( '/user/{username}/post/{postId}', {
83
+ segmentNameStartChar: '{',
84
+ segmentNameEndChar: '}'
85
+ })
86
+
87
+ const result = pattern.match ( '/user/john/post/123' )
88
+
89
+ console.log ( result )
90
+ // Output: { username: 'john', postId: '123' }
91
+ ```
92
+
93
+ ## API Reference
94
+
95
+ ### `urlPattern(pattern, [options])`
96
+
97
+ Creates a new pattern instance.
98
+
99
+ - **pattern** `{string}` - URL pattern string with named segments (`:name`), optional segments (`(segment)`), or wildcards (`*`)
100
+ - **options** `{object}` - Optional configuration object
101
+
102
+ #### Options
103
+
104
+ - **escapeChar** `{string}` - Character used for escaping special characters (default: `'\\'`)
105
+ - **segmentNameStartChar** `{string}` - Character that starts a named segment (default: `':'`)
106
+ - **segmentNameEndChar** `{string}` - Character that ends a named segment (default: `undefined`)
107
+ - **segmentNameCharset** `{string}` - Characters allowed in segment names (default: `'a-zA-Z0-9'`)
108
+ - **segmentValueCharset** `{string}` - Characters allowed in segment values (default: `'a-zA-Z0-9-_~ %'`)
109
+ - **optionalSegmentStartChar** `{string}` - Character that starts an optional segment (default: `'('`)
110
+ - **optionalSegmentEndChar** `{string}` - Character that ends an optional segment (default: `')'`)
111
+ - **wildcardChar** `{string}` - Character that denotes a wildcard (default: `'*'`)
112
+
113
+ ### Pattern Methods
114
+
115
+ #### `pattern.match(string)`
116
+
117
+ Matches a string against the pattern and returns an object with captured values, or `null` if no match.
118
+
119
+ #### `pattern.stringify(data)`
120
+
121
+ Generates a URL string from provided data object.
122
+
123
+ #### `pattern.compile()`
124
+
125
+ Returns an object with `regex` (compiled RegExp), `segments` (parsed segments array), and `segmentNames` (segment name mappings).
126
+
127
+ ## Links
128
+
129
+ - [History of changes](CHANGELOG.md)
130
+ - [TypeScript definitions](types/index.d.ts)
131
+
132
+ ## Credits
133
+
134
+ '@peter.naydenov/url-pattern' was created and supported by [Peter Naydenov](https://github.com/PeterNaydenov).
135
+
136
+ '@peter.naydenov/url-pattern' is released under the [MIT License](https://github.com/PeterNaydenov/git-url-pattern/blob/main/LICENSE).