-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathparse.ts
115 lines (90 loc) · 3.05 KB
/
parse.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
// Copyright 2023-latest the httpland authors. All rights reserved. MIT license.
// This module is browser compatible.
import { isNotEmpty, isNumber, isString, trim } from "./deps.ts";
import { isRangeUnitFormat } from "./validate.ts";
import type { Range, RangeSpec, RangesSpecifier } from "./types.ts";
const enum Msg {
InvalidToken = "Unexpected token",
InvalidIntRangeSemantic = "<last-pos> is less than <first-pos>",
Unexpected = "Unreachable",
InvalidRangeUnit = "invalid <range-unit> syntax.",
}
/** Parses a string into {@link Range}.
*
* @example
* ```ts
* import { parseRange } from "https://deno.land/x/range_parser@$VERSION/parse.ts";
* import { assertEquals } from "https://deno.land/std/testing/asserts.ts";
*
* const actual = parseRange("bytes=0-100, 200-, -300");
*
* assertEquals(actual, {
* rangeUnit: "bytes",
* rangeSet: [
* { firstPos: 0, lastPos: 100 },
* { firstPos: 200, lastPos: undefined },
* { suffixLength: 300 },
* ],
* });
* ```
*
* @throws {SyntaxError} If the input is invalid `Range` header format.
* @throws {RangeError} If the input is invalid semantic.
*/
export function parseRange(input: string): Range {
return parseRangesSpecifier(input);
}
/**
* @deprecated Rename to {@link parseRange}
*/
export const parse = parseRange;
const ReRangeSpecifier = /^(.*?)=(.*?)$/;
export function parseRangesSpecifier(input: string): RangesSpecifier {
const result = ReRangeSpecifier.exec(input);
if (!result || !isString(result[1]) || !isString(result[2])) {
throw SyntaxError(Msg.InvalidToken);
}
const rangeUnit = result[1];
if (!isRangeUnitFormat(rangeUnit)) {
throw SyntaxError(`${Msg.InvalidRangeUnit} ${rangeUnit}`);
}
const rangeSet = parseRangeSet(result[2]);
return { rangeUnit, rangeSet };
}
const RangeSpecRe =
/^((?<firstPos>\d+)-(?<lastPos>\d+)?)$|^(-(?<suffixLength>\d+))$|^(?<otherRange>[\x21-\x2B\x2D-\x7E]+)$/;
export function parseRangeSpec(input: string): RangeSpec {
const result = RangeSpecRe.exec(input);
if (!result || !result.groups) {
throw SyntaxError(Msg.InvalidToken);
}
const firstPosStr = result.groups.firstPos;
const lastPosValue = result.groups.lastPos;
const suffixLength = result.groups.suffixLength;
const otherRange = result.groups.otherRange;
if (isString(firstPosStr)) {
const firstPos = Number.parseInt(firstPosStr);
const lastPos = isString(lastPosValue)
? Number.parseInt(lastPosValue)
: undefined;
if (isNumber(lastPos) && lastPos < firstPos) {
throw RangeError(Msg.InvalidIntRangeSemantic);
}
return { firstPos, lastPos };
}
if (isString(suffixLength)) {
const suffix = Number.parseInt(suffixLength);
return { suffixLength: suffix };
}
if (isString(otherRange)) return otherRange;
throw SyntaxError(Msg.Unexpected);
}
export function parseRangeSet(input: string): [RangeSpec, ...RangeSpec[]] {
const ranges = input
.split(",")
.map(trim)
.filter(Boolean)
.map(parseRangeSpec);
if (!isNotEmpty(ranges)) throw SyntaxError(Msg.InvalidToken);
return ranges;
}