diff --git a/houston-common-lib/.prettierrc.json b/houston-common-lib/.prettierrc.json new file mode 100644 index 0000000..8ae2bdf --- /dev/null +++ b/houston-common-lib/.prettierrc.json @@ -0,0 +1,8 @@ +{ + "$schema": "https://json.schemastore.org/prettierrc", + "semi": true, + "tabWidth": 2, + "singleQuote": false, + "printWidth": 100, + "trailingComma": "es5" +} diff --git a/houston-common-lib/lib/download.ts b/houston-common-lib/lib/download.ts index a4fa493..7494f60 100644 --- a/houston-common-lib/lib/download.ts +++ b/houston-common-lib/lib/download.ts @@ -20,7 +20,8 @@ export namespace Download { type: "application/octet-stream", }); const url = URL.createObjectURL(object); - if (Object.hasOwnProperty.call(window, "chrome")) { // chromium based + if (Object.hasOwnProperty.call(window, "chrome")) { + // chromium based Download.url(url, filename); } else { window.open(url, "filename")?.focus(); // non-chromium based diff --git a/houston-common-lib/lib/houston.ts b/houston-common-lib/lib/houston.ts index 6200a60..7345222 100644 --- a/houston-common-lib/lib/houston.ts +++ b/houston-common-lib/lib/houston.ts @@ -13,9 +13,7 @@ export * from "@/user"; export * from "@/group"; export * from "@/filesystem"; -export function getServer( - host: string = "localhost" -): ResultAsync { +export function getServer(host: string = "localhost"): ResultAsync { const server = new Server(host); return server.isAccessible().map(() => server); } @@ -25,19 +23,17 @@ export namespace _internal { nodes: { name: string; addrs: { addr: string }[] }[]; }; export const pcsNodesParseAddrs = (commandOutput: string) => - safeJsonParse(commandOutput).andThen( - (clusterConfig) => { - const addrs = - clusterConfig.nodes - ?.map((node) => node.addrs[0]?.addr) - .filter((addr): addr is string => addr !== undefined) ?? []; - if (addrs.length === 0) { - console.warn("pcs cluster config output:", clusterConfig); - return err(new ParsingError("no nodes found in output")); - } - return ok(addrs); + safeJsonParse(commandOutput).andThen((clusterConfig) => { + const addrs = + clusterConfig.nodes + ?.map((node) => node.addrs[0]?.addr) + .filter((addr): addr is string => addr !== undefined) ?? []; + if (addrs.length === 0) { + console.warn("pcs cluster config output:", clusterConfig); + return err(new ParsingError("no nodes found in output")); } - ); + return ok(addrs); + }); } export function getServerCluster( @@ -53,25 +49,23 @@ export function getServerCluster( const ctdbNodesFile = new File(server, "/etc/ctdb/nodes"); return ctdbNodesFile.exists().andThen((ctdbNodesFileExists) => { if (ctdbNodesFileExists) { - return ctdbNodesFile - .read({ superuser: "try" }) - .andThen((nodesString) => - ResultAsync.combine( - nodesString - .split(RegexSnippets.newlineSplitter) - .map((n) => n.trim()) - .filter((n) => n) - .map((node) => getServer(node)) - ).map((servers) => { - if (servers.length < 1) { - console.warn( - "getServerCluster('ctdb'): Found /etc/ctdb/nodes file, but contained no hosts. Assuming single-server." - ); - return [server] as [Server, ...Server[]]; - } - return servers as [Server, ...Server[]]; - }) - ); + return ctdbNodesFile.read({ superuser: "try" }).andThen((nodesString) => + ResultAsync.combine( + nodesString + .split(RegexSnippets.newlineSplitter) + .map((n) => n.trim()) + .filter((n) => n) + .map((node) => getServer(node)) + ).map((servers) => { + if (servers.length < 1) { + console.warn( + "getServerCluster('ctdb'): Found /etc/ctdb/nodes file, but contained no hosts. Assuming single-server." + ); + return [server] as [Server, ...Server[]]; + } + return servers as [Server, ...Server[]]; + }) + ); } else { console.warn( "getServerCluster('ctdb'): File not found: /etc/ctdb/nodes. Assuming single-server." @@ -84,12 +78,9 @@ export function getServerCluster( return localServerResult.andThen((localServer) => localServer .execute( - new Command( - ["pcs", "cluster", "config", "--output-format", "json"], - { - superuser: "try", - } - ) + new Command(["pcs", "cluster", "config", "--output-format", "json"], { + superuser: "try", + }) ) .map((proc) => proc.getStdout()) .andThen(_internal.pcsNodesParseAddrs) diff --git a/houston-common-lib/lib/path.ts b/houston-common-lib/lib/path.ts index 8b945d0..eda4692 100644 --- a/houston-common-lib/lib/path.ts +++ b/houston-common-lib/lib/path.ts @@ -41,11 +41,7 @@ export class Mode { } toNumber(): number { - return ( - (this.owner.toNumber() << 6) | - (this.group.toNumber() << 3) | - this.other.toNumber() - ); + return (this.owner.toNumber() << 6) | (this.group.toNumber() << 3) | this.other.toNumber(); } toString(): string { return `${this.owner.toString()}${this.group.toString()}${this.other.toString()} (0${this.toOctal()})`; @@ -128,17 +124,11 @@ export class Path { commandOptions?: CommandOptions ): ResultAsync { return server - .execute( - new Command(["test", testFlag, this.path], commandOptions), - false - ) + .execute(new Command(["test", testFlag, this.path], commandOptions), false) .map((proc) => proc.exitStatus === 0); } - isBlockOn( - server: Server, - commandOptions?: CommandOptions - ): ResultAsync { + isBlockOn(server: Server, commandOptions?: CommandOptions): ResultAsync { return this.testOn(server, "-b", commandOptions); } @@ -156,17 +146,11 @@ export class Path { return this.testOn(server, "-d", commandOptions); } - existsOn( - server: Server, - commandOptions?: CommandOptions - ): ResultAsync { + existsOn(server: Server, commandOptions?: CommandOptions): ResultAsync { return this.testOn(server, "-e", commandOptions); } - isFileOn( - server: Server, - commandOptions?: CommandOptions - ): ResultAsync { + isFileOn(server: Server, commandOptions?: CommandOptions): ResultAsync { return this.testOn(server, "-f", commandOptions); } @@ -177,17 +161,11 @@ export class Path { return this.testOn(server, "-L", commandOptions); } - isPipeOn( - server: Server, - commandOptions?: CommandOptions - ): ResultAsync { + isPipeOn(server: Server, commandOptions?: CommandOptions): ResultAsync { return this.testOn(server, "-p", commandOptions); } - isSocketOn( - server: Server, - commandOptions?: CommandOptions - ): ResultAsync { + isSocketOn(server: Server, commandOptions?: CommandOptions): ResultAsync { return this.testOn(server, "-S", commandOptions); } @@ -212,19 +190,11 @@ export class Path { const createResult = ( parents ? server - .execute( - new Command(["mkdir", "-p", this.parent().path], commandOptions) - ) + .execute(new Command(["mkdir", "-p", this.parent().path], commandOptions)) .map(() => null) : okAsync(null) ) - .map( - () => - new Command( - [type === "file" ? "touch" : "mkdir", this.path], - commandOptions - ) - ) + .map(() => new Command([type === "file" ? "touch" : "mkdir", this.path], commandOptions)) .andThen((cmd) => server.execute(cmd, true)); return type === "file" ? createResult.map(() => new File(server, this)) @@ -236,26 +206,12 @@ export class Path { commandOptions?: CommandOptions ): ResultAsync { return server - .execute( - new Command( - ["df", "--output=source,target,fstype", this.path], - commandOptions - ) - ) - .map( - (proc) => - proc.getStdout().trim().split(RegexSnippets.newlineSplitter)[1] - ) + .execute(new Command(["df", "--output=source,target,fstype", this.path], commandOptions)) + .map((proc) => proc.getStdout().trim().split(RegexSnippets.newlineSplitter)[1]) .andThen((tokens) => { const [source, mountpoint, realType] = tokens?.split(/\s+/g) ?? []; - if ( - source === undefined || - mountpoint === undefined || - realType === undefined - ) { - return errAsync( - new ParsingError(`Failed to parse filesystem mount:\n${tokens}`) - ); + if (source === undefined || mountpoint === undefined || realType === undefined) { + return errAsync(new ParsingError(`Failed to parse filesystem mount:\n${tokens}`)); } return okAsync({ filesystem: { @@ -276,11 +232,7 @@ export class Path { return server .execute( new Command( - [ - "realpath", - mustExist ? "--canonicalize-existing" : "--canonicalize-missing", - this.path, - ], + ["realpath", mustExist ? "--canonicalize-existing" : "--canonicalize-missing", this.path], commandOptions ) ) @@ -293,9 +245,7 @@ export class Path { ): ResultAsync { if (!this.isAbsolute()) { return errAsync( - new ProcessError( - `Path.findLongestExistingStemOn: Path not absolute: ${this.path}` - ) + new ProcessError(`Path.findLongestExistingStemOn: Path not absolute: ${this.path}`) ); } let path: Path = this; @@ -317,16 +267,9 @@ export class Path { commandOptions?: CommandOptions ): ResultAsync { return server - .execute( - new Command( - ["stat", "--printf", "%#a", "--", this.path], - commandOptions - ) - ) + .execute(new Command(["stat", "--printf", "%#a", "--", this.path], commandOptions)) .map((proc) => parseInt(proc.getStdout().trim(), 8)) - .andThen((mode) => - isNaN(mode) ? err(new ParsingError("Failed to parse mode")) : ok(mode) - ) + .andThen((mode) => (isNaN(mode) ? err(new ParsingError("Failed to parse mode")) : ok(mode))) .map((mode) => new Mode(mode)); } @@ -337,9 +280,7 @@ export class Path { ): ResultAsync { mode = typeof mode === "number" ? new Mode(mode) : mode; return server - .execute( - new Command(["chmod", "--", mode.toOctal(), this.path], commandOptions) - ) + .execute(new Command(["chmod", "--", mode.toOctal(), this.path], commandOptions)) .map(() => this); } @@ -348,31 +289,16 @@ export class Path { commandOptions?: CommandOptions ): ResultAsync { return server - .execute( - new Command( - ["stat", "--printf", "%u:%g", "--", this.path], - commandOptions - ) - ) + .execute(new Command(["stat", "--printf", "%u:%g", "--", this.path], commandOptions)) .andThen((proc) => { const ownershipString = proc.getStdout().trim(); const [uid, gid] = ownershipString.split(":").map((s) => parseInt(s)); - if ( - uid === undefined || - gid === undefined || - isNaN(uid) || - isNaN(gid) - ) { - return err( - new ParsingError( - `Failed to parse ownership from ${ownershipString}` - ) - ); + if (uid === undefined || gid === undefined || isNaN(uid) || isNaN(gid)) { + return err(new ParsingError(`Failed to parse ownership from ${ownershipString}`)); } - return ResultAsync.combine([ - server.getUserByUid(uid), - server.getGroupByGid(gid), - ]).map(([user, group]) => new Ownership(user, group)); + return ResultAsync.combine([server.getUserByUid(uid), server.getGroupByGid(gid)]).map( + ([user, group]) => new Ownership(user, group) + ); }); } @@ -382,12 +308,7 @@ export class Path { commandOptions?: CommandOptions ): ResultAsync { return server - .execute( - new Command( - ["chown", "--", ownership.toChownString(), this.path], - commandOptions - ) - ) + .execute(new Command(["chown", "--", ownership.toChownString(), this.path], commandOptions)) .map(() => this); } @@ -397,12 +318,7 @@ export class Path { ): ResultAsync { const kvParser = KeyValueSyntax({ duplicateKey: "error" }); return server - .execute( - new Command( - ["getfattr", "--dump", "--match=-", "--", this.path], - commandOptions - ) - ) + .execute(new Command(["getfattr", "--dump", "--match=-", "--", this.path], commandOptions)) .map((proc) => proc.getStdout()) .andThen(kvParser.apply); } @@ -457,13 +373,7 @@ export class Path { return server .execute( new Command( - [ - "setfattr", - `--name=${attributeName}`, - `--value=${attributeValue}`, - "--", - this.path, - ], + ["setfattr", `--name=${attributeName}`, `--value=${attributeValue}`, "--", this.path], commandOptions ) ) @@ -477,10 +387,7 @@ export class Path { ): ResultAsync { return server .execute( - new Command( - ["setfattr", `--remove=${attributeName}`, "--", this.path], - commandOptions - ) + new Command(["setfattr", `--remove=${attributeName}`, "--", this.path], commandOptions) ) .map(() => this); } @@ -512,15 +419,11 @@ export class FileSystemNode extends Path { return this.isBlockOn(this.server, commandOptions); } - isCharacter( - commandOptions?: CommandOptions - ): ResultAsync { + isCharacter(commandOptions?: CommandOptions): ResultAsync { return this.isCharacterOn(this.server, commandOptions); } - isDirectory( - commandOptions?: CommandOptions - ): ResultAsync { + isDirectory(commandOptions?: CommandOptions): ResultAsync { return this.isDirectoryOn(this.server, commandOptions); } @@ -532,9 +435,7 @@ export class FileSystemNode extends Path { return this.isFileOn(this.server, commandOptions); } - isSymbolicLink( - commandOptions?: CommandOptions - ): ResultAsync { + isSymbolicLink(commandOptions?: CommandOptions): ResultAsync { return this.isSymbolicLinkOn(this.server, commandOptions); } @@ -542,15 +443,11 @@ export class FileSystemNode extends Path { return this.isPipeOn(this.server, commandOptions); } - isSocket( - commandOptions?: CommandOptions - ): ResultAsync { + isSocket(commandOptions?: CommandOptions): ResultAsync { return this.isSocketOn(this.server, commandOptions); } - getFilesystemMount( - commandOptions?: CommandOptions - ): ResultAsync { + getFilesystemMount(commandOptions?: CommandOptions): ResultAsync { return this.getFilesystemMountOn(this.server, commandOptions); } @@ -571,16 +468,11 @@ export class FileSystemNode extends Path { ); } - getMode( - commandOptions?: CommandOptions - ): ResultAsync { + getMode(commandOptions?: CommandOptions): ResultAsync { return this.getModeOn(this.server, commandOptions); } - setMode( - mode: Mode | number, - commandOptions?: CommandOptions - ): ResultAsync; + setMode(mode: Mode | number, commandOptions?: CommandOptions): ResultAsync; setMode( reference: FileSystemNode, commandOptions?: CommandOptions @@ -618,9 +510,7 @@ export class FileSystemNode extends Path { ownership instanceof FileSystemNode ? ownership.getOwnership(commandOptions) : okAsync(ownership) - ).andThen((ownership) => - this.setOwnershipOn(this.server, ownership, commandOptions) - ); + ).andThen((ownership) => this.setOwnershipOn(this.server, ownership, commandOptions)); } assertExists( @@ -630,19 +520,13 @@ export class FileSystemNode extends Path { return this.exists(commandOptions).andThen((exists) => exists === expected ? ok(this) - : err( - new ProcessError(`assertExists(${expected}) failed: ${this.path}`) - ) + : err(new ProcessError(`assertExists(${expected}) failed: ${this.path}`)) ); } - assertIsFile( - commandOptions?: CommandOptions | undefined - ): ResultAsync { + assertIsFile(commandOptions?: CommandOptions | undefined): ResultAsync { return this.isFile(commandOptions).andThen((isFile) => - isFile - ? ok(new File(this)) - : err(new ProcessError(`assertIsFile failed: ${this.path}`)) + isFile ? ok(new File(this)) : err(new ProcessError(`assertIsFile failed: ${this.path}`)) ); } @@ -666,22 +550,14 @@ export class FileSystemNode extends Path { attributes: ExtendedAttributes, commandOptions?: CommandOptions ): ResultAsync { - return this.setExtendedAttributesOn( - this.server, - attributes, - commandOptions - ); + return this.setExtendedAttributesOn(this.server, attributes, commandOptions); } getExtendedAttribute( attributeName: string, commandOptions?: CommandOptions ): ResultAsync, ProcessError> { - return this.getExtendedAttributeOn( - this.server, - attributeName, - commandOptions - ); + return this.getExtendedAttributeOn(this.server, attributeName, commandOptions); } setExtendedAttribute( @@ -689,23 +565,14 @@ export class FileSystemNode extends Path { attributeValue: string, commandOptions?: CommandOptions ): ResultAsync { - return this.setExtendedAttributeOn( - this.server, - attributeName, - attributeValue, - commandOptions - ); + return this.setExtendedAttributeOn(this.server, attributeName, attributeValue, commandOptions); } removeExtendedAttribute( attributeName: string, commandOptions?: CommandOptions ): ResultAsync { - return this.removeExtendedAttributeOn( - this.server, - attributeName, - commandOptions - ); + return this.removeExtendedAttributeOn(this.server, attributeName, commandOptions); } move( @@ -726,14 +593,7 @@ export class FileSystemNode extends Path { return this.server .execute( new Command( - [ - "mv", - ...existingFlagsLUT[existing], - "--no-target-directory", - "--", - this.path, - newPath, - ], + ["mv", ...existingFlagsLUT[existing], "--no-target-directory", "--", this.path, newPath], options ) ) @@ -742,10 +602,7 @@ export class FileSystemNode extends Path { } export class File extends FileSystemNode { - create( - parents?: boolean, - commandOptions?: CommandOptions - ): ResultAsync { + create(parents?: boolean, commandOptions?: CommandOptions): ResultAsync { return this.createOn(this.server, "file", parents, commandOptions); } @@ -755,19 +612,13 @@ export class File extends FileSystemNode { .map(() => this); } - read( - commandOptions?: CommandOptions & { binary?: false } - ): ResultAsync; - read( - commandOptions: CommandOptions & { binary: true } - ): ResultAsync; + read(commandOptions?: CommandOptions & { binary?: false }): ResultAsync; + read(commandOptions: CommandOptions & { binary: true }): ResultAsync; read( commandOptions: CommandOptions & { binary?: boolean } = {} ): ResultAsync | ResultAsync { const { binary, ...options } = commandOptions; - const procResult = this.server.execute( - new Command(["cat", this.path], options) - ); + const procResult = this.server.execute(new Command(["cat", this.path], options)); if (binary) { return procResult.map((p) => p.getStdout(true)); } @@ -794,9 +645,7 @@ export class File extends FileSystemNode { return proc.wait(true).map(() => this); } - move( - ...args: Parameters - ): ResultAsync { + move(...args: Parameters): ResultAsync { return super.move(...args).map((node) => new File(node)); } @@ -826,9 +675,7 @@ export class File extends FileSystemNode { if (typeof newContentOrReplacer === "function") { if (binary) { return this.read({ ...options, binary: true }) - .map( - newContentOrReplacer as (oldContent: Uint8Array) => Uint8Array - ) + .map(newContentOrReplacer as (oldContent: Uint8Array) => Uint8Array) .andThen((newContent) => tempFile.write(newContent, options)); } return this.read({ ...options, binary: false }) @@ -858,10 +705,7 @@ export class File extends FileSystemNode { return server .execute( new Command( - [ - "mktemp", - ...(directory === undefined ? [] : [`--tmpdir=${directory}`]), - ], + ["mktemp", ...(directory === undefined ? [] : [`--tmpdir=${directory}`])], commandOptions ) ) @@ -870,10 +714,7 @@ export class File extends FileSystemNode { } export class Directory extends FileSystemNode { - create( - parents?: boolean, - commandOptions?: CommandOptions - ): ResultAsync { + create(parents?: boolean, commandOptions?: CommandOptions): ResultAsync { return this.createOn(this.server, "directory", parents, commandOptions); } @@ -906,9 +747,7 @@ export class Directory extends FileSystemNode { .execute( new BashCommand( 'find -H "$1" -mindepth 1 -maxdepth 1 -name "$2" -path "$3" -print0' + - (opts.limit === "none" - ? "" - : ` | head -z -n ${opts.limit.toString()}`), + (opts.limit === "none" ? "" : ` | head -z -n ${opts.limit.toString()}`), [this.path, opts.nameFilter ?? "*", opts.pathFilter ?? "*"], commandOptions ) @@ -923,9 +762,7 @@ export class Directory extends FileSystemNode { ); } - move( - ...args: Parameters - ): ResultAsync { + move(...args: Parameters): ResultAsync { return super.move(...args).map((node) => new Directory(node)); } } diff --git a/houston-common-lib/lib/process.ts b/houston-common-lib/lib/process.ts index f09f950..a46728c 100644 --- a/houston-common-lib/lib/process.ts +++ b/houston-common-lib/lib/process.ts @@ -7,10 +7,7 @@ import { Maybe } from "monet"; const utf8Decoder = new TextDecoder("utf-8", { fatal: false }); const utf8Encoder = new TextEncoder(); -export type CommandOptions = Omit< - Cockpit.SpawnOptions, - "host" | "binary" | "err" ->; +export type CommandOptions = Omit; export class Command { public readonly argv: string[]; @@ -30,18 +27,12 @@ export class Command { } public toString(): string { - return `Command(${JSON.stringify(this.argv)}, ${JSON.stringify( - this.spawnOptions - )})`; + return `Command(${JSON.stringify(this.argv)}, ${JSON.stringify(this.spawnOptions)})`; } } export class BashCommand extends Command { - constructor( - script: string, - args: string[] = [], - opts: CommandOptions & { arg0?: string } = {} - ) { + constructor(script: string, args: string[] = [], opts: CommandOptions & { arg0?: string } = {}) { const arg0 = opts.arg0 ?? "HoustonBashCommand"; super(["/usr/bin/env", "bash", "-c", script, arg0, ...args], opts); } @@ -58,9 +49,7 @@ export class ProcessBase { public prefixMessage(message: string): string { const arg0Prefix = `${this.getName()}: `; - message = message.startsWith(arg0Prefix) - ? message.replace(arg0Prefix, "") - : message; + message = message.startsWith(arg0Prefix) ? message.replace(arg0Prefix, "") : message; return Maybe.fromUndefined(this.server.host) .fold("")((host) => `${host}: `) .concat(arg0Prefix, message); @@ -151,28 +140,16 @@ export class Process extends ProcessBase { return this; } - public wait( - failIfNonZero: boolean = true - ): ResultAsync { + public wait(failIfNonZero: boolean = true): ResultAsync { return ResultAsync.fromPromise( new Promise((resolve, reject) => { if (this.spawnHandle === undefined) { - return reject( - new ProcessError(this.prefixMessage("Process never started!")) - ); + return reject(new ProcessError(this.prefixMessage("Process never started!"))); } this.spawnHandle .then((stdout, stderr) => { const exitStatus = 0; - resolve( - new ExitedProcess( - this.server, - this.command, - exitStatus, - stdout, - stderr - ) - ); + resolve(new ExitedProcess(this.server, this.command, exitStatus, stdout, stderr)); }) .catch((ex, stdout) => { if ( @@ -180,27 +157,15 @@ export class Process extends ProcessBase { ex.exit_status === null || ex.exit_status === undefined ) { - return reject( - new ProcessError( - this.prefixMessage(`${ex.message} (${ex.problem})`) - ) - ); + return reject(new ProcessError(this.prefixMessage(`${ex.message} (${ex.problem})`))); } if (failIfNonZero && ex.exit_status !== 0) { return reject( - new NonZeroExit( - this.prefixMessage(`${ex.message} (${ex.exit_status})`) - ) + new NonZeroExit(this.prefixMessage(`${ex.message} (${ex.exit_status})`)) ); } resolve( - new ExitedProcess( - this.server, - this.command, - ex.exit_status, - stdout, - ex.message - ) + new ExitedProcess(this.server, this.command, ex.exit_status, stdout, ex.message) ); }); }), @@ -215,10 +180,7 @@ export class Process extends ProcessBase { ); } - public write( - data: string | Uint8Array, - stream: boolean = false - ): Result { + public write(data: string | Uint8Array, stream: boolean = false): Result { if (this.spawnHandle === undefined) { return err(new ProcessError(this.prefixMessage("process not running!"))); } diff --git a/houston-common-lib/lib/server.ts b/houston-common-lib/lib/server.ts index 20b8660..14bc22e 100644 --- a/houston-common-lib/lib/server.ts +++ b/houston-common-lib/lib/server.ts @@ -30,23 +30,14 @@ export class Server { return okAsync(this.hostname); } - getIpAddress( - cache: boolean = true - ): ResultAsync { + getIpAddress(cache: boolean = true): ResultAsync { if (this.ipAddress === undefined || cache === false) { const target = "1.1.1.1"; - return this.execute( - new Command(["ip", "route", "get", target]), - true - ).andThen((proc) => { + return this.execute(new Command(["ip", "route", "get", target]), true).andThen((proc) => { const stdout = proc.getStdout(); - const match = stdout.match( - /\bsrc\s+(?\d{1,3}(?:\.\d{1,3}){3})\b/ - ); + const match = stdout.match(/\bsrc\s+(?\d{1,3}(?:\.\d{1,3}){3})\b/); if (match === null || match.groups === undefined) { - return err( - new ParsingError(`Malformed output from ${proc}`, { cause: stdout }) - ); + return err(new ParsingError(`Malformed output from ${proc}`, { cause: stdout })); } this.ipAddress = match.groups["ipAddress"] as string; return ok(this.ipAddress); @@ -75,31 +66,25 @@ export class Server { binary: "raw", spawn: command.argv, external: { - "content-disposition": - 'attachment; filename="' + encodeURIComponent(filename) + '"', + "content-disposition": 'attachment; filename="' + encodeURIComponent(filename) + '"', "content-type": "application/x-xz, application/octet-stream", }, }) ); - const prefix = new URL( - cockpit.transport.uri("channel/" + cockpit.transport.csrf_token) - ).pathname; + const prefix = new URL(cockpit.transport.uri("channel/" + cockpit.transport.csrf_token)) + .pathname; const url = prefix + "?" + query; Download.url(url, filename); } getLocalUsers(cache: boolean = true): ResultAsync { if (this.localUsers === undefined || cache === false) { - return this.execute( - new Command(["getent", "-s", "files", "passwd"]), - true - ).map((proc) => { + return this.execute(new Command(["getent", "-s", "files", "passwd"]), true).map((proc) => { this.localUsers = proc .getStdout() .split("\n") .map((line) => { - const [login, _, uidStr, gidStr, name, home, shell] = - line.split(":"); + const [login, _, uidStr, gidStr, name, home, shell] = line.split(":"); if ( login === undefined || uidStr === undefined || @@ -132,24 +117,15 @@ export class Server { return okAsync(this.localUsers); } - getLocalGroups( - cache: boolean = true - ): ResultAsync { + getLocalGroups(cache: boolean = true): ResultAsync { if (this.localGroups === undefined || cache === false) { - return this.execute( - new Command(["getent", "-s", "files", "group"]), - true - ).map((proc) => { + return this.execute(new Command(["getent", "-s", "files", "group"]), true).map((proc) => { this.localGroups = proc .getStdout() .split("\n") .map((line) => { const [name, _, gidStr, membersStr] = line.split(":"); - if ( - name === undefined || - gidStr === undefined || - membersStr === undefined - ) { + if (name === undefined || gidStr === undefined || membersStr === undefined) { return null; } const gid = parseInt(gidStr); @@ -165,13 +141,9 @@ export class Server { return okAsync(this.localGroups); } - getUserGroups( - user: User - ): ResultAsync { + getUserGroups(user: User): ResultAsync { if (!isLocalUser(user)) { - return errAsync( - new ValueError(`Can't get groups from non-local user ${user.uid}`) - ); + return errAsync(new ValueError(`Can't get groups from non-local user ${user.uid}`)); } return this.execute(new Command(["groups", user.login]), true) .map((proc) => @@ -189,22 +161,16 @@ export class Server { }); } - getGroupMembers( - group: Group - ): ResultAsync { + getGroupMembers(group: Group): ResultAsync { if (!isLocalGroup(group)) { - return errAsync( - new ValueError(`Can't get members of non-local group ${group.gid}`) - ); + return errAsync(new ValueError(`Can't get members of non-local group ${group.gid}`)); } return this.getLocalUsers().map((localUsers) => localUsers.filter((user) => user.login in group.members) ); } - getUserByLogin( - login: string - ): ResultAsync { + getUserByLogin(login: string): ResultAsync { return this.getLocalUsers() .map((localUsers) => localUsers.filter((user) => user.login === login)) .andThen((userMatches) => @@ -219,28 +185,14 @@ export class Server { .map((localUsers) => localUsers.filter((user) => user.uid === uid)) .andThen((userMatches) => userMatches.length === 0 - ? ok( - User( - this, - undefined, - uid, - undefined, - undefined, - undefined, - undefined - ) - ) + ? ok(User(this, undefined, uid, undefined, undefined, undefined, undefined)) : ok(userMatches[0]!) ); } - getGroupByName( - groupName: string - ): ResultAsync { + getGroupByName(groupName: string): ResultAsync { return this.getLocalGroups() - .map((localGroups) => - localGroups.filter((group) => group.name === groupName) - ) + .map((localGroups) => localGroups.filter((group) => group.name === groupName)) .andThen((groupMatches) => groupMatches.length === 0 ? err(new ValueError(`Group not found: ${groupName}`)) diff --git a/houston-common-lib/lib/syntax/ini-syntax.test.ts b/houston-common-lib/lib/syntax/ini-syntax.test.ts index b282604..7b8527f 100644 --- a/houston-common-lib/lib/syntax/ini-syntax.test.ts +++ b/houston-common-lib/lib/syntax/ini-syntax.test.ts @@ -122,14 +122,10 @@ suite("IniSyntax", () => { expect(iniSyntax.unapply(data)).toEqual(ok(cleanRaw)); }); test("apply(unapply(data)) == data", () => { - expect( - ok(data).andThen(iniSyntax.unapply).andThen(iniSyntax.apply) - ).toEqual(ok(data)); + expect(ok(data).andThen(iniSyntax.unapply).andThen(iniSyntax.apply)).toEqual(ok(data)); }); test("unapply(apply(raw)) == cleanRaw", () => { - expect( - ok(raw).andThen(iniSyntax.apply).andThen(iniSyntax.unapply) - ).toEqual(ok(cleanRaw)); + expect(ok(raw).andThen(iniSyntax.apply).andThen(iniSyntax.unapply)).toEqual(ok(cleanRaw)); }); } suite("duplicateKeys:", () => { @@ -145,9 +141,7 @@ suite("IniSyntax", () => { }); const applyResult = iniSyntax.apply(input); expect(applyResult).toEqual(ok({ section: { key: "value3" } })); - expect(applyResult.andThen(iniSyntax.unapply)).toEqual( - ok("[section]\n\tkey = value3\n") - ); + expect(applyResult.andThen(iniSyntax.unapply)).toEqual(ok("[section]\n\tkey = value3\n")); }); test("ignore", () => { const iniSyntax = IniSyntax({ @@ -156,9 +150,7 @@ suite("IniSyntax", () => { }); const applyResult = iniSyntax.apply(input); expect(applyResult).toEqual(ok({ section: { key: "value1" } })); - expect(applyResult.andThen(iniSyntax.unapply)).toEqual( - ok("[section]\n\tkey = value1\n") - ); + expect(applyResult.andThen(iniSyntax.unapply)).toEqual(ok("[section]\n\tkey = value1\n")); }); test("append", () => { const iniSyntax = IniSyntax({ @@ -166,9 +158,7 @@ suite("IniSyntax", () => { duplicateKey: "append", }); const applyResult = iniSyntax.apply(input); - expect(applyResult).toEqual( - ok({ section: { key: ["value1", "value2", "value3"] } }) - ); + expect(applyResult).toEqual(ok({ section: { key: ["value1", "value2", "value3"] } })); expect(applyResult.andThen(iniSyntax.unapply)).toEqual(ok(input)); }); test("error", () => { diff --git a/houston-common-lib/lib/syntax/ini-syntax.ts b/houston-common-lib/lib/syntax/ini-syntax.ts index 7f2db5b..9631b0f 100644 --- a/houston-common-lib/lib/syntax/ini-syntax.ts +++ b/houston-common-lib/lib/syntax/ini-syntax.ts @@ -4,9 +4,10 @@ import { RegexSnippets } from "./regex-snippets"; import { KeyValueData, KeyValueSyntax } from "@/syntax/key-value-syntax"; import { ParsingError } from "@/errors"; -export type IniConfigData< - TValue extends string | [string, ...string[]] = string, -> = Record>; +export type IniConfigData = Record< + string, + Record +>; export type IniSyntaxOptions = { paramIndent?: number | string; @@ -66,11 +67,7 @@ export function IniSyntax( } } else if (paramRegex.test(line)) { const match = line.match(paramRegex); - if ( - currentSection !== null && - match !== null && - match.groups !== undefined - ) { + if (currentSection !== null && match !== null && match.groups !== undefined) { const key = match.groups["key"] as string; const value = match.groups["value"] as string; if (data[currentSection] === undefined) { @@ -86,32 +83,19 @@ export function IniSyntax( } else if (Array.isArray(currentValue)) { currentKeyValues[key] = [...currentValue, value]; } - } else if ( - currentValue === undefined || - duplicateKey === "overwrite" - ) { + } else if (currentValue === undefined || duplicateKey === "overwrite") { currentKeyValues[key] = value; } else if (duplicateKey === "error") { - return err( - new ParsingError( - `Duplicate key '${key}' at line ${index}:\n${line}` - ) - ); + return err(new ParsingError(`Duplicate key '${key}' at line ${index}:\n${line}`)); } // else ignore } } else { - return err( - new ParsingError(`Invalid INI format at line ${index}:\n${line}`) - ); + return err(new ParsingError(`Invalid INI format at line ${index}:\n${line}`)); } } return ok(data); }, - unapply: ( - data: - | IniConfigData - | IniConfigData - ) => { + unapply: (data: IniConfigData | IniConfigData) => { return Result.combine( Object.entries(data).map(([sectionName, params]) => (duplicateKey === "append" @@ -129,9 +113,7 @@ export function IniSyntax( }).unapply(params as KeyValueData) ).map((paramsText) => `[${sectionName}]\n${paramsText}`) ) - ).map( - (sections) => sections.join("\n\n") + (trailingNewline ? "\n" : "") - ); + ).map((sections) => sections.join("\n\n") + (trailingNewline ? "\n" : "")); }, }; } diff --git a/houston-common-lib/lib/syntax/key-value-syntax.test.ts b/houston-common-lib/lib/syntax/key-value-syntax.test.ts index cbe86cf..28a840b 100644 --- a/houston-common-lib/lib/syntax/key-value-syntax.test.ts +++ b/houston-common-lib/lib/syntax/key-value-syntax.test.ts @@ -41,14 +41,10 @@ suite("KeyValueSyntax", () => { expect(kvSyntax.unapply(data)).toEqual(ok(cleanRaw)); }); test("apply(unapply(data)) == data", () => { - expect( - ok(data).andThen(kvSyntax.unapply).andThen(kvSyntax.apply) - ).toEqual(ok(data)); + expect(ok(data).andThen(kvSyntax.unapply).andThen(kvSyntax.apply)).toEqual(ok(data)); }); test("unapply(apply(raw)) == cleanRaw", () => { - expect(ok(raw).andThen(kvSyntax.apply).andThen(kvSyntax.unapply)).toEqual( - ok(cleanRaw) - ); + expect(ok(raw).andThen(kvSyntax.apply).andThen(kvSyntax.unapply)).toEqual(ok(cleanRaw)); }); } test("catches error", () => { @@ -66,17 +62,13 @@ key = value3 const kvSyntax = KeyValueSyntax({ duplicateKey: "overwrite" }); const applyResult = kvSyntax.apply(input); expect(applyResult).toEqual(ok({ key: "value3" })); - expect(applyResult.andThen(kvSyntax.unapply)).toEqual( - ok("key = value3\n") - ); + expect(applyResult.andThen(kvSyntax.unapply)).toEqual(ok("key = value3\n")); }); test("ignore", () => { const kvSyntax = KeyValueSyntax({ duplicateKey: "ignore" }); const applyResult = kvSyntax.apply(input); expect(applyResult).toEqual(ok({ key: "value1" })); - expect(applyResult.andThen(kvSyntax.unapply)).toEqual( - ok("key = value1\n") - ); + expect(applyResult.andThen(kvSyntax.unapply)).toEqual(ok("key = value1\n")); }); test("append", () => { const kvSyntax = KeyValueSyntax({ duplicateKey: "append" }); diff --git a/houston-common-lib/lib/syntax/key-value-syntax.ts b/houston-common-lib/lib/syntax/key-value-syntax.ts index b1951d2..98a45ec 100644 --- a/houston-common-lib/lib/syntax/key-value-syntax.ts +++ b/houston-common-lib/lib/syntax/key-value-syntax.ts @@ -3,9 +3,10 @@ import { ParsingError } from "@/errors"; import { Result, ok, err } from "neverthrow"; import { RegexSnippets } from "./regex-snippets"; -export type KeyValueData< - TValue extends string | [string, ...string[]] = string, -> = Record; +export type KeyValueData = Record< + string, + TValue +>; export type KeyValueSyntaxOptions = { indent?: number | string; @@ -36,9 +37,7 @@ export function KeyValueSyntax( export function KeyValueSyntax( opts: KeyValueSyntaxOptions = {} -): - | SyntaxParser> - | SyntaxParser> { +): SyntaxParser> | SyntaxParser> { let { indent = "", commentRegex = /^\s*[#;]/, @@ -54,27 +53,18 @@ export function KeyValueSyntax( text // split lines .split(RegexSnippets.newlineSplitter) - .map( - ( - line, - lineIndex - ): Result<{ key: string; value: string } | null, ParsingError> => { - if (commentRegex.test(line) || line.trim() === "") { - return ok(null); - } - const [key, value] = line - .split(RegexSnippets.keyValueSplitter) - .map((s) => s.trim()); - if (key === undefined || value === undefined || key === "") { - return err( - new ParsingError( - `Invalid key = value format at line ${lineIndex}:\n${line}` - ) - ); - } - return ok({ key, value }); + .map((line, lineIndex): Result<{ key: string; value: string } | null, ParsingError> => { + if (commentRegex.test(line) || line.trim() === "") { + return ok(null); } - ) + const [key, value] = line.split(RegexSnippets.keyValueSplitter).map((s) => s.trim()); + if (key === undefined || value === undefined || key === "") { + return err( + new ParsingError(`Invalid key = value format at line ${lineIndex}:\n${line}`) + ); + } + return ok({ key, value }); + }) ).andThen((keyValuePairs) => { const data = opts.duplicateKey === "append" @@ -92,27 +82,18 @@ export function KeyValueSyntax( } else if (Array.isArray(currentValue)) { data[key] = [...currentValue, value]; } - } else if ( - currentValue === undefined || - duplicateKey === "overwrite" - ) { + } else if (currentValue === undefined || duplicateKey === "overwrite") { data[key] = value; } else if (duplicateKey === "error") { - return err( - new ParsingError(`Duplicate key '${key}' at line ${index}`) - ); + return err(new ParsingError(`Duplicate key '${key}' at line ${index}`)); } // else ignore } return ok(data); }), - unapply: ( - data: KeyValueData | KeyValueData - ) => + unapply: (data: KeyValueData | KeyValueData) => ok( Object.entries(data) - .map(([key, values]) => - [values].flat().map((value) => `${indent}${key} = ${value}`) - ) + .map(([key, values]) => [values].flat().map((value) => `${indent}${key} = ${value}`)) .flat() .join("\n") + (trailingNewline ? "\n" : "") ), diff --git a/houston-common-lib/lib/syntax/regex-snippets.ts b/houston-common-lib/lib/syntax/regex-snippets.ts index 56daee3..f2a8c35 100644 --- a/houston-common-lib/lib/syntax/regex-snippets.ts +++ b/houston-common-lib/lib/syntax/regex-snippets.ts @@ -1,5 +1,4 @@ export namespace RegexSnippets { - export const newlineSplitter = /[\r\n]+/; - export const keyValueSplitter = /=(.*)/; - + export const newlineSplitter = /[\r\n]+/; + export const keyValueSplitter = /=(.*)/; } diff --git a/houston-common-lib/lib/syntax/schema-transformer.ts b/houston-common-lib/lib/syntax/schema-transformer.ts index a8366cb..7a38e11 100644 --- a/houston-common-lib/lib/syntax/schema-transformer.ts +++ b/houston-common-lib/lib/syntax/schema-transformer.ts @@ -1,4 +1,4 @@ -import { Result, /* Ok, Err, Option, Some, None */ } from "neverthrow"; +import { Result /* Ok, Err, Option, Some, None */ } from "neverthrow"; import { ParsingError } from "@/errors"; // import { Transformer } from "./transformer"; @@ -18,10 +18,7 @@ export function definePropertyTransformSchema< To extends {} | null, const InputKeys extends readonly [string, ...string[]], >( - propertyTransformSchema: Omit< - PropertyTransformSchema, - "_type" - > + propertyTransformSchema: Omit, "_type"> ): PropertyTransformSchema { return { ...propertyTransformSchema, @@ -87,9 +84,7 @@ export type ParsedType< ? To : S extends ObjectTransformSchema ? { - [P in TKey as P extends "inputKeys" | "_type" - ? never - : P]: ParsedType; + [P in TKey as P extends "inputKeys" | "_type" ? never : P]: ParsedType; } : never; @@ -105,9 +100,7 @@ export type UnparsedKeys< : never; export type UnparsedType< - S extends - | ObjectTransformSchema - | PropertyTransformSchema, + S extends ObjectTransformSchema | PropertyTransformSchema, > = S extends PropertyTransformSchema ? From diff --git a/houston-common-lib/lib/syntax/transformer.ts b/houston-common-lib/lib/syntax/transformer.ts index 17dfcaf..740196b 100644 --- a/houston-common-lib/lib/syntax/transformer.ts +++ b/houston-common-lib/lib/syntax/transformer.ts @@ -2,22 +2,17 @@ import { Result } from "neverthrow"; import { Maybe } from "monet"; import { ParsingError } from "@/errors"; -export type MaybeOption< - UseOption extends boolean, - T extends {}, -> = UseOption extends true ? Maybe : T; +export type MaybeOption = UseOption extends true + ? Maybe + : T; export type Transformer< TParsed extends {}, TUnparsed extends {}, ReturnsOption extends boolean = false, > = { - apply: ( - unparsed: TUnparsed - ) => Result, ParsingError>; - unapply: ( - parsed: TParsed - ) => Result, ParsingError>; + apply: (unparsed: TUnparsed) => Result, ParsingError>; + unapply: (parsed: TParsed) => Result, ParsingError>; }; // export type PropertyMapper< diff --git a/houston-common-lib/lib/typedoc.index.ts b/houston-common-lib/lib/typedoc.index.ts index b1bb572..82760b3 100644 --- a/houston-common-lib/lib/typedoc.index.ts +++ b/houston-common-lib/lib/typedoc.index.ts @@ -1,4 +1,4 @@ -export * from './index'; +export * from "./index"; import type cockpit from "cockpit"; diff --git a/houston-common-lib/lib/upload.ts b/houston-common-lib/lib/upload.ts index cc2d3f2..77a3d1e 100644 --- a/houston-common-lib/lib/upload.ts +++ b/houston-common-lib/lib/upload.ts @@ -15,20 +15,14 @@ export namespace Upload { } export function text(accept?: string) { - return file(accept).andThen((file) => - ResultAsync.fromSafePromise(file.text()) - ); + return file(accept).andThen((file) => ResultAsync.fromSafePromise(file.text())); } export function binary(accept?: string) { - return file(accept).andThen((file) => - ResultAsync.fromSafePromise(file.arrayBuffer()) - ); + return file(accept).andThen((file) => ResultAsync.fromSafePromise(file.arrayBuffer())); } - function getUpload( - options: Options = {} - ): ResultAsync { + function getUpload(options: Options = {}): ResultAsync { const promise = new Promise((resolve, reject) => { fakeUploadClick(resolve, reject, options); }); @@ -53,11 +47,7 @@ export namespace Upload { input.multiple = options.multiple ?? input.multiple; input.accept = options.accept ?? input.accept; input.addEventListener("change", ({ target }: Event) => { - if ( - target instanceof HTMLInputElement && - target.files && - target.files.length > 0 - ) { + if (target instanceof HTMLInputElement && target.files && target.files.length > 0) { resolver(target.files); document.body.removeChild(input); } diff --git a/houston-common-lib/lib/utils.test.ts b/houston-common-lib/lib/utils.test.ts index 9c9477d..06ab036 100644 --- a/houston-common-lib/lib/utils.test.ts +++ b/houston-common-lib/lib/utils.test.ts @@ -36,22 +36,15 @@ suite("utils", () => { }); test("Mapping functor", () => { const testStrings = ["HeLlO, WoRlD!", "lorem ipsum", "LOREM IPSUM"]; - expect(testStrings.map(toLowerCase)).toEqual( - testStrings.map((s) => s.toLowerCase()) - ); - expect(testStrings.map(toUpperCase)).toEqual( - testStrings.map((s) => s.toUpperCase()) - ); + expect(testStrings.map(toLowerCase)).toEqual(testStrings.map((s) => s.toLowerCase())); + expect(testStrings.map(toUpperCase)).toEqual(testStrings.map((s) => s.toUpperCase())); }); test("with bound args", () => { const testStrings = ["HeLlO, WoRlD!", "lorem ipsum", "LOREM IPSUM"]; const firstChar = MethodFunctor(String, "charAt", 0); - const slicer = (start: number, end: number) => - MethodFunctor(String, "slice", start, end); + const slicer = (start: number, end: number) => MethodFunctor(String, "slice", start, end); expect(testStrings.map(firstChar)).toEqual(testStrings.map((s) => s[0])); - expect(testStrings.map(slicer(1, 3))).toEqual( - testStrings.map((s) => s.slice(1, 3)) - ); + expect(testStrings.map(slicer(1, 3))).toEqual(testStrings.map((s) => s.slice(1, 3))); }); }); suite("Caster", () => { @@ -60,55 +53,23 @@ suite("utils", () => { const trueValues = ["true", "yes", "1"]; const falseValues = ["false", "no", "0"]; const invalidValues = ["hello", "world", ":3"]; - const trueValuesUpper = trueValues.map( - MethodFunctor(String, "toUpperCase") - ); - const falseValuesUpper = falseValues.map( - MethodFunctor(String, "toUpperCase") - ); - expect(trueValues.map(caster)).toEqual([ - Some(true), - Some(true), - Some(true), - ]); - expect(falseValues.map(caster)).toEqual([ - Some(false), - Some(false), - Some(false), - ]); + const trueValuesUpper = trueValues.map(MethodFunctor(String, "toUpperCase")); + const falseValuesUpper = falseValues.map(MethodFunctor(String, "toUpperCase")); + expect(trueValues.map(caster)).toEqual([Some(true), Some(true), Some(true)]); + expect(falseValues.map(caster)).toEqual([Some(false), Some(false), Some(false)]); expect(invalidValues.map(caster)).toEqual([None(), None(), None()]); - expect(trueValuesUpper.map(caster)).toEqual([ - Some(true), - Some(true), - Some(true), - ]); - expect(falseValuesUpper.map(caster)).toEqual([ - Some(false), - Some(false), - Some(false), - ]); + expect(trueValuesUpper.map(caster)).toEqual([Some(true), Some(true), Some(true)]); + expect(falseValuesUpper.map(caster)).toEqual([Some(false), Some(false), Some(false)]); }); test("case sensitive StringToBooleanCaster", () => { const caster = StringToBooleanCaster({ ignoreCase: false }); const trueValues = ["true", "yes", "1"]; const falseValues = ["false", "no", "0"]; const invalidValues = ["hello", "world", ":3"]; - const trueValuesUpper = trueValues.map( - MethodFunctor(String, "toUpperCase") - ); - const falseValuesUpper = falseValues.map( - MethodFunctor(String, "toUpperCase") - ); - expect(trueValues.map(caster)).toEqual([ - Some(true), - Some(true), - Some(true), - ]); - expect(falseValues.map(caster)).toEqual([ - Some(false), - Some(false), - Some(false), - ]); + const trueValuesUpper = trueValues.map(MethodFunctor(String, "toUpperCase")); + const falseValuesUpper = falseValues.map(MethodFunctor(String, "toUpperCase")); + expect(trueValues.map(caster)).toEqual([Some(true), Some(true), Some(true)]); + expect(falseValues.map(caster)).toEqual([Some(false), Some(false), Some(false)]); expect(invalidValues.map(caster)).toEqual([None(), None(), None()]); expect(trueValuesUpper.map(caster)).toEqual([None(), None(), Some(true)]); // "1" is case insensitive expect(falseValuesUpper.map(caster)).toEqual([None(), None(), Some(false)]); // "0" is case insensitive @@ -131,22 +92,10 @@ suite("utils", () => { const trueFalseCaster = BooleanToStringCaster("true", "false"); const yesNoCaster = BooleanToStringCaster("yes", "no"); const testPattern = [true, false, true]; - expect(testPattern.map(trueFalseCaster)).toEqual([ - Some("true"), - Some("false"), - Some("true"), - ]); - expect(testPattern.map(yesNoCaster)).toEqual([ - Some("yes"), - Some("no"), - Some("yes"), - ]); + expect(testPattern.map(trueFalseCaster)).toEqual([Some("true"), Some("false"), Some("true")]); + expect(testPattern.map(yesNoCaster)).toEqual([Some("yes"), Some("no"), Some("yes")]); expect( - testPattern - .map(yesNoCaster) - .map(Unwrapper()) - .map(StringToBooleanCaster()) - .map(Unwrapper()) + testPattern.map(yesNoCaster).map(Unwrapper()).map(StringToBooleanCaster()).map(Unwrapper()) ).toEqual(testPattern); }); test("StringToIntCaster", () => { @@ -156,35 +105,13 @@ suite("utils", () => { const decCaster = StringToIntCaster(10); const hexCaster = StringToIntCaster(16); const octCaster = StringToIntCaster(8); - expect(input.map(anyCaster)).toEqual([ - Some(0), - Some(1), - Some(10), - Some(100), - ]); - expect(input.map(decCaster)).toEqual([ - Some(0), - Some(1), - Some(10), - Some(100), - ]); - expect(input.map(hexCaster)).toEqual([ - Some(0), - Some(1), - Some(16), - Some(256), - ]); - expect(input.map(octCaster)).toEqual([ - Some(0), - Some(1), - Some(8), - Some(64), - ]); + expect(input.map(anyCaster)).toEqual([Some(0), Some(1), Some(10), Some(100)]); + expect(input.map(decCaster)).toEqual([Some(0), Some(1), Some(10), Some(100)]); + expect(input.map(hexCaster)).toEqual([Some(0), Some(1), Some(16), Some(256)]); + expect(input.map(octCaster)).toEqual([Some(0), Some(1), Some(8), Some(64)]); [decCaster, hexCaster, octCaster].forEach((caster) => { const invalidInput = ["hello", "lorem ipsum", ";4lkjdf()"]; - expect(invalidInput.map(caster)).toEqual( - Array(invalidInput.length).fill(None()) - ); + expect(invalidInput.map(caster)).toEqual(Array(invalidInput.length).fill(None())); }); expect(inputHex.map(hexCaster)).toEqual(input.map(hexCaster)); expect(inputHex.map(anyCaster)).toEqual(input.map(hexCaster)); @@ -195,24 +122,9 @@ suite("utils", () => { const decCaster = IntToStringCaster(10); const hexCaster = IntToStringCaster(16); const octCaster = IntToStringCaster(8); - expect(input.map(decCaster)).toEqual([ - Some("0"), - Some("1"), - Some("10"), - Some("100"), - ]); - expect(input.map(hexCaster)).toEqual([ - Some("0"), - Some("1"), - Some("a"), - Some("64"), - ]); - expect(input.map(octCaster)).toEqual([ - Some("0"), - Some("1"), - Some("12"), - Some("144"), - ]); + expect(input.map(decCaster)).toEqual([Some("0"), Some("1"), Some("10"), Some("100")]); + expect(input.map(hexCaster)).toEqual([Some("0"), Some("1"), Some("a"), Some("64")]); + expect(input.map(octCaster)).toEqual([Some("0"), Some("1"), Some("12"), Some("144")]); expect(input.map(defaultCaster)).toEqual(input.map(decCaster)); [defaultCaster, decCaster, hexCaster, octCaster].forEach((caster) => { expect(caster(NaN)).toEqual(None()); @@ -220,27 +132,15 @@ suite("utils", () => { }); test("KVMapper", () => { const numMapper = KVMapper(["a number"], "aNumber", StringToIntCaster()); - const boolMapper = KVMapper( - ["a boolean"], - "aBoolean", - StringToBooleanCaster() - ); + const boolMapper = KVMapper(["a boolean"], "aBoolean", StringToBooleanCaster()); expect(numMapper(["a number", "1234"])).toEqual(Some(["aNumber", 1234])); expect(numMapper(["anumber", "1234"])).toEqual(None()); expect(numMapper(["a number", "asdv1234"])).toEqual(None()); - expect(boolMapper(["a boolean", "yes"])).toEqual( - Some(["aBoolean", true]) - ); - expect(boolMapper(["a boolean", "true"])).toEqual( - Some(["aBoolean", true]) - ); + expect(boolMapper(["a boolean", "yes"])).toEqual(Some(["aBoolean", true])); + expect(boolMapper(["a boolean", "true"])).toEqual(Some(["aBoolean", true])); expect(boolMapper(["a boolean", "1"])).toEqual(Some(["aBoolean", true])); - expect(boolMapper(["a boolean", "no"])).toEqual( - Some(["aBoolean", false]) - ); - expect(boolMapper(["a boolean", "false"])).toEqual( - Some(["aBoolean", false]) - ); + expect(boolMapper(["a boolean", "no"])).toEqual(Some(["aBoolean", false])); + expect(boolMapper(["a boolean", "false"])).toEqual(Some(["aBoolean", false])); expect(boolMapper(["a boolean", "0"])).toEqual(Some(["aBoolean", false])); expect(boolMapper(["aboolean", "0"])).toEqual(None()); expect(boolMapper(["a boolean", "lkjsdlfjksdf"])).toEqual(None()); @@ -257,24 +157,9 @@ suite("utils", () => { const output: Partial & Pick = { adv: {}, }; - const numGrabber = KVGrabber( - output, - "aNumber", - ["a number"], - StringToIntCaster() - ); - const boolGrabber = KVGrabber( - output, - "aBoolean", - ["a boolean"], - StringToBooleanCaster() - ); - const stringGrabber = KVGrabber( - output, - "aString", - ["a string"], - IdentityCaster() - ); + const numGrabber = KVGrabber(output, "aNumber", ["a number"], StringToIntCaster()); + const boolGrabber = KVGrabber(output, "aBoolean", ["a boolean"], StringToBooleanCaster()); + const stringGrabber = KVGrabber(output, "aString", ["a string"], IdentityCaster()); const advGrabber = KVRemainderGrabber(output, "adv"); expect( [numGrabber, boolGrabber, stringGrabber].map((grabber) => @@ -293,9 +178,7 @@ suite("utils", () => { adv: {}, }); expect(advGrabber(["advKey1", "some advanced value"])).toEqual(true); - expect(advGrabber(["advKey2", "some other advanced value"])).toEqual( - true - ); + expect(advGrabber(["advKey2", "some other advanced value"])).toEqual(true); expect(output).toEqual({ aNumber: 1234, aBoolean: true, @@ -360,9 +243,7 @@ suite("utils", () => { "ignore" ); Object.entries(input).forEach(([key, value]) => { - expect( - [overwriteGrabber, ignoreGrabber].every((g) => g([key, value])) - ).toEqual(true); + expect([overwriteGrabber, ignoreGrabber].every((g) => g([key, value]))).toEqual(true); }); expect(overwriteOutput).toEqual({ aNumber: 2 }); expect(ignoreOutput).toEqual({ aNumber: 0 }); diff --git a/houston-common-lib/lib/utils.ts b/houston-common-lib/lib/utils.ts index 43662e2..e7a8c2c 100644 --- a/houston-common-lib/lib/utils.ts +++ b/houston-common-lib/lib/utils.ts @@ -2,12 +2,7 @@ import { KeyValueData } from "@/syntax"; import { Maybe, None, Some } from "monet"; import { Result } from "neverthrow"; -export type ValueElseUndefiend = T extends - | string - | number - | boolean - | symbol - | object +export type ValueElseUndefiend = T extends string | number | boolean | symbol | object ? T : undefined; @@ -26,9 +21,9 @@ export function MethodFunctor< export const IdentityFunctor = (o: T): T => o; -export function Unwrapper< - Container extends { unwrap: () => any } | { some: () => any }, ->(exceptionFactory: (e?: any) => Error = (e) => e) { +export function Unwrapper any } | { some: () => any }>( + exceptionFactory: (e?: any) => Error = (e) => e +) { return (c: Container) => { try { return "unwrap" in c ? c.unwrap() : c.some(); @@ -68,23 +63,14 @@ export function StringToBooleanCaster( ignoreCase ? MethodFunctor(String, "toLowerCase") : IdentityFunctor ); const caster = (text: string) => - Maybe.fromNull( - truthyWords.includes(text) - ? true - : falsyWords.includes(text) - ? false - : null - ); + Maybe.fromNull(truthyWords.includes(text) ? true : falsyWords.includes(text) ? false : null); if (ignoreCase) { return (text: string) => caster(text.toLowerCase()); } return caster; } -export function BooleanToStringCaster< - TruthyWord extends string, - FalsyWord extends string, ->( +export function BooleanToStringCaster( truthyWord: TruthyWord, falsyWord: FalsyWord ): Caster { @@ -121,15 +107,13 @@ export function KVMapper< caster: Caster ): KVMapper { return ([inKey, inValue]) => - inKeys.includes(inKey) - ? caster(inValue).map((outValue) => [outKey, outValue]) - : None(); + inKeys.includes(inKey) ? caster(inValue).map((outValue) => [outKey, outValue]) : None(); } -export type KVGrabber< - InKey extends string | number | symbol, - InValue extends {} | null, -> = ([key, value]: [InKey, InValue]) => boolean; +export type KVGrabber = ([ + key, + value, +]: [InKey, InValue]) => boolean; export function KVGrabber< InKeys extends [string | number | symbol, ...(string | number | symbol)[]], @@ -183,10 +167,7 @@ export type KeyValueDiff = { same: KeyValueData; }; -export function keyValueDiff( - originalObj: KeyValueData, - modifiedObj: KeyValueData -) { +export function keyValueDiff(originalObj: KeyValueData, modifiedObj: KeyValueData) { const added: KeyValueData = {}; const removed: KeyValueData = {}; const changed: KeyValueData = {}; @@ -215,11 +196,8 @@ export function keyValueDiff( }; } -export const safeJsonParse = ( - ...args: Parameters -) => +export const safeJsonParse = (...args: Parameters) => Result.fromThrowable( - (...args: Parameters) => - JSON.parse(...args) as Partial, + (...args: Parameters) => JSON.parse(...args) as Partial, (e) => (e instanceof SyntaxError ? e : new SyntaxError(`${e}`)) )(...args); diff --git a/houston-common-lib/package.json b/houston-common-lib/package.json index 565bb87..e009757 100644 --- a/houston-common-lib/package.json +++ b/houston-common-lib/package.json @@ -24,6 +24,7 @@ "docs": "typedoc --out ../docs" }, "devDependencies": { + "prettier": "^3.3.1", "typedoc": "^0.25.13", "typedoc-plugin-mdn-links": "^3.1.25", "typedoc-umlclass": "^0.9.0", diff --git a/houston-common-ui/.prettierrc.json b/houston-common-ui/.prettierrc.json new file mode 100644 index 0000000..8ae2bdf --- /dev/null +++ b/houston-common-ui/.prettierrc.json @@ -0,0 +1,8 @@ +{ + "$schema": "https://json.schemastore.org/prettierrc", + "semi": true, + "tabWidth": 2, + "singleQuote": false, + "printWidth": 100, + "trailingComma": "es5" +} diff --git a/yarn.lock b/yarn.lock index 75306ea..a8ed3dd 100644 --- a/yarn.lock +++ b/yarn.lock @@ -22,6 +22,7 @@ __metadata: dependencies: monet: "npm:^0.9.3" neverthrow: "npm:^6.2.1" + prettier: "npm:^3.3.1" typedoc: "npm:^0.25.13" typedoc-plugin-mdn-links: "npm:^3.1.25" typedoc-umlclass: "npm:^0.9.0" @@ -5109,6 +5110,15 @@ __metadata: languageName: node linkType: hard +"prettier@npm:^3.3.1": + version: 3.3.1 + resolution: "prettier@npm:3.3.1" + bin: + prettier: bin/prettier.cjs + checksum: 10/31ca48d07a163fe6bff5483feb9bdf3bd7e4305e8d976373375cddc2949180a007be3ef08c36f4d7b31e449acef1ebbf46d3b94dc32f5a276837bf48c393be69 + languageName: node + linkType: hard + "pretty-format@npm:^29.7.0": version: 29.7.0 resolution: "pretty-format@npm:29.7.0"