From 662d8cd2e65be9097045819bb0e6f78ddb571ca6 Mon Sep 17 00:00:00 2001 From: Chewing_Bever Date: Wed, 15 Jun 2022 12:40:35 +0200 Subject: [PATCH] Moved over files from vieter repo --- .gitignore | 1 + README.md | 4 +++ v.mod | 7 ++++ vconf.v | 104 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 116 insertions(+) create mode 100644 .gitignore create mode 100644 README.md create mode 100644 v.mod create mode 100644 vconf.v diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..140f8cf --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +*.so diff --git a/README.md b/README.md new file mode 100644 index 0000000..4e90886 --- /dev/null +++ b/README.md @@ -0,0 +1,4 @@ +# Vconf + +Vconf is a small V library for loading in configuration from a TOML config file +and/or environment variables. diff --git a/v.mod b/v.mod new file mode 100644 index 0000000..7a44ed0 --- /dev/null +++ b/v.mod @@ -0,0 +1,7 @@ +Module { + name: 'vconf' + description: 'A small library for reading configuration from a TOML file & environment variables' + version: '0.0.0' + license: 'MIT' + dependencies: [] +} diff --git a/vconf.v b/vconf.v new file mode 100644 index 0000000..393865f --- /dev/null +++ b/vconf.v @@ -0,0 +1,104 @@ +module vconf + +import os +import toml + +// get_env_var tries to read the contents of the given environment variable. It +// looks for either `${env.prefix}${field_name.to_upper()}` or +// `${env.prefix}${field_name.to_upper()}${env.file_suffix}`, returning the +// contents of the file instead if the latter. If both or neither exist, the +// function returns an error. +fn get_env_var(prefix string, field_name string, file_suffix string) ?string { + env_var_name := '$prefix$field_name.to_upper()' + env_file_name := '$prefix$field_name.to_upper()$file_suffix' + env_var := os.getenv(env_var_name) + env_file := os.getenv(env_file_name) + + // If both are missing, we return an empty string + if env_var == '' && env_file == '' { + return '' + } + + // If they're both set, we report a conflict + if env_var != '' && env_file != '' { + return error('Only one of $env_var_name or $env_file_name can be defined.') + } + + // If it's the env var itself, we return it. + // I'm pretty sure this also prevents variable ending in _FILE (e.g. + // VIETER_LOG_FILE) from being mistakingely read as an _FILE suffixed env + // var. + if env_var != '' { + return env_var + } + + // Otherwise, we process the file + return os.read_file(env_file) or { + error('Failed to read file defined in $env_file_name: ${err.msg()}.') + } +} + +[params] +struct LoadConfig { + prefix string + file_suffix string = '_FILE' + default_path string +} + +// load attempts to create an object of type T from the given path to a toml +// file & environment variables. For each field, it will select either a value +// given from an environment variable, a value defined in the config file or a +// configured default if present, in that order. +pub fn load(conf LoadConfig) ?T { + mut res := T{} + + // Later, this could be read from an env var as well. + path := conf.default_path + + if os.exists(path) { + // We don't use reflect here because reflect also sets any fields not + // in the toml back to their zero value, which we don't want + doc := toml.parse_file(path)? + + $for field in T.fields { + s := doc.value(field.name) + + if s !is toml.Null { + $if field.typ is string { + res.$(field.name) = s.string() + } $else $if field.typ is int { + res.$(field.name) = s.int() + } + } + } + } + + $for field in T.fields { + env_value := get_env_var(conf.prefix, field.name, conf.file_suffix)? + + // The value of an env var will always take precedence over the toml + // file. + if env_value != '' { + $if field.typ is string { + res.$(field.name) = env_value + } $else $if field.typ is int { + res.$(field.name) = env_value.int() + } + } + + // Now, we check whether a value is present. If there isn't, that means + // it isn't in the config file, nor is there a default or an env var. + mut has_value := false + + $if field.typ is string { + has_value = res.$(field.name) != '' + } $else $if field.typ is int { + has_value = res.$(field.name) != 0 + } + + if !has_value { + return error("Missing config variable '$field.name' with no provided default. Either add it to the config file or provide it using an environment variable.") + } + } + return res +}