From 14a6723f7c92384dfca14ffe9fc5d388bfc6ba15 Mon Sep 17 00:00:00 2001 From: Matthew Kaney Date: Wed, 18 Oct 2023 08:37:30 -0400 Subject: [PATCH 1/9] Add JSON schema support to settings editor --- app/desktop/src/renderer/main.ts | 7 +- core/extensions/settings/editor.ts | 11 ++ core/extensions/settings/index.ts | 9 -- package-lock.json | 168 +++++++++++++++++++++++++++-- package.json | 4 + 5 files changed, 180 insertions(+), 19 deletions(-) create mode 100644 core/extensions/settings/editor.ts delete mode 100644 core/extensions/settings/index.ts diff --git a/app/desktop/src/renderer/main.ts b/app/desktop/src/renderer/main.ts index a842d95..983172c 100644 --- a/app/desktop/src/renderer/main.ts +++ b/app/desktop/src/renderer/main.ts @@ -6,6 +6,8 @@ import { basicSetup } from "@core/extensions/basicSetup"; import { oneDark } from "@core/extensions/theme/theme"; import { tidal } from "@management/lang-tidal/editor"; +import { settings } from "@core/extensions/settings/editor"; + import { LayoutView } from "@core/extensions/layout"; import { console as electronConsole } from "@core/extensions/console"; // import { peer } from "@core/extensions/peer"; @@ -52,6 +54,9 @@ export class Editor { }); api.onOpen(({ id, path }) => { + // TODO: This is a hacky heuristic + let languageMode = path?.endsWith("settings.json") ? settings() : tidal(); + let offContent = api.onContent(id, ({ doc: docJSON, version, saved }) => { let doc = Text.of(docJSON); @@ -61,7 +66,7 @@ export class Editor { view: new EditorTabView(layout, id, api, { doc, extensions: [ - tidal(), + languageMode, evaluation(api.evaluate), basicSetup, oneDark, diff --git a/core/extensions/settings/editor.ts b/core/extensions/settings/editor.ts new file mode 100644 index 0000000..e3cf22f --- /dev/null +++ b/core/extensions/settings/editor.ts @@ -0,0 +1,11 @@ +import { Extension } from "@codemirror/state"; + +import { autocompletion } from "@codemirror/autocomplete"; +import { json } from "@codemirror/lang-json"; +import { jsonSchema } from "codemirror-json-schema"; + +import { TidalSettingsSchema } from "packages/languages/tidal/settings"; + +export function settings(): Extension { + return [autocompletion(), json(), jsonSchema(TidalSettingsSchema)]; +} diff --git a/core/extensions/settings/index.ts b/core/extensions/settings/index.ts deleted file mode 100644 index 8f8e0ec..0000000 --- a/core/extensions/settings/index.ts +++ /dev/null @@ -1,9 +0,0 @@ -export class SettingsPage { - dom: HTMLDivElement; - - constructor() { - this.dom = document.createElement("div"); - - this.dom.innerText = "Settings"; - } -} diff --git a/package-lock.json b/package-lock.json index 612920d..1714217 100644 --- a/package-lock.json +++ b/package-lock.json @@ -14,15 +14,19 @@ "packages/languages/*" ], "dependencies": { + "@codemirror/autocomplete": "^6.10.2", "@codemirror/collab": "^6.0.0", "@codemirror/commands": "^6.2.4", "@codemirror/lang-javascript": "^6.1.9", + "@codemirror/lang-json": "^6.0.1", "@codemirror/language": "^6.8.0", + "@codemirror/lint": "^6.4.2", "@codemirror/state": "^6.2.1", "@codemirror/view": "^6.14.0", "@fortawesome/fontawesome-svg-core": "^6.4.0", "@fortawesome/free-solid-svg-icons": "^6.4.0", "@lezer/highlight": "^1.1.6", + "codemirror-json-schema": "^0.4.4", "commander": "^9.5.0", "firebase": "^9.23.0", "rxjs": "^7.8.1" @@ -805,10 +809,42 @@ "integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==", "dev": true }, + "node_modules/@changesets/changelog-github": { + "version": "0.4.8", + "resolved": "https://registry.npmjs.org/@changesets/changelog-github/-/changelog-github-0.4.8.tgz", + "integrity": "sha512-jR1DHibkMAb5v/8ym77E4AMNWZKB5NPzw5a5Wtqm1JepAuIF+hrKp2u04NKM14oBZhHglkCfrla9uq8ORnK/dw==", + "dependencies": { + "@changesets/get-github-info": "^0.5.2", + "@changesets/types": "^5.2.1", + "dotenv": "^8.1.0" + } + }, + "node_modules/@changesets/changelog-github/node_modules/dotenv": { + "version": "8.6.0", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-8.6.0.tgz", + "integrity": "sha512-IrPdXQsk2BbzvCBGBOTmmSH5SodmqZNt4ERAZDmW4CT+tL8VtvinqywuANaFu4bOMWki16nqf0e4oC0QIaDr/g==", + "engines": { + "node": ">=10" + } + }, + "node_modules/@changesets/get-github-info": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/@changesets/get-github-info/-/get-github-info-0.5.2.tgz", + "integrity": "sha512-JppheLu7S114aEs157fOZDjFqUDpm7eHdq5E8SSR0gUBTEK0cNSHsrSR5a66xs0z3RWuo46QvA3vawp8BxDHvg==", + "dependencies": { + "dataloader": "^1.4.0", + "node-fetch": "^2.5.0" + } + }, + "node_modules/@changesets/types": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/@changesets/types/-/types-5.2.1.tgz", + "integrity": "sha512-myLfHbVOqaq9UtUKqR/nZA/OY7xFjQMdfgfqeZIBK4d0hA6pgxArvdv8M+6NUzzBsjWLOtvApv8YHr4qM+Kpfg==" + }, "node_modules/@codemirror/autocomplete": { - "version": "6.9.1", - "resolved": "https://registry.npmjs.org/@codemirror/autocomplete/-/autocomplete-6.9.1.tgz", - "integrity": "sha512-yma56tqD7khIZK4gy4X5lX3/k5ArMiCGat7HEWRF/8L2kqOjVdp2qKZqpcJjwTIjSj6fqKAHqi7IjtH3QFE+Bw==", + "version": "6.10.2", + "resolved": "https://registry.npmjs.org/@codemirror/autocomplete/-/autocomplete-6.10.2.tgz", + "integrity": "sha512-3dCL7b0j2GdtZzWN5j7HDpRAJ26ip07R4NGYz7QYthIYMiX8I4E4TNrYcdTayPJGeVQtd/xe7lWU4XL7THFb/w==", "dependencies": { "@codemirror/language": "^6.0.0", "@codemirror/state": "^6.0.0", @@ -855,6 +891,15 @@ "@lezer/javascript": "^1.0.0" } }, + "node_modules/@codemirror/lang-json": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/@codemirror/lang-json/-/lang-json-6.0.1.tgz", + "integrity": "sha512-+T1flHdgpqDDlJZ2Lkil/rLiRy684WMLc74xUnjJH48GQdfJo/pudlTRreZmKwzP8/tGdKf83wlbAdOCzlJOGQ==", + "dependencies": { + "@codemirror/language": "^6.0.0", + "@lezer/json": "^1.0.0" + } + }, "node_modules/@codemirror/language": { "version": "6.9.1", "resolved": "https://registry.npmjs.org/@codemirror/language/-/language-6.9.1.tgz", @@ -2570,6 +2615,15 @@ "@lezer/lr": "^1.3.0" } }, + "node_modules/@lezer/json": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@lezer/json/-/json-1.0.1.tgz", + "integrity": "sha512-nkVC27qiEZEjySbi6gQRuMwa2sDu2PtfjSgz0A4QF81QyRGm3kb2YRzLcOPcTEtmcwvrX/cej7mlhbwViA4WJw==", + "dependencies": { + "@lezer/highlight": "^1.0.0", + "@lezer/lr": "^1.0.0" + } + }, "node_modules/@lezer/lr": { "version": "1.3.13", "resolved": "https://registry.npmjs.org/@lezer/lr/-/lr-1.3.13.tgz", @@ -4605,6 +4659,20 @@ "resolved": "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.0.tgz", "integrity": "sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw==" }, + "node_modules/@sagold/json-pointer": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/@sagold/json-pointer/-/json-pointer-5.1.1.tgz", + "integrity": "sha512-/iskWuyGNu09qy09HYmyLnvzpKryymH9T+vTBi2LdFp1TuKvERDADvPMv2ZkQKsrRklOzivmOz9QXof0dKqvgA==" + }, + "node_modules/@sagold/json-query": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/@sagold/json-query/-/json-query-6.1.1.tgz", + "integrity": "sha512-5/Wu0rTnXmO5Uvtm9Of16Vx3mKjSnYA0Um9LgBtyPhIucYlppKgKC4N3g8gD0Fk00a5kizQTs4gwxKPXCpmeww==", + "dependencies": { + "@sagold/json-pointer": "^5.1.1", + "ebnf": "^1.9.1" + } + }, "node_modules/@sinclair/typebox": { "version": "0.27.8", "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.8.tgz", @@ -6955,6 +7023,51 @@ "node": ">= 0.12.0" } }, + "node_modules/codemirror-json-schema": { + "version": "0.4.4", + "resolved": "https://registry.npmjs.org/codemirror-json-schema/-/codemirror-json-schema-0.4.4.tgz", + "integrity": "sha512-rISOVrYUzd8pihiLzl5hFBQp/9PRSofmpDOsGC4sl2z0vepFjlxvWqVUJz3arlPPg3YrQ9/6sKaRDIXu6c50UA==", + "dependencies": { + "@changesets/changelog-github": "^0.4.8", + "@sagold/json-pointer": "^5.1.1", + "@types/json-schema": "^7.0.12", + "@types/node": "^20.4.2", + "json-schema": "^0.2.3", + "json-schema-library": "^8.0.0" + }, + "optionalDependencies": { + "@codemirror/lang-json": "^6.0.1", + "codemirror-json5": "^1.0.3", + "json5": "^2.2.3" + }, + "peerDependencies": { + "@codemirror/language": "^6.8.0", + "@codemirror/lint": "^6.4.0", + "@codemirror/state": "^6.2.1", + "@codemirror/view": "^6.14.1", + "@lezer/common": "^1.0.3" + } + }, + "node_modules/codemirror-json-schema/node_modules/json-schema": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.2.5.tgz", + "integrity": "sha512-gWJOWYFrhQ8j7pVm0EM8Slr+EPVq1Phf6lvzvD/WCeqkrx/f2xBI0xOsRRS9xCn3I4vKtP519dvs3TP09r24wQ==" + }, + "node_modules/codemirror-json5": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/codemirror-json5/-/codemirror-json5-1.0.3.tgz", + "integrity": "sha512-HmmoYO2huQxoaoG5ARKjqQc9mz7/qmNPvMbISVfIE2Gk1+4vZQg9X3G6g49MYM5IK00Ol3aijd7OKrySuOkA7Q==", + "optional": true, + "dependencies": { + "@codemirror/language": "^6.0.0", + "@codemirror/state": "^6.0.0", + "@codemirror/view": "^6.0.0", + "@lezer/common": "^1.0.0", + "@lezer/highlight": "^1.0.0", + "json5": "^2.2.1", + "lezer-json5": "^2.0.2" + } + }, "node_modules/collect-v8-coverage": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/collect-v8-coverage/-/collect-v8-coverage-1.0.2.tgz", @@ -7733,6 +7846,11 @@ "node": ">=12" } }, + "node_modules/dataloader": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/dataloader/-/dataloader-1.4.0.tgz", + "integrity": "sha512-68s5jYdlvasItOJnCuI2Q9s4q98g0pCyL3HrcKJu8KNugUl8ahgmZYg38ysLTgQjjXX3H8CJLkAvWrclWfcalw==" + }, "node_modules/dct": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/dct/-/dct-0.1.0.tgz", @@ -7821,7 +7939,6 @@ "version": "4.3.1", "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz", "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==", - "dev": true, "engines": { "node": ">=0.10.0" } @@ -8200,6 +8317,14 @@ "dev": true, "optional": true }, + "node_modules/ebnf": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/ebnf/-/ebnf-1.9.1.tgz", + "integrity": "sha512-uW2UKSsuty9ANJ3YByIQE4ANkD8nqUPO7r6Fwcc1ADKPe9FRdcPpMl3VEput4JSvKBJ4J86npIC2MLP0pYkCuw==", + "bin": { + "ebnf": "dist/bin.js" + } + }, "node_modules/ecc-jsbn": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz", @@ -8951,11 +9076,15 @@ "node >=0.6.0" ] }, + "node_modules/fast-copy": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/fast-copy/-/fast-copy-3.0.1.tgz", + "integrity": "sha512-Knr7NOtK3HWRYGtHoJrjkaWepqT8thIVGAwt0p0aUs1zqkAzXZV4vo9fFNwyb5fcqK1GKYFYxldQdIDVKhUAfA==" + }, "node_modules/fast-deep-equal": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", - "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", - "dev": true + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==" }, "node_modules/fast-json-stable-stringify": { "version": "2.1.0", @@ -12001,6 +12130,19 @@ "integrity": "sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA==", "dev": true }, + "node_modules/json-schema-library": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/json-schema-library/-/json-schema-library-8.0.0.tgz", + "integrity": "sha512-qqsEdyhuA68YHzuWNGrOk9ViknRKw1NfIbhT9wQ0z6l5cpfuYoqKRkbu8tgHAXjahmLEkpNdGwHq+gCgIrMYeA==", + "dependencies": { + "@sagold/json-pointer": "^5.1.1", + "@sagold/json-query": "^6.1.0", + "deepmerge": "^4.3.1", + "fast-copy": "^3.0.1", + "fast-deep-equal": "^3.1.3", + "valid-url": "^1.0.9" + } + }, "node_modules/json-schema-to-ts": { "version": "2.9.2", "resolved": "https://registry.npmjs.org/json-schema-to-ts/-/json-schema-to-ts-2.9.2.tgz", @@ -12030,7 +12172,7 @@ "version": "2.2.3", "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", - "dev": true, + "devOptional": true, "bin": { "json5": "lib/cli.js" }, @@ -12236,6 +12378,15 @@ "node": ">= 0.8.0" } }, + "node_modules/lezer-json5": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/lezer-json5/-/lezer-json5-2.0.2.tgz", + "integrity": "sha512-NRmtBlKW/f8mA7xatKq8IUOq045t8GVHI4kZXrUtYYUdiVeGiO6zKGAV7/nUAnf5q+rYTY+SWX/gvQdFXMjNxQ==", + "optional": true, + "dependencies": { + "@lezer/lr": "^1.0.0" + } + }, "node_modules/libsodium": { "version": "0.7.13", "resolved": "https://registry.npmjs.org/libsodium/-/libsodium-0.7.13.tgz", @@ -16886,8 +17037,7 @@ "node_modules/valid-url": { "version": "1.0.9", "resolved": "https://registry.npmjs.org/valid-url/-/valid-url-1.0.9.tgz", - "integrity": "sha512-QQDsV8OnSf5Uc30CKSwG9lnhMPe6exHtTXLRYX8uMwKENy640pU+2BgBL0LRbDh/eYRahNCS7aewCx0wf3NYVA==", - "dev": true + "integrity": "sha512-QQDsV8OnSf5Uc30CKSwG9lnhMPe6exHtTXLRYX8uMwKENy640pU+2BgBL0LRbDh/eYRahNCS7aewCx0wf3NYVA==" }, "node_modules/vary": { "version": "1.1.2", diff --git a/package.json b/package.json index 7205295..8d978d4 100644 --- a/package.json +++ b/package.json @@ -38,15 +38,19 @@ "/dist" ], "dependencies": { + "@codemirror/autocomplete": "^6.10.2", "@codemirror/collab": "^6.0.0", "@codemirror/commands": "^6.2.4", "@codemirror/lang-javascript": "^6.1.9", + "@codemirror/lang-json": "^6.0.1", "@codemirror/language": "^6.8.0", + "@codemirror/lint": "^6.4.2", "@codemirror/state": "^6.2.1", "@codemirror/view": "^6.14.0", "@fortawesome/fontawesome-svg-core": "^6.4.0", "@fortawesome/free-solid-svg-icons": "^6.4.0", "@lezer/highlight": "^1.1.6", + "codemirror-json-schema": "^0.4.4", "commander": "^9.5.0", "firebase": "^9.23.0", "rxjs": "^7.8.1" From fe03804f5e87d00cc774ca124c523db7effe0a4f Mon Sep 17 00:00:00 2001 From: Matthew Kaney Date: Wed, 18 Oct 2023 15:10:58 -0400 Subject: [PATCH 2/9] Continue implementing JSONSchema support --- app/web/src/main.ts | 7 +- core/extensions/settings/editor.ts | 8 ++- core/extensions/settings/schema.ts | 74 ++++++++++++++++++++ package-lock.json | 97 +++++++++++++++++++++++++-- package.json | 2 + packages/languages/tidal/ghci.ts | 20 ++++-- packages/languages/tidal/package.json | 1 - packages/languages/tidal/settings.ts | 60 ++--------------- 8 files changed, 201 insertions(+), 68 deletions(-) create mode 100644 core/extensions/settings/schema.ts diff --git a/app/web/src/main.ts b/app/web/src/main.ts index 144389a..a020653 100644 --- a/app/web/src/main.ts +++ b/app/web/src/main.ts @@ -13,7 +13,6 @@ import { EditorTabView } from "@core/extensions/layout/tabs/editor"; import { ElectronAPI } from "@core/api"; import { console as electronConsole } from "@core/extensions/console"; import { peer } from "@core/extensions/peer"; -import { SettingsPage } from "@core/extensions/settings"; import { toolbar } from "@core/extensions/toolbar"; import { fileSync } from "../../desktop/src/renderer/file"; @@ -43,7 +42,11 @@ window.addEventListener("load", () => { export class Editor { constructor(parent: HTMLElement) { - let layout = new LayoutView(parent, () => {}, () => {}); + let layout = new LayoutView( + parent, + () => {}, + () => {} + ); layout.dispatch({ changes: [ { diff --git a/core/extensions/settings/editor.ts b/core/extensions/settings/editor.ts index e3cf22f..f97fdd2 100644 --- a/core/extensions/settings/editor.ts +++ b/core/extensions/settings/editor.ts @@ -4,8 +4,14 @@ import { autocompletion } from "@codemirror/autocomplete"; import { json } from "@codemirror/lang-json"; import { jsonSchema } from "codemirror-json-schema"; +import { asJSONSchema } from "./schema"; import { TidalSettingsSchema } from "packages/languages/tidal/settings"; export function settings(): Extension { - return [autocompletion(), json(), jsonSchema(TidalSettingsSchema)]; + return [ + autocompletion(), + json(), + // TODO: Figure out how to get all the JSON Schema extensions to work together + jsonSchema(asJSONSchema(TidalSettingsSchema) as any), + ]; } diff --git a/core/extensions/settings/schema.ts b/core/extensions/settings/schema.ts new file mode 100644 index 0000000..cbb81bc --- /dev/null +++ b/core/extensions/settings/schema.ts @@ -0,0 +1,74 @@ +import { JSONSchema7, FromSchema as FromJSONSchema } from "json-schema-to-ts"; +import { Draft07 } from "json-schema-library"; + +export interface SettingsSchema { + [name: string]: JSONSchema7; +} + +export type FromSchema = { + [Name in keyof S]: FromJSONSchema; +}; + +export function asJSONSchema(schema: SettingsSchema) { + return { + type: "object", + properties: schema, + } as const; +} + +export function getSettings( + schema: S, + data: unknown +) { + let settings: { [name in keyof S]: S[name] } = {}; + + for (let name in schema) { + if (typeof data === "object" && data && name in data) { + settings[name] = data[name as keyof data]; + } + } + + return; +} + +// function normalizeSettings( +// typedSchema: S, +// settings: unknown +// ) { +// let completeSettings: any = {}; + +// let schema = typedSchema as any; + +// for (const [propName, propSchema] of Object.entries(schema.properties)) { +// // This shouldn't be necessary if schema has a specific type +// if (typeof propSchema !== "object" || propSchema === null) break; + +// if (typeof settings === "object" && settings && propName in settings) { +// // TODO: Validate + +// completeSettings[propName] = (settings as { [key: string]: unknown })[ +// propName +// ]; + +// continue; +// } + +// if ("const" in propSchema) { +// completeSettings[propName] = propSchema.const; +// } else if ("default" in propSchema) { +// completeSettings[propName] = propSchema.default; +// } else if ("type" in propSchema && propSchema.type === "array") { +// completeSettings[propName] = []; +// } else { +// throw Error( +// `No way to generate a default value for settings property "${propName}"` +// ); +// } +// } + +// return completeSettings as Required>; +// } + +// export function normalizeTidalSettings(settings: unknown) { +// return normalizeSettings(TidalSettingsSchema, settings); +// } diff --git a/package-lock.json b/package-lock.json index 1714217..7e29507 100644 --- a/package-lock.json +++ b/package-lock.json @@ -29,6 +29,8 @@ "codemirror-json-schema": "^0.4.4", "commander": "^9.5.0", "firebase": "^9.23.0", + "json-schema-library": "^9.1.2", + "json-schema-to-ts": "^2.9.2", "rxjs": "^7.8.1" }, "devDependencies": { @@ -7053,6 +7055,19 @@ "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.2.5.tgz", "integrity": "sha512-gWJOWYFrhQ8j7pVm0EM8Slr+EPVq1Phf6lvzvD/WCeqkrx/f2xBI0xOsRRS9xCn3I4vKtP519dvs3TP09r24wQ==" }, + "node_modules/codemirror-json-schema/node_modules/json-schema-library": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/json-schema-library/-/json-schema-library-8.0.0.tgz", + "integrity": "sha512-qqsEdyhuA68YHzuWNGrOk9ViknRKw1NfIbhT9wQ0z6l5cpfuYoqKRkbu8tgHAXjahmLEkpNdGwHq+gCgIrMYeA==", + "dependencies": { + "@sagold/json-pointer": "^5.1.1", + "@sagold/json-query": "^6.1.0", + "deepmerge": "^4.3.1", + "fast-copy": "^3.0.1", + "fast-deep-equal": "^3.1.3", + "valid-url": "^1.0.9" + } + }, "node_modules/codemirror-json5": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/codemirror-json5/-/codemirror-json5-1.0.3.tgz", @@ -8122,6 +8137,11 @@ "minimatch": "^3.0.4" } }, + "node_modules/discontinuous-range": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/discontinuous-range/-/discontinuous-range-1.0.0.tgz", + "integrity": "sha512-c68LpLbO+7kP/b1Hr1qs8/BJ09F5khZGTxqxZuhzxpmwJKOgRFHJWIb9/KmqnqHhLdO55aOxFH/EGBvUQbL/RQ==" + }, "node_modules/dmg-builder": { "version": "24.6.4", "resolved": "https://registry.npmjs.org/dmg-builder/-/dmg-builder-24.6.4.tgz", @@ -12131,15 +12151,16 @@ "dev": true }, "node_modules/json-schema-library": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/json-schema-library/-/json-schema-library-8.0.0.tgz", - "integrity": "sha512-qqsEdyhuA68YHzuWNGrOk9ViknRKw1NfIbhT9wQ0z6l5cpfuYoqKRkbu8tgHAXjahmLEkpNdGwHq+gCgIrMYeA==", + "version": "9.1.2", + "resolved": "https://registry.npmjs.org/json-schema-library/-/json-schema-library-9.1.2.tgz", + "integrity": "sha512-uQnFb2V+VakLl6XIGGtUQzfjkP31f/dCT5lJq9NOUdypSSpjbWL/V0R2KvoNJp3hU8VErwh9DqVoZPqlC+B3IA==", "dependencies": { "@sagold/json-pointer": "^5.1.1", - "@sagold/json-query": "^6.1.0", + "@sagold/json-query": "^6.1.1", "deepmerge": "^4.3.1", "fast-copy": "^3.0.1", "fast-deep-equal": "^3.1.3", + "smtp-address-parser": "1.0.10", "valid-url": "^1.0.9" } }, @@ -13424,6 +13445,11 @@ "mkdirp": "bin/cmd.js" } }, + "node_modules/moo": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/moo/-/moo-0.5.2.tgz", + "integrity": "sha512-iSAJLHYKnX41mKcJKjqvnAN9sf0LMDTXDEvFv+ffuRR9a1MIuXLjMNL6EsnDHSkKLTWNqQQ5uo61P4EbU4NU+Q==" + }, "node_modules/morgan": { "version": "1.10.0", "resolved": "https://registry.npmjs.org/morgan/-/morgan-1.10.0.tgz", @@ -13519,6 +13545,32 @@ "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", "dev": true }, + "node_modules/nearley": { + "version": "2.20.1", + "resolved": "https://registry.npmjs.org/nearley/-/nearley-2.20.1.tgz", + "integrity": "sha512-+Mc8UaAebFzgV+KpI5n7DasuuQCHA89dmwm7JXw3TV43ukfNQ9DnBH3Mdb2g/I4Fdxc26pwimBWvjIw0UAILSQ==", + "dependencies": { + "commander": "^2.19.0", + "moo": "^0.5.0", + "railroad-diagrams": "^1.0.0", + "randexp": "0.4.6" + }, + "bin": { + "nearley-railroad": "bin/nearley-railroad.js", + "nearley-test": "bin/nearley-test.js", + "nearley-unparse": "bin/nearley-unparse.js", + "nearleyc": "bin/nearleyc.js" + }, + "funding": { + "type": "individual", + "url": "https://nearley.js.org/#give-to-nearley" + } + }, + "node_modules/nearley/node_modules/commander": { + "version": "2.20.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==" + }, "node_modules/negotiator": { "version": "0.6.3", "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", @@ -14810,6 +14862,23 @@ "right-now": "^1.0.0" } }, + "node_modules/railroad-diagrams": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/railroad-diagrams/-/railroad-diagrams-1.0.0.tgz", + "integrity": "sha512-cz93DjNeLY0idrCNOH6PviZGRN9GJhsdm9hpn1YCS879fj4W+x5IFJhhkRZcwVgMmFF7R82UA/7Oh+R8lLZg6A==" + }, + "node_modules/randexp": { + "version": "0.4.6", + "resolved": "https://registry.npmjs.org/randexp/-/randexp-0.4.6.tgz", + "integrity": "sha512-80WNmd9DA0tmZrw9qQa62GPPWfuXJknrmVmLcxvq4uZBdYqb1wYoKTmnlGUchvVWe0XiLupYkBoXVOxz3C8DYQ==", + "dependencies": { + "discontinuous-range": "1.0.0", + "ret": "~0.1.10" + }, + "engines": { + "node": ">=0.12" + } + }, "node_modules/range-parser": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", @@ -15239,6 +15308,14 @@ "node": ">=8" } }, + "node_modules/ret": { + "version": "0.1.15", + "resolved": "https://registry.npmjs.org/ret/-/ret-0.1.15.tgz", + "integrity": "sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg==", + "engines": { + "node": ">=0.12" + } + }, "node_modules/retry": { "version": "0.13.1", "resolved": "https://registry.npmjs.org/retry/-/retry-0.13.1.tgz", @@ -15731,6 +15808,17 @@ "npm": ">= 3.0.0" } }, + "node_modules/smtp-address-parser": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/smtp-address-parser/-/smtp-address-parser-1.0.10.tgz", + "integrity": "sha512-Osg9LmvGeAG/hyao4mldbflLOkkr3a+h4m1lwKCK5U8M6ZAr7tdXEz/+/vr752TSGE4MNUlUl9cIK2cB8cgzXg==", + "dependencies": { + "nearley": "^2.20.1" + }, + "engines": { + "node": ">=0.10" + } + }, "node_modules/socks": { "version": "2.7.1", "resolved": "https://registry.npmjs.org/socks/-/socks-2.7.1.tgz", @@ -17582,7 +17670,6 @@ "dependencies": { "@codemirror/language": "^6.0.0", "@codemirror/legacy-modes": "^6.0.0", - "json-schema-to-ts": "^2.9.2", "semver": "^7.5.4" }, "devDependencies": { diff --git a/package.json b/package.json index 8d978d4..9a9a039 100644 --- a/package.json +++ b/package.json @@ -53,6 +53,8 @@ "codemirror-json-schema": "^0.4.4", "commander": "^9.5.0", "firebase": "^9.23.0", + "json-schema-library": "^9.1.2", + "json-schema-to-ts": "^2.9.2", "rxjs": "^7.8.1" }, "devDependencies": { diff --git a/packages/languages/tidal/ghci.ts b/packages/languages/tidal/ghci.ts index a48b818..1c3366f 100644 --- a/packages/languages/tidal/ghci.ts +++ b/packages/languages/tidal/ghci.ts @@ -13,7 +13,8 @@ import { parse } from "@core/osc/osc"; import { TerminalMessage } from "@core/api"; import { Engine } from "../core/engine"; -import { TidalSettings, normalizeTidalSettings } from "./settings"; +import { getSettings } from "@core/extensions/settings/schema"; +import { TidalSettingsSchema, TidalSettings } from "./settings"; import { generateIntegrationCode } from "./editor-integration"; @@ -49,13 +50,19 @@ export class GHCI extends Engine { const settings = JSON.parse(await readFile(this.settingsPath, "utf-8")); // TODO: Update/validate settings, etc - return normalizeTidalSettings(settings); + return getSettings(TidalSettingsSchema, settings); } catch (err) { - if ((err as NodeJS.ErrnoException).code !== "ENOENT") { - throw err; - } + // if ((err as NodeJS.ErrnoException).code !== "ENOENT") { + // throw err; + // } + + this.emit("message", { + level: "error", + source: "Tidal", + text: "Couldn't load settings file.", + }); - return normalizeTidalSettings({}); + return getSettings(TidalSettingsSchema, {}); } } @@ -79,6 +86,7 @@ export class GHCI extends Engine { } private async initProcess() { + console.log(JSON.stringify(await this.settings, null, 2)); const { "tidal.boot.disableEditorIntegration": disableEditorIntegration, "tidal.boot.useDefaultFile": useDefaultBootfile, diff --git a/packages/languages/tidal/package.json b/packages/languages/tidal/package.json index 30f8ab1..a9265bb 100644 --- a/packages/languages/tidal/package.json +++ b/packages/languages/tidal/package.json @@ -21,7 +21,6 @@ "dependencies": { "@codemirror/language": "^6.0.0", "@codemirror/legacy-modes": "^6.0.0", - "json-schema-to-ts": "^2.9.2", "semver": "^7.5.4" }, "devDependencies": { diff --git a/packages/languages/tidal/settings.ts b/packages/languages/tidal/settings.ts index c0149e5..4fa8ff5 100644 --- a/packages/languages/tidal/settings.ts +++ b/packages/languages/tidal/settings.ts @@ -1,56 +1,10 @@ -import type { JSONSchema, FromSchema } from "json-schema-to-ts"; +import { SettingsSchema, FromSchema } from "@core/extensions/settings/schema"; export const TidalSettingsSchema = { - type: "object", - properties: { - // environment: { const: "ghci" }, - "tidal.boot.useDefaultFile": { type: "boolean", default: true }, - "tidal.boot.customFiles": { type: "array", items: { type: "string" } }, - "tidal.boot.disableEditorIntegration": { type: "boolean", default: false }, - }, - additionalProperties: false, -} as const satisfies JSONSchema; + // environment: { const: "ghci" }, + "tidal.boot.useDefaultFile": { type: "boolean", default: true }, + "tidal.boot.customFiles": { type: "array", items: { type: "string" } }, + "tidal.boot.disableEditorIntegration": { type: "boolean", default: false }, +} satisfies SettingsSchema; -export type TidalSettings = Required>; - -function normalizeSettings( - typedSchema: S, - settings: unknown -) { - let completeSettings: any = {}; - - let schema = typedSchema as any; - - for (const [propName, propSchema] of Object.entries(schema.properties)) { - // This shouldn't be necessary if schema has a specific type - if (typeof propSchema !== "object" || propSchema === null) break; - - if (typeof settings === "object" && settings && propName in settings) { - // TODO: Validate - - completeSettings[propName] = (settings as { [key: string]: unknown })[ - propName - ]; - - continue; - } - - if ("const" in propSchema) { - completeSettings[propName] = propSchema.const; - } else if ("default" in propSchema) { - completeSettings[propName] = propSchema.default; - } else if ("type" in propSchema && propSchema.type === "array") { - completeSettings[propName] = []; - } else { - throw Error( - `No way to generate a default value for settings property "${propName}"` - ); - } - } - - return completeSettings as Required>; -} - -export function normalizeTidalSettings(settings: unknown) { - return normalizeSettings(TidalSettingsSchema, settings); -} +export type TidalSettings = FromSchema; From 0c04cab763b559b84d59df4ff8cf492a62176547 Mon Sep 17 00:00:00 2001 From: Matthew Kaney Date: Sat, 21 Oct 2023 17:34:07 -0400 Subject: [PATCH 3/9] Continuing playing with JSON Schema types --- core/extensions/settings/schema.ts | 75 ++++++++-------------------- packages/languages/tidal/settings.ts | 15 +++--- 2 files changed, 31 insertions(+), 59 deletions(-) diff --git a/core/extensions/settings/schema.ts b/core/extensions/settings/schema.ts index cbb81bc..c7780c6 100644 --- a/core/extensions/settings/schema.ts +++ b/core/extensions/settings/schema.ts @@ -1,14 +1,9 @@ -import { JSONSchema7, FromSchema as FromJSONSchema } from "json-schema-to-ts"; -import { Draft07 } from "json-schema-library"; +import { JSONSchema7, FromSchema } from "json-schema-to-ts"; export interface SettingsSchema { [name: string]: JSONSchema7; } -export type FromSchema = { - [Name in keyof S]: FromJSONSchema; -}; - export function asJSONSchema(schema: SettingsSchema) { return { type: "object", @@ -16,59 +11,33 @@ export function asJSONSchema(schema: SettingsSchema) { } as const; } -export function getSettings( +export function getSettings( schema: S, data: unknown -) { - let settings: { [name in keyof S]: S[name] } = {}; +): FromSchema { + let settings: { [name in keyof S]?: S[name] } = {}; - for (let name in schema) { + for (let name in (schema as any).properties) { if (typeof data === "object" && data && name in data) { - settings[name] = data[name as keyof data]; - } - } - - return; -} - -// function normalizeSettings( -// typedSchema: S, -// settings: unknown -// ) { -// let completeSettings: any = {}; + settings[name] = (data as any)[name]; + } else { + let settingSchema = schema[name]; -// let schema = typedSchema as any; + if (typeof settingSchema === "boolean") { + throw Error("Unexpected boolean JSON schema"); + } -// for (const [propName, propSchema] of Object.entries(schema.properties)) { -// // This shouldn't be necessary if schema has a specific type -// if (typeof propSchema !== "object" || propSchema === null) break; + let value = settingSchema.default; -// if (typeof settings === "object" && settings && propName in settings) { -// // TODO: Validate + if (value === undefined) { + if (settingSchema.type === "array") { + value = []; + } + } -// completeSettings[propName] = (settings as { [key: string]: unknown })[ -// propName -// ]; - -// continue; -// } - -// if ("const" in propSchema) { -// completeSettings[propName] = propSchema.const; -// } else if ("default" in propSchema) { -// completeSettings[propName] = propSchema.default; -// } else if ("type" in propSchema && propSchema.type === "array") { -// completeSettings[propName] = []; -// } else { -// throw Error( -// `No way to generate a default value for settings property "${propName}"` -// ); -// } -// } - -// return completeSettings as Required>; -// } + settings[name] = value as any; + } + } -// export function normalizeTidalSettings(settings: unknown) { -// return normalizeSettings(TidalSettingsSchema, settings); -// } + return settings as any; +} diff --git a/packages/languages/tidal/settings.ts b/packages/languages/tidal/settings.ts index 4fa8ff5..3807433 100644 --- a/packages/languages/tidal/settings.ts +++ b/packages/languages/tidal/settings.ts @@ -1,10 +1,13 @@ -import { SettingsSchema, FromSchema } from "@core/extensions/settings/schema"; +import { JSONSchema7, FromSchema } from "json-schema-to-ts"; export const TidalSettingsSchema = { - // environment: { const: "ghci" }, - "tidal.boot.useDefaultFile": { type: "boolean", default: true }, - "tidal.boot.customFiles": { type: "array", items: { type: "string" } }, - "tidal.boot.disableEditorIntegration": { type: "boolean", default: false }, -} satisfies SettingsSchema; + type: "object", + properties: { + // environment: { const: "ghci" }, + "tidal.boot.useDefaultFile": { type: "boolean", default: true }, + "tidal.boot.customFiles": { type: "array", items: { type: "string" } }, + "tidal.boot.disableEditorIntegration": { type: "boolean", default: false }, + }, +} satisfies JSONSchema7; export type TidalSettings = FromSchema; From f77b54aa5731526f5af53ba84210374166fbd960 Mon Sep 17 00:00:00 2001 From: Matthew Kaney Date: Wed, 25 Oct 2023 22:25:54 -0500 Subject: [PATCH 4/9] Clean up JSON schema type issues --- core/extensions/settings/editor.ts | 3 +- core/extensions/settings/schema.ts | 45 +++++++++++++--------------- packages/languages/tidal/settings.ts | 4 +-- 3 files changed, 23 insertions(+), 29 deletions(-) diff --git a/core/extensions/settings/editor.ts b/core/extensions/settings/editor.ts index f97fdd2..40a2aa7 100644 --- a/core/extensions/settings/editor.ts +++ b/core/extensions/settings/editor.ts @@ -4,7 +4,6 @@ import { autocompletion } from "@codemirror/autocomplete"; import { json } from "@codemirror/lang-json"; import { jsonSchema } from "codemirror-json-schema"; -import { asJSONSchema } from "./schema"; import { TidalSettingsSchema } from "packages/languages/tidal/settings"; export function settings(): Extension { @@ -12,6 +11,6 @@ export function settings(): Extension { autocompletion(), json(), // TODO: Figure out how to get all the JSON Schema extensions to work together - jsonSchema(asJSONSchema(TidalSettingsSchema) as any), + jsonSchema(TidalSettingsSchema), ]; } diff --git a/core/extensions/settings/schema.ts b/core/extensions/settings/schema.ts index c7780c6..11f267e 100644 --- a/core/extensions/settings/schema.ts +++ b/core/extensions/settings/schema.ts @@ -4,38 +4,33 @@ export interface SettingsSchema { [name: string]: JSONSchema7; } -export function asJSONSchema(schema: SettingsSchema) { - return { - type: "object", - properties: schema, - } as const; -} - export function getSettings( schema: S, data: unknown -): FromSchema { - let settings: { [name in keyof S]?: S[name] } = {}; - - for (let name in (schema as any).properties) { - if (typeof data === "object" && data && name in data) { - settings[name] = (data as any)[name]; - } else { - let settingSchema = schema[name]; - - if (typeof settingSchema === "boolean") { - throw Error("Unexpected boolean JSON schema"); - } +): Required> { + let settings: { [name: string]: any } = {}; + + if (typeof schema === "object" && "properties" in schema) { + for (let name in schema.properties) { + if (typeof data === "object" && data && name in data) { + settings[name] = (data as any)[name]; + } else { + let settingSchema = schema.properties[name]; + + if (typeof settingSchema === "boolean") { + throw Error("Unexpected boolean JSON schema"); + } - let value = settingSchema.default; + let value = settingSchema.default; - if (value === undefined) { - if (settingSchema.type === "array") { - value = []; + if (value === undefined) { + if (settingSchema.type === "array") { + value = []; + } } - } - settings[name] = value as any; + settings[name] = value as any; + } } } diff --git a/packages/languages/tidal/settings.ts b/packages/languages/tidal/settings.ts index 3807433..48652dd 100644 --- a/packages/languages/tidal/settings.ts +++ b/packages/languages/tidal/settings.ts @@ -8,6 +8,6 @@ export const TidalSettingsSchema = { "tidal.boot.customFiles": { type: "array", items: { type: "string" } }, "tidal.boot.disableEditorIntegration": { type: "boolean", default: false }, }, -} satisfies JSONSchema7; +} as const satisfies JSONSchema7; -export type TidalSettings = FromSchema; +export type TidalSettings = Required>; From 48cdf5bd1d3e9c6dac4e339d02a6626b8420a19c Mon Sep 17 00:00:00 2001 From: Matthew Kaney Date: Tue, 30 Apr 2024 12:18:52 -0400 Subject: [PATCH 5/9] Start setting up general state management --- core/extensions/settings/editor.ts | 4 +- core/extensions/settings/schema.ts | 38 -------- core/state/index.ts | 23 +++++ core/state/schema.ts | 137 +++++++++++++++++++++++++++++ package-lock.json | 6 ++ package.json | 1 + 6 files changed, 168 insertions(+), 41 deletions(-) delete mode 100644 core/extensions/settings/schema.ts create mode 100644 core/state/index.ts create mode 100644 core/state/schema.ts diff --git a/core/extensions/settings/editor.ts b/core/extensions/settings/editor.ts index 40a2aa7..e5cfb1b 100644 --- a/core/extensions/settings/editor.ts +++ b/core/extensions/settings/editor.ts @@ -1,12 +1,10 @@ -import { Extension } from "@codemirror/state"; - import { autocompletion } from "@codemirror/autocomplete"; import { json } from "@codemirror/lang-json"; import { jsonSchema } from "codemirror-json-schema"; import { TidalSettingsSchema } from "packages/languages/tidal/settings"; -export function settings(): Extension { +export function settings() { return [ autocompletion(), json(), diff --git a/core/extensions/settings/schema.ts b/core/extensions/settings/schema.ts deleted file mode 100644 index 11f267e..0000000 --- a/core/extensions/settings/schema.ts +++ /dev/null @@ -1,38 +0,0 @@ -import { JSONSchema7, FromSchema } from "json-schema-to-ts"; - -export interface SettingsSchema { - [name: string]: JSONSchema7; -} - -export function getSettings( - schema: S, - data: unknown -): Required> { - let settings: { [name: string]: any } = {}; - - if (typeof schema === "object" && "properties" in schema) { - for (let name in schema.properties) { - if (typeof data === "object" && data && name in data) { - settings[name] = (data as any)[name]; - } else { - let settingSchema = schema.properties[name]; - - if (typeof settingSchema === "boolean") { - throw Error("Unexpected boolean JSON schema"); - } - - let value = settingSchema.default; - - if (value === undefined) { - if (settingSchema.type === "array") { - value = []; - } - } - - settings[name] = value as any; - } - } - } - - return settings as any; -} diff --git a/core/state/index.ts b/core/state/index.ts new file mode 100644 index 0000000..bcfa657 --- /dev/null +++ b/core/state/index.ts @@ -0,0 +1,23 @@ +import rfdc from "rfdc"; + +import { EventEmitter } from "@core/events"; + +import { SettingsSchema } from "./schema"; + +const clone = rfdc(); + +interface StateEvents { + change: Required>; +} + +class StateManagement extends EventEmitter< + StateEvents +> { + private data: FromSchema; + + // constructor(private spec: S, initial: Partial> = {}) { + // super(); + + // this.data = clone(initial); + // } +} diff --git a/core/state/schema.ts b/core/state/schema.ts new file mode 100644 index 0000000..be7bc04 --- /dev/null +++ b/core/state/schema.ts @@ -0,0 +1,137 @@ +export interface SettingsSchema { + [name: string]: ValueSchema; +} + +type ValueSchema = + | NumberValueSchema + | StringValueSchema + | BooleanValueSchema + | ArrayValueSchema; + +interface NumberValueSchema extends BaseValueSchema { + type: "number"; + default?: number; +} + +interface StringValueSchema extends BaseValueSchema { + type: "string"; + default?: string; +} + +interface BooleanValueSchema extends BaseValueSchema { + type: "boolean"; + default?: boolean; +} + +type PrimitiveValueSchema = + | NumberValueSchema + | StringValueSchema + | BooleanValueSchema; + +interface ArrayValueSchema extends BaseValueSchema { + type: "array"; + items: PrimitiveValueSchema; +} + +interface BaseValueSchema {} + +export function getDefaults(schema: S) { + const defaults: any = {}; + + for (let key in schema) { + let valueOptions = schema[key]; + + switch (valueOptions.type) { + case "number": + defaults[key] = valueOptions.default ?? 0; + break; + case "string": + defaults[key] = valueOptions.default ?? ""; + break; + case "boolean": + defaults[key] = valueOptions.default ?? false; + break; + case "array": + defaults[key] = []; + break; + } + } + + return defaults; +} + +export function getValid(schema: S, data: any) { + const validData: any = {}; + + function getValidPrimitive(schema: PrimitiveValueSchema, value: any) { + if (schema.type === "number" && typeof value === "number") { + return value; + } else if (schema.type === "string" && typeof value === "string") { + return value; + } else if (schema.type === "boolean" && typeof value === "boolean") { + return value; + } + } + + if (typeof data === "object") { + for (let key in data) { + if (key in schema) { + if ( + schema[key].type === "number" || + schema[key].type === "string" || + schema[key].type === "boolean" + ) { + const value = getValidPrimitive( + schema[key] as PrimitiveValueSchema, + data[key] + ); + if (value !== undefined) { + validData[key] = value; + } + validData[key] = value; + } else if (schema[key].type === "array") { + const value = data[key]; + if (Array.isArray(value)) { + const arraySchema = (schema[key] as ArrayValueSchema).items; + validData[key] = value.filter((v) => + getValidPrimitive(arraySchema, v) + ); + } + } + } + } + } +} + +// export function getSettings( +// schema: S, +// data: unknown +// ): Required> { +// let settings: { [name: string]: any } = {}; + +// if (typeof schema === "object" && "properties" in schema) { +// for (let name in schema.properties) { +// if (typeof data === "object" && data && name in data) { +// settings[name] = (data as any)[name]; +// } else { +// let settingSchema = schema.properties[name]; + +// if (typeof settingSchema === "boolean") { +// throw Error("Unexpected boolean JSON schema"); +// } + +// let value = settingSchema.default; + +// if (value === undefined) { +// if (settingSchema.type === "array") { +// value = []; +// } +// } + +// settings[name] = value as any; +// } +// } +// } + +// return settings as any; +// } diff --git a/package-lock.json b/package-lock.json index 7e29507..8a8f227 100644 --- a/package-lock.json +++ b/package-lock.json @@ -31,6 +31,7 @@ "firebase": "^9.23.0", "json-schema-library": "^9.1.2", "json-schema-to-ts": "^2.9.2", + "rfdc": "^1.3.1", "rxjs": "^7.8.1" }, "devDependencies": { @@ -15361,6 +15362,11 @@ "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", "dev": true }, + "node_modules/rfdc": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.3.1.tgz", + "integrity": "sha512-r5a3l5HzYlIC68TpmYKlxWjmOP6wiPJ1vWv2HeLhNsRZMrCkxeqxiHlQ21oXmQ4F3SiryXBHhAD7JZqvOJjFmg==" + }, "node_modules/right-now": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/right-now/-/right-now-1.0.0.tgz", diff --git a/package.json b/package.json index 9a9a039..6fd24a1 100644 --- a/package.json +++ b/package.json @@ -55,6 +55,7 @@ "firebase": "^9.23.0", "json-schema-library": "^9.1.2", "json-schema-to-ts": "^2.9.2", + "rfdc": "^1.3.1", "rxjs": "^7.8.1" }, "devDependencies": { From 77f6020cbea595c2f146de5237a4c54391fd3a95 Mon Sep 17 00:00:00 2001 From: Matthew Kaney Date: Tue, 30 Apr 2024 16:48:19 -0400 Subject: [PATCH 6/9] Continue building out initial pass of state updates --- app/desktop/src/main/index.ts | 47 +++++++++++++++++++++------ core/state/index.ts | 37 ++++++++++++--------- core/state/schema.ts | 35 ++------------------ packages/languages/tidal/ghci.ts | 48 ++++++---------------------- packages/languages/tidal/settings.ts | 17 ++++------ 5 files changed, 79 insertions(+), 105 deletions(-) diff --git a/app/desktop/src/main/index.ts b/app/desktop/src/main/index.ts index 84c8173..422794e 100644 --- a/app/desktop/src/main/index.ts +++ b/app/desktop/src/main/index.ts @@ -10,7 +10,9 @@ import { autoUpdater } from "electron-updater"; autoUpdater.checkForUpdatesAndNotify(); -import { GHCI } from "@management/lang-tidal"; +import { StateManagement } from "@core/state"; + +import { GHCI, TidalSettingsSchema } from "@management/lang-tidal"; import { Filesystem } from "./filesystem"; import { wrapIPC } from "./ipcMain"; @@ -18,7 +20,13 @@ import { menu } from "./menu"; const filesystem = new Filesystem(); -const createWindow = () => { +const settingsPath = resolve(app.getPath("userData"), "settings.json"); + +const createWindow = ( + configuration: StateManagement +) => { + const tidal = new GHCI(configuration); + const window = new BrowserWindow({ show: false, width: 800, @@ -28,8 +36,6 @@ const createWindow = () => { }, }); - const tidal = new GHCI(resolve(app.getPath("userData"))); - let listeners: (() => void)[] = []; let docsListeners: { [id: string]: typeof listeners } = {}; @@ -93,7 +99,7 @@ const createWindow = () => { listen("newTab", () => { filesystem.loadDoc(); }) - ) + ); listeners.push( listen("requestClose", async ({ id }) => { @@ -149,11 +155,19 @@ const createWindow = () => { listeners.push( menu.on("settings", async () => { - let settingsDoc = filesystem.loadDoc(tidal.settingsPath, "{}"); + let settingsDoc = filesystem.loadDoc(settingsPath, "{}"); settingsDoc.on("status", ({ saved }) => { if (saved === true) { - tidal.reloadSettings(); + try { + let settingsText = settingsDoc.content?.doc.toString(); + + if (typeof settingsText === "string") { + configuration.update(JSON.parse(settingsText)); + } + } catch (error) { + console.log("Error updating settings"); + } } }); }) @@ -197,8 +211,23 @@ const createWindow = () => { }); }; -app.whenReady().then(() => { - createWindow(); +import { readFile } from "fs/promises"; + +app.whenReady().then(async () => { + const settings = new StateManagement(TidalSettingsSchema); + + // Try loading settings + let settingsData = {}; + + try { + settingsData = JSON.parse(await readFile(settingsPath, "utf-8")); + } catch (err) { + // TODO: Throw some sort of error? For now, just fall back to the empty object + } + + settings.update(settingsData); + + createWindow(settings); // app.on("activate", () => { // if (BrowserWindow.getAllWindows().length === 0) createWindow(); diff --git a/core/state/index.ts b/core/state/index.ts index bcfa657..7ddb02d 100644 --- a/core/state/index.ts +++ b/core/state/index.ts @@ -1,23 +1,30 @@ -import rfdc from "rfdc"; - import { EventEmitter } from "@core/events"; -import { SettingsSchema } from "./schema"; - -const clone = rfdc(); +import { SettingsSchema, getDefaults, getValid } from "./schema"; -interface StateEvents { - change: Required>; +interface StateEvents { + change: any; } -class StateManagement extends EventEmitter< - StateEvents -> { - private data: FromSchema; +export class StateManagement< + S extends SettingsSchema +> extends EventEmitter { + private defaults: any; + private data: any; + + constructor(private schema: S, initial: any = {}) { + super(); + + this.defaults = getDefaults(schema); + this.data = getValid(schema, initial); + } - // constructor(private spec: S, initial: Partial> = {}) { - // super(); + update(data: any) { + this.data = getValid(this.schema, data); + this.emit("change", this.getData()); + } - // this.data = clone(initial); - // } + getData() { + return { ...this.defaults, ...this.data }; + } } diff --git a/core/state/schema.ts b/core/state/schema.ts index be7bc04..e20cbfe 100644 --- a/core/state/schema.ts +++ b/core/state/schema.ts @@ -101,37 +101,6 @@ export function getValid(schema: S, data: any) { } } } -} - -// export function getSettings( -// schema: S, -// data: unknown -// ): Required> { -// let settings: { [name: string]: any } = {}; - -// if (typeof schema === "object" && "properties" in schema) { -// for (let name in schema.properties) { -// if (typeof data === "object" && data && name in data) { -// settings[name] = (data as any)[name]; -// } else { -// let settingSchema = schema.properties[name]; - -// if (typeof settingSchema === "boolean") { -// throw Error("Unexpected boolean JSON schema"); -// } -// let value = settingSchema.default; - -// if (value === undefined) { -// if (settingSchema.type === "array") { -// value = []; -// } -// } - -// settings[name] = value as any; -// } -// } -// } - -// return settings as any; -// } + return validData; +} diff --git a/packages/languages/tidal/ghci.ts b/packages/languages/tidal/ghci.ts index 1c3366f..430274b 100644 --- a/packages/languages/tidal/ghci.ts +++ b/packages/languages/tidal/ghci.ts @@ -13,7 +13,8 @@ import { parse } from "@core/osc/osc"; import { TerminalMessage } from "@core/api"; import { Engine } from "../core/engine"; -import { getSettings } from "@core/extensions/settings/schema"; +import { StateManagement } from "@core/state"; +export { TidalSettingsSchema } from "./settings"; import { TidalSettingsSchema, TidalSettings } from "./settings"; import { generateIntegrationCode } from "./editor-integration"; @@ -25,16 +26,18 @@ interface GHCIEvents { } export class GHCI extends Engine { - private _settings: Promise; private socket: Promise; private process: Promise; private history: TerminalMessage[] = []; - constructor(private extensionFolder: string) { + constructor(private settings: StateManagement) { super(); - this._settings = this.loadSettings(); + this.settings.on("change", () => { + this.reloadSettings; + }); + this.socket = this.initSocket(); this.process = this.initProcess(); @@ -45,27 +48,6 @@ export class GHCI extends Engine { }; } - private async loadSettings() { - try { - const settings = JSON.parse(await readFile(this.settingsPath, "utf-8")); - - // TODO: Update/validate settings, etc - return getSettings(TidalSettingsSchema, settings); - } catch (err) { - // if ((err as NodeJS.ErrnoException).code !== "ENOENT") { - // throw err; - // } - - this.emit("message", { - level: "error", - source: "Tidal", - text: "Couldn't load settings file.", - }); - - return getSettings(TidalSettingsSchema, {}); - } - } - private initSocket() { return new Promise((resolve) => { const socket = createSocket("udp4"); @@ -86,12 +68,12 @@ export class GHCI extends Engine { } private async initProcess() { - console.log(JSON.stringify(await this.settings, null, 2)); + console.log(JSON.stringify(this.settings.getData())); const { "tidal.boot.disableEditorIntegration": disableEditorIntegration, "tidal.boot.useDefaultFile": useDefaultBootfile, "tidal.boot.customFiles": bootFiles, - } = await this.settings; + } = this.settings.getData(); const port = (await this.socket).address().port.toString(); // Add filters for prettier code @@ -221,9 +203,7 @@ export class GHCI extends Engine { return join(stdout, "BootTidal.hs"); } - async reloadSettings() { - this._settings = this.loadSettings(); - + private async reloadSettings() { // TODO: Some sort of check that settings have actually changed? this.emit("message", { level: "info", @@ -286,14 +266,6 @@ export class GHCI extends Engine { return this.version; } - get settings() { - return this._settings; - } - - get settingsPath() { - return join(this.extensionFolder, "settings.json"); - } - async close() { let process = await this.process; diff --git a/packages/languages/tidal/settings.ts b/packages/languages/tidal/settings.ts index 48652dd..07a63e9 100644 --- a/packages/languages/tidal/settings.ts +++ b/packages/languages/tidal/settings.ts @@ -1,13 +1,10 @@ -import { JSONSchema7, FromSchema } from "json-schema-to-ts"; +import { SettingsSchema } from "@core/state/schema"; export const TidalSettingsSchema = { - type: "object", - properties: { - // environment: { const: "ghci" }, - "tidal.boot.useDefaultFile": { type: "boolean", default: true }, - "tidal.boot.customFiles": { type: "array", items: { type: "string" } }, - "tidal.boot.disableEditorIntegration": { type: "boolean", default: false }, - }, -} as const satisfies JSONSchema7; + // environment: { const: "ghci" }, + "tidal.boot.useDefaultFile": { type: "boolean", default: true }, + "tidal.boot.customFiles": { type: "array", items: { type: "string" } }, + "tidal.boot.disableEditorIntegration": { type: "boolean", default: false }, +} as const satisfies SettingsSchema; -export type TidalSettings = Required>; +export type TidalSettings = any; From b9ef39c42af91ec6ab13c5d5c657d4d441d004a4 Mon Sep 17 00:00:00 2001 From: Matthew Kaney Date: Wed, 1 May 2024 07:56:24 -0400 Subject: [PATCH 7/9] Clean up and shore up types a bit --- core/state/index.ts | 18 ++++++++-------- core/state/schema.ts | 31 +++++++++++++++++++++++++--- packages/languages/tidal/ghci.ts | 5 ++--- packages/languages/tidal/settings.ts | 2 -- 4 files changed, 39 insertions(+), 17 deletions(-) diff --git a/core/state/index.ts b/core/state/index.ts index 7ddb02d..00ad287 100644 --- a/core/state/index.ts +++ b/core/state/index.ts @@ -1,16 +1,16 @@ import { EventEmitter } from "@core/events"; -import { SettingsSchema, getDefaults, getValid } from "./schema"; +import { FromSchema, SettingsSchema, getDefaults, getValid } from "./schema"; -interface StateEvents { - change: any; +interface StateEvents { + change: FromSchema; } -export class StateManagement< - S extends SettingsSchema -> extends EventEmitter { - private defaults: any; - private data: any; +export class StateManagement extends EventEmitter< + StateEvents +> { + private defaults: FromSchema; + private data: Partial>; constructor(private schema: S, initial: any = {}) { super(); @@ -24,7 +24,7 @@ export class StateManagement< this.emit("change", this.getData()); } - getData() { + getData(): FromSchema { return { ...this.defaults, ...this.data }; } } diff --git a/core/state/schema.ts b/core/state/schema.ts index e20cbfe..0c205b6 100644 --- a/core/state/schema.ts +++ b/core/state/schema.ts @@ -35,7 +35,30 @@ interface ArrayValueSchema extends BaseValueSchema { interface BaseValueSchema {} -export function getDefaults(schema: S) { +export type FromSchema = { + [Property in keyof S]: S[Property] extends NumberValueSchema + ? number + : S[Property] extends StringValueSchema + ? string + : S[Property] extends BooleanValueSchema + ? boolean + : S[Property] extends ArrayValueSchema + ? FromArraySchema + : never; +}; + +type FromArraySchema = + S["items"] extends NumberValueSchema + ? number[] + : S["items"] extends StringValueSchema + ? string[] + : S["items"] extends BooleanValueSchema + ? boolean[] + : never; + +export function getDefaults( + schema: S +): FromSchema { const defaults: any = {}; for (let key in schema) { @@ -60,7 +83,10 @@ export function getDefaults(schema: S) { return defaults; } -export function getValid(schema: S, data: any) { +export function getValid( + schema: S, + data: any +): Partial> { const validData: any = {}; function getValidPrimitive(schema: PrimitiveValueSchema, value: any) { @@ -88,7 +114,6 @@ export function getValid(schema: S, data: any) { if (value !== undefined) { validData[key] = value; } - validData[key] = value; } else if (schema[key].type === "array") { const value = data[key]; if (Array.isArray(value)) { diff --git a/packages/languages/tidal/ghci.ts b/packages/languages/tidal/ghci.ts index 430274b..a72272f 100644 --- a/packages/languages/tidal/ghci.ts +++ b/packages/languages/tidal/ghci.ts @@ -7,7 +7,6 @@ import { once } from "events"; import { createInterface } from "readline"; import { join } from "path"; import { createReadStream } from "fs"; -import { readFile } from "fs/promises"; import { parse } from "@core/osc/osc"; import { TerminalMessage } from "@core/api"; @@ -15,7 +14,7 @@ import { Engine } from "../core/engine"; import { StateManagement } from "@core/state"; export { TidalSettingsSchema } from "./settings"; -import { TidalSettingsSchema, TidalSettings } from "./settings"; +import { TidalSettingsSchema } from "./settings"; import { generateIntegrationCode } from "./editor-integration"; @@ -31,7 +30,7 @@ export class GHCI extends Engine { private history: TerminalMessage[] = []; - constructor(private settings: StateManagement) { + constructor(private settings: StateManagement) { super(); this.settings.on("change", () => { diff --git a/packages/languages/tidal/settings.ts b/packages/languages/tidal/settings.ts index 07a63e9..225c8a6 100644 --- a/packages/languages/tidal/settings.ts +++ b/packages/languages/tidal/settings.ts @@ -6,5 +6,3 @@ export const TidalSettingsSchema = { "tidal.boot.customFiles": { type: "array", items: { type: "string" } }, "tidal.boot.disableEditorIntegration": { type: "boolean", default: false }, } as const satisfies SettingsSchema; - -export type TidalSettings = any; From 3dce3f4316ca771d1e1ba5127865878bde2b675c Mon Sep 17 00:00:00 2001 From: Matthew Kaney Date: Thu, 2 May 2024 10:02:13 -0400 Subject: [PATCH 8/9] Try getting json-schema-to-ts working again --- core/state/schema.ts | 43 ++++++++++--------- package-lock.json | 98 +++----------------------------------------- package.json | 3 +- 3 files changed, 29 insertions(+), 115 deletions(-) diff --git a/core/state/schema.ts b/core/state/schema.ts index 0c205b6..68f5016 100644 --- a/core/state/schema.ts +++ b/core/state/schema.ts @@ -35,26 +35,29 @@ interface ArrayValueSchema extends BaseValueSchema { interface BaseValueSchema {} -export type FromSchema = { - [Property in keyof S]: S[Property] extends NumberValueSchema - ? number - : S[Property] extends StringValueSchema - ? string - : S[Property] extends BooleanValueSchema - ? boolean - : S[Property] extends ArrayValueSchema - ? FromArraySchema - : never; -}; - -type FromArraySchema = - S["items"] extends NumberValueSchema - ? number[] - : S["items"] extends StringValueSchema - ? string[] - : S["items"] extends BooleanValueSchema - ? boolean[] - : never; +export { FromSchema } from "json-schema-to-ts"; +import { FromSchema } from "json-schema-to-ts"; + +// export type FromSchema = { +// [Property in keyof S]: S[Property] extends NumberValueSchema +// ? number +// : S[Property] extends StringValueSchema +// ? string +// : S[Property] extends BooleanValueSchema +// ? boolean +// : S[Property] extends ArrayValueSchema +// ? FromArraySchema +// : never; +// }; + +// type FromArraySchema = +// S["items"] extends NumberValueSchema +// ? number[] +// : S["items"] extends StringValueSchema +// ? string[] +// : S["items"] extends BooleanValueSchema +// ? boolean[] +// : never; export function getDefaults( schema: S diff --git a/package-lock.json b/package-lock.json index 8a8f227..555a5a3 100644 --- a/package-lock.json +++ b/package-lock.json @@ -29,8 +29,7 @@ "codemirror-json-schema": "^0.4.4", "commander": "^9.5.0", "firebase": "^9.23.0", - "json-schema-library": "^9.1.2", - "json-schema-to-ts": "^2.9.2", + "json-schema-to-ts": "^3.0.1", "rfdc": "^1.3.1", "rxjs": "^7.8.1" }, @@ -8138,11 +8137,6 @@ "minimatch": "^3.0.4" } }, - "node_modules/discontinuous-range": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/discontinuous-range/-/discontinuous-range-1.0.0.tgz", - "integrity": "sha512-c68LpLbO+7kP/b1Hr1qs8/BJ09F5khZGTxqxZuhzxpmwJKOgRFHJWIb9/KmqnqHhLdO55aOxFH/EGBvUQbL/RQ==" - }, "node_modules/dmg-builder": { "version": "24.6.4", "resolved": "https://registry.npmjs.org/dmg-builder/-/dmg-builder-24.6.4.tgz", @@ -12151,28 +12145,13 @@ "integrity": "sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA==", "dev": true }, - "node_modules/json-schema-library": { - "version": "9.1.2", - "resolved": "https://registry.npmjs.org/json-schema-library/-/json-schema-library-9.1.2.tgz", - "integrity": "sha512-uQnFb2V+VakLl6XIGGtUQzfjkP31f/dCT5lJq9NOUdypSSpjbWL/V0R2KvoNJp3hU8VErwh9DqVoZPqlC+B3IA==", - "dependencies": { - "@sagold/json-pointer": "^5.1.1", - "@sagold/json-query": "^6.1.1", - "deepmerge": "^4.3.1", - "fast-copy": "^3.0.1", - "fast-deep-equal": "^3.1.3", - "smtp-address-parser": "1.0.10", - "valid-url": "^1.0.9" - } - }, "node_modules/json-schema-to-ts": { - "version": "2.9.2", - "resolved": "https://registry.npmjs.org/json-schema-to-ts/-/json-schema-to-ts-2.9.2.tgz", - "integrity": "sha512-h9WqLkTVpBbiaPb5OmeUpz/FBLS/kvIJw4oRCPiEisIu2WjMh+aai0QIY2LoOhRFx5r92taGLcerIrzxKBAP6g==", + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/json-schema-to-ts/-/json-schema-to-ts-3.0.1.tgz", + "integrity": "sha512-ANphQxnKbzLWPeYDmdoci8C9g9ttpfMx8etTlJJ8UCEmNXH9jxGkn3AAbMe+lR4N5OG/01nYxPrDyugLdsRt+A==", "dependencies": { "@babel/runtime": "^7.18.3", - "@types/json-schema": "^7.0.9", - "ts-algebra": "^1.2.0" + "ts-algebra": "^1.2.2" }, "engines": { "node": ">=16" @@ -13446,11 +13425,6 @@ "mkdirp": "bin/cmd.js" } }, - "node_modules/moo": { - "version": "0.5.2", - "resolved": "https://registry.npmjs.org/moo/-/moo-0.5.2.tgz", - "integrity": "sha512-iSAJLHYKnX41mKcJKjqvnAN9sf0LMDTXDEvFv+ffuRR9a1MIuXLjMNL6EsnDHSkKLTWNqQQ5uo61P4EbU4NU+Q==" - }, "node_modules/morgan": { "version": "1.10.0", "resolved": "https://registry.npmjs.org/morgan/-/morgan-1.10.0.tgz", @@ -13546,32 +13520,6 @@ "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", "dev": true }, - "node_modules/nearley": { - "version": "2.20.1", - "resolved": "https://registry.npmjs.org/nearley/-/nearley-2.20.1.tgz", - "integrity": "sha512-+Mc8UaAebFzgV+KpI5n7DasuuQCHA89dmwm7JXw3TV43ukfNQ9DnBH3Mdb2g/I4Fdxc26pwimBWvjIw0UAILSQ==", - "dependencies": { - "commander": "^2.19.0", - "moo": "^0.5.0", - "railroad-diagrams": "^1.0.0", - "randexp": "0.4.6" - }, - "bin": { - "nearley-railroad": "bin/nearley-railroad.js", - "nearley-test": "bin/nearley-test.js", - "nearley-unparse": "bin/nearley-unparse.js", - "nearleyc": "bin/nearleyc.js" - }, - "funding": { - "type": "individual", - "url": "https://nearley.js.org/#give-to-nearley" - } - }, - "node_modules/nearley/node_modules/commander": { - "version": "2.20.3", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", - "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==" - }, "node_modules/negotiator": { "version": "0.6.3", "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", @@ -14863,23 +14811,6 @@ "right-now": "^1.0.0" } }, - "node_modules/railroad-diagrams": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/railroad-diagrams/-/railroad-diagrams-1.0.0.tgz", - "integrity": "sha512-cz93DjNeLY0idrCNOH6PviZGRN9GJhsdm9hpn1YCS879fj4W+x5IFJhhkRZcwVgMmFF7R82UA/7Oh+R8lLZg6A==" - }, - "node_modules/randexp": { - "version": "0.4.6", - "resolved": "https://registry.npmjs.org/randexp/-/randexp-0.4.6.tgz", - "integrity": "sha512-80WNmd9DA0tmZrw9qQa62GPPWfuXJknrmVmLcxvq4uZBdYqb1wYoKTmnlGUchvVWe0XiLupYkBoXVOxz3C8DYQ==", - "dependencies": { - "discontinuous-range": "1.0.0", - "ret": "~0.1.10" - }, - "engines": { - "node": ">=0.12" - } - }, "node_modules/range-parser": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", @@ -15309,14 +15240,6 @@ "node": ">=8" } }, - "node_modules/ret": { - "version": "0.1.15", - "resolved": "https://registry.npmjs.org/ret/-/ret-0.1.15.tgz", - "integrity": "sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg==", - "engines": { - "node": ">=0.12" - } - }, "node_modules/retry": { "version": "0.13.1", "resolved": "https://registry.npmjs.org/retry/-/retry-0.13.1.tgz", @@ -15814,17 +15737,6 @@ "npm": ">= 3.0.0" } }, - "node_modules/smtp-address-parser": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/smtp-address-parser/-/smtp-address-parser-1.0.10.tgz", - "integrity": "sha512-Osg9LmvGeAG/hyao4mldbflLOkkr3a+h4m1lwKCK5U8M6ZAr7tdXEz/+/vr752TSGE4MNUlUl9cIK2cB8cgzXg==", - "dependencies": { - "nearley": "^2.20.1" - }, - "engines": { - "node": ">=0.10" - } - }, "node_modules/socks": { "version": "2.7.1", "resolved": "https://registry.npmjs.org/socks/-/socks-2.7.1.tgz", diff --git a/package.json b/package.json index 6fd24a1..f0bbd63 100644 --- a/package.json +++ b/package.json @@ -53,8 +53,7 @@ "codemirror-json-schema": "^0.4.4", "commander": "^9.5.0", "firebase": "^9.23.0", - "json-schema-library": "^9.1.2", - "json-schema-to-ts": "^2.9.2", + "json-schema-to-ts": "^3.0.1", "rfdc": "^1.3.1", "rxjs": "^7.8.1" }, From 8f54b9fc421d780a80c24d9c26a7dfe31303f9a0 Mon Sep 17 00:00:00 2001 From: Matthew Kaney Date: Wed, 8 May 2024 09:39:07 -0400 Subject: [PATCH 9/9] Migrate back to JSON schema --- core/state/index.ts | 4 +- core/state/schema.ts | 99 +++++++--------------------- packages/languages/tidal/ghci.ts | 2 +- packages/languages/tidal/settings.ts | 10 +-- 4 files changed, 34 insertions(+), 81 deletions(-) diff --git a/core/state/index.ts b/core/state/index.ts index 00ad287..ed61a01 100644 --- a/core/state/index.ts +++ b/core/state/index.ts @@ -1,6 +1,8 @@ import { EventEmitter } from "@core/events"; -import { FromSchema, SettingsSchema, getDefaults, getValid } from "./schema"; +import { getDefaults, getValid } from "./schema"; + +import { SettingsSchema, FromSchema } from "./schema"; interface StateEvents { change: FromSchema; diff --git a/core/state/schema.ts b/core/state/schema.ts index 68f5016..4a839a9 100644 --- a/core/state/schema.ts +++ b/core/state/schema.ts @@ -1,71 +1,22 @@ -export interface SettingsSchema { - [name: string]: ValueSchema; -} - -type ValueSchema = - | NumberValueSchema - | StringValueSchema - | BooleanValueSchema - | ArrayValueSchema; - -interface NumberValueSchema extends BaseValueSchema { - type: "number"; - default?: number; -} - -interface StringValueSchema extends BaseValueSchema { - type: "string"; - default?: string; -} +import { JSONSchema, FromSchema as FromJSONSchema } from "json-schema-to-ts"; -interface BooleanValueSchema extends BaseValueSchema { - type: "boolean"; - default?: boolean; -} - -type PrimitiveValueSchema = - | NumberValueSchema - | StringValueSchema - | BooleanValueSchema; - -interface ArrayValueSchema extends BaseValueSchema { - type: "array"; - items: PrimitiveValueSchema; +export interface SettingsSchema { + properties: Readonly>; } -interface BaseValueSchema {} - -export { FromSchema } from "json-schema-to-ts"; -import { FromSchema } from "json-schema-to-ts"; - -// export type FromSchema = { -// [Property in keyof S]: S[Property] extends NumberValueSchema -// ? number -// : S[Property] extends StringValueSchema -// ? string -// : S[Property] extends BooleanValueSchema -// ? boolean -// : S[Property] extends ArrayValueSchema -// ? FromArraySchema -// : never; -// }; - -// type FromArraySchema = -// S["items"] extends NumberValueSchema -// ? number[] -// : S["items"] extends StringValueSchema -// ? string[] -// : S["items"] extends BooleanValueSchema -// ? boolean[] -// : never; +export type FromSchema = FromJSONSchema< + S & { type: "object" } +> & + object; -export function getDefaults( - schema: S -): FromSchema { +export function getDefaults< + S extends SettingsSchema, + SchemaData = FromSchema +>(schema: S): SchemaData { const defaults: any = {}; - for (let key in schema) { - let valueOptions = schema[key]; + for (let key in schema.properties) { + let valueOptions = schema.properties[key]; switch (valueOptions.type) { case "number": @@ -86,13 +37,13 @@ export function getDefaults( return defaults; } -export function getValid( +export function getValid>( schema: S, data: any -): Partial> { +): Partial { const validData: any = {}; - function getValidPrimitive(schema: PrimitiveValueSchema, value: any) { + function getValidPrimitive(schema: JSONSchema & object, value: any) { if (schema.type === "number" && typeof value === "number") { return value; } else if (schema.type === "string" && typeof value === "string") { @@ -104,23 +55,21 @@ export function getValid( if (typeof data === "object") { for (let key in data) { - if (key in schema) { + if (key in schema.properties) { + let prop = schema.properties[key]; if ( - schema[key].type === "number" || - schema[key].type === "string" || - schema[key].type === "boolean" + prop.type === "number" || + prop.type === "string" || + prop.type === "boolean" ) { - const value = getValidPrimitive( - schema[key] as PrimitiveValueSchema, - data[key] - ); + const value = getValidPrimitive(prop, data[key]); if (value !== undefined) { validData[key] = value; } - } else if (schema[key].type === "array") { + } else if (prop.type === "array" && typeof prop.items === "object") { const value = data[key]; if (Array.isArray(value)) { - const arraySchema = (schema[key] as ArrayValueSchema).items; + const arraySchema = prop.items; validData[key] = value.filter((v) => getValidPrimitive(arraySchema, v) ); diff --git a/packages/languages/tidal/ghci.ts b/packages/languages/tidal/ghci.ts index 7fca671..7e674a7 100644 --- a/packages/languages/tidal/ghci.ts +++ b/packages/languages/tidal/ghci.ts @@ -117,7 +117,7 @@ export class GHCI extends Engine { this.sendFile(await this.defaultBootfile()); } - for (let path of bootFiles) { + for (let path of bootFiles ?? []) { try { this.sendFile(path); } catch (err) { diff --git a/packages/languages/tidal/settings.ts b/packages/languages/tidal/settings.ts index 225c8a6..7b86008 100644 --- a/packages/languages/tidal/settings.ts +++ b/packages/languages/tidal/settings.ts @@ -1,8 +1,10 @@ import { SettingsSchema } from "@core/state/schema"; export const TidalSettingsSchema = { - // environment: { const: "ghci" }, - "tidal.boot.useDefaultFile": { type: "boolean", default: true }, - "tidal.boot.customFiles": { type: "array", items: { type: "string" } }, - "tidal.boot.disableEditorIntegration": { type: "boolean", default: false }, + properties: { + // environment: { const: "ghci" }, + "tidal.boot.useDefaultFile": { type: "boolean", default: true }, + "tidal.boot.customFiles": { type: "array", items: { type: "string" } }, + "tidal.boot.disableEditorIntegration": { type: "boolean", default: false }, + }, } as const satisfies SettingsSchema;