diff --git a/src/util/talerconfig.ts b/src/util/talerconfig.ts new file mode 100644 index 000000000..333bcd1bb --- /dev/null +++ b/src/util/talerconfig.ts @@ -0,0 +1,120 @@ +/* + 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 + */ + +/** + * Utilities to handle Taler-style configuration files. + * + * @author Florian Dold + */ + +/** + * Imports + */ +import { AmountJson } from "./amounts"; +import * as Amounts from "./amounts"; + +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 { + 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); + } +} + +export class Configuration { + private sectionMap: SectionMap = {}; + + constructor() {} + + 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) { + console.log("parsing line", JSON.stringify(line)); + if (reEmptyLine.test(line)) { + continue; + } + if (reComment.test(line)) { + continue; + } + const secMatch = line.match(reSection); + if (secMatch) { + currentSection = secMatch[1]; + console.log("setting section to", currentSection); + continue; + } + if (currentSection === undefined) { + throw Error("invalid configuration, expected section header"); + } + const paramMatch = line.match(reParam); + if (paramMatch) { + const optName = paramMatch[1]; + 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", + ); + } + + console.log("parsed config", JSON.stringify(this.sectionMap, undefined, 2)); + } + + getString(section: string, option: string): ConfigValue { + const val = (this.sectionMap[section] ?? {})[option]; + return new ConfigValue(section, option, val, x => x); + } + + getAmount(section: string, option: string): ConfigValue { + const val = (this.sectionMap[section] ?? {})[option]; + return new ConfigValue(section, option, val, x => Amounts.parseOrThrow(x)); + } +}