diff --git a/libxtracfg/js/types/generate.ts b/libxtracfg/js/types/generate.ts deleted file mode 100644 index 73604d9..0000000 --- a/libxtracfg/js/types/generate.ts +++ /dev/null @@ -1,72 +0,0 @@ -import { mkdirSync } from "fs"; - -import { generateJsonSchema } from "./json-schema/index.ts"; -import { generateJsValidators } from "./js/index.ts"; -import { generateJavaClasses } from "./generate-java-classes.ts"; -import { generateGo } from "./go/index.ts"; -import { Result, write } from "./common/index.ts"; - -const basePath = "./src/gen"; -const javaBasePath = "../../java/src/main/java"; -const javaPkg = "de.ii.xtraplatform.cli.gen"; -const goBasePath = "../../go"; -const goPkg = "gen"; - -const dataNs = ["Command", "Options", "Result", "Misc"]; - -type SchemaResult = Result & { obj: any }; - -type GenCfg = { - basePath: string; -}; - -type GenLangCfg = GenCfg & { - pkg: string; -}; - -type Cfg = { - java?: GenLangCfg; - go?: GenLangCfg; - verbose?: boolean; -}; - -//TODO: restructure -> xtracfg/bin, xtracfg/lib/*, gen in root? -const generateAll = (cfg: Cfg) => { - console.log("Generating code from TypeScript definitions"); - - const { verbose, java: javaCfg, go: goCfg } = cfg; - - mkdirSync(basePath, { recursive: true }); - - const schema: SchemaResult = generateJsonSchema( - "JSON Schema", - "./src/index.ts", - dataNs - ); - - write(schema, basePath, verbose); - - const js: Result = generateJsValidators(schema.obj); - - write(js, basePath, verbose); - - const java: Result = generateJavaClasses(schema.obj, javaPkg, dataNs); - - write(java, javaBasePath, verbose); - - const go: Result = generateGo("Go Client", schema.obj, goPkg, dataNs); - - write(go, goBasePath, verbose); -}; - -generateAll({ - verbose: true, - java: { - basePath: "../../java/src/main/java", - pkg: "de.ii.xtraplatform.cli.gen", - }, - go: { - basePath: "../../go", - pkg: "gen", - }, -}); diff --git a/libxtracfg/js/types/index.ts b/libxtracfg/js/types/index.ts new file mode 100644 index 0000000..4abe6f8 --- /dev/null +++ b/libxtracfg/js/types/index.ts @@ -0,0 +1,32 @@ +import { resolve } from "path"; + +import { generate } from "./ts2x/index.ts"; +import { onClass, onNamespace, getAdditional } from "./java.ts"; + +//TODO: restructure -> xtracfg/bin, xtracfg/lib/*, gen in root? + +generate({ + source: resolve("./src/index.ts"), + verbose: false, + schema: { + basePath: "./src/gen", + }, + go: { + basePath: "../../go", + pkg: "gen", + filePrefixes: { Command: "cmd-", Options: "opts-", Result: "res-" }, + }, + java: { + basePath: "../../java/src/main/java", + pkg: "de.ii.xtraplatform.cli.gen", + classSuffixes: ["Command", "Options", "Result"], + additionalClasses: getAdditional("de.ii.xtraplatform.cli.gen"), + hooks: { + onNamespace, + onClass, + }, + }, + ts: { + basePath: "./src/gen", + }, +}); diff --git a/libxtracfg/js/types/generate-java-classes.ts b/libxtracfg/js/types/java.ts similarity index 71% rename from libxtracfg/js/types/generate-java-classes.ts rename to libxtracfg/js/types/java.ts index 5ef3afa..8a40920 100644 --- a/libxtracfg/js/types/generate-java-classes.ts +++ b/libxtracfg/js/types/java.ts @@ -1,50 +1,36 @@ -import { Definition, Generator, Result } from "./common/index.ts"; -import { generateClass, generateJava } from "./java/index.ts"; +import { ClassGenerator, Definition, Generator } from "./ts2x/index.ts"; + +//TODO: generalize as java commandHandler option -const suffixNs = ["Command", "Options"]; const commandNs = "Command"; -const baseResult = "BaseResult"; +const baseResult = "Result"; const failureResult = "FailureResult"; -export const generateJavaClasses = ( - schema: Definition, - pkg: string, - dataNs: string[] -): Result => { - const commands: string[] = []; - let initCommand: string | undefined; - let baseCommand: string | undefined; - - const onNs = ( - ns: string, - nsInterface?: string, - nsInterfaceDef?: Definition - ) => { - if (ns === commandNs) { - baseCommand = nsInterface; - } - }; +const commands: string[] = []; +let initCommand: string | undefined; +let baseCommand: string | undefined; + +export const onNamespace = ( + ns: string, + nsInterface?: string, + nsInterfaceDef?: Definition +) => { + if (ns === commandNs) { + baseCommand = nsInterface; + } +}; - const onClass = (ns: string, name: string, def?: any) => { - if (ns === commandNs) { - if (def?.javaContextInit) { - initCommand = name; - } else { - commands.push(name); - } +export const onClass = (ns: string, name: string, def?: any) => { + if (ns === commandNs) { + if (def?.javaContextInit) { + initCommand = name; + } else { + commands.push(name); } - }; - - const result = generateJava( - "Java Backend", - schema, - pkg, - dataNs, - suffixNs, - onNs, - onClass - ); + } +}; +export const getAdditional = (pkg: string) => (): ClassGenerator[] => { if (!initCommand) { throw new Error("No init command found"); } @@ -52,21 +38,19 @@ export const generateJavaClasses = ( throw new Error("No base command found"); } - result.files.push( - generateClass( - "Handler", + return [ + { + name: "Handler", pkg, - generateHandler( + codeOrGenerator: generateHandler( commands, initCommand, baseCommand, baseResult, failureResult - ) - ) - ); - - return result; + ), + }, + ]; }; const generateHandler = diff --git a/libxtracfg/js/types/package.json b/libxtracfg/js/types/package.json index 8f36807..98157ab 100644 --- a/libxtracfg/js/types/package.json +++ b/libxtracfg/js/types/package.json @@ -8,11 +8,11 @@ "private": true, "scripts": { "test": "tsx ./test.ts", - "parse": "tsx ./generate.ts" + "parse": "tsx ./index.ts" }, "devDependencies": { "@tsconfig/node22": "^22.0.0", - "tsx": "^4.7.2" + "tsx": "4.19.2" }, "dependencies": { "ajv": "^8.17.1", diff --git a/libxtracfg/js/types/src/index.ts b/libxtracfg/js/types/src/index.ts index b3a135b..67c64b2 100644 --- a/libxtracfg/js/types/src/index.ts +++ b/libxtracfg/js/types/src/index.ts @@ -1,136 +1,137 @@ -export namespace Enums { - export enum Main { - Connect = "Connect", - Info = "Info", - Check = "Check", - } - export enum StoreSubs { - Cfg = "Cfg", - Entities = "Entities", - Layout = "Layout", - } - export enum MessageType { - ERROR = "ERROR", - WARNING = "WARNING", - SUCCESS = "SUCCESS", - INFO = "INFO", - CONFIRMATION = "CONFIRMATION", +export namespace Ts2x { + export namespace Enums { + export enum Main { + Connect = "Connect", + Info = "Info", + Check = "Check", + } + export enum StoreSubs { + Cfg = "Cfg", + Entities = "Entities", + Layout = "Layout", + } + export enum MessageType { + ERROR = "ERROR", + WARNING = "WARNING", + SUCCESS = "SUCCESS", + INFO = "INFO", + CONFIRMATION = "CONFIRMATION", + } } -} -//TODO: java specialty? -export namespace Consts { - export type Connect = "Connect"; - export type Info = "Info"; - export type Check = "Check"; -} + export namespace Consts { + //export type Connect = "Connect"; + //export type Info = "Info"; + //export type Check = "Check"; + } -export namespace Options { - /** - * @interface true - */ - export class Options { + export namespace Options { /** - * @default ./ + * @interface true */ - readonly source?: string; - /** - * @default false - */ - readonly verbose?: boolean; - /** - * @default false - */ - readonly debug?: boolean; - } + export class Options { + /** + * @default ./ + */ + readonly source?: string; + /** + * @default false + */ + readonly verbose?: boolean; + /** + * @default false + */ + readonly debug?: boolean; + } - export class Base extends Options {} + export class Base extends Options {} + + export class Store extends Options { + /** + * @optional true + */ + readonly subcommand?: Enums.StoreSubs; + /** + * @default false + */ + readonly ignoreRedundant?: boolean; + /** + * @optional true + */ + readonly path?: string; + } + } - export class Store extends Options { + export namespace Command { /** - * @optional true + * @interface true + * @discriminator command */ - readonly subcommand?: Enums.StoreSubs; - /** - * @default false - */ - readonly ignoreRedundant?: boolean; + //TODO: should be abstract so that it cannot be instantiated, but that breaks the JSON schema generation + export class Command { + readonly command: Enums.Main; + readonly options: Options.Options; + + constructor(command: Enums.Main, options: Options.Options) { + this.command = command; + this.options = options; + } + } + /** - * @optional true + * @javaContextInit true */ - readonly path?: string; - } -} + export class Connect extends Command { + declare readonly command: Enums.Main.Connect; + declare readonly options: Options.Options; -export namespace Command { - /** - * @interface true - * @discriminator command - */ - //TODO: should be abstract so that it cannot be instantiated, but that breaks the JSON schema generation - export class Command { - readonly command: Enums.Main; - readonly options: Options.Options; - - constructor(command: Enums.Main, options: Options.Options) { - this.command = command; - this.options = options; + constructor(options: Options.Options = {}) { + super(Enums.Main.Connect, options); + } } - } - /** - * @javaContextInit true - */ - export class Connect extends Command { - declare readonly command: Enums.Main.Connect; - declare readonly options: Options.Options; + export class Info extends Command { + declare readonly command: Enums.Main.Info; + declare readonly options: Options.Base; - constructor(options: Options.Options = {}) { - super(Enums.Main.Connect, options); + constructor(options: Options.Base = {}) { + super(Enums.Main.Info, options); + } } - } - export class Info extends Command { - declare readonly command: Enums.Main.Info; - declare readonly options: Options.Base; + export class Check extends Command { + declare readonly command: Enums.Main.Check; + declare readonly options: Options.Store; - constructor(options: Options.Base = {}) { - super(Enums.Main.Info, options); + constructor(options: Options.Store = {}) { + super(Enums.Main.Check, options); + } } } - export class Check extends Command { - declare readonly command: Enums.Main.Check; - declare readonly options: Options.Store; + export namespace Result { + export interface Regular { + /** + * @minItems 1 + */ + readonly messages: Misc.Message[]; + readonly details?: { [key: string]: any }; + } - constructor(options: Options.Store = {}) { - super(Enums.Main.Check, options); + export interface Failure { + readonly error: string; } - } -} -export namespace Result { - export interface Regular { /** - * @minItems 1 + * @interface true */ - readonly messages: Misc.Message[]; - readonly details?: { [key: string]: any }; - } - - export interface Failure { - readonly error: string; + export type Result = Regular | Failure; } - /** - * @interface true - */ - export type Result = Regular | Failure; -} - -export namespace Misc { - export interface Message { - readonly status: Enums.MessageType; - readonly text: string; + export namespace Misc { + export interface Message { + readonly status: Enums.MessageType; + readonly text: string; + } } } diff --git a/libxtracfg/js/types/test.ts b/libxtracfg/js/types/test.ts index c2b214a..6876cdb 100644 --- a/libxtracfg/js/types/test.ts +++ b/libxtracfg/js/types/test.ts @@ -1,9 +1,9 @@ -import { Command } from "./src/index.ts"; +import { Ts2x } from "./src/index.ts"; import { validateCommand } from "./src/gen/validate.ts"; const options = { source: "libxtracfg", foo: "bar" }; -const info = new Command.Connect(options); +const info = new Ts2x.Command.Connect(options); //info.command = "info"; //info.options.source = "libxtracfg"; diff --git a/libxtracfg/js/types/ts2x/common/index.ts b/libxtracfg/js/types/ts2x/common/index.ts new file mode 100644 index 0000000..e3e0bda --- /dev/null +++ b/libxtracfg/js/types/ts2x/common/index.ts @@ -0,0 +1 @@ +export type Generator = (name: string, pkg: string) => string; diff --git a/libxtracfg/js/types/ts2x/common/io.ts b/libxtracfg/js/types/ts2x/common/io.ts new file mode 100644 index 0000000..67ec3a2 --- /dev/null +++ b/libxtracfg/js/types/ts2x/common/io.ts @@ -0,0 +1,38 @@ +import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs"; +import { dirname } from "path"; + +export type File = { path: string; content: string }; + +export type Result = { name: string; files: File[] }; + +export const write = ( + result: Result, + basePath: string, + verbose?: boolean +): boolean => { + let noChanges = true; + const messages = []; + + for (const { path, content } of result.files) { + const fullPath = `${basePath}/${path}`; + const oldContent = existsSync(fullPath) + ? readFileSync(fullPath, "utf8") + : ""; + + if (oldContent === content) { + if (verbose) messages.push(` - ${path}: NO CHANGES`); + continue; + } + + mkdirSync(dirname(fullPath), { recursive: true }); + writeFileSync(fullPath, content); + noChanges = false; + + if (verbose) messages.push(` - ${path}: UPDATED`); + } + + console.log(`- ${result.name}: ${noChanges ? "NO CHANGES" : "UPDATED"}`); + messages.forEach((msg) => console.log(msg)); + + return noChanges; +}; diff --git a/libxtracfg/js/types/common/index.ts b/libxtracfg/js/types/ts2x/common/schema.ts similarity index 76% rename from libxtracfg/js/types/common/index.ts rename to libxtracfg/js/types/ts2x/common/schema.ts index c2d837b..58c54a5 100644 --- a/libxtracfg/js/types/common/index.ts +++ b/libxtracfg/js/types/ts2x/common/schema.ts @@ -1,21 +1,7 @@ import { Definition, DefinitionOrBoolean } from "typescript-json-schema"; -import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs"; -import { dirname } from "path"; - -export { Definition, DefinitionOrBoolean }; export type Defs = [string, Definition][]; -export type Generator = (name: string, pkg: string) => string; - -type Files = { path: string; content: string }[]; - -export type Result = { name: string; files: Files }; - -export const defs = (entry: DefinitionOrBoolean): Defs => { - return typeof entry !== "boolean" ? Object.entries(entry as Definition) : []; -}; - export const constsNs = "Consts"; export const enumsNs = "Enums"; export const constsPath = "/Consts/"; @@ -31,36 +17,8 @@ export enum DataType { OPTIONAL = "optional", } -export const write = ( - result: Result, - basePath: string, - verbose?: boolean -): boolean => { - let noChanges = true; - const messages = []; - - for (const { path, content } of result.files) { - const fullPath = `${basePath}/${path}`; - const oldContent = existsSync(fullPath) - ? readFileSync(fullPath, "utf8") - : ""; - - if (oldContent === content) { - if (verbose) messages.push(` - ${path}: NO CHANGES`); - continue; - } - - mkdirSync(dirname(fullPath), { recursive: true }); - writeFileSync(fullPath, content); - noChanges = false; - - if (verbose) messages.push(` - ${path}: UPDATED`); - } - - console.log(`- ${result.name}: ${noChanges ? "NO CHANGES" : "UPDATED"}`); - messages.forEach((msg) => console.log(msg)); - - return noChanges; +export const defs = (entry: DefinitionOrBoolean): Defs => { + return typeof entry !== "boolean" ? Object.entries(entry as Definition) : []; }; export const getType = ( @@ -121,7 +79,7 @@ export const getType = ( export const getValue = ( schema: DefinitionOrBoolean, prefix: string = "", - stringNotEnum?: boolean + ignoreQualifier?: boolean ): string => { if (typeof schema === "boolean") { return schema ? "true" : "false"; @@ -137,7 +95,7 @@ export const getValue = ( (schema.$ref.includes(constsPath) || schema.$ref.includes(enumsPath)) ) { const name = - stringNotEnum && schema.$ref.includes(".") + ignoreQualifier && schema.$ref.includes(".") ? schema.$ref.substring(schema.$ref.lastIndexOf(".") + 1) : schema.$ref.substring(schema.$ref.lastIndexOf("/") + 1); return `${prefix}${name}`; diff --git a/libxtracfg/js/types/go/index.ts b/libxtracfg/js/types/ts2x/go/index.ts similarity index 83% rename from libxtracfg/js/types/go/index.ts rename to libxtracfg/js/types/ts2x/go/index.ts index 0612745..e07e5b3 100644 --- a/libxtracfg/js/types/go/index.ts +++ b/libxtracfg/js/types/ts2x/go/index.ts @@ -1,29 +1,26 @@ +import { Definition, DefinitionOrBoolean } from "typescript-json-schema"; import { constsNs, DataType, defs, - Definition, - DefinitionOrBoolean, Defs, enumsNs, firstLetterUpperCase, - Generator, - Result, getDefault, getType, getValue, isConst, isEnum, -} from "../common/index.ts"; - -//TODO: move -const prefixNs = { Command: "cmd-", Options: "opts-", Result: "res-" }; +} from "../common/schema.ts"; +import { Result } from "../common/io.ts"; +import { Generator } from "../common/index.ts"; export const generateGo = ( name: string, schema: Definition, pkg: string, - dataNs: string[] + dataNs: string[], + filePrefixes: Record = {} ) => { const definitions = schema.definitions || {}; let consts: Defs = []; @@ -50,7 +47,7 @@ export const generateGo = ( } result.files.push( - generateFile(intface, ns, pkg, generateInterface(def)) + generateFile(intface, ns, pkg, filePrefixes, generateInterface(def)) ); } } @@ -58,19 +55,35 @@ export const generateGo = ( for (const [key, def] of Object.entries(entries)) { if (dataNs.includes(ns) && def && !def.interface) { result.files.push( - generateFile(key, ns, pkg, generateDataStruct(def, nsInterface)) + generateFile( + key, + ns, + pkg, + filePrefixes, + generateDataStruct(def, nsInterface) + ) ); } } } result.files.push( - generateFile("consts", "", pkg + "/consts", generateConsts(consts)) + generateFile( + "consts", + "", + pkg + "/consts", + filePrefixes, + generateConsts(consts) + ) ); - result.files.push(generateFile("enums", "", pkg, generateEnums(enums))); + result.files.push( + generateFile("enums", "", pkg, filePrefixes, generateEnums(enums)) + ); - result.files.push(generateFile("util", "", pkg, generateUtil())); + result.files.push( + generateFile("util", "", pkg, filePrefixes, generateUtil()) + ); return result; }; @@ -78,10 +91,10 @@ export const generateGo = ( const getName = ( name: string, ns?: string, - prefixNs?: Record + filePrefixes?: Record ) => { - return ns && prefixNs && prefixNs[ns] - ? `${prefixNs[ns]}${name.toLowerCase()}` + return ns && filePrefixes && filePrefixes[ns] + ? `${filePrefixes[ns]}${name.toLowerCase()}` : name.toLowerCase(); }; @@ -89,10 +102,11 @@ const generateFile = ( name: string, ns: string, pkg: string, + filePrefixes: Record, generate: Generator ) => { const dir = pkg.replaceAll(".", "/"); - const file = getName(name, ns, prefixNs); + const file = getName(name, ns, filePrefixes); const code = generate(getName(name), pkg); return { path: `${dir}/${file}.go`, content: code }; diff --git a/libxtracfg/js/types/ts2x/index.ts b/libxtracfg/js/types/ts2x/index.ts new file mode 100644 index 0000000..279cd39 --- /dev/null +++ b/libxtracfg/js/types/ts2x/index.ts @@ -0,0 +1,106 @@ +import { Result, write } from "./common/io.ts"; +import { constsNs, enumsNs } from "./common/schema.ts"; +import { generateGo } from "./go/index.ts"; +import { ClassGenerators, generateJava, Hooks } from "./java/index.ts"; +import { generateJsValidators } from "./js/index.ts"; +import { generateJsonSchema, SchemaResult } from "./json-schema/index.ts"; + +export { + type Definition, + type DefinitionOrBoolean, +} from "typescript-json-schema"; + +export { + type ClassGenerators, + type ClassGenerator, + type Hooks, + type OnNamespace, + type OnClass, +} from "./java/index.ts"; + +export { type Generator } from "./common/index.ts"; +export { write, type File, type Result } from "./common/io.ts"; + +export type GenCfg = { + basePath: string; + label?: string; +}; + +export type GenLangCfg = GenCfg & { + pkg: string; +}; + +export type GoCfg = GenLangCfg & { + filePrefixes: Record; +}; + +export type JavaCfg = GenLangCfg & { + classSuffixes?: string[]; + additionalClasses?: ClassGenerators; + hooks?: Hooks; +}; + +export type Cfg = { + source: string; + go?: GoCfg; + java?: JavaCfg; + ts?: GenCfg; + schema?: GenCfg; + verbose?: boolean; +}; + +export const generate = (cfg: Cfg) => { + console.log("Generating code from TypeScript definitions"); + + const { source, verbose, schema: schemaCfg, java, go, ts } = cfg; + + const schema: SchemaResult = generateJsonSchema( + schemaCfg?.label || "JSON Schema", + source + ); + + if (schemaCfg) { + write(schema, schemaCfg.basePath, verbose); + } + + //TODO: do we need dedicated namespaces for consts and enums? + const dataNs = Array.from(schema.namespaces).filter( + (ns) => ns !== constsNs && ns !== enumsNs + ); + //console.log("Data namespaces:", dataNs); + + if (ts) { + const code: Result = generateJsValidators( + schema.obj, + ts.label || "Typescript code" + ); + + write(code, ts.basePath, verbose); + } + + if (java) { + const code: Result = generateJava( + java.label || "Java code", + schema.obj, + java.pkg, + dataNs, + java.classSuffixes || [], + java.additionalClasses || [], + java.hooks + ); + + write(code, java.basePath, verbose); + } + + if (go) { + const code: Result = generateGo( + go.label || "Go code", + schema.obj, + go.pkg, + dataNs, + go.filePrefixes + ); + + write(code, go.basePath, verbose); + } +}; diff --git a/libxtracfg/js/types/java/data.ts b/libxtracfg/js/types/ts2x/java/data.ts similarity index 96% rename from libxtracfg/js/types/java/data.ts rename to libxtracfg/js/types/ts2x/java/data.ts index 18ecc58..76c7727 100644 --- a/libxtracfg/js/types/java/data.ts +++ b/libxtracfg/js/types/ts2x/java/data.ts @@ -1,17 +1,15 @@ +import { Definition, DefinitionOrBoolean } from "typescript-json-schema"; import { - Definition, - DefinitionOrBoolean, DataType, Defs, defs, - firstLetterUpperCase, - Generator, getDefault, getType, getValue, isConst, isEnum, -} from "../common/index.ts"; +} from "../common/schema.ts"; +import { Generator } from "../common/index.ts"; const identifiersPrefix = "Identifiers."; @@ -210,7 +208,7 @@ import shadow.com.fasterxml.jackson.annotation.JsonTypeInfo; @JsonSubTypes({`; for (const [key, value] of Object.entries(discriminators)) { code += ` - @JsonSubTypes.Type(value = ${key}.class, name = ${value}),`; + @JsonSubTypes.Type(value = ${key}.class, name = "${value}"),`; } code += ` diff --git a/libxtracfg/js/types/java/index.ts b/libxtracfg/js/types/ts2x/java/index.ts similarity index 62% rename from libxtracfg/js/types/java/index.ts rename to libxtracfg/js/types/ts2x/java/index.ts index 9a0aef3..84c48ea 100644 --- a/libxtracfg/js/types/java/index.ts +++ b/libxtracfg/js/types/ts2x/java/index.ts @@ -1,28 +1,42 @@ -import { - Definition, - Generator, - Defs, - Result, - constsNs, - enumsNs, - defs, - getValue, -} from "../common/index.ts"; +import { Definition } from "typescript-json-schema"; +import { Defs, constsNs, enumsNs, defs, getValue } from "../common/schema.ts"; +import { Result, File } from "../common/io.ts"; +import { Generator } from "../common/index.ts"; import { generateDataRecord, generateIdentifiersClass, generateInterface, } from "./data.ts"; -//TODO: remove suffixNs +export type OnNamespace = ( + ns: string, + nsInterface?: string, + nsInterfaceDef?: Definition +) => void; + +export type OnClass = (ns: string, name: string, def?: any) => void; + +export type ClassGenerator = { + name: string; + pkg: string; + codeOrGenerator: string | Generator; +}; + +export type ClassGenerators = (() => ClassGenerator[]) | ClassGenerator[]; + +export type Hooks = { + onNamespace?: OnNamespace; + onClass?: OnClass; +}; + export const generateJava = ( name: string, schema: Definition, pkg: string, dataNs: string[], suffixNs: string[], - onNs: (ns: string, nsInterface?: string, nsInterfaceDef?: Definition) => void, - onClass: (ns: string, name: string, def?: any) => void + additionalClasses: ClassGenerators = [], + hooks?: Hooks ): Result => { const definitions = schema.definitions || {}; let consts: Defs = []; @@ -53,7 +67,9 @@ export const generateJava = ( } } - onNs(ns, nsInterface, nsInterfaceDef); + if (hooks && hooks.onNamespace) { + hooks.onNamespace(ns, nsInterface, nsInterfaceDef); + } for (const [key, def] of Object.entries(entries)) { if (dataNs.includes(ns) && def && !def.interface) { @@ -69,7 +85,9 @@ export const generateJava = ( ); } - onClass(ns, getName(key, ns, suffixNs), def); + if (hooks && hooks.onClass) { + hooks.onClass(ns, getName(key, ns, suffixNs), def); + } result.files.push( generateClass( @@ -104,6 +122,19 @@ export const generateJava = ( ) ); + const classes = + typeof additionalClasses === "function" + ? additionalClasses() + : additionalClasses; + + classes.forEach((cls) => { + if (typeof cls.codeOrGenerator === "string") { + result.files.push(toFile(cls.name, cls.pkg, cls.codeOrGenerator)); + } else { + result.files.push(generateClass(cls.name, cls.pkg, cls.codeOrGenerator)); + } + }); + return result; }; @@ -111,13 +142,16 @@ const getName = (name: string, ns: string, suffixNs: string[]) => { return suffixNs.includes(ns) && name !== ns ? name + ns : name; }; -export const generateClass = ( +const generateClass = ( name: string, pkg: string, generate: Generator -) => { +): File => { + return toFile(name, pkg, generate(name, pkg)); +}; + +const toFile = (name: string, pkg: string, code: string): File => { const dir = pkg.replaceAll(".", "/"); - const code = generate(name, pkg); return { path: `${dir}/${name}.java`, content: code }; }; diff --git a/libxtracfg/js/types/js/index.ts b/libxtracfg/js/types/ts2x/js/index.ts similarity index 91% rename from libxtracfg/js/types/js/index.ts rename to libxtracfg/js/types/ts2x/js/index.ts index ee1bc26..3168bac 100644 --- a/libxtracfg/js/types/js/index.ts +++ b/libxtracfg/js/types/ts2x/js/index.ts @@ -1,16 +1,20 @@ -import { Definition } from "../common/index.ts"; +import { Definition } from "typescript-json-schema"; + import { validationKeywordsBoolean, validationKeywordsString, } from "../json-schema/index.ts"; -//TODO: configurable settings -export const generateJsValidators = (schema: Definition) => { +export const generateJsValidators = ( + schema: Definition, + name: string, + fileName: string = "validate" +) => { const code = generateValidators(schema); return { - name: "JS Validators", - files: [{ path: "validate.ts", content: code }], + name, + files: [{ path: `${fileName}.ts`, content: code }], }; }; diff --git a/libxtracfg/js/types/json-schema/index.ts b/libxtracfg/js/types/ts2x/json-schema/index.ts similarity index 76% rename from libxtracfg/js/types/json-schema/index.ts rename to libxtracfg/js/types/ts2x/json-schema/index.ts index 3b11400..5ef8549 100644 --- a/libxtracfg/js/types/json-schema/index.ts +++ b/libxtracfg/js/types/ts2x/json-schema/index.ts @@ -1,19 +1,20 @@ import * as TJS from "typescript-json-schema"; -import { resolve } from "path"; -import { constsNs, enumsNs } from "../common/index.ts"; +import { Result } from "../common/io.ts"; + +export type SchemaResult = Result & { obj: any; namespaces: string[] }; export const generateJsonSchema = ( name: string, source: string, - dataNs: string[], fileName: string = "schema" -) => { - const schema = generate(source, dataNs); +): SchemaResult => { + const schema = generate(source); return { name, obj: schema.js, + namespaces: schema.namespaces, files: [{ path: `${fileName}.json`, content: schema.string }], }; }; @@ -26,9 +27,7 @@ export const validationKeywordsBoolean = [ ]; export const validationKeywordsString = ["discriminator"]; -const generate = (source: string, dataNs: string[]) => { - const namespaces = [constsNs, enumsNs, ...dataNs]; - +const generate = (source: string) => { // optionally pass argument to schema generator const settings: TJS.PartialArgs = { required: true, @@ -49,11 +48,7 @@ const generate = (source: string, dataNs: string[]) => { strictNullChecks: true, }; - const program = TJS.getProgramFromFiles( - [resolve(source)], - compilerOptions, - "./" - ); + const program = TJS.getProgramFromFiles([source], compilerOptions, "./"); const generator = TJS.buildGenerator(program, settings); @@ -63,17 +58,24 @@ const generate = (source: string, dataNs: string[]) => { const symbols = generator .getUserSymbols() - .filter( - (s) => !s.endsWith("_1") && namespaces.some((ns) => s.startsWith(ns)) - ); + .filter((s) => !s.endsWith("_1") && s.startsWith("Ts2x.")); const schema = generator.getSchemaForSymbols(symbols, true, true); + const namespaces = Array.from( + new Set( + symbols + .filter((s) => s.split(".").length >= 3) + .map((s) => s.split(".")[1]) + ) + ); + const fixedSchema = fix(schema, namespaces); return { js: fixedSchema, string: JSON.stringify(fixedSchema, null, 2), + namespaces, }; }; @@ -85,13 +87,15 @@ const fix = (schema: TJS.Definition, namespaces: string[]) => { }); for (const [key, value] of Object.entries(definitions)) { - handleKey(key, value, newDefinitions, namespaces); + const key2 = key.replace("Ts2x.", ""); + handleKey(key2, value, newDefinitions, namespaces); } const newSchema = { ...schema, definitions: newDefinitions }; return JSON.parse( JSON.stringify(newSchema) + .replaceAll("Ts2x.", "") .replaceAll(new RegExp(`(${namespaces.join("|")})\\.`, "g"), "$1/") .replaceAll("_1", "") );