diff --git a/ivette/.eslintignore b/ivette/.eslintignore index e0d530b07981ceaa194fcffe211a324d309dc00e..e5fe0a814d6250a91f43a72a3dda9cee662b1b47 100644 --- a/ivette/.eslintignore +++ b/ivette/.eslintignore @@ -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 diff --git a/ivette/.eslintrc.js b/ivette/.eslintrc.js index 1d1eaf1486b532b7221407287a5350c65b4823a1..10db14fc4fdb270ff4b03fd3e7523f6d42b9c924 100644 --- a/ivette/.eslintrc.js +++ b/ivette/.eslintrc.js @@ -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", } }; diff --git a/ivette/api/server_tsc.ml b/ivette/api/server_tsc.ml index 8f5eb6e829b7992c706487f59d1b23b226f80421..2c17909b1007893e522bffed614b6858e666968d 100644 --- a/ivette/api/server_tsc.ml +++ b/ivette/api/server_tsc.ml @@ -76,7 +76,7 @@ let makeJtype ?self ~names = | Jstring | Jalpha -> Format.pp_print_string fmt "string" | 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 -> @@ -172,10 +172,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 @@ -245,11 +245,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 in pp fmt js (* -------------------------------------------------------------------------- *) diff --git a/ivette/src/dome/src/renderer/data/compare.ts b/ivette/src/dome/src/renderer/data/compare.ts index 31185f33ee006c019b53b8438c9f1b3f390ef10e..b8cb955f537d46ed630dc805df3b696afc931706 100644 --- a/ivette/src/dome/src/renderer/data/compare.ts +++ b/ivette/src/dome/src/renderer/data/compare.ts @@ -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)) + ); } /** @@ -79,17 +82,18 @@ export function float(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; @@ -99,9 +103,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); }; } @@ -109,9 +113,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); }; } @@ -125,7 +129,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; }; @@ -134,11 +138,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; @@ -148,20 +154,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; }; } @@ -187,7 +194,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. @@ -196,36 +203,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; @@ -239,10 +249,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; }; @@ -264,13 +276,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; }; @@ -283,7 +296,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); }; } @@ -298,9 +311,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); }; } @@ -317,11 +330,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); }; } @@ -339,13 +352,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); }; } @@ -355,11 +368,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; @@ -369,14 +387,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; @@ -389,10 +409,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. */ @@ -409,17 +428,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); -}; +} // -------------------------------------------------------------------------- diff --git a/ivette/src/dome/src/renderer/data/json.ts b/ivette/src/dome/src/renderer/data/json.ts index ba0be2fc66fcef5b3a37e5e436633d1f4ebfba3f..033aa4e88286f42fa17933a301bad367b2ab2dfd 100644 --- a/ivette/src/dome/src/renderer/data/json.ts +++ b/ivette/src/dome/src/renderer/data/json.ts @@ -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; @@ -123,7 +123,7 @@ export const jString: Loose<string> = (js: json) => ( 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); } /** @@ -132,8 +132,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); } @@ -146,16 +146,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) + ); } /** @@ -166,7 +170,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); }; } @@ -206,10 +210,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; }; @@ -219,7 +224,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) { @@ -238,7 +243,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) : []); } /** @@ -249,7 +254,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); }); @@ -261,9 +266,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); @@ -278,10 +283,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]]. */ @@ -290,11 +295,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]]. */ @@ -304,12 +309,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]]. */ @@ -320,13 +325,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); } /** @@ -335,7 +340,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. @@ -345,7 +350,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]; @@ -354,7 +360,7 @@ export function jObject<A>(fp: Props<A>): Loose<A> { if (fv !== undefined) buffer[k as keyof A] = fv; } } - } + }); return buffer; } return undefined; @@ -366,8 +372,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; @@ -379,7 +385,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. @@ -389,7 +395,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]; @@ -398,9 +405,9 @@ export function eObject<A>(fp: EProps<A>): Encoder<A> { if (r !== undefined) js[k] = r; } } - } + }); return js; - } + }; } // Intentionnaly internal and only declared @@ -413,56 +420,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; }; @@ -472,16 +466,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; }; } diff --git a/ivette/src/dome/src/renderer/data/states.ts b/ivette/src/dome/src/renderer/data/states.ts index c1fbb827435c3b1f721268d059e9a4640d7aeb0b..ce7704696991024b8dc1ecb49baaa7825a6c3466 100644 --- a/ivette/src/dome/src/renderer/data/states.ts +++ b/ivette/src/dome/src/renderer/data/states.ts @@ -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); - } - } // -------------------------------------------------------------------------- diff --git a/src/plugins/server/package.ml b/src/plugins/server/package.ml index 6f2e56f956b5cafbb98573b3d8712d95206ed9d6..7bc235b55a5d3e95c1f9e47c5aa0565141a52cdf 100644 --- a/src/plugins/server/package.ml +++ b/src/plugins/server/package.ml @@ -169,7 +169,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 @@ -290,14 +290,14 @@ let rec isRecursive = function | Jdata _ | Jenum _ | Jany | Jnull | Jboolean | Jnumber | Jstring | Jalpha | Jkey _ | Jindex _ -> 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 _ -> () - | 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 -> @@ -469,8 +469,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) diff --git a/src/plugins/server/package.mli b/src/plugins/server/package.mli index c040c985bc85129a3c15bb222e1afdffaf2262a4..019e1dd38130c103534e23001220ee13624f3eaf 100644 --- a/src/plugins/server/package.mli +++ b/src/plugins/server/package.mli @@ -37,7 +37,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