Add a local YAML config file for persistent settings
Reads $XDG_CONFIG_HOME/slocker-lite/config.yaml (falling back to $HOME/.config/slocker-lite/config.yaml), organized into sections. Only the "global" section's log-level is supported for now -- other options are one-shot flags, not standing preferences. An explicit --log-level on the command line always overrides the config file, the same way SPDLOG_LEVEL already does. Uses libyaml directly (yaml_dep was already declared in meson.build but unused). A missing config file isn't an error; unknown sections/keys are ignored for forward-compatibility; malformed YAML syntax is a hard error. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Gv3s5jckJKzh6JkMoi2Akz
This commit is contained in:
@@ -0,0 +1,95 @@
|
||||
// Copyright (C) 2026 Viorel Munteanu
|
||||
//
|
||||
// This program 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 2 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program 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 this program; if not, write to the Free Software Foundation, Inc.,
|
||||
// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
|
||||
#include "config_file.h"
|
||||
|
||||
#include <cstdlib>
|
||||
#include <string_view>
|
||||
|
||||
#include <spdlog/spdlog.h>
|
||||
#include <yaml.h>
|
||||
|
||||
namespace {
|
||||
|
||||
std::string_view scalar_value(const yaml_node_t& node) {
|
||||
return {reinterpret_cast<const char*>(node.data.scalar.value), node.data.scalar.length};
|
||||
}
|
||||
|
||||
// Finds `key` in a YAML_MAPPING_NODE and returns its value node, or nullptr if
|
||||
// `node` isn't a mapping or has no such (scalar) key.
|
||||
const yaml_node_t* find_in_mapping(yaml_document_t& document, const yaml_node_t& node,
|
||||
std::string_view key) {
|
||||
if (node.type != YAML_MAPPING_NODE) {
|
||||
return nullptr;
|
||||
}
|
||||
for (auto* pair = node.data.mapping.pairs.start; pair < node.data.mapping.pairs.top; ++pair) {
|
||||
yaml_node_t* key_node = yaml_document_get_node(&document, pair->key);
|
||||
if (key_node && key_node->type == YAML_SCALAR_NODE && scalar_value(*key_node) == key) {
|
||||
return yaml_document_get_node(&document, pair->value);
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
std::filesystem::path config_file_path() {
|
||||
const char* xdg_config_home = std::getenv("XDG_CONFIG_HOME");
|
||||
std::filesystem::path config_home;
|
||||
if (xdg_config_home && *xdg_config_home) {
|
||||
config_home = xdg_config_home;
|
||||
} else {
|
||||
const char* home = std::getenv("HOME");
|
||||
config_home = std::filesystem::path(home ? home : "") / ".config";
|
||||
}
|
||||
return config_home / "slocker-lite" / "config.yaml";
|
||||
}
|
||||
|
||||
std::optional<AppConfig> load_config_file(const std::filesystem::path& path) {
|
||||
FILE* file = std::fopen(path.c_str(), "r");
|
||||
if (!file) {
|
||||
return AppConfig{};
|
||||
}
|
||||
|
||||
yaml_parser_t parser;
|
||||
yaml_parser_initialize(&parser);
|
||||
yaml_parser_set_input_file(&parser, file);
|
||||
|
||||
yaml_document_t document;
|
||||
bool loaded = yaml_parser_load(&parser, &document) != 0;
|
||||
yaml_parser_delete(&parser);
|
||||
std::fclose(file);
|
||||
|
||||
if (!loaded) {
|
||||
spdlog::error("failed to parse config file {}", path.string());
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
AppConfig config;
|
||||
yaml_node_t* root = yaml_document_get_root_node(&document);
|
||||
if (root) {
|
||||
if (const yaml_node_t* global = find_in_mapping(document, *root, "global")) {
|
||||
if (const yaml_node_t* log_level = find_in_mapping(document, *global, "log-level")) {
|
||||
if (log_level->type == YAML_SCALAR_NODE) {
|
||||
config.log_level = std::string(scalar_value(*log_level));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
yaml_document_delete(&document);
|
||||
return config;
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
// Copyright (C) 2026 Viorel Munteanu
|
||||
//
|
||||
// This program 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 2 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program 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 this program; if not, write to the Free Software Foundation, Inc.,
|
||||
// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <filesystem>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
|
||||
// Fields that make sense to persist across invocations (one-shot flags like
|
||||
// -m/-r/--user don't belong here). Only the "global" section's log-level is
|
||||
// supported today; add more optional fields as more long options gain config-file
|
||||
// support.
|
||||
struct AppConfig {
|
||||
std::optional<std::string> log_level; // global.log-level
|
||||
};
|
||||
|
||||
// $XDG_CONFIG_HOME/slocker-lite/config.yaml, or $HOME/.config/slocker-lite/config.yaml
|
||||
// if XDG_CONFIG_HOME is unset/empty.
|
||||
std::filesystem::path config_file_path();
|
||||
|
||||
// Loads and parses `path`'s "global" section. A missing file is not an error --
|
||||
// returns a default-constructed AppConfig (nothing set). Unknown sections/keys are
|
||||
// ignored, so the format stays forward-compatible. Malformed YAML syntax logs a
|
||||
// specific error and returns nullopt.
|
||||
std::optional<AppConfig> load_config_file(const std::filesystem::path& path);
|
||||
@@ -31,6 +31,7 @@
|
||||
|
||||
#include "bwrap.h"
|
||||
#include "config.h"
|
||||
#include "config_file.h"
|
||||
#include "containers_storage.h"
|
||||
#include "oci_image.h"
|
||||
#include "process.h"
|
||||
@@ -311,6 +312,14 @@ int run_container(const std::filesystem::path& image_tar,
|
||||
int main(int argc, char* argv[]) {
|
||||
spdlog::cfg::load_env_levels();
|
||||
|
||||
auto config = load_config_file(config_file_path());
|
||||
if (!config) {
|
||||
return 1;
|
||||
}
|
||||
if (config->log_level) {
|
||||
apply_log_level(*config->log_level);
|
||||
}
|
||||
|
||||
Mode mode = Mode::kNone;
|
||||
std::string mode_arg;
|
||||
bool disable_nsenter = false;
|
||||
|
||||
Reference in New Issue
Block a user