Skip to content
Snippets Groups Projects
Commit 94a29632 authored by Loïc Correnson's avatar Loïc Correnson Committed by Michele Alberti
Browse files

[dome] lint data

parent 2ea6f707
No related branches found
No related tags found
No related merge requests found
......@@ -11,4 +11,3 @@ src/api
# lint Dome step by step
src/dome/src/renderer/layout
src/dome/src/renderer/table
src/dome/src/renderer/data
......@@ -55,6 +55,8 @@ module.exports = {
"padded-blocks": "off",
// Allow braces on their own line
"@typescript-eslint/brace-style": "off",
// Already has built-in compiler checks in TSC for that
"@typescript-eslint/no-unused-vars": "off",
// Allow range conditions such as 0 <= x && x < 10
"yoda": [2, "never", { "onlyEquality": true }],
// Allow single command on new line after 'if' statement
......@@ -89,5 +91,12 @@ module.exports = {
"jsx-a11y/no-autofocus": "off",
// Completely broken rule
"react/prop-types": "off",
// Enable ++ and --
"no-plusplus": "off",
// Enable nested ternary operations
"no-nested-ternary": "off",
// Checked by TSC compiler
"default-case": "off",
"consistent-return": "off",
}
};
......@@ -77,7 +77,7 @@ let makeJtype ?self ~names =
| Jtag a -> Format.fprintf fmt "\"%s\"" a
| Jkey kd -> Format.fprintf fmt "Json.key<'#%s'>" kd
| Jindex kd -> Format.fprintf fmt "Json.index<'#%s'>" kd
| Jdict(kd,js) -> Format.fprintf fmt "Json.Dict<'#%s',%a>" kd pp js
| Jdict js -> Format.fprintf fmt "@[<hov 2>Json.Dict<@,%a>@]" pp js
| Jdata id | Jenum id -> pp_ident fmt id
| Joption js -> Format.fprintf fmt "%a |@ undefined" pp js
| Jtuple js ->
......@@ -174,10 +174,10 @@ let rec makeDecoder ~safe ?self ~names fmt js =
| Jenum id -> jsafe ~safe (Pkg.name_of_ident id) (jenum names) fmt id
| Jself -> jcall names fmt (Pkg.Derived.decode ~safe (getSelf self))
| Joption js -> makeLoose fmt js
| Jdict(kd,js) ->
Format.fprintf fmt "@[<hov 2>Json.jDictionary('#%s',@,%a)@]" kd makeLoose js
| Jdict js ->
Format.fprintf fmt "@[<hov 2>Json.jDict(@,%a)@]" makeLoose js
| Jlist js ->
Format.fprintf fmt "@[<hov 2>Json.jList(%a)@]" makeLoose js
Format.fprintf fmt "@[<hov 2>Json.jList(@,%a)@]" makeLoose js
| Jarray js ->
if safe
then Format.fprintf fmt "@[<hov 2>Json.jArray(%a)@]" makeSafe js
......@@ -246,11 +246,11 @@ let makeOrder ~self ~names fmt js =
List.iter
(fun (fd,js) -> Format.fprintf fmt "@ @[<hov 2>%s: %a,@]" fd pp js) jfs ;
Format.fprintf fmt "@]@ })@]" ;
| Jdict(kd,js) ->
| Jdict js ->
let jtype fmt js = makeJtype ~names fmt js in
Format.fprintf fmt
"@[<hov 2>Compare.dictionary<@,Json.dict<'#%s'@,%a>>(@,%a)@]"
kd jtype js pp js
"@[<hov 2>Compare.dictionary<@,Json.dict<%a>>(@,%a)@]"
jtype js pp js
| Jany | Junion _ | Jtag _ ->
Format.fprintf fmt "Compare.structural"
in pp fmt js
......
......@@ -13,7 +13,7 @@ import FastCompare from 'react-fast-compare';
/**
Interface for comparison functions.
These function shall fullfill the following contract:
- `compare(x,y) == 0` shall be an equivalence relation
- `compare(x,y) === 0` shall be an equivalence relation
(reflexive, symmetric, transitive)
- `compare(x,y) <= 0` shall be a complete order
(reflexive, antisymetric, transitive)
......@@ -38,7 +38,10 @@ export type bignum = bigint | number;
/** Detect Non-NaN numbers and big-ints. */
export function isBigNum(x: any): x is bignum {
return typeof (x) === 'bigint' || (typeof (x) === 'number' && !Number.isNaN(x));
return (
(typeof (x) === 'bigint') ||
(typeof (x) === 'number' && !Number.isNaN(x))
);
}
/** @internal */
......@@ -88,17 +91,18 @@ export function number(x: number, y: number) {
*/
export function alpha(x: string, y: string) {
const cmp = primitive(x.toLowerCase(), y.toLowerCase());
return cmp != 0 ? cmp : primitive(x, y);
return cmp !== 0 ? cmp : primitive(x, y);
}
/** Combine comparison orders in sequence. */
export function sequence<A>(...orders: (Order<A> | undefined)[]): Order<A> {
return (x: A, y: A) => {
if (x === y) return 0;
for (const order of orders) {
for (let k = 0; k < orders.length; k++) {
const order = orders[k];
if (order) {
const cmp = order(x, y);
if (cmp != 0) return cmp;
if (cmp !== 0) return cmp;
}
}
return 0;
......@@ -108,9 +112,9 @@ export function sequence<A>(...orders: (Order<A> | undefined)[]): Order<A> {
/** Compare optional values. Undefined values come first. */
export function option<A>(order: Order<A>): Order<undefined | A> {
return (x?: A, y?: A) => {
if (x == undefined && y == undefined) return 0;
if (x == undefined) return -1;
if (y == undefined) return 1;
if (x === undefined && y === undefined) return 0;
if (x === undefined) return -1;
if (y === undefined) return 1;
return order(x, y);
};
}
......@@ -118,9 +122,9 @@ export function option<A>(order: Order<A>): Order<undefined | A> {
/** Compare optional values. Undefined values come last. */
export function defined<A>(order: Order<A>): Order<undefined | A> {
return (x?: A, y?: A) => {
if (x == undefined && y == undefined) return 0;
if (x == undefined) return 1;
if (y == undefined) return -1;
if (x === undefined && y === undefined) return 0;
if (x === undefined) return 1;
if (y === undefined) return -1;
return order(x, y);
};
}
......@@ -134,7 +138,7 @@ export function array<A>(order: Order<A>): Order<A[]> {
const m = p < q ? p : q;
for (let k = 0; k < m; k++) {
const cmp = order(x[k], y[k]);
if (cmp != 0) return cmp;
if (cmp !== 0) return cmp;
}
return p - q;
};
......@@ -143,11 +147,13 @@ export function array<A>(order: Order<A>): Order<A[]> {
/** Order by dictionary order.
Can be used directly with an enum type declaration.
*/
export function byEnum<A extends string>(d: { [key: string]: A }): Order<A> {
export function byEnum<A extends string>(
d: { [key: string]: A },
): Order<A> {
const ranks: { [index: string]: number } = {};
const values = Object.keys(d);
const wildcard = values.length;
values.forEach((C, k) => ranks[C] = k);
values.forEach((C, k) => { ranks[C] = k; });
return (x: A, y: A) => {
if (x === y) return 0;
const rx = ranks[x] ?? wildcard;
......@@ -157,20 +163,21 @@ export function byEnum<A extends string>(d: { [key: string]: A }): Order<A> {
}
/** Order string enumeration constants.
`byRank(v1,...,vN)` will order constant following the order of arguments.
Non-listed constants appear at the end, or at the rank specified by `'*'`. */
`byRank(v1,...,vN)` will order constant following the
order of arguments.
Non-listed constants appear at the end, or at the rank
specified by `'*'`. */
export function byRank(...args: string[]): Order<string> {
const ranks: { [index: string]: number } = {};
args.forEach((C, k) => ranks[C] = k);
args.forEach((C, k) => { ranks[C] = k; });
const wildcard = ranks['*'] ?? ranks.length;
return (x: string, y: string) => {
if (x === y) return 0;
const rx = ranks[x] ?? wildcard;
const ry = ranks[y] ?? wildcard;
if (rx == wildcard && ry == wildcard)
if (rx === wildcard && ry === wildcard)
return primitive(x, y);
else
return rx - ry;
return rx - ry;
};
}
......@@ -196,7 +203,7 @@ export function getKeys<T>(a: T): (keyof T)[] {
*/
export type ByFields<A> = {
[P in keyof A]?: Order<A[P]>;
}
};
/**
Maps each field of `A` to some comparison of the associated type.
......@@ -205,36 +212,39 @@ export type ByFields<A> = {
*/
export type ByAllFields<A> = {
[P in keyof A]: Order<A[P]>;
}
};
/** Object comparison by (some) fields.
/**
Object comparison by (some) fields.
Compare objects field by field, using the comparison orders provided by the
`order` argument. Order of field comparison is taken from the `order`
argument, not from the compared values.
Compare objects field by field, using the comparison orders provided by the
`order` argument. Order of field comparison is taken from the `order`
argument, not from the compared values.
You may not compare _all_ fields of the compared values. For optional
fields, you shall provide a comparison function compatible with type
`undefined`.
You may not compare _all_ fields of the compared values. For optional
fields, you shall provide a comparison function compatible with type
`undefined`.
It might be difficult for Typescript to typecheck `byFields(…)` expressions
when dealing with optional types. In such cases, you shall use `byFields<A>(…)`
and explicitly mention the type of compared values.
It might be difficult for Typescript to typecheck `byFields(…)` expressions
when dealing with optional types. In such cases, you shall use
`byFields<A>(…)` and explicitly mention the type of compared values.
Example:
Example:
type foo = { id: number, name?: string, descr?: string }
const compare = fields<foo>({ id: number, name: option(alpha) });
* type foo = { id: number, name?: string, descr?: string }
* const compare = fields<foo>({ id: number, name: option(alpha) });
*/
export function byFields<A>(order: ByFields<A>): Order<A> {
return (x: A, y: A) => {
if (x === y) return 0;
for (const fd of getKeys(order)) {
const fds = getKeys(order);
for (let k = 0; k < fds.length; k++) {
const fd = fds[k];
const byFd = order[fd];
if (byFd !== undefined) {
const cmp = byFd(x[fd], y[fd]);
if (cmp != 0) return cmp;
if (cmp !== 0) return cmp;
}
}
return 0;
......@@ -248,10 +258,12 @@ export function byFields<A>(order: ByFields<A>): Order<A> {
export function byAllFields<A>(order: ByAllFields<A>): Order<A> {
return (x: A, y: A) => {
if (x === y) return 0;
for (const fd of getKeys<ByFields<A>>(order)) {
const fds = getKeys<ByFields<A>>(order);
for (let k = 0; k < fds.length; k++) {
const fd = fds[k];
const byFd = order[fd];
const cmp = byFd(x[fd], y[fd]);
if (cmp != 0) return cmp;
if (cmp !== 0) return cmp;
}
return 0;
};
......@@ -273,13 +285,14 @@ export function dictionary<A>(order: Order<A>): Order<dict<A>> {
const p = fs.length;
const q = gs.length;
for (let i = 0, j = 0; i < p && j < q;) {
let a = undefined, b = undefined;
let a;
let b;
const f = fs[i];
const g = gs[j];
if (f <= g) { a = dx[f]; i++; }
if (g <= f) { b = dy[g]; j++; }
const cmp = phi(a, b);
if (cmp != 0) return cmp;
if (cmp !== 0) return cmp;
}
return p - q;
};
......@@ -292,7 +305,7 @@ export function pair<A, B>(ordA: Order<A>, ordB: Order<B>): Order<[A, B]> {
const [x1, y1] = u;
const [x2, y2] = v;
const cmp = ordA(x1, x2);
return cmp != 0 ? cmp : ordB(y1, y2);
return cmp !== 0 ? cmp : ordB(y1, y2);
};
}
......@@ -307,9 +320,9 @@ export function triple<A, B, C>(
const [x1, y1, z1] = u;
const [x2, y2, z2] = v;
const cmp1 = ordA(x1, x2);
if (cmp1 != 0) return cmp1;
if (cmp1 !== 0) return cmp1;
const cmp2 = ordB(y1, y2);
if (cmp2 != 0) return cmp2;
if (cmp2 !== 0) return cmp2;
return ordC(z1, z2);
};
}
......@@ -326,11 +339,11 @@ export function tuple4<A, B, C, D>(
const [x1, y1, z1, t1] = u;
const [x2, y2, z2, t2] = v;
const cmp1 = ordA(x1, x2);
if (cmp1 != 0) return cmp1;
if (cmp1 !== 0) return cmp1;
const cmp2 = ordB(y1, y2);
if (cmp2 != 0) return cmp2;
if (cmp2 !== 0) return cmp2;
const cmp3 = ordC(z1, z2);
if (cmp3 != 0) return cmp3;
if (cmp3 !== 0) return cmp3;
return ordD(t1, t2);
};
}
......@@ -348,13 +361,13 @@ export function tuple5<A, B, C, D, E>(
const [x1, y1, z1, t1, w1] = u;
const [x2, y2, z2, t2, w2] = v;
const cmp1 = ordA(x1, x2);
if (cmp1 != 0) return cmp1;
if (cmp1 !== 0) return cmp1;
const cmp2 = ordB(y1, y2);
if (cmp2 != 0) return cmp2;
if (cmp2 !== 0) return cmp2;
const cmp3 = ordC(z1, z2);
if (cmp3 != 0) return cmp3;
if (cmp3 !== 0) return cmp3;
const cmp4 = ordD(t1, t2);
if (cmp4 != 0) return cmp4;
if (cmp4 !== 0) return cmp4;
return ordE(w1, w2);
};
}
......@@ -364,11 +377,16 @@ export function tuple5<A, B, C, D, E>(
// --------------------------------------------------------------------------
/** @internal */
enum RANK { UNDEFINED, BOOLEAN, SYMBOL, NAN, BIGNUM, STRING, ARRAY, OBJECT, FUNCTION };
enum RANK {
UNDEFINED,
BOOLEAN, SYMBOL, NAN, BIGNUM,
STRING,
ARRAY, OBJECT, FUNCTION
}
/** @internal */
function rank(x: any): RANK {
let t = typeof x;
const t = typeof x;
switch (t) {
case 'undefined': return RANK.UNDEFINED;
case 'boolean': return RANK.BOOLEAN;
......@@ -378,14 +396,16 @@ function rank(x: any): RANK {
case 'bigint':
return RANK.BIGNUM;
case 'string': return RANK.STRING;
case 'object': return Array.isArray(x) ? RANK.ARRAY : RANK.OBJECT;
case 'function': return RANK.FUNCTION;
case 'object':
return Array.isArray(x) ? RANK.ARRAY : RANK.OBJECT;
}
}
/**
Universal structural comparison.
Values are ordered by _rank_, each being associated with some type of values:
Values are ordered by _rank_, each being
associated with some type of values:
1. undefined values;
2. booleans;
3. symbols;
......@@ -398,10 +418,9 @@ function rank(x: any): RANK {
For values of same primitive type, primitive ordering is performed.
For array values, lexicographic ordering is performed.
For object values, lexicographic ordering is performed over their properties:
properties are ordered by name, and recursive structural ordering is performed
on property values.
For object values, lexicographic ordering is performed over their
properties: they are ordered by name, and recursive structural
ordering is performed on property values.
All functions are compared equal.
*/
......@@ -418,17 +437,18 @@ export function structural(x: any, y: any): number {
const p = fs.length;
const q = gs.length;
for (let i = 0, j = 0; i < p && j < q;) {
let a = undefined, b = undefined;
let a;
let b;
const f = fs[i];
const g = gs[j];
if (f <= g) { a = x[f]; i++; }
if (g <= f) { b = y[g]; j++; }
const cmp = structural(a, b);
if (cmp != 0) return cmp;
if (cmp !== 0) return cmp;
}
return p - q;
}
return rank(x) - rank(y);
};
}
// --------------------------------------------------------------------------
......@@ -11,7 +11,7 @@
import { DEVEL } from 'dome/system';
export type json =
undefined | null | number | string | json[] | { [key: string]: json }
undefined | null | number | string | json[] | { [key: string]: json };
/**
Parse without _revivals_.
......@@ -71,14 +71,14 @@ export interface Encoder<D> {
}
/** Can be used for most encoders. */
export function identity<A>(v: A): A { return v; };
export function identity<A>(v: A): A { return v; }
// --------------------------------------------------------------------------
// --- Primitives
// --------------------------------------------------------------------------
/** Always returns `undefined` on any input. */
export const jNull: Safe<undefined> = (_: json) => undefined;
export const jNull: Safe<undefined> = () => undefined;
/** Identity. */
export const jAny: Safe<json> = (js: json) => js;
......@@ -132,7 +132,7 @@ export function jTag<A>(tg: A): Loose<A> {
Can be used directly for enum types, eg. `jEnum(myEnumType)`.
*/
export function jEnum<A>(d: { [tag: string]: A }): Loose<A> {
return (v: json) => typeof v === 'string' ? d[v] : undefined;
return (v: json) => (typeof v === 'string' ? d[v] : undefined);
}
/**
......@@ -141,8 +141,8 @@ export function jEnum<A>(d: { [tag: string]: A }): Loose<A> {
type `A`. However, it will not protected you from missings constants in `A`.
*/
export function jTags<A>(...values: ((string | number) & A)[]): Loose<A> {
var m = new Map<string | number, A>();
values.forEach(v => m.set(v, v));
const m = new Map<string | number, A>();
values.forEach((v) => m.set(v, v));
return (v: json) => (typeof v === 'string' ? m.get(v) : undefined);
}
......@@ -155,16 +155,20 @@ export function jDefault<A>(
fn: Loose<A>,
defaultValue: A,
): Safe<A> {
return (js: json) =>
js === undefined ? defaultValue : (fn(js) ?? defaultValue);
return (js: json) => (
js === undefined ? defaultValue : (fn(js) ?? defaultValue)
);
}
/**
Force returning `undefined` or a default value for `undefined` _or_ `null` JSON input.
Force returning `undefined` or a default value for
`undefined` _or_ `null` JSON input.
Typically useful to leverage an existing `Safe<A>` decoder.
*/
export function jOption<A>(fn: Safe<A>, defaultValue?: A): Loose<A> {
return (js: json) => (js === undefined || js === null ? defaultValue : fn(js));
return (js: json) => (
js === undefined || js === null ? defaultValue : fn(js)
);
}
/**
......@@ -175,7 +179,7 @@ export function jFail<A>(fn: Loose<A>, error: string | Error): Safe<A> {
return (js: json) => {
const d = fn(js);
if (d !== undefined) return d;
throw error;
throw (typeof (error) === 'string' ? new Error(error) : error);
};
}
......@@ -215,10 +219,11 @@ export function jMap<A>(fn: Loose<A>): Safe<Map<string, A>> {
return (js: json) => {
const m = new Map<string, A>();
if (js !== null && typeof js === 'object' && !Array.isArray(js)) {
for (let k of Object.keys(js)) {
const keys = Object.keys(js);
keys.forEach((k) => {
const v = fn(js[k]);
if (v !== undefined) m.set(k, v);
}
});
}
return m;
};
......@@ -228,7 +233,7 @@ export function jMap<A>(fn: Loose<A>): Safe<Map<string, A>> {
Converts dictionaries to maps.
*/
export function eMap<A>(fn: Encoder<A>): Encoder<Map<string, undefined | A>> {
return m => {
return (m) => {
const js: json = {};
m.forEach((v, k) => {
if (v !== undefined) {
......@@ -247,7 +252,7 @@ export function eMap<A>(fn: Encoder<A>): Encoder<Map<string, undefined | A>> {
to discard undefined elements, or use a true _safe_ decoder.
*/
export function jArray<A>(fn: Safe<A>): Safe<A[]> {
return (js: json) => Array.isArray(js) ? js.map(fn) : [];
return (js: json) => (Array.isArray(js) ? js.map(fn) : []);
}
/**
......@@ -258,7 +263,7 @@ export function jArray<A>(fn: Safe<A>): Safe<A[]> {
export function jList<A>(fn: Loose<A>): Safe<A[]> {
return (js: json) => {
const buffer: A[] = [];
if (Array.isArray(js)) js.forEach(vj => {
if (Array.isArray(js)) js.forEach((vj) => {
const d = fn(vj);
if (d !== undefined) buffer.push(d);
});
......@@ -270,9 +275,9 @@ export function jList<A>(fn: Loose<A>): Safe<A[]> {
Exports all non-undefined elements.
*/
export function eList<A>(fn: Encoder<A>): Encoder<(A | undefined)[]> {
return m => {
return (m) => {
const js: json[] = [];
m.forEach(v => {
m.forEach((v) => {
if (v !== undefined) {
const u = fn(v);
if (u !== undefined) js.push(u);
......@@ -287,10 +292,10 @@ export function jPair<A, B>(
fa: Safe<A>,
fb: Safe<B>,
): Loose<[A, B]> {
return (js: json) => Array.isArray(js) ? [
return (js: json) => (Array.isArray(js) ? [
fa(js[0]),
fb(js[1]),
] : undefined;
] : undefined);
}
/** Similar to [[jPair]]. */
......@@ -299,11 +304,11 @@ export function jTriple<A, B, C>(
fb: Safe<B>,
fc: Safe<C>,
): Loose<[A, B, C]> {
return (js: json) => Array.isArray(js) ? [
return (js: json) => (Array.isArray(js) ? [
fa(js[0]),
fb(js[1]),
fc(js[2]),
] : undefined;
] : undefined);
}
/** Similar to [[jPair]]. */
......@@ -313,12 +318,12 @@ export function jTuple4<A, B, C, D>(
fc: Safe<C>,
fd: Safe<D>,
): Loose<[A, B, C, D]> {
return (js: json) => Array.isArray(js) ? [
return (js: json) => (Array.isArray(js) ? [
fa(js[0]),
fb(js[1]),
fc(js[2]),
fd(js[3]),
] : undefined;
] : undefined);
}
/** Similar to [[jPair]]. */
......@@ -329,13 +334,13 @@ export function jTuple5<A, B, C, D, E>(
fd: Safe<D>,
fe: Safe<E>,
): Loose<[A, B, C, D, E]> {
return (js: json) => Array.isArray(js) ? [
return (js: json) => (Array.isArray(js) ? [
fa(js[0]),
fb(js[1]),
fc(js[2]),
fd(js[3]),
fe(js[4]),
] : undefined;
] : undefined);
}
/**
......@@ -344,7 +349,7 @@ export function jTuple5<A, B, C, D, E>(
*/
export type Props<A> = {
[P in keyof A]: Safe<A[P]>;
}
};
/**
Decode an object given the decoders of its fields.
......@@ -354,7 +359,8 @@ export function jObject<A>(fp: Props<A>): Loose<A> {
return (js: json) => {
if (js !== null && typeof js === 'object' && !Array.isArray(js)) {
const buffer = {} as A;
for (var k of Object.keys(fp)) {
const keys = Object.keys(fp);
keys.forEach((k) => {
const fn = fp[k as keyof A];
if (fn !== undefined) {
const fj = js[k];
......@@ -363,7 +369,7 @@ export function jObject<A>(fp: Props<A>): Loose<A> {
if (fv !== undefined) buffer[k as keyof A] = fv;
}
}
}
});
return buffer;
}
return undefined;
......@@ -375,8 +381,8 @@ export function jObject<A>(fp: Props<A>): Loose<A> {
*/
export function jUnion<A>(...cases: Loose<A>[]): Loose<A> {
return (js: json) => {
for (var fn of cases) {
const fv = fn(js);
for (let i = 0; i < cases.length; i++) {
const fv = cases[i](js);
if (fv !== undefined) return fv;
}
return undefined;
......@@ -388,7 +394,7 @@ export function jUnion<A>(...cases: Loose<A>[]): Loose<A> {
*/
export type EProps<A> = {
[P in keyof A]?: Encoder<A[P]>;
}
};
/**
Encode an object given the provided encoders by fields.
......@@ -398,7 +404,8 @@ export type EProps<A> = {
export function eObject<A>(fp: EProps<A>): Encoder<A> {
return (m: A) => {
const js: json = {};
for (var k of Object.keys(fp)) {
const keys = Object.keys(fp);
keys.forEach((k) => {
const fn = fp[k as keyof A];
if (fn !== undefined) {
const fv = m[k as keyof A];
......@@ -407,9 +414,9 @@ export function eObject<A>(fp: EProps<A>): Encoder<A> {
if (r !== undefined) js[k] = r;
}
}
}
});
return js;
}
};
}
// Intentionnaly internal and only declared
......@@ -422,56 +429,43 @@ export function forge<K, A>(_tag: K, data: A): phantom<K, A> {
return data as any;
}
/** String key with kind. Can be used as a `string` but shall be created with [forge]. */
/** String key with kind.
Can be used as a `string` but shall be created with [forge]. */
export type key<K> = phantom<K, string>;
/** Number index with kind. Can be used as a `number` but shall be created with [forge]. */
/** Number index with kind.
Can be used as a `number` but shall be created with [forge]. */
export type index<K> = phantom<K, number>;
/** Decoder for `key<K>` strings. */
export function jKey<K>(kd: K): Loose<key<K>> {
return (js: json) => typeof js === 'string' ? forge(kd, js) : undefined;
return (js: json) => (typeof js === 'string' ? forge(kd, js) : undefined);
}
/** Decoder for `index<K>` numbers. */
export function jIndex<K>(kd: K): Loose<index<K>> {
return (js: json) => typeof js === 'number' ? forge(kd, js) : undefined;
}
/** Dictionaries with « typed » keys. */
export type dict<K, A> = phantom<K, { [key: string]: A }>
/** Lookup into dictionary.
Better than a direct access to `d[k]` for undefined values. */
export function lookup<K, A>(d: dict<K, A>, k: key<K>): A | undefined {
return d[k];
}
/** Empty dictionary. */
export function empty<K, A>(kd: K): dict<K, A> {
return forge(kd, {} as any);
return (js: json) => (typeof js === 'number' ? forge(kd, js) : undefined);
}
/** Dictionary extension. */
export function index<K, A>(d: dict<K, A>, key: key<K>, value: A) {
d[key] = value;
}
/** Dictionaries. */
export type dict<A> = { [key: string]: A };
/**
Decode a JSON dictionary, discarding all inconsistent entries.
If the JSON contains no valid entry, still returns `{}`.
*/
export function jDictionary<K, A>(kd: K, fn: Loose<A>): Safe<dict<K, A>> {
export function jDict<A>(fn: Loose<A>): Safe<dict<A>> {
return (js: json) => {
const buffer: dict<K, A> = empty(kd);
const buffer: dict<A> = {};
if (js !== null && typeof js === 'object' && !Array.isArray(js)) {
for (var key of Object.keys(js)) {
const keys = Object.keys(js);
keys.forEach((key) => {
const fd = js[key];
if (fd !== undefined) {
const fv = fn(fd);
if (fv !== undefined) index(buffer, forge(kd, key), fv);
if (fv !== undefined) buffer[key] = fv;
}
}
});
}
return buffer;
};
......@@ -481,16 +475,17 @@ export function jDictionary<K, A>(kd: K, fn: Loose<A>): Safe<dict<K, A>> {
Encode a dictionary into JSON, discarding all inconsistent entries.
If the dictionary contains no valid entry, still returns `{}`.
*/
export function eDictionary<K, A>(fn: Encoder<A>): Encoder<dict<K, A>> {
return (d: dict<K, A>) => {
export function eDict<A>(fn: Encoder<A>): Encoder<dict<A>> {
return (d: dict<A>) => {
const js: json = {};
for (var k of Object.keys(d)) {
const keys = Object.keys(d);
keys.forEach((k) => {
const fv = d[k];
if (fv !== undefined) {
const fr = fn(fv);
if (fr !== undefined) js[k] = fr;
}
}
});
return js;
};
}
......
......@@ -11,9 +11,6 @@
import React from 'react';
import Emitter from 'events';
import isEqual from 'react-fast-compare';
import { DEVEL } from 'dome/misc/system';
import * as Dome from 'dome';
import * as JSON from './json';
const UPDATE = 'dome.states.update';
......@@ -25,7 +22,7 @@ export class State<A> {
constructor(initValue: A) {
this.value = initValue;
this.emitter = new Emitter;
this.emitter = new Emitter();
this.getValue = this.getValue.bind(this);
this.setValue = this.setValue.bind(this);
}
......@@ -59,222 +56,8 @@ export function useState<A>(s: State<A>): [A, (update: A) => void] {
React.useEffect(() => {
s.on(setCurrent);
return () => s.off(setCurrent);
});
}, [s]);
return [current, s.setValue];
};
// --------------------------------------------------------------------------
// --- Settings
// --------------------------------------------------------------------------
/**
Generic interface to Window and Global Settings.
To be used with [[useSettings]] with instances of its derived classes,
typically [[WindowSettings]] and [[GlobalSettings]]. You should never have
to implement a Settings class on your own.
All setting values are identified with
an untyped `dataKey: string`, that can be dynamically modified
for each component. Hence, two components might share both datakeys
and settings.
When several components share the same setting `dataKey` the behavior will be
different depending on the situation:
- for Window Settings, each component in each window retains its own
setting value, although the last modified value from _any_ of them will be
saved and used for any further initial value;
- for Global Settings, all components synchronize to the last modified value
from any component of any window.
Type safety is ensured by safe JSON encoders and decoders, however, they
might fail at runtime, causing settings value to be initialized to their
fallback and not to be saved nor synchronized.
This is not harmful but annoying.
To mitigate this effect, each instance of a Settings class has its
own, private, unique symbol that we call its « role ». A given `dataKey`
shall always be used with the same « role » otherwise it is discarded,
and an error message is logged when in DEVEL mode.
*/
export abstract class Settings<A> {
private static keyRoles = new Map<string, symbol>();
private readonly role: symbol;
protected readonly decoder: JSON.Safe<A>;
protected readonly encoder: JSON.Encoder<A>;
/**
Encoders shall be protected against exception.
Use [[dome/data/json.jTry]] and [[dome/data/json.jCatch]] in case of uncertainty.
Decoders are automatically protected internally to the Settings class.
@param role Debugging name of instance roles (each instance has its unique
role, though)
@param decoder JSON decoder for the setting values
@param encoder JSON encoder for the setting values
@param fallback If provided, used to automatically protect your encoders
against exceptions.
*/
constructor(
role: string,
decoder: JSON.Safe<A>,
encoder: JSON.Encoder<A>,
fallback?: A,
) {
this.role = Symbol(role);
this.encoder = encoder;
this.decoder =
fallback !== undefined ? JSON.jCatch(decoder, fallback) : decoder;
}
/**
Returns identity if the data key is only
used with the same setting instance.
Otherwise, returns `undefined`.
*/
validateKey(dataKey?: string): string | undefined {
if (dataKey === undefined) return undefined;
const rq = this.role;
const rk = Settings.keyRoles.get(dataKey);
if (rk === undefined) {
Settings.keyRoles.set(dataKey, rq);
} else {
if (rk !== rq) {
if (DEVEL) console.error(
`[Dome.settings] Key ${dataKey} used with incompatible roles`, rk, rq,
);
return undefined;
}
}
return dataKey;
}
/** @internal */
abstract loadData(key: string): JSON.json;
/** @internal */
abstract saveData(key: string, data: JSON.json): void;
/** @internal */
abstract event: string;
/** Returns the current setting value for the provided data key. You shall
only use validated keys otherwise you might fallback to default values. */
loadValue(dataKey?: string) {
return this.decoder(dataKey ? this.loadData(dataKey) : undefined)
}
/** Push the new setting value for the provided data key.
You shall only use validated keys otherwise further loads
might fail and fallback to defaults. */
saveValue(dataKey: string, value: A) {
try { this.saveData(dataKey, this.encoder(value)); }
catch (err) {
if (DEVEL) console.error(
'[Dome.settings] Error while encoding value',
dataKey, value, err,
);
}
}
}
/**
Generic React Hook to be used with any kind of [[Settings]].
You may share `dataKey` between components, or change it dynamically.
However, a given data key shall always be used for the same Setting instance.
See [[Settings]] documentation for details.
@param S The instance settings to be used.
@param dataKey Identifies which value in the settings to be used.
*/
export function useSettings<A>(
S: Settings<A>,
dataKey?: string,
): [A, (update: A) => void] {
const theKey = React.useMemo(() => S.validateKey(dataKey), [S, dataKey]);
const [value, setValue] = React.useState<A>(() => S.loadValue(theKey));
React.useEffect(() => {
if (theKey) {
const callback = () => setValue(S.loadValue(theKey));
Dome.on(S.event, callback);
return () => Dome.off(S.event, callback);
}
return undefined;
});
const updateValue = React.useCallback((update: A) => {
if (!isEqual(value, update)) {
setValue(update);
if (theKey) S.saveValue(theKey, update);
}
}, [S, theKey]);
return [value, updateValue];
}
/** Window Settings for non-JSON data.
In most situations, you can use [[WindowSettings]] instead.
You can use a [[dome/data/json.Loose]] decoder for optional values. */
export class WindowSettingsData<A> extends Settings<A> {
constructor(
role: string,
decoder: JSON.Safe<A>,
encoder: JSON.Encoder<A>,
fallback?: A,
) {
super(role, decoder, encoder, fallback);
}
event = 'dome.defaults';
loadData(key: string) { return Dome.getWindowSetting(key) as JSON.json; }
saveData(key: string, data: JSON.json) { Dome.setWindowSetting(key, data); }
}
/** Global Settings for non-JSON data.
In most situations, you can use [[WindowSettings]] instead.
You can use a [[dome/data/json.Loose]] decoder for optional values. */
export class GlobalSettingsData<A> extends Settings<A> {
constructor(
role: string,
decoder: JSON.Safe<A>,
encoder: JSON.Encoder<A>,
fallback?: A,
) {
super(role, decoder, encoder, fallback);
}
event = 'dome.settings';
loadData(key: string) { return Dome.getGlobalSetting(key) as JSON.json; }
saveData(key: string, data: JSON.json) { Dome.setGlobalSetting(key, data); }
}
/** Window Settings.
For non-JSON data, use [[WindowSettingsData]] instead.
You can use a [[dome/data/json.Loose]] decoder for optional values. */
export class WindowSettings<A extends JSON.json> extends WindowSettingsData<A> {
constructor(role: string, decoder: JSON.Safe<A>, fallback?: A) {
super(role, decoder, JSON.identity, fallback);
}
}
/** Global Settings.
For non-JSON data, use [[WindowSettingsData]] instead.
You can use a [[dome/data/json.Loose]] decoder for optional values. */
export class GlobalSettings<A extends JSON.json> extends GlobalSettingsData<A> {
constructor(role: string, decoder: JSON.Safe<A>, fallback?: A) {
super(role, decoder, JSON.identity, fallback);
}
}
// --------------------------------------------------------------------------
......@@ -170,7 +170,7 @@ type jtype =
| Jkey of string (* kind of a string used for indexing *)
| Jindex of string (* kind of an integer used for indexing *)
| Joption of jtype
| Jdict of string * jtype (* kind of keys *)
| Jdict of jtype (* dictionaries *)
| Jlist of jtype (* order does not matter *)
| Jarray of jtype (* order matters *)
| Jtuple of jtype list
......@@ -291,14 +291,14 @@ let rec isRecursive = function
| Jdata _ | Jenum _
| Jany | Jnull | Jboolean | Jnumber
| Jstring | Jalpha | Jkey _ | Jindex _ | Jtag _ -> false
| Joption js | Jdict(_,js) | Jarray js | Jlist js -> isRecursive js
| Joption js | Jdict js | Jarray js | Jlist js -> isRecursive js
| Jtuple js | Junion js -> List.exists isRecursive js
| Jrecord fjs -> List.exists (fun (_,js) -> isRecursive js) fjs
let rec visit_jtype fn = function
| Jany | Jself | Jnull | Jboolean | Jnumber
| Jstring | Jalpha | Jkey _ | Jindex _ | Jtag _ -> ()
| Joption js | Jdict(_,js) | Jarray js | Jlist js -> visit_jtype fn js
| Joption js | Jdict js | Jarray js | Jlist js -> visit_jtype fn js
| Jtuple js | Junion js -> List.iter (visit_jtype fn) js
| Jrecord fjs -> List.iter (fun (_,js) -> visit_jtype fn js) fjs
| Jdata id | Jenum id ->
......@@ -471,8 +471,8 @@ let rec md_jtype pp = function
| Junion js -> md_jlist pp "|" js
| Jarray js | Jlist js -> protect pp js @ Md.code "[]"
| Jrecord fjs -> Md.code "{" @ fields pp fjs @ Md.code "}"
| Jdict (id,js) ->
Md.code "{[" @ key id @ Md.code "]:" @ md_jtype pp js @ Md.code "}"
| Jdict js ->
Md.code "{[key]:" @ md_jtype pp js @ Md.code "}"
and md_jlist pp sep js =
Md.glue ~sep:(Md.plain sep) (List.map (md_jtype pp) js)
......
......@@ -38,7 +38,7 @@ type jtype =
| Jkey of string (** kind of a string used for indexing *)
| Jindex of string (** kind of an integer used for indexing *)
| Joption of jtype
| Jdict of string * jtype (** kind of keys *)
| Jdict of jtype (** dictionaries *)
| Jlist of jtype (** order does not matter *)
| Jarray of jtype (** order matters *)
| Jtuple of jtype list
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment