[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]
[libeufin] branch master updated: util: rudimentary taler config parser
From: |
gnunet |
Subject: |
[libeufin] branch master updated: util: rudimentary taler config parser |
Date: |
Thu, 21 Sep 2023 18:36:59 +0200 |
This is an automated email from the git hooks/post-receive script.
dold pushed a commit to branch master
in repository libeufin.
The following commit(s) were added to refs/heads/master by this push:
new 39889ac1 util: rudimentary taler config parser
39889ac1 is described below
commit 39889ac1ebff9b654186ec3d3e974b87f66fc1b6
Author: Florian Dold <florian@dold.me>
AuthorDate: Thu Sep 21 18:35:58 2023 +0200
util: rudimentary taler config parser
---
util/src/main/kotlin/TalerConfig.kt | 224 ++++++++++++++++++++++++++++++++
util/src/test/kotlin/TalerConfigTest.kt | 45 +++++++
2 files changed, 269 insertions(+)
diff --git a/util/src/main/kotlin/TalerConfig.kt
b/util/src/main/kotlin/TalerConfig.kt
new file mode 100644
index 00000000..83ca40a0
--- /dev/null
+++ b/util/src/main/kotlin/TalerConfig.kt
@@ -0,0 +1,224 @@
+/*
+ * This file is part of LibEuFin.
+ * Copyright (C) 2023 Taler Systems S.A.
+ *
+ * LibEuFin is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation; either version 3, or
+ * (at your option) any later version.
+ *
+ * LibEuFin 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 Affero General
+ * Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public
+ * License along with LibEuFin; see the file COPYING. If not, see
+ * <http://www.gnu.org/licenses/>
+ */
+
+import java.io.File
+import java.nio.file.Paths
+import java.util.*
+import kotlin.io.path.Path
+import kotlin.io.path.listDirectoryEntries
+
+private data class Entry(val value: String)
+
+private data class Section(
+ val entries: MutableMap<String, Entry>
+)
+
+private val reEmptyLine = Regex("^\\s*$")
+private val reComment = Regex("^\\s*#.*$")
+private val reSection = Regex("^\\s*\\[\\s*([^]]*)\\s*]\\s*$")
+private val reParam = Regex("^\\s*([^=]+?)\\s*=\\s*(.*?)\\s*$")
+private val reDirective = Regex("^\\s*@([a-zA-Z-_]+)@\\s*(.*?)\\s*$")
+
+class TalerConfigError(m: String) : Exception(m)
+
+/**
+ * Reader and writer for Taler-style configuration files.
+ *
+ * The configuration file format is similar to INI files
+ * and fully described in the taler.conf man page.
+ */
+class TalerConfig {
+ private val sectionMap: MutableMap<String, Section> = mutableMapOf()
+
+ private fun internalLoadFromString(s: String) {
+ val lines = s.lines()
+ var lineNum = 0
+ var currentSection: String? = null
+ for (line in lines) {
+ lineNum++
+ if (reEmptyLine.matches(line)) {
+ continue
+ }
+ if (reComment.matches(line)) {
+ continue
+ }
+
+ val directiveMatch = reDirective.matchEntire(line)
+ if (directiveMatch != null) {
+ throw NotImplementedError("config directives are not
implemented yet")
+ }
+
+ val secMatch = reSection.matchEntire(line)
+ if (secMatch != null) {
+ currentSection = secMatch.groups[1]!!.value.uppercase()
+ continue
+ }
+ if (currentSection == null) {
+ throw TalerConfigError("section expected")
+ }
+
+ val paramMatch = reParam.matchEntire(line)
+
+ if (paramMatch != null) {
+ val optName = paramMatch.groups[1]!!.value.uppercase()
+ var optVal = paramMatch.groups[2]!!.value
+ if (optVal.startsWith('"') && optVal.endsWith('"')) {
+ optVal = optVal.substring(1, optVal.length - 1)
+ }
+ val section = provideSection(currentSection)
+ section.entries[optName] = Entry(
+ value = optVal
+ )
+ continue
+ }
+ throw TalerConfigError("expected section header, option assignment
or directive in line $lineNum")
+ }
+ }
+
+ private fun provideSection(name: String): Section {
+ val existingSec = this.sectionMap[name]
+ if (existingSec != null) {
+ return existingSec
+ }
+ val newSection = Section(entries = mutableMapOf())
+ this.sectionMap[name] = newSection
+ return newSection
+ }
+
+ fun loadFromString(s: String) {
+ internalLoadFromString(s)
+ }
+
+ private fun lookupEntry(section: String, option: String): Entry? {
+ val canonSection = section.uppercase()
+ val canonOption = option.uppercase()
+ return this.sectionMap[canonSection]?.entries?.get(canonOption)
+ }
+
+ /**
+ * Look up a string value from the configuration.
+ *
+ * Return an empty Optional if the value was not found in the
configuration.
+ */
+ fun lookupValueString(section: String, option: String): Optional<String> {
+ return Optional.ofNullable(lookupEntry(section, option)?.value)
+ }
+
+ /**
+ * Create a string representation of the loaded configuration.
+ */
+ fun stringify(): String {
+ val outStr = StringBuilder()
+ this.sectionMap.forEach { (sectionName, section) ->
+ var headerWritten = false
+ section.entries.forEach { (optionName, entry) ->
+ if (!headerWritten) {
+ outStr.appendLine("[$sectionName]")
+ headerWritten = true
+ }
+ outStr.appendLine("$optionName = ${entry.value}")
+ }
+ if (headerWritten) {
+ outStr.appendLine()
+ }
+ }
+ return outStr.toString()
+ }
+
+ fun loadFromFilename(filename: String) {
+ val f = File(filename)
+ val contents = f.readText()
+ loadFromString(contents)
+ }
+
+ private fun loadDefaultsFromDir(dirname: String) {
+ for (filePath in Path(dirname).listDirectoryEntries()) {
+ loadFromFilename(filePath.toString())
+ }
+ }
+
+ fun loadDefaults() {
+ val installDir = getTalerInstallPath()
+ val baseConfigDir = Paths.get(installDir,
"share/taler/config.d").toString()
+ loadDefaultsFromDir(baseConfigDir)
+ }
+
+ companion object {
+ /**
+ * Load configuration values from the file system.
+ * If no entrypoint is specified, the default entrypoint
+ * is used.
+ */
+ fun load(entrypoint: String? = null): TalerConfig {
+ val cfg = TalerConfig()
+ cfg.loadDefaults()
+ if (entrypoint != null) {
+ cfg.loadFromFilename(entrypoint)
+ } else {
+ val defaultFilename = findDefaultConfigFilename()
+ if (defaultFilename != null) {
+ cfg.loadFromFilename(defaultFilename)
+ }
+ }
+ return cfg
+ }
+
+
+ /**
+ * Determine the filename of the default configuration file.
+ *
+ * If no such file can be found, return null.
+ */
+ private fun findDefaultConfigFilename(): String? {
+ val xdg = System.getenv("XDG_CONFIG_HOME")
+ val home = System.getenv("HOME")
+
+ var filename: String? = null
+ if (xdg != null) {
+ filename = Paths.get(xdg, "taler.conf").toString()
+ } else if (home != null) {
+ filename = Paths.get(home, ".config/taler.conf").toString()
+ }
+ if (filename != null && File(filename).exists()) {
+ return filename
+ }
+ val etc1 = "/etc/taler.conf"
+ if (File(etc1).exists()) {
+ return etc1
+ }
+ val etc2 = "/etc/taler/taler.conf"
+ if (File(etc2).exists()) {
+ return etc2
+ }
+ return null
+ }
+
+ fun getTalerInstallPath(): String {
+ val pathEnv = System.getenv("PATH")
+ val paths = pathEnv.split(":")
+ for (p in paths) {
+ val possiblePath = Paths.get(p, "taler-config").toString()
+ if (File(possiblePath).exists()) {
+ return Paths.get(p, "..").toRealPath().toString()
+ }
+ }
+ return "/usr"
+ }
+ }
+}
diff --git a/util/src/test/kotlin/TalerConfigTest.kt
b/util/src/test/kotlin/TalerConfigTest.kt
new file mode 100644
index 00000000..216528c0
--- /dev/null
+++ b/util/src/test/kotlin/TalerConfigTest.kt
@@ -0,0 +1,45 @@
+/*
+ * This file is part of LibEuFin.
+ * Copyright (C) 2023 Taler Systems S.A.
+ *
+ * LibEuFin is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation; either version 3, or
+ * (at your option) any later version.
+ *
+ * LibEuFin 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 Affero General
+ * Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public
+ * License along with LibEuFin; see the file COPYING. If not, see
+ * <http://www.gnu.org/licenses/>
+ */
+
+import org.junit.Test
+import kotlin.test.assertEquals
+
+class TalerConfigTest {
+
+ @Test
+ fun parsing() {
+ val conf = TalerConfig()
+ conf.loadDefaults()
+ conf.loadFromString(
+ """
+
+ [foo]
+
+ bar = baz
+
+ """.trimIndent()
+ )
+
+ println(conf.stringify())
+
+ assertEquals("baz", conf.lookupValueString("foo", "bar").orElseThrow())
+
+ println(TalerConfig.getTalerInstallPath())
+ }
+}
--
To stop receiving notification emails like this one, please contact
gnunet@gnunet.org.
[Prev in Thread] |
Current Thread |
[Next in Thread] |
- [libeufin] branch master updated: util: rudimentary taler config parser,
gnunet <=