@api-client/core 0.17.7 → 0.18.1
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.
- package/build/src/amf/ApiSchemaGenerator.d.ts +2 -2
- package/build/src/amf/ApiSchemaGenerator.d.ts.map +1 -1
- package/build/src/amf/ApiSchemaGenerator.js.map +1 -1
- package/build/src/amf/ApiSchemaValues.d.ts.map +1 -1
- package/build/src/amf/ApiSchemaValues.js +8 -1
- package/build/src/amf/ApiSchemaValues.js.map +1 -1
- package/build/src/amf/shape/ShapeBase.d.ts +1 -1
- package/build/src/amf/shape/ShapeBase.d.ts.map +1 -1
- package/build/src/amf/shape/ShapeBase.js.map +1 -1
- package/build/src/amf/shape/ShapeJsonSchemaGenerator.d.ts +1 -1
- package/build/src/amf/shape/ShapeJsonSchemaGenerator.d.ts.map +1 -1
- package/build/src/amf/shape/ShapeJsonSchemaGenerator.js +7 -1
- package/build/src/amf/shape/ShapeJsonSchemaGenerator.js.map +1 -1
- package/build/src/amf/shape/ShapeXmlSchemaGenerator.d.ts +1 -1
- package/build/src/amf/shape/ShapeXmlSchemaGenerator.d.ts.map +1 -1
- package/build/src/amf/shape/ShapeXmlSchemaGenerator.js +8 -2
- package/build/src/amf/shape/ShapeXmlSchemaGenerator.js.map +1 -1
- package/build/src/mocking/RandExp.d.ts +55 -0
- package/build/src/mocking/RandExp.d.ts.map +1 -0
- package/build/src/mocking/RandExp.js +302 -0
- package/build/src/mocking/RandExp.js.map +1 -0
- package/build/src/mocking/lib/ret.d.ts +16 -0
- package/build/src/mocking/lib/ret.d.ts.map +1 -0
- package/build/src/mocking/lib/ret.js +284 -0
- package/build/src/mocking/lib/ret.js.map +1 -0
- package/build/src/modeling/Bindings.d.ts +0 -4
- package/build/src/modeling/Bindings.d.ts.map +1 -1
- package/build/src/modeling/Bindings.js.map +1 -1
- package/build/src/modeling/DomainEntity.js +3 -3
- package/build/src/modeling/DomainEntity.js.map +1 -1
- package/build/src/modeling/DomainProperty.d.ts +18 -0
- package/build/src/modeling/DomainProperty.d.ts.map +1 -1
- package/build/src/modeling/DomainProperty.js +31 -0
- package/build/src/modeling/DomainProperty.js.map +1 -1
- package/build/src/modeling/amf/ShapeGenerator.js +3 -3
- package/build/src/modeling/amf/ShapeGenerator.js.map +1 -1
- package/build/src/modeling/types.d.ts +4 -0
- package/build/src/modeling/types.d.ts.map +1 -1
- package/build/src/modeling/types.js.map +1 -1
- package/build/src/models/DataCatalog.d.ts +12 -1
- package/build/src/models/DataCatalog.d.ts.map +1 -1
- package/build/src/models/DataCatalog.js.map +1 -1
- package/build/tsconfig.tsbuildinfo +1 -1
- package/package.json +1 -1
- package/src/amf/ApiSchemaGenerator.ts +2 -2
- package/src/amf/ApiSchemaValues.ts +8 -1
- package/src/amf/shape/ShapeBase.ts +1 -1
- package/src/amf/shape/ShapeJsonSchemaGenerator.ts +7 -2
- package/src/amf/shape/ShapeXmlSchemaGenerator.ts +8 -3
- package/src/mocking/RandExp.ts +335 -0
- package/src/mocking/lib/ret.ts +279 -0
- package/src/modeling/Bindings.ts +0 -4
- package/src/modeling/DomainEntity.ts +3 -3
- package/src/modeling/DomainProperty.ts +33 -0
- package/src/modeling/amf/ShapeGenerator.ts +3 -3
- package/src/modeling/types.ts +4 -0
- package/src/models/DataCatalog.ts +13 -1
- package/tests/unit/modeling/amf/shape_generator.spec.ts +3 -8
- package/tests/unit/modeling/domain_property.spec.ts +335 -0
package/package.json
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { AmfNamespace as ns } from './definitions/Namespace.js'
|
|
2
|
-
import { IApiAnyShape, IApiDataExample, IShapeUnion } from './definitions/Shapes.js'
|
|
2
|
+
import { IApiAnyShape, IApiDataExample, IApiPropertyShape, IShapeUnion } from './definitions/Shapes.js'
|
|
3
3
|
import { ShapeBase, IShapeRenderOptions } from './shape/ShapeBase.js'
|
|
4
4
|
import { ShapeJsonSchemaGenerator } from './shape/ShapeJsonSchemaGenerator.js'
|
|
5
5
|
import { ShapeXmlSchemaGenerator } from './shape/ShapeXmlSchemaGenerator.js'
|
|
@@ -69,7 +69,7 @@ export class ApiSchemaGenerator {
|
|
|
69
69
|
*
|
|
70
70
|
* @param shape The Shape definition
|
|
71
71
|
*/
|
|
72
|
-
generate(shape: IShapeUnion): string | number | boolean | null | undefined {
|
|
72
|
+
generate(shape: IShapeUnion | IApiPropertyShape<IShapeUnion>): string | number | boolean | null | undefined {
|
|
73
73
|
const { generator } = this
|
|
74
74
|
if (!generator) {
|
|
75
75
|
return undefined
|
|
@@ -16,6 +16,7 @@ import {
|
|
|
16
16
|
IShapeUnion,
|
|
17
17
|
} from './definitions/Shapes.js'
|
|
18
18
|
import type { ILoremWordInit, ITypeHashInit, ITypeNumberInit } from '@pawel-up/data-mock/types.js'
|
|
19
|
+
import { RandExp } from '../mocking/RandExp.js'
|
|
19
20
|
|
|
20
21
|
export interface IApiSchemaReadOptions {
|
|
21
22
|
/**
|
|
@@ -275,7 +276,13 @@ export class ApiSchemaValues {
|
|
|
275
276
|
}
|
|
276
277
|
|
|
277
278
|
static generateStringValue(schema: IApiScalarShape): string {
|
|
278
|
-
const { minLength, maxLength, name = '', format } = schema
|
|
279
|
+
const { minLength, maxLength, name = '', format, pattern } = schema
|
|
280
|
+
if (pattern) {
|
|
281
|
+
// if the pattern is set we generate a random string that matches the pattern.
|
|
282
|
+
// Note, this is not a full regex support, just a simple one.
|
|
283
|
+
const randExp = new RandExp(pattern, undefined, { max: 10 })
|
|
284
|
+
return randExp.gen()
|
|
285
|
+
}
|
|
279
286
|
const lowerName = name.toLowerCase()
|
|
280
287
|
// we employ some heuristics to generate content based on the property name.
|
|
281
288
|
if (lowerName === 'description') {
|
|
@@ -138,7 +138,7 @@ export abstract class ShapeBase {
|
|
|
138
138
|
* @param schema The Shape definition
|
|
139
139
|
* @returns The generated example
|
|
140
140
|
*/
|
|
141
|
-
abstract generate(schema: IShapeUnion): string | number | boolean | null | undefined
|
|
141
|
+
abstract generate(schema: IShapeUnion | IApiPropertyShape<IShapeUnion>): string | number | boolean | null | undefined
|
|
142
142
|
|
|
143
143
|
/**
|
|
144
144
|
* Serializes generated values into the final mime type related form.
|
|
@@ -20,8 +20,13 @@ export class ShapeJsonSchemaGenerator extends ShapeBase {
|
|
|
20
20
|
*
|
|
21
21
|
* @param schema The Shape definition
|
|
22
22
|
*/
|
|
23
|
-
generate(schema: IShapeUnion): string | number | boolean | null | undefined {
|
|
24
|
-
|
|
23
|
+
generate(schema: IShapeUnion | IApiPropertyShape<IShapeUnion>): string | number | boolean | null | undefined {
|
|
24
|
+
let result: string | number | boolean | object | object[] | null | undefined
|
|
25
|
+
if (schema.types.includes(ns.w3.shacl.PropertyShape)) {
|
|
26
|
+
result = this._propertyShapeObject(schema as IApiPropertyShape<IShapeUnion>)
|
|
27
|
+
} else {
|
|
28
|
+
result = this.toObject(schema as IShapeUnion)
|
|
29
|
+
}
|
|
25
30
|
if (result !== null && typeof result === 'object') {
|
|
26
31
|
return this.serialize(result)
|
|
27
32
|
}
|
|
@@ -55,9 +55,14 @@ export class ShapeXmlSchemaGenerator extends ShapeBase {
|
|
|
55
55
|
*
|
|
56
56
|
* @param schema The Shape definition
|
|
57
57
|
*/
|
|
58
|
-
generate(schema: IShapeUnion): string | undefined {
|
|
59
|
-
|
|
60
|
-
|
|
58
|
+
generate(schema: IShapeUnion | IApiPropertyShape<IShapeUnion>): string | undefined {
|
|
59
|
+
let result: string | undefined
|
|
60
|
+
if (schema.types.includes(ns.w3.shacl.PropertyShape)) {
|
|
61
|
+
result = this._propertyShapeObject(schema as IApiPropertyShape<IShapeUnion>)
|
|
62
|
+
} else {
|
|
63
|
+
result = this.processNode(schema as IShapeUnion, {}, true)
|
|
64
|
+
}
|
|
65
|
+
return result
|
|
61
66
|
}
|
|
62
67
|
|
|
63
68
|
/**
|
|
@@ -0,0 +1,335 @@
|
|
|
1
|
+
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
2
|
+
/**
|
|
3
|
+
* randexp v0.4.6
|
|
4
|
+
* Create random strings that match a given regular expression.
|
|
5
|
+
*
|
|
6
|
+
* Copyright (C) 2017 by fent (https://github.com/fent)
|
|
7
|
+
* MIT License
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { ret, Token, types } from './lib/ret.js'
|
|
11
|
+
|
|
12
|
+
class DRange {
|
|
13
|
+
low: number
|
|
14
|
+
high: number
|
|
15
|
+
length: number
|
|
16
|
+
|
|
17
|
+
constructor(low: number, high: number) {
|
|
18
|
+
this.low = low
|
|
19
|
+
this.high = high
|
|
20
|
+
this.length = 1 + high - low
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
overlaps(range: DRange): boolean {
|
|
24
|
+
return !(this.high < range.low || this.low > range.high)
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
touches(range: DRange): boolean {
|
|
28
|
+
return !(this.high + 1 < range.low || this.low - 1 > range.high)
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
add(range: DRange): DRange | null {
|
|
32
|
+
if (this.touches(range)) {
|
|
33
|
+
return new DRange(Math.min(this.low, range.low), Math.max(this.high, range.high))
|
|
34
|
+
}
|
|
35
|
+
return null
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
subtract(range: DRange): DRange[] {
|
|
39
|
+
if (!this.overlaps(range)) {
|
|
40
|
+
return [this.clone()]
|
|
41
|
+
}
|
|
42
|
+
if (range.low <= this.low && range.high >= this.high) {
|
|
43
|
+
return []
|
|
44
|
+
}
|
|
45
|
+
if (range.low > this.low && range.high < this.high) {
|
|
46
|
+
return [new DRange(this.low, range.low - 1), new DRange(range.high + 1, this.high)]
|
|
47
|
+
}
|
|
48
|
+
if (range.low <= this.low) {
|
|
49
|
+
return [new DRange(range.high + 1, this.high)]
|
|
50
|
+
}
|
|
51
|
+
// if (range.high >= this.high)
|
|
52
|
+
return [new DRange(this.low, range.low - 1)]
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
toString(): string {
|
|
56
|
+
return this.low === this.high ? String(this.low) : `${this.low}-${this.high}`
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
clone(): DRange {
|
|
60
|
+
return new DRange(this.low, this.high)
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
class DiscontinuousRange {
|
|
65
|
+
ranges: DRange[] = []
|
|
66
|
+
length = 0
|
|
67
|
+
|
|
68
|
+
constructor(low?: number | DiscontinuousRange, high?: number) {
|
|
69
|
+
if (low !== undefined) {
|
|
70
|
+
this.add(low, high)
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
private _setLength(): void {
|
|
75
|
+
this.length = this.ranges.reduce((sum, range) => sum + range.length, 0)
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
add(low: number | DiscontinuousRange | DRange, high?: number): this {
|
|
79
|
+
const addRange = (range: DRange) => {
|
|
80
|
+
const newRanges: DRange[] = []
|
|
81
|
+
let i = 0
|
|
82
|
+
while (i < this.ranges.length && !range.touches(this.ranges[i])) {
|
|
83
|
+
newRanges.push(this.ranges[i].clone())
|
|
84
|
+
i++
|
|
85
|
+
}
|
|
86
|
+
while (i < this.ranges.length && range.touches(this.ranges[i])) {
|
|
87
|
+
range = range.add(this.ranges[i]) as DRange
|
|
88
|
+
i++
|
|
89
|
+
}
|
|
90
|
+
newRanges.push(range)
|
|
91
|
+
while (i < this.ranges.length) {
|
|
92
|
+
newRanges.push(this.ranges[i].clone())
|
|
93
|
+
i++
|
|
94
|
+
}
|
|
95
|
+
this.ranges = newRanges
|
|
96
|
+
this._setLength()
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
if (low instanceof DiscontinuousRange) {
|
|
100
|
+
low.ranges.forEach(addRange)
|
|
101
|
+
} else if (low instanceof DRange) {
|
|
102
|
+
addRange(low)
|
|
103
|
+
} else {
|
|
104
|
+
if (high === undefined) high = low
|
|
105
|
+
addRange(new DRange(low, high))
|
|
106
|
+
}
|
|
107
|
+
return this
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
subtract(low: number | DiscontinuousRange | DRange, high?: number): this {
|
|
111
|
+
const subtractRange = (range: DRange) => {
|
|
112
|
+
const newRanges: DRange[] = []
|
|
113
|
+
let i = 0
|
|
114
|
+
while (i < this.ranges.length && !range.overlaps(this.ranges[i])) {
|
|
115
|
+
newRanges.push(this.ranges[i].clone())
|
|
116
|
+
i++
|
|
117
|
+
}
|
|
118
|
+
while (i < this.ranges.length && range.overlaps(this.ranges[i])) {
|
|
119
|
+
newRanges.push(...this.ranges[i].subtract(range))
|
|
120
|
+
i++
|
|
121
|
+
}
|
|
122
|
+
while (i < this.ranges.length) {
|
|
123
|
+
newRanges.push(this.ranges[i].clone())
|
|
124
|
+
i++
|
|
125
|
+
}
|
|
126
|
+
this.ranges = newRanges
|
|
127
|
+
this._setLength()
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
if (low instanceof DiscontinuousRange) {
|
|
131
|
+
low.ranges.forEach(subtractRange)
|
|
132
|
+
} else if (low instanceof DRange) {
|
|
133
|
+
subtractRange(low)
|
|
134
|
+
} else {
|
|
135
|
+
if (high === undefined) high = low
|
|
136
|
+
subtractRange(new DRange(low, high))
|
|
137
|
+
}
|
|
138
|
+
return this
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
index(i: number): number | null {
|
|
142
|
+
let j = 0
|
|
143
|
+
while (j < this.ranges.length && this.ranges[j].length <= i) {
|
|
144
|
+
i -= this.ranges[j].length
|
|
145
|
+
j++
|
|
146
|
+
}
|
|
147
|
+
if (j >= this.ranges.length) {
|
|
148
|
+
return null
|
|
149
|
+
}
|
|
150
|
+
return this.ranges[j].low + i
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
toString(): string {
|
|
154
|
+
return `[ ${this.ranges.join(', ')} ]`
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
clone(): DiscontinuousRange {
|
|
158
|
+
return new DiscontinuousRange(this)
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
export interface RandExpOptions {
|
|
163
|
+
max?: number
|
|
164
|
+
defaultRange?: DiscontinuousRange
|
|
165
|
+
randInt?: (min: number, max: number) => number
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
export class RandExp {
|
|
169
|
+
max = 10 // Reduced from 100 to 10 for more reasonable string lengths
|
|
170
|
+
defaultRange: DiscontinuousRange = new DiscontinuousRange(32, 126)
|
|
171
|
+
randInt: (min: number, max: number) => number = (min, max) => {
|
|
172
|
+
return min + Math.floor(Math.random() * (max - min + 1))
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
private tokens: Token
|
|
176
|
+
private ignoreCase: boolean
|
|
177
|
+
private multiline: boolean
|
|
178
|
+
|
|
179
|
+
constructor(regexp: RegExp | string, flags?: string, options?: RandExpOptions) {
|
|
180
|
+
this.defaultRange = this.defaultRange.clone()
|
|
181
|
+
|
|
182
|
+
// Apply options if provided
|
|
183
|
+
if (options) {
|
|
184
|
+
if (typeof options.max === 'number') {
|
|
185
|
+
this.max = options.max
|
|
186
|
+
}
|
|
187
|
+
if (options.defaultRange instanceof DiscontinuousRange) {
|
|
188
|
+
this.defaultRange = options.defaultRange
|
|
189
|
+
}
|
|
190
|
+
if (typeof options.randInt === 'function') {
|
|
191
|
+
this.randInt = options.randInt
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
if (regexp instanceof RegExp) {
|
|
196
|
+
this.ignoreCase = regexp.ignoreCase
|
|
197
|
+
this.multiline = regexp.multiline
|
|
198
|
+
this._setDefaults(regexp)
|
|
199
|
+
regexp = regexp.source
|
|
200
|
+
} else if (typeof regexp === 'string') {
|
|
201
|
+
this.ignoreCase = flags?.includes('i') ?? false
|
|
202
|
+
this.multiline = flags?.includes('m') ?? false
|
|
203
|
+
} else {
|
|
204
|
+
throw new Error('Expected a regexp or string')
|
|
205
|
+
}
|
|
206
|
+
this.tokens = ret(regexp)
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
private _setDefaults(regexp: any): void {
|
|
210
|
+
if (typeof regexp.max === 'number') {
|
|
211
|
+
this.max = regexp.max
|
|
212
|
+
}
|
|
213
|
+
if (regexp.defaultRange instanceof DiscontinuousRange) {
|
|
214
|
+
this.defaultRange = regexp.defaultRange
|
|
215
|
+
}
|
|
216
|
+
if (typeof regexp.randInt === 'function') {
|
|
217
|
+
this.randInt = regexp.randInt
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
gen(): string {
|
|
222
|
+
return this._gen(this.tokens, [])
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
private _gen(token: Token, groups: (string | null)[]): string {
|
|
226
|
+
let stack, str, i, l
|
|
227
|
+
|
|
228
|
+
switch (token.type) {
|
|
229
|
+
case types.ROOT:
|
|
230
|
+
case types.GROUP:
|
|
231
|
+
if (token.followedBy || token.notFollowedBy) return ''
|
|
232
|
+
|
|
233
|
+
if (token.remember && token.groupNumber === undefined) {
|
|
234
|
+
token.groupNumber = groups.push(null) - 1
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
stack = token.options ? this._choice(token.options) : token.stack
|
|
238
|
+
str = ''
|
|
239
|
+
for (i = 0, l = stack.length; i < l; i++) {
|
|
240
|
+
str += this._gen(stack[i], groups)
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
if (token.remember) {
|
|
244
|
+
groups[token.groupNumber] = str
|
|
245
|
+
}
|
|
246
|
+
return str
|
|
247
|
+
|
|
248
|
+
case types.POSITION:
|
|
249
|
+
return ''
|
|
250
|
+
|
|
251
|
+
case types.SET: {
|
|
252
|
+
const set = this._processSet(token)
|
|
253
|
+
if (!set.length) return ''
|
|
254
|
+
return String.fromCharCode(this._choice(set) as number)
|
|
255
|
+
}
|
|
256
|
+
case types.REPETITION: {
|
|
257
|
+
const n = this.randInt(token.min, token.max === Infinity ? token.min + this.max : token.max)
|
|
258
|
+
str = ''
|
|
259
|
+
for (i = 0; i < n; i++) {
|
|
260
|
+
str += this._gen(token.value, groups)
|
|
261
|
+
}
|
|
262
|
+
return str
|
|
263
|
+
}
|
|
264
|
+
case types.REFERENCE:
|
|
265
|
+
return groups[token.value - 1] || ''
|
|
266
|
+
|
|
267
|
+
case types.CHAR: {
|
|
268
|
+
const code = this.ignoreCase && this._coin() ? RandExp._toCaseInverse(token.value) : token.value
|
|
269
|
+
return String.fromCharCode(code)
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
return ''
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
private _processSet(token: Token): DiscontinuousRange {
|
|
276
|
+
if (token.type === types.CHAR) {
|
|
277
|
+
return new DiscontinuousRange(token.value)
|
|
278
|
+
}
|
|
279
|
+
if (token.type === types.RANGE) {
|
|
280
|
+
return new DiscontinuousRange(token.from, token.to)
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
const dr = new DiscontinuousRange()
|
|
284
|
+
for (const char of token.set) {
|
|
285
|
+
const sub = this._processSet(char)
|
|
286
|
+
dr.add(sub)
|
|
287
|
+
if (this.ignoreCase) {
|
|
288
|
+
for (let j = 0; j < sub.length; j++) {
|
|
289
|
+
const charCode = sub.index(j) as number
|
|
290
|
+
const otherCase = RandExp._toCaseInverse(charCode)
|
|
291
|
+
if (charCode !== otherCase) {
|
|
292
|
+
dr.add(otherCase)
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
return token.not ? this.defaultRange.clone().subtract(dr) : dr
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
private static _toCaseInverse(code: number): number {
|
|
302
|
+
if (code >= 97 && code <= 122) return code - 32
|
|
303
|
+
if (code >= 65 && code <= 90) return code + 32
|
|
304
|
+
return code
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
private _coin(): boolean {
|
|
308
|
+
return this.randInt(0, 1) === 0
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
private _choice(choices: any[] | DiscontinuousRange): any {
|
|
312
|
+
if (choices instanceof DiscontinuousRange) {
|
|
313
|
+
return choices.index(this.randInt(0, choices.length - 1))
|
|
314
|
+
}
|
|
315
|
+
return choices[this.randInt(0, choices.length - 1)]
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
static randexp(regexp: RegExp | string, flags?: string, options?: RandExpOptions): string {
|
|
319
|
+
let randexpInstance
|
|
320
|
+
if ((regexp as any)._randexp === undefined) {
|
|
321
|
+
randexpInstance = new RandExp(regexp, flags, options)
|
|
322
|
+
;(regexp as any)._randexp = randexpInstance
|
|
323
|
+
} else {
|
|
324
|
+
randexpInstance = (regexp as any)._randexp
|
|
325
|
+
}
|
|
326
|
+
randexpInstance._setDefaults(regexp)
|
|
327
|
+
return randexpInstance.gen()
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
static sugar(): void {
|
|
331
|
+
;(RegExp.prototype as any).gen = function () {
|
|
332
|
+
return RandExp.randexp(this)
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
}
|
|
@@ -0,0 +1,279 @@
|
|
|
1
|
+
// Regular Expression Tokenizer
|
|
2
|
+
// https://github.com/fent/ret.js
|
|
3
|
+
|
|
4
|
+
export enum types {
|
|
5
|
+
ROOT = 0,
|
|
6
|
+
GROUP = 1,
|
|
7
|
+
POSITION = 2,
|
|
8
|
+
SET = 3,
|
|
9
|
+
RANGE = 4,
|
|
10
|
+
REPETITION = 5,
|
|
11
|
+
REFERENCE = 6,
|
|
12
|
+
CHAR = 7,
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export interface Token {
|
|
16
|
+
type: types
|
|
17
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
18
|
+
[key: string]: any
|
|
19
|
+
}
|
|
20
|
+
const wordBoundary = (): Token => ({ type: types.POSITION, value: 'b' })
|
|
21
|
+
const nonWordBoundary = (): Token => ({ type: types.POSITION, value: 'B' })
|
|
22
|
+
const begin = (): Token => ({ type: types.POSITION, value: '^' })
|
|
23
|
+
const end = (): Token => ({ type: types.POSITION, value: '$' })
|
|
24
|
+
|
|
25
|
+
const ints = (): Token[] => [{ type: types.RANGE, from: 48, to: 57 }]
|
|
26
|
+
const words = (): Token[] => [
|
|
27
|
+
{ type: types.CHAR, value: 95 },
|
|
28
|
+
{ type: types.RANGE, from: 97, to: 122 },
|
|
29
|
+
{ type: types.RANGE, from: 65, to: 90 },
|
|
30
|
+
...ints(),
|
|
31
|
+
]
|
|
32
|
+
const whitespace = (): Token[] => [
|
|
33
|
+
{ type: types.CHAR, value: 9 },
|
|
34
|
+
{ type: types.CHAR, value: 10 },
|
|
35
|
+
{ type: types.CHAR, value: 11 },
|
|
36
|
+
{ type: types.CHAR, value: 12 },
|
|
37
|
+
{ type: types.CHAR, value: 13 },
|
|
38
|
+
{ type: types.CHAR, value: 32 },
|
|
39
|
+
{ type: types.CHAR, value: 160 },
|
|
40
|
+
{ type: types.CHAR, value: 5760 },
|
|
41
|
+
{ type: types.CHAR, value: 6158 },
|
|
42
|
+
{ type: types.CHAR, value: 8192 },
|
|
43
|
+
{ type: types.CHAR, value: 8193 },
|
|
44
|
+
{ type: types.CHAR, value: 8194 },
|
|
45
|
+
{ type: types.CHAR, value: 8195 },
|
|
46
|
+
{ type: types.CHAR, value: 8196 },
|
|
47
|
+
{ type: types.CHAR, value: 8197 },
|
|
48
|
+
{ type: types.CHAR, value: 8198 },
|
|
49
|
+
{ type: types.CHAR, value: 8199 },
|
|
50
|
+
{ type: types.CHAR, value: 8200 },
|
|
51
|
+
{ type: types.CHAR, value: 8201 },
|
|
52
|
+
{ type: types.CHAR, value: 8202 },
|
|
53
|
+
{ type: types.CHAR, value: 8232 },
|
|
54
|
+
{ type: types.CHAR, value: 8233 },
|
|
55
|
+
{ type: types.CHAR, value: 8239 },
|
|
56
|
+
{ type: types.CHAR, value: 8287 },
|
|
57
|
+
{ type: types.CHAR, value: 12288 },
|
|
58
|
+
{ type: types.CHAR, value: 65279 },
|
|
59
|
+
]
|
|
60
|
+
const notanychar = (): Token[] => [
|
|
61
|
+
{ type: types.CHAR, value: 10 },
|
|
62
|
+
{ type: types.CHAR, value: 13 },
|
|
63
|
+
{ type: types.CHAR, value: 8232 },
|
|
64
|
+
{ type: types.CHAR, value: 8233 },
|
|
65
|
+
]
|
|
66
|
+
|
|
67
|
+
const words_ = (): Token => ({ type: types.SET, set: words(), not: false })
|
|
68
|
+
const notWords = (): Token => ({ type: types.SET, set: words(), not: true })
|
|
69
|
+
const ints_ = (): Token => ({ type: types.SET, set: ints(), not: false })
|
|
70
|
+
const notInts = (): Token => ({ type: types.SET, set: ints(), not: true })
|
|
71
|
+
const whitespace_ = (): Token => ({ type: types.SET, set: whitespace(), not: false })
|
|
72
|
+
const notWhitespace = (): Token => ({ type: types.SET, set: whitespace(), not: true })
|
|
73
|
+
const anyChar = (): Token => ({ type: types.SET, set: notanychar(), not: true })
|
|
74
|
+
|
|
75
|
+
const specialChars: Record<string, number> = {
|
|
76
|
+
'0': 0,
|
|
77
|
+
't': 9,
|
|
78
|
+
'n': 10,
|
|
79
|
+
'v': 11,
|
|
80
|
+
'f': 12,
|
|
81
|
+
'r': 13,
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
const strToChars = (str: string): string => {
|
|
85
|
+
const chars = /(\[\\b\])|(\\)?\\(?:u([A-F0-9]{4})|x([A-F0-9]{2})|(0?[0-7]{2})|c([@A-Z[\\\]^?])|([0tnvfr]))/g
|
|
86
|
+
return str.replace(chars, (match, b, s, uh, xh, o, c, sp) => {
|
|
87
|
+
if (s) return match
|
|
88
|
+
const code = b
|
|
89
|
+
? 8
|
|
90
|
+
: uh
|
|
91
|
+
? parseInt(uh, 16)
|
|
92
|
+
: xh
|
|
93
|
+
? parseInt(xh, 16)
|
|
94
|
+
: o
|
|
95
|
+
? parseInt(o, 8)
|
|
96
|
+
: c
|
|
97
|
+
? '@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^ ?'.indexOf(c)
|
|
98
|
+
: specialChars[sp]
|
|
99
|
+
let ch = String.fromCharCode(code)
|
|
100
|
+
if (/[[{}^$.|?*+()]/.test(ch)) {
|
|
101
|
+
ch = `\\${ch}`
|
|
102
|
+
}
|
|
103
|
+
return ch
|
|
104
|
+
})
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
const tokenizeClass = (str: string, regexpStr: string): [Token[], number] => {
|
|
108
|
+
const tokens: Token[] = []
|
|
109
|
+
const classTokens = /\\(?:(w)|(d)|(s)|(W)|(D)|(S))|((?:(?:\\)(.)|([^\]\\]))-(?:\\)?([^\]]))|(\])|(?:\\)?(.)/g
|
|
110
|
+
let match
|
|
111
|
+
let char
|
|
112
|
+
while ((match = classTokens.exec(str)) !== null) {
|
|
113
|
+
if (match[1]) {
|
|
114
|
+
tokens.push(words_())
|
|
115
|
+
} else if (match[2]) {
|
|
116
|
+
tokens.push(ints_())
|
|
117
|
+
} else if (match[3]) {
|
|
118
|
+
tokens.push(whitespace_())
|
|
119
|
+
} else if (match[4]) {
|
|
120
|
+
tokens.push(notWords())
|
|
121
|
+
} else if (match[5]) {
|
|
122
|
+
tokens.push(notInts())
|
|
123
|
+
} else if (match[6]) {
|
|
124
|
+
tokens.push(notWhitespace())
|
|
125
|
+
} else if (match[7]) {
|
|
126
|
+
tokens.push({
|
|
127
|
+
type: types.RANGE,
|
|
128
|
+
from: (match[8] || match[9]).charCodeAt(0),
|
|
129
|
+
to: match[10].charCodeAt(0),
|
|
130
|
+
})
|
|
131
|
+
} else if ((char = match[12])) {
|
|
132
|
+
tokens.push({ type: types.CHAR, value: char.charCodeAt(0) })
|
|
133
|
+
} else {
|
|
134
|
+
return [tokens, classTokens.lastIndex]
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
throw new SyntaxError(`Invalid regular expression: /${regexpStr}/: Unterminated character class`)
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
const error = (regexpStr: string, msg: string): never => {
|
|
141
|
+
throw new SyntaxError(`Invalid regular expression: /${regexpStr}/: ${msg}`)
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
export const ret = (regexpStr: string): Token => {
|
|
145
|
+
let i = 0
|
|
146
|
+
const root: Token = { type: types.ROOT, stack: [] }
|
|
147
|
+
let p: Token = root
|
|
148
|
+
let stack: Token[] = root.stack
|
|
149
|
+
const groupStack: Token[] = []
|
|
150
|
+
|
|
151
|
+
const repeatErr = (i: number) => error(regexpStr, `Nothing to repeat at column ${i - 1}`)
|
|
152
|
+
const chars = strToChars(regexpStr)
|
|
153
|
+
|
|
154
|
+
while (i < chars.length) {
|
|
155
|
+
let c = chars[i++]
|
|
156
|
+
switch (c) {
|
|
157
|
+
case '\\':
|
|
158
|
+
c = chars[i++]
|
|
159
|
+
switch (c) {
|
|
160
|
+
case 'b':
|
|
161
|
+
stack.push(wordBoundary())
|
|
162
|
+
break
|
|
163
|
+
case 'B':
|
|
164
|
+
stack.push(nonWordBoundary())
|
|
165
|
+
break
|
|
166
|
+
case 'w':
|
|
167
|
+
stack.push(words_())
|
|
168
|
+
break
|
|
169
|
+
case 'W':
|
|
170
|
+
stack.push(notWords())
|
|
171
|
+
break
|
|
172
|
+
case 'd':
|
|
173
|
+
stack.push(ints_())
|
|
174
|
+
break
|
|
175
|
+
case 'D':
|
|
176
|
+
stack.push(notInts())
|
|
177
|
+
break
|
|
178
|
+
case 's':
|
|
179
|
+
stack.push(whitespace_())
|
|
180
|
+
break
|
|
181
|
+
case 'S':
|
|
182
|
+
stack.push(notWhitespace())
|
|
183
|
+
break
|
|
184
|
+
default:
|
|
185
|
+
if (/\d/.test(c)) {
|
|
186
|
+
stack.push({ type: types.REFERENCE, value: parseInt(c, 10) })
|
|
187
|
+
} else {
|
|
188
|
+
stack.push({ type: types.CHAR, value: c.charCodeAt(0) })
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
break
|
|
192
|
+
case '^':
|
|
193
|
+
stack.push(begin())
|
|
194
|
+
break
|
|
195
|
+
case '$':
|
|
196
|
+
stack.push(end())
|
|
197
|
+
break
|
|
198
|
+
case '[': {
|
|
199
|
+
let not = false
|
|
200
|
+
if (chars[i] === '^') {
|
|
201
|
+
not = true
|
|
202
|
+
i++
|
|
203
|
+
}
|
|
204
|
+
const [set, lastIndex] = tokenizeClass(chars.slice(i), regexpStr)
|
|
205
|
+
i += lastIndex
|
|
206
|
+
stack.push({ type: types.SET, set, not })
|
|
207
|
+
break
|
|
208
|
+
}
|
|
209
|
+
case '.':
|
|
210
|
+
stack.push(anyChar())
|
|
211
|
+
break
|
|
212
|
+
case '(': {
|
|
213
|
+
const group: Token = { type: types.GROUP, stack: [], remember: true }
|
|
214
|
+
if (chars[i] === '?') {
|
|
215
|
+
c = chars[i + 1]
|
|
216
|
+
i += 2
|
|
217
|
+
if (c === '=') {
|
|
218
|
+
group.followedBy = true
|
|
219
|
+
} else if (c === '!') {
|
|
220
|
+
group.notFollowedBy = true
|
|
221
|
+
} else if (c !== ':') {
|
|
222
|
+
error(regexpStr, `Invalid group, character '${c}' after '?' at column ${i - 1}`)
|
|
223
|
+
}
|
|
224
|
+
group.remember = false
|
|
225
|
+
}
|
|
226
|
+
stack.push(group)
|
|
227
|
+
groupStack.push(p)
|
|
228
|
+
p = group
|
|
229
|
+
stack = group.stack
|
|
230
|
+
break
|
|
231
|
+
}
|
|
232
|
+
case ')':
|
|
233
|
+
if (groupStack.length === 0) error(regexpStr, `Unmatched ) at column ${i - 1}`)
|
|
234
|
+
p = groupStack.pop() as Token
|
|
235
|
+
stack = p.options ? p.options[p.options.length - 1] : p.stack
|
|
236
|
+
break
|
|
237
|
+
case '|':
|
|
238
|
+
if (!p.options) {
|
|
239
|
+
p.options = [p.stack]
|
|
240
|
+
delete p.stack
|
|
241
|
+
}
|
|
242
|
+
{
|
|
243
|
+
const newStack: Token[] = []
|
|
244
|
+
p.options.push(newStack)
|
|
245
|
+
stack = newStack
|
|
246
|
+
}
|
|
247
|
+
break
|
|
248
|
+
case '{': {
|
|
249
|
+
const repetition = /^(\d+)(,(\d+)?)?\}/.exec(chars.slice(i))
|
|
250
|
+
if (repetition) {
|
|
251
|
+
if (stack.length === 0) repeatErr(i)
|
|
252
|
+
const min = parseInt(repetition[1], 10)
|
|
253
|
+
const max = repetition[2] ? (repetition[3] ? parseInt(repetition[3], 10) : Infinity) : min
|
|
254
|
+
i += repetition[0].length
|
|
255
|
+
stack.push({ type: types.REPETITION, min, max, value: stack.pop() })
|
|
256
|
+
} else {
|
|
257
|
+
stack.push({ type: types.CHAR, value: 123 })
|
|
258
|
+
}
|
|
259
|
+
break
|
|
260
|
+
}
|
|
261
|
+
case '?':
|
|
262
|
+
if (stack.length === 0) repeatErr(i)
|
|
263
|
+
stack.push({ type: types.REPETITION, min: 0, max: 1, value: stack.pop() })
|
|
264
|
+
break
|
|
265
|
+
case '+':
|
|
266
|
+
if (stack.length === 0) repeatErr(i)
|
|
267
|
+
stack.push({ type: types.REPETITION, min: 1, max: Infinity, value: stack.pop() })
|
|
268
|
+
break
|
|
269
|
+
case '*':
|
|
270
|
+
if (stack.length === 0) repeatErr(i)
|
|
271
|
+
stack.push({ type: types.REPETITION, min: 0, max: Infinity, value: stack.pop() })
|
|
272
|
+
break
|
|
273
|
+
default:
|
|
274
|
+
stack.push({ type: types.CHAR, value: c.charCodeAt(0) })
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
if (groupStack.length !== 0) error(regexpStr, 'Unterminated group')
|
|
278
|
+
return root
|
|
279
|
+
}
|