Merge pull request #2 from Start9Labs/feat/multiselect

Rename enum and add multiselect (replaces list of enums)
This commit is contained in:
J H
2023-03-25 18:35:27 -06:00
committed by GitHub
9 changed files with 80 additions and 75 deletions

View File

@@ -19,7 +19,7 @@ import { Value } from "./value";
The idea of a config is that now the form is going to ask for The idea of a config is that now the form is going to ask for
Test: [ ] and the value is going to be checked as a boolean. Test: [ ] and the value is going to be checked as a boolean.
There are more complex values like enums, lists, and objects. See {@link Value} There are more complex values like selects, lists, and objects. See {@link Value}
Also, there is the ability to get a validator/parser from this config spec. Also, there is the ability to get a validator/parser from this config spec.
```ts ```ts
@@ -81,7 +81,7 @@ import { Value } from "./value";
"warning": null, "warning": null,
}); });
export const auth = Value.list(authorizationList); export const auth = Value.list(authorizationList);
export const serialversion = Value.enum({ export const serialversion = Value.select({
"name": "Serialization Version", "name": "Serialization Version",
"description": "description":
"Return raw transaction or block hex with Segwit or non-SegWit serialization.", "Return raw transaction or block hex with Segwit or non-SegWit serialization.",

View File

@@ -59,24 +59,6 @@ export class List<A extends ValueSpecList> extends IBuilder<A> {
...a, ...a,
}); });
} }
static enum<
A extends Description &
Default<string[]> & {
range: string;
spec: {
values: string[];
"value-names": {
[key: string]: string;
};
};
}
>(a: A) {
return new List({
type: "list" as const,
subtype: "enum" as const,
...a,
});
}
static obj< static obj<
A extends Description & A extends Description &
Default<Record<string, unknown>[]> & { Default<Record<string, unknown>[]> & {

View File

@@ -95,7 +95,7 @@ export class Value<A extends ValueSpec> extends IBuilder<A> {
...a, ...a,
} as ValueSpecNumber); } as ValueSpecNumber);
} }
static enum< static select<
A extends Description & A extends Description &
Default<string> & { Default<string> & {
values: readonly string[] | string[]; values: readonly string[] | string[];
@@ -103,7 +103,7 @@ export class Value<A extends ValueSpec> extends IBuilder<A> {
} }
>(a: A) { >(a: A) {
return new Value({ return new Value({
type: "enum" as const, type: "select" as const,
...a, ...a,
}); });
} }

View File

@@ -3,9 +3,9 @@ import { BuilderExtract, IBuilder } from "./builder";
import { Config } from "."; import { Config } from ".";
/** /**
* Used in the the Value.enum { @link './value.ts' } * Used in the the Value.select { @link './value.ts' }
* to indicate the type of enums variants that are available. The key for the record passed in will be the * to indicate the type of select variants that are available. The key for the record passed in will be the
* key to the tag.id in the Value.enum * key to the tag.id in the Value.select
```ts ```ts
export const pruningSettingsVariants = Variants.of({ export const pruningSettingsVariants = Variants.of({
"disabled": disabled, "disabled": disabled,

View File

@@ -97,7 +97,7 @@ export default async function makeFileContent(
)})`; )})`;
} }
case "enum": { case "enum": {
return `Value.enum(${JSON.stringify( return `Value.select(${JSON.stringify(
{ {
name: value.name || null, name: value.name || null,
description: value.description || null, description: value.description || null,
@@ -205,14 +205,12 @@ export default async function makeFileContent(
)})`; )})`;
} }
case "enum": { case "enum": {
return `List.enum(${JSON.stringify( return `Value.multiselect(${JSON.stringify(
{ {
name: value.name || null, name: value.name || null,
range: value.range || null, range: value.range || null,
spec: { values: value?.spec?.["values"] || null,
values: value?.spec?.["values"] || null, "value-names": value?.spec?.["value-names"] || {},
"value-names": value?.spec?.["value-names"] || {},
},
default: value.default || null, default: value.default || null,
description: value.description || null, description: value.description || null,
warning: value.warning || null, warning: value.warning || null,

View File

@@ -4,22 +4,25 @@ export type ValueType =
| "string" | "string"
| "number" | "number"
| "boolean" | "boolean"
| "enum" | "select"
| "multiselect"
| "list" | "list"
| "object" | "object"
| "file" | "file"
| "union"; | "union";
export type ValueSpec = ValueSpecOf<ValueType>; export type ValueSpec = ValueSpecOf<ValueType>;
// core spec types. These types provide the metadata for performing validations /** core spec types. These types provide the metadata for performing validations */
export type ValueSpecOf<T extends ValueType> = T extends "string" export type ValueSpecOf<T extends ValueType> = T extends "string"
? ValueSpecString ? ValueSpecString
: T extends "number" : T extends "number"
? ValueSpecNumber ? ValueSpecNumber
: T extends "boolean" : T extends "boolean"
? ValueSpecBoolean ? ValueSpecBoolean
: T extends "enum" : T extends "select"
? ValueSpecEnum ? ValueSpecSelect
: T extends "multiselect"
? ValueSpecMultiselect
: T extends "list" : T extends "list"
? ValueSpecList ? ValueSpecList
: T extends "object" : T extends "object"
@@ -43,11 +46,18 @@ export interface ValueSpecNumber extends ListValueSpecNumber, WithStandalone {
default: null | number; default: null | number;
} }
export interface ValueSpecEnum extends ListValueSpecEnum, WithStandalone { export interface ValueSpecSelect extends SelectBase, WithStandalone {
type: "enum"; type: "select";
default: string; default: string;
} }
export interface ValueSpecMultiselect extends SelectBase, WithStandalone {
type: "multiselect";
/**'[0,1]' (inclusive) OR '[0,*)' (right unbounded), normal math rules */
range: string;
default: string[];
}
export interface ValueSpecBoolean extends WithStandalone { export interface ValueSpecBoolean extends WithStandalone {
type: "boolean"; type: "boolean";
default: boolean; default: boolean;
@@ -61,7 +71,7 @@ export interface ValueSpecUnion {
} }
export interface ValueSpecFile extends WithStandalone { export interface ValueSpecFile extends WithStandalone {
type: 'file'; type: "file";
placeholder: null | string; placeholder: null | string;
nullable: boolean; nullable: boolean;
extensions: string[]; extensions: string[];
@@ -78,31 +88,28 @@ export interface WithStandalone {
warning: null | string; warning: null | string;
} }
// no lists of booleans, lists export interface SelectBase {
export type ListValueSpecType = values: string[] | readonly string[];
| "string" "value-names": { [value: string]: string };
| "number" }
| "enum"
| "object"
| "union";
// represents a spec for the values of a list /** no lists of booleans, lists*/
export type ListValueSpecType = "string" | "number" | "object" | "union";
/** represents a spec for the values of a list */
export type ListValueSpecOf<T extends ListValueSpecType> = T extends "string" export type ListValueSpecOf<T extends ListValueSpecType> = T extends "string"
? ListValueSpecString ? ListValueSpecString
: T extends "number" : T extends "number"
? ListValueSpecNumber ? ListValueSpecNumber
: T extends "enum"
? ListValueSpecEnum
: T extends "object" : T extends "object"
? ListValueSpecObject ? ListValueSpecObject
: T extends "union" : T extends "union"
? ListValueSpecUnion ? ListValueSpecUnion
: never; : never;
// represents a spec for a list /** represents a spec for a list */
export type ValueSpecList = ValueSpecListOf<ListValueSpecType>; export type ValueSpecList = ValueSpecListOf<ListValueSpecType>;
export interface ValueSpecListOf<T extends ListValueSpecType> export interface ValueSpecListOf<T extends ListValueSpecType> extends WithStandalone {
extends WithStandalone {
type: "list"; type: "list";
subtype: T; subtype: T;
spec: ListValueSpecOf<T>; spec: ListValueSpecOf<T>;
@@ -119,10 +126,7 @@ export interface ValueSpecListOf<T extends ListValueSpecType>
} }
// sometimes the type checker needs just a little bit of help // sometimes the type checker needs just a little bit of help
export function isValueSpecListOf<S extends ListValueSpecType>( export function isValueSpecListOf<S extends ListValueSpecType>(t: ValueSpecList, s: S): t is ValueSpecListOf<S> {
t: ValueSpecList,
s: S
): t is ValueSpecListOf<S> {
return t.subtype === s; return t.subtype === s;
} }
@@ -134,21 +138,20 @@ export interface ListValueSpecString {
} }
export interface ListValueSpecNumber { export interface ListValueSpecNumber {
/** '[0,1]' (inclusive) OR '[0,*)' (right unbounded), normal math rules */
range: string; range: string;
integral: boolean; integral: boolean;
units: null | string; units: null | string;
placeholder: null | string; placeholder: null | string;
} }
export interface ListValueSpecEnum {
values: string[] | readonly string[];
"value-names": { [value: string]: string };
}
export interface ListValueSpecObject { export interface ListValueSpecObject {
spec: InputSpec; // this is a mapped type of the config object at this level, replacing the object's values with specs on those values /** this is a mapped type of the config object at this level, replacing the object's values with specs on those values */
"unique-by": UniqueBy; // indicates whether duplicates can be permitted in the list spec: InputSpec;
"display-as": null | string; // this should be a handlebars template which can make use of the entire config which corresponds to 'spec' /** indicates whether duplicates can be permitted in the list */
"unique-by": UniqueBy;
/** this should be a handlebars template which can make use of the entire config which corresponds to 'spec' */
"display-as": null | string;
} }
export type UniqueBy = export type UniqueBy =
@@ -161,15 +164,18 @@ export type UniqueBy =
export interface ListValueSpecUnion { export interface ListValueSpecUnion {
tag: UnionTagSpec; tag: UnionTagSpec;
variants: { [key: string]: InputSpec }; variants: { [key: string]: InputSpec };
"display-as": null | string; // this may be a handlebars template which can conditionally (on tag.id) make use of each union's entries, or if left blank will display as tag.id /** this may be a handlebars template which can conditionally (on tag.id) make use of each union's entries, or if left blank will display as tag.id*/
"display-as": null | string;
"unique-by": UniqueBy; "unique-by": UniqueBy;
default: string; // this should be the variantName which one prefers a user to start with by default when creating a new union instance in a list /** this should be the variantName which one prefers a user to start with by default when creating a new union instance in a list*/
default: string;
} }
export interface UnionTagSpec { export interface UnionTagSpec {
id: string; // The name of the field containing one of the union variants /** The name of the field containing one of the union variants*/
id: string;
"variant-names": { "variant-names": {
// the name of each variant /** the name of each variant*/
[variant: string]: string; [variant: string]: string;
}; };
name: string; name: string;

View File

@@ -1,5 +1,4 @@
import { writeConvertedFile } from "../../scripts/oldSpecToBuilder"; import { writeConvertedFile } from "../../scripts/oldSpecToBuilder";
import { writeFileSync, readFileSync } from "fs";
writeConvertedFile( writeConvertedFile(
"./lib/util/artifacts/output.ts", "./lib/util/artifacts/output.ts",
@@ -66,7 +65,7 @@ writeConvertedFile(
name: "Serialization Version", name: "Serialization Version",
description: description:
"Return raw transaction or block hex with Segwit or non-SegWit serialization.", "Return raw transaction or block hex with Segwit or non-SegWit serialization.",
type: "enum", type: "select",
values: ["non-segwit", "segwit"], values: ["non-segwit", "segwit"],
"value-names": {}, "value-names": {},
default: "segwit", default: "segwit",

View File

@@ -51,7 +51,7 @@ export const authorizationList = List.string({
warning: null, warning: null,
}); });
export const auth = Value.list(authorizationList); export const auth = Value.list(authorizationList);
export const serialversion = Value.enum({ export const serialversion = Value.select({
name: "Serialization Version", name: "Serialization Version",
description: description:
"Return raw transaction or block hex with Segwit or non-SegWit serialization.", "Return raw transaction or block hex with Segwit or non-SegWit serialization.",

View File

@@ -6,7 +6,8 @@ type TypeString = "string";
type TypeNumber = "number"; type TypeNumber = "number";
type TypeObject = "object"; type TypeObject = "object";
type TypeList = "list"; type TypeList = "list";
type TypeEnum = "enum"; type TypeSelect = "select";
type TypeMultiselect = "multiselect";
type TypePointer = "pointer"; type TypePointer = "pointer";
type TypeUnion = "union"; type TypeUnion = "union";
@@ -49,8 +50,12 @@ type GuardPointer<A> =
A extends {readonly type:TypePointer} ? (string | null) : A extends {readonly type:TypePointer} ? (string | null) :
unknown unknown
// prettier-ignore // prettier-ignore
type GuardEnum<A> = type GuardSelect<A> =
A extends {readonly type:TypeEnum, readonly values: ArrayLike<infer B>} ? GuardDefaultNullable<A, B> : A extends {readonly type:TypeSelect, readonly values: ArrayLike<infer B>} ? GuardDefaultNullable<A, B> :
unknown
// prettier-ignore
type GuardMultiselect<A> =
A extends {readonly type:TypeMultiselect, readonly values: ArrayLike<infer B>} ? GuardDefaultNullable<A, B> :
unknown unknown
// prettier-ignore // prettier-ignore
type GuardUnion<A> = type GuardUnion<A> =
@@ -65,7 +70,8 @@ export type GuardAll<A> = GuardNumber<A> &
GuardList<A> & GuardList<A> &
GuardPointer<A> & GuardPointer<A> &
GuardUnion<A> & GuardUnion<A> &
GuardEnum<A>; GuardSelect<A> &
GuardMultiselect<A>;
// prettier-ignore // prettier-ignore
export type TypeFromProps<A> = export type TypeFromProps<A> =
A extends Record<string, unknown> ? {readonly [K in keyof A & string]: _<GuardAll<A[K]>>} : A extends Record<string, unknown> ? {readonly [K in keyof A & string]: _<GuardAll<A[K]>>} :
@@ -247,7 +253,7 @@ export function guardAll<A extends ValueSpecAny>(
value value
) as any; ) as any;
} }
case "enum": case "select":
if (matchValues.test(value)) { if (matchValues.test(value)) {
return defaultNullable( return defaultNullable(
matches.literals(value.values[0], ...value.values), matches.literals(value.values[0], ...value.values),
@@ -255,6 +261,20 @@ export function guardAll<A extends ValueSpecAny>(
) as any; ) as any;
} }
return matches.unknown as any; return matches.unknown as any;
case "multiselect":
if (matchValues.test(value)) {
const rangeValidate =
(matchRange.test(value) && matchNumberWithRange(value.range).test) ||
(() => true);
return defaultNullable(
matches
.literals(value.values[0], ...value.values)
.validate((x) => rangeValidate(x.length), "valid length"),
value
) as any;
}
return matches.unknown as any;
case "union": case "union":
if (matchUnion.test(value)) { if (matchUnion.test(value)) {
return matches.some( return matches.some(