mirror of
https://github.com/Start9Labs/start-os.git
synced 2026-03-30 12:11:56 +00:00
* chore: enable strict mode * refactor: remove sync data access from PatchDbService * launchable even when no LAN url Co-authored-by: Matt Hill <matthewonthemoon@gmail.com>
236 lines
5.6 KiB
TypeScript
236 lines
5.6 KiB
TypeScript
import { ValueSpec, DefaultString } from './config-types'
|
|
|
|
export class Range {
|
|
min?: number
|
|
max?: number
|
|
minInclusive!: boolean
|
|
maxInclusive!: boolean
|
|
|
|
static from(s: string): Range {
|
|
const r = new Range()
|
|
r.minInclusive = s.startsWith('[')
|
|
r.maxInclusive = s.endsWith(']')
|
|
const [minStr, maxStr] = s.split(',').map(a => a.trim())
|
|
r.min = minStr === '(*' ? undefined : Number(minStr.slice(1))
|
|
r.max = maxStr === '*)' ? undefined : Number(maxStr.slice(0, -1))
|
|
return r
|
|
}
|
|
|
|
checkIncludes(n: number) {
|
|
if (
|
|
this.hasMin() &&
|
|
(this.min > n || (!this.minInclusive && this.min == n))
|
|
) {
|
|
throw new Error(this.minMessage())
|
|
}
|
|
if (
|
|
this.hasMax() &&
|
|
(this.max < n || (!this.maxInclusive && this.max == n))
|
|
) {
|
|
throw new Error(this.maxMessage())
|
|
}
|
|
}
|
|
|
|
hasMin(): this is Range & { min: number } {
|
|
return this.min !== undefined
|
|
}
|
|
|
|
hasMax(): this is Range & { max: number } {
|
|
return this.max !== undefined
|
|
}
|
|
|
|
minMessage(): string {
|
|
return `greater than${this.minInclusive ? ' or equal to' : ''} ${this.min}`
|
|
}
|
|
|
|
maxMessage(): string {
|
|
return `less than${this.maxInclusive ? ' or equal to' : ''} ${this.max}`
|
|
}
|
|
|
|
description(): string {
|
|
let message = 'Value can be any number.'
|
|
|
|
if (this.hasMin() || this.hasMax()) {
|
|
message = 'Value must be'
|
|
}
|
|
|
|
if (this.hasMin() && this.hasMax()) {
|
|
message = `${message} ${this.minMessage()} AND ${this.maxMessage()}.`
|
|
} else if (this.hasMin() && !this.hasMax()) {
|
|
message = `${message} ${this.minMessage()}.`
|
|
} else if (!this.hasMin() && this.hasMax()) {
|
|
message = `${message} ${this.maxMessage()}.`
|
|
}
|
|
|
|
return message
|
|
}
|
|
|
|
integralMin(): number | undefined {
|
|
if (this.min) {
|
|
const ceil = Math.ceil(this.min)
|
|
if (this.minInclusive) {
|
|
return ceil
|
|
} else {
|
|
if (ceil === this.min) {
|
|
return ceil + 1
|
|
} else {
|
|
return ceil
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
integralMax(): number | undefined {
|
|
if (this.max) {
|
|
const floor = Math.floor(this.max)
|
|
if (this.maxInclusive) {
|
|
return floor
|
|
} else {
|
|
if (floor === this.max) {
|
|
return floor - 1
|
|
} else {
|
|
return floor
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
export function getDefaultDescription(spec: ValueSpec): string {
|
|
let toReturn: string | undefined
|
|
switch (spec.type) {
|
|
case 'string':
|
|
if (typeof spec.default === 'string') {
|
|
toReturn = spec.default
|
|
} else if (typeof spec.default === 'object') {
|
|
toReturn = 'random'
|
|
}
|
|
break
|
|
case 'number':
|
|
if (typeof spec.default === 'number') {
|
|
toReturn = String(spec.default)
|
|
}
|
|
break
|
|
case 'boolean':
|
|
toReturn = spec.default === true ? 'True' : 'False'
|
|
break
|
|
case 'enum':
|
|
toReturn = spec['value-names'][spec.default]
|
|
break
|
|
}
|
|
|
|
return toReturn || ''
|
|
}
|
|
|
|
export function getDefaultString(defaultSpec: DefaultString): string {
|
|
if (typeof defaultSpec === 'string') {
|
|
return defaultSpec
|
|
} else {
|
|
let s = ''
|
|
for (let i = 0; i < defaultSpec.len; i++) {
|
|
s = s + getRandomCharInSet(defaultSpec.charset)
|
|
}
|
|
|
|
return s
|
|
}
|
|
}
|
|
|
|
// a,g,h,A-Z,,,,-
|
|
export function getRandomCharInSet(charset: string): string {
|
|
const set = stringToCharSet(charset)
|
|
let charIdx = Math.floor(Math.random() * set.len)
|
|
for (let range of set.ranges) {
|
|
if (range.len > charIdx) {
|
|
return String.fromCharCode(range.start.charCodeAt(0) + charIdx)
|
|
}
|
|
charIdx -= range.len
|
|
}
|
|
throw new Error('unreachable')
|
|
}
|
|
|
|
function stringToCharSet(charset: string): CharSet {
|
|
let set: CharSet = { ranges: [], len: 0 }
|
|
let start: string | null = null
|
|
let end: string | null = null
|
|
let in_range = false
|
|
for (let char of charset) {
|
|
switch (char) {
|
|
case ',':
|
|
if (start !== null && end !== null) {
|
|
if (start!.charCodeAt(0) > end!.charCodeAt(0)) {
|
|
throw new Error('start > end of charset')
|
|
}
|
|
const len = end.charCodeAt(0) - start.charCodeAt(0) + 1
|
|
set.ranges.push({
|
|
start,
|
|
end,
|
|
len,
|
|
})
|
|
set.len += len
|
|
start = null
|
|
end = null
|
|
in_range = false
|
|
} else if (start !== null && !in_range) {
|
|
set.len += 1
|
|
set.ranges.push({ start, end: start, len: 1 })
|
|
start = null
|
|
} else if (start !== null && in_range) {
|
|
end = ','
|
|
} else if (start === null && end === null && !in_range) {
|
|
start = ','
|
|
} else {
|
|
throw new Error('unexpected ","')
|
|
}
|
|
break
|
|
case '-':
|
|
if (start === null) {
|
|
start = '-'
|
|
} else if (!in_range) {
|
|
in_range = true
|
|
} else if (in_range && end === null) {
|
|
end = '-'
|
|
} else {
|
|
throw new Error('unexpected "-"')
|
|
}
|
|
break
|
|
default:
|
|
if (start === null) {
|
|
start = char
|
|
} else if (in_range && end === null) {
|
|
end = char
|
|
} else {
|
|
throw new Error(`unexpected "${char}"`)
|
|
}
|
|
}
|
|
}
|
|
if (start !== null && end !== null) {
|
|
if (start!.charCodeAt(0) > end!.charCodeAt(0)) {
|
|
throw new Error('start > end of charset')
|
|
}
|
|
const len = end.charCodeAt(0) - start.charCodeAt(0) + 1
|
|
set.ranges.push({
|
|
start,
|
|
end,
|
|
len,
|
|
})
|
|
set.len += len
|
|
} else if (start !== null) {
|
|
set.len += 1
|
|
set.ranges.push({
|
|
start,
|
|
end: start,
|
|
len: 1,
|
|
})
|
|
}
|
|
return set
|
|
}
|
|
|
|
interface CharSet {
|
|
ranges: {
|
|
start: string
|
|
end: string
|
|
len: number
|
|
}[]
|
|
len: number
|
|
}
|