diff options
author | Florian Dold <florian@dold.me> | 2021-03-17 17:56:37 +0100 |
---|---|---|
committer | Florian Dold <florian@dold.me> | 2021-03-17 17:56:37 +0100 |
commit | 07cdfb2e4ec761021477271776b81f33af0e731d (patch) | |
tree | cb62b1d1a04e1e64b8ee47e78196e858727d2c0a /packages/taler-wallet-core/src/util/talerconfig.ts | |
parent | 42a4d666f42ce94274995bfdae644444ff5f6d53 (diff) | |
download | wallet-core-07cdfb2e4ec761021477271776b81f33af0e731d.tar.xz |
towards wallet-core / util split
Diffstat (limited to 'packages/taler-wallet-core/src/util/talerconfig.ts')
-rw-r--r-- | packages/taler-wallet-core/src/util/talerconfig.ts | 318 |
1 files changed, 0 insertions, 318 deletions
diff --git a/packages/taler-wallet-core/src/util/talerconfig.ts b/packages/taler-wallet-core/src/util/talerconfig.ts deleted file mode 100644 index fa8c2d40f..000000000 --- a/packages/taler-wallet-core/src/util/talerconfig.ts +++ /dev/null @@ -1,318 +0,0 @@ -/* - This file is part of GNU Taler - (C) 2020 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 - Foundation; either version 3, or (at your option) any later version. - - GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY - WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR - A PARTICULAR PURPOSE. See the GNU General Public License for more details. - - You should have received a copy of the GNU General Public License along with - GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/> - */ - -/** - * Utilities to handle Taler-style configuration files. - * - * @author Florian Dold <dold@taler.net> - */ - -/** - * Imports - */ -import { AmountJson } from "./amounts"; -import * as Amounts from "./amounts"; -import fs from "fs"; - -export class ConfigError extends Error { - constructor(message: string) { - super(); - Object.setPrototypeOf(this, ConfigError.prototype); - this.name = "ConfigError"; - this.message = message; - } -} - -type OptionMap = { [optionName: string]: string }; -type SectionMap = { [sectionName: string]: OptionMap }; - -export class ConfigValue<T> { - constructor( - private sectionName: string, - private optionName: string, - private val: string | undefined, - private converter: (x: string) => T, - ) {} - - required(): T { - if (!this.val) { - throw new ConfigError( - `required option [${this.sectionName}]/${this.optionName} not found`, - ); - } - return this.converter(this.val); - } - - orUndefined(): T | undefined { - if (this.val !== undefined) { - return this.converter(this.val); - } else { - return undefined; - } - } - - orDefault(v: T): T | undefined { - if (this.val !== undefined) { - return this.converter(this.val); - } else { - return v; - } - } - - isDefined(): boolean { - return this.val !== undefined; - } -} - -/** - * Shell-style path substitution. - * - * Supported patterns: - * "$x" (look up "x") - * "${x}" (look up "x") - * "${x:-y}" (look up "x", fall back to expanded y) - */ -export function pathsub( - x: string, - lookup: (s: string, depth: number) => string | undefined, - depth = 0, -): string { - if (depth >= 10) { - throw Error("recursion in path substitution"); - } - let s = x; - let l = 0; - while (l < s.length) { - if (s[l] === "$") { - if (s[l + 1] === "{") { - let depth = 1; - const start = l; - let p = start + 2; - let insideNamePart = true; - let hasDefault = false; - for (; p < s.length; p++) { - if (s[p] == "}") { - insideNamePart = false; - depth--; - } else if (s[p] === "$" && s[p + 1] === "{") { - insideNamePart = false; - depth++; - } - if (insideNamePart && s[p] === ":" && s[p + 1] === "-") { - hasDefault = true; - } - if (depth == 0) { - break; - } - } - if (depth == 0) { - const inner = s.slice(start + 2, p); - let varname: string; - let defaultValue: string | undefined; - if (hasDefault) { - [varname, defaultValue] = inner.split(":-", 2); - } else { - varname = inner; - defaultValue = undefined; - } - - const r = lookup(inner, depth + 1); - if (r !== undefined) { - s = s.substr(0, start) + r + s.substr(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); - l = start + resolvedDefault.length; - continue; - } - } - l = p; - continue; - } else { - const m = /^[a-zA-Z-_][a-zA-Z0-9-_]*/.exec(s.substring(l + 1)); - 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); - l = l + r.length; - continue; - } - } - } - } - l++; - } - return s; -} - -export class Configuration { - private sectionMap: SectionMap = {}; - - loadFromString(s: string): void { - const reComment = /^\s*#.*$/; - const reSection = /^\s*\[\s*([^\]]*)\s*\]\s*$/; - const reParam = /^\s*([^=]+?)\s*=\s*(.*?)\s*$/; - const reEmptyLine = /^\s*$/; - - let currentSection: string | undefined = undefined; - - const lines = s.split("\n"); - for (const line of lines) { - if (reEmptyLine.test(line)) { - continue; - } - if (reComment.test(line)) { - continue; - } - const secMatch = line.match(reSection); - if (secMatch) { - currentSection = secMatch[1]; - continue; - } - if (currentSection === undefined) { - throw Error("invalid configuration, expected section header"); - } - currentSection = currentSection.toUpperCase(); - const paramMatch = line.match(reParam); - if (paramMatch) { - const optName = paramMatch[1].toUpperCase(); - let val = paramMatch[2]; - if (val.startsWith('"') && val.endsWith('"')) { - val = val.slice(1, val.length - 1); - } - const sec = this.sectionMap[currentSection] ?? {}; - this.sectionMap[currentSection] = Object.assign(sec, { - [optName]: val, - }); - continue; - } - throw Error( - "invalid configuration, expected section header or option assignment", - ); - } - } - - setString(section: string, option: string, value: string): void { - const secNorm = section.toUpperCase(); - const sec = this.sectionMap[secNorm] ?? (this.sectionMap[secNorm] = {}); - sec[option.toUpperCase()] = value; - } - - /** - * Get lower-cased section names. - */ - getSectionNames(): string[] { - return Object.keys(this.sectionMap).map((x) => x.toLowerCase()); - } - - getString(section: string, option: string): ConfigValue<string> { - const secNorm = section.toUpperCase(); - const optNorm = option.toUpperCase(); - const val = (this.sectionMap[secNorm] ?? {})[optNorm]; - return new ConfigValue(secNorm, optNorm, val, (x) => x); - } - - getPath(section: string, option: string): ConfigValue<string> { - const secNorm = section.toUpperCase(); - const optNorm = option.toUpperCase(); - const val = (this.sectionMap[secNorm] ?? {})[optNorm]; - return new ConfigValue(secNorm, optNorm, val, (x) => - pathsub(x, (v, d) => this.lookupVariable(v, d + 1)), - ); - } - - getYesNo(section: string, option: string): ConfigValue<boolean> { - const secNorm = section.toUpperCase(); - const optNorm = option.toUpperCase(); - const val = (this.sectionMap[secNorm] ?? {})[optNorm]; - const convert = (x: string): boolean => { - x = x.toLowerCase(); - if (x === "yes") { - return true; - } else if (x === "no") { - return false; - } - throw Error( - `invalid config value for [${secNorm}]/${optNorm}, expected yes/no`, - ); - }; - return new ConfigValue(secNorm, optNorm, val, convert); - } - - getNumber(section: string, option: string): ConfigValue<number> { - const secNorm = section.toUpperCase(); - const optNorm = option.toUpperCase(); - const val = (this.sectionMap[secNorm] ?? {})[optNorm]; - const convert = (x: string): number => { - try { - return Number.parseInt(x, 10); - } catch (e) { - throw Error( - `invalid config value for [${secNorm}]/${optNorm}, expected number`, - ); - } - }; - return new ConfigValue(secNorm, optNorm, val, convert); - } - - 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.sectionMap["PATHS"] ?? {})[x.toUpperCase()]; - if (val !== undefined) { - return pathsub(val, (v, d) => this.lookupVariable(v, d), depth); - } - // Environment variables can be case sensitive, respect that. - const envVal = process.env[x]; - if (envVal !== undefined) { - return envVal; - } - return; - } - - getAmount(section: string, option: string): ConfigValue<AmountJson> { - const val = (this.sectionMap[section] ?? {})[option]; - return new ConfigValue(section, option, val, (x) => - Amounts.parseOrThrow(x), - ); - } - - static load(filename: string): Configuration { - const s = fs.readFileSync(filename, "utf-8"); - const cfg = new Configuration(); - cfg.loadFromString(s); - return cfg; - } - - write(filename: string): void { - let s = ""; - for (const sectionName of Object.keys(this.sectionMap)) { - s += `[${sectionName}]\n`; - for (const optionName of Object.keys( - this.sectionMap[sectionName] ?? {}, - )) { - const val = this.sectionMap[sectionName][optionName]; - if (val !== undefined) { - s += `${optionName} = ${val}\n`; - } - } - s += "\n"; - } - fs.writeFileSync(filename, s); - } -} |