diff --git a/packages/taler-harness/src/index.ts b/packages/taler-harness/src/index.ts index a172bc9da..841e17dc7 100644 --- a/packages/taler-harness/src/index.ts +++ b/packages/taler-harness/src/index.ts @@ -170,6 +170,39 @@ const sandcastleCli = testingCli.subcommand("sandcastleArgs", "sandcastle", { help: "Subcommands for handling GNU Taler sandcastle deployments.", }); +const configCli = testingCli.subcommand("configArgs", "config", { + help: "Subcommands for handling the Taler configuration.", +}); + +configCli.subcommand("show", "show").action(async (args) => { + const config = Configuration.load(); + const cfgStr = config.stringify({ + diagnostics: true, + }); + console.log(cfgStr); +}); + +configCli + .subcommand("get", "get") + .requiredArgument("section", clk.STRING) + .requiredArgument("option", clk.STRING) + .flag("file", ["-f"]) + .action(async (args) => { + const config = Configuration.load(); + let res; + if (args.get.file) { + res = config.getString(args.get.section, args.get.option); + } else { + res = config.getPath(args.get.section, args.get.option); + } + if (res.isDefined()) { + console.log(res.value); + } else { + console.warn("not found"); + process.exit(1); + } + }); + const deploymentCli = testingCli.subcommand("deploymentArgs", "deployment", { help: "Subcommands for handling GNU Taler deployments.", }); diff --git a/packages/taler-util/src/talerconfig.ts b/packages/taler-util/src/talerconfig.ts index 948ccb9c4..d86c58678 100644 --- a/packages/taler-util/src/talerconfig.ts +++ b/packages/taler-util/src/talerconfig.ts @@ -1,6 +1,6 @@ /* This file is part of GNU Taler - (C) 2020 Taler Systems S.A. + (C) 2020-2023 Taler Systems S.A. GNU Taler is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software @@ -40,12 +40,22 @@ export class ConfigError extends Error { } enum EntryOrigin { - // From a default file - Default = 1, - // Loaded from file or string - Loaded = 2, - // Changed after loading - Changed = 3, + /** + * From a default file. + */ + DefaultFile = 1, + /** + * From a system/installation specific default value. + */ + DefaultSystem = 2, + /** + * Loaded from file or string + */ + Loaded = 3, + /** + * Changed after loading + */ + Changed = 4, } interface Entry { @@ -169,12 +179,12 @@ export function pathsub( const r = lookup(inner, depth + 1); if (r !== undefined) { - s = s.substr(0, start) + r + s.substr(p + 1); + s = s.substring(0, start) + r + s.substring(p + 1); l = start + r.length; continue; } else if (defaultValue !== undefined) { const resolvedDefault = pathsub(defaultValue, lookup, depth + 1); - s = s.substr(0, start) + resolvedDefault + s.substr(p + 1); + s = s.substring(0, start) + resolvedDefault + s.substring(p + 1); l = start + resolvedDefault.length; continue; } @@ -186,7 +196,7 @@ export function pathsub( if (m && m[0]) { const r = lookup(m[0], depth + 1); if (r !== undefined) { - s = s.substr(0, l) + r + s.substr(l + 1 + m[0].length); + s = s.substring(0, l) + r + s.substring(l + 1 + m[0].length); l = l + r.length; continue; } @@ -492,7 +502,9 @@ export class Configuration { value: val, sourceFile: opts.filename ?? "", sourceLine: lineNo, - origin: isDefaultSource ? EntryOrigin.Default : EntryOrigin.Loaded, + origin: isDefaultSource + ? EntryOrigin.DefaultFile + : EntryOrigin.Loaded, }; continue; } @@ -535,6 +547,23 @@ export class Configuration { }; } + /** + * Set a string value to a value from default locations. + */ + private setStringSystemDefault( + section: string, + option: string, + value: string, + ): void { + const sec = this.provideSection(section); + sec.entries[option.toUpperCase()] = { + value, + sourceLine: 0, + sourceFile: "", + origin: EntryOrigin.DefaultSystem, + }; + } + /** * Get upper-cased section names. */ @@ -595,7 +624,7 @@ export class Configuration { lookupVariable(x: string, depth: number = 0): string | undefined { // We loop up options in PATHS in upper case, as option names // are case insensitive - const val = this.findEntry("PATHS", x)?.value; + let val = this.findEntry("PATHS", x)?.value; if (val !== undefined) { return pathsub(val, (v, d) => this.lookupVariable(v, d), depth); } @@ -623,21 +652,54 @@ export class Configuration { } private loadDefaults(): void { - let bc = process.env["TALER_BASE_CONFIG"]; - if (!bc) { + let baseConfigDir = process.env["TALER_BASE_CONFIG"]; + if (!baseConfigDir) { /* Try to locate the configuration based on the location * of the taler-config binary. */ const path = which("taler-config"); if (path) { - bc = nodejs_fs.realpathSync( + baseConfigDir = nodejs_fs.realpathSync( nodejs_path.dirname(path) + "/../share/taler/config.d", ); } } - if (!bc) { - bc = "/usr/share/taler/config.d"; + if (!baseConfigDir) { + baseConfigDir = "/usr/share/taler/config.d"; } - this.loadDefaultsFromDir(bc); + + let installPrefix = process.env["TALER_PREFIX"]; + if (!installPrefix) { + /* Try to locate install path based on the location + * of the taler-config binary. */ + const path = which("taler-config"); + if (path) { + installPrefix = nodejs_fs.realpathSync( + nodejs_path.dirname(path) + "/..", + ); + } + } + if (!installPrefix) { + installPrefix = "/usr"; + } + + this.setStringSystemDefault( + "PATHS", + "LIBEXECDIR", + `${installPrefix}/taler/libexec/`, + ); + this.setStringSystemDefault( + "PATHS", + "DOCDIR", + `${installPrefix}/share/doc/taler/`, + ); + this.setStringSystemDefault("PATHS", "ICONDIR", `${installPrefix}/share/icons/`); + this.setStringSystemDefault("PATHS", "LOCALEDIR", `${installPrefix}/share/locale/`); + this.setStringSystemDefault("PATHS", "PREFIX", `${installPrefix}/`); + this.setStringSystemDefault("PATHS", "BINDIR", `${installPrefix}/bin`); + this.setStringSystemDefault("PATHS", "LIBDIR", `${installPrefix}/lib/taler/`); + this.setStringSystemDefault("PATHS", "DATADIR", `${installPrefix}/share/taler/`); + + this.loadDefaultsFromDir(baseConfigDir); } getDefaultConfigFilename(): string | undefined { @@ -698,7 +760,11 @@ export class Configuration { let headerWritten = false; for (const optionName of Object.keys(sec.entries)) { const entry = this.sectionMap[sectionName].entries[optionName]; - if (opts.excludeDefaults && entry.origin === EntryOrigin.Default) { + if ( + opts.excludeDefaults && + (entry.origin === EntryOrigin.DefaultSystem || + entry.origin === EntryOrigin.DefaultFile) + ) { continue; } if (!headerWritten) { @@ -711,7 +777,16 @@ export class Configuration { } if (entry !== undefined) { if (opts.diagnostics) { - s += `# ${entry.sourceFile}:${entry.sourceLine}\n`; + switch (entry.origin) { + case EntryOrigin.DefaultFile: + case EntryOrigin.Changed: + case EntryOrigin.Loaded: + s += `# ${entry.sourceFile}:${entry.sourceLine}\n`; + break; + case EntryOrigin.DefaultSystem: + s += `# (system/installation default)\n`; + break; + } } s += `${optionName} = ${entry.value}\n`; }