Verified Commit 7c97b5a4 authored by Alberto Miranda's avatar Alberto Miranda ♨️
Browse files

Refactor `scord_ctl::config_file::command|environment` classes

parent 7018495d
Loading
Loading
Loading
Loading
+1 −0
Original line number Diff line number Diff line
@@ -28,6 +28,7 @@ add_executable(scord-ctl)
target_sources(
  scord-ctl PRIVATE scord_ctl.cpp rpc_server.cpp rpc_server.hpp
  ${CMAKE_CURRENT_BINARY_DIR}/defaults.hpp config_file.hpp config_file.cpp
  command.hpp command.cpp
)

configure_file(defaults.hpp.in ${CMAKE_CURRENT_BINARY_DIR}/defaults.hpp @ONLY)
+121 −0
Original line number Diff line number Diff line
#include <ranges>
#include <regex>
#include <cassert>
#include "command.hpp"

namespace scord_ctl {

void
environment::set(const std::string& key, const std::string& value) {
    m_env[key] = value;
}

std::string
environment::get(const std::string& key) const {
    return m_env.count(key) == 0 ? std::string{} : m_env.at(key);
}

std::vector<std::string>
environment::as_vector() const {

    std::vector<std::string> tmp;
    tmp.reserve(m_env.size());
    for(const auto& [key, value] : m_env) {
        tmp.emplace_back(fmt::format("{}={}", key, value));
    }

    return tmp;
}

std::size_t
environment::size() const {
    return m_env.size();
}

std::unordered_map<std::string, std::string>::const_iterator
environment::begin() const {
    return m_env.begin();
}

std::unordered_map<std::string, std::string>::const_iterator
environment::end() const {
    return m_env.end();
}

command::command(std::string cmdline, std::optional<environment> env)
    : m_cmdline(std::move(cmdline)), m_env(std::move(env)) {}

const std::string&
command::cmdline() const {
    return m_cmdline;
}

const std::optional<environment>&
command::env() const {
    return m_env;
}

command
command::eval(const std::string& adhoc_id,
              const std::filesystem::path& adhoc_directory,
              const std::vector<std::string>& adhoc_nodes) const {

    // generate a regex from a map of key/value pairs
    constexpr auto regex_from_map =
            [](const std::map<std::string, std::string>& m) -> std::regex {
        std::string result;
        for(const auto& [key, value] : m) {
            const auto escaped_key =
                    std::regex_replace(key, std::regex{R"([{}])"}, R"(\$&)");
            result += fmt::format("{}|", escaped_key);
        }
        result.pop_back();
        return std::regex{result};
    };

    const std::map<std::string, std::string> replacements{
            {std::string{keywords.at(0)}, adhoc_id},
            {std::string{keywords.at(1)}, adhoc_directory.string()},
            {std::string{keywords.at(2)},
             fmt::format("\"{}\"", fmt::join(adhoc_nodes, ","))}};

    // make sure that we fail if we ever add a new keyword and forget to add
    // a replacement for it
    assert(replacements.size() == keywords.size());

    std::string result;

    const auto re = regex_from_map(replacements);
    auto it = std::sregex_iterator(m_cmdline.begin(), m_cmdline.end(), re);
    auto end = std::sregex_iterator{};

    std::string::size_type last_pos = 0;

    for(; it != end; ++it) {
        const auto& match = *it;
        result += m_cmdline.substr(last_pos, match.position() - last_pos);
        result += replacements.at(match.str());
        last_pos = match.position() + match.length();
    }

    result += m_cmdline.substr(last_pos, m_cmdline.length() - last_pos);

    return command{result, m_env};
}

std::vector<std::string>
command::as_vector() const {
    std::vector<std::string> tmp;

    for(auto&& r : std::views::split(m_cmdline, ' ') |
                           std::views::transform([](auto&& v) -> std::string {
                               auto c = v | std::views::common;
                               return std::string{c.begin(), c.end()};
                           })) {
        tmp.emplace_back(std::move(r));
    }

    return tmp;
}

} // namespace scord_ctl
+191 −0
Original line number Diff line number Diff line
/******************************************************************************
 * Copyright 2021-2023, Barcelona Supercomputing Center (BSC), Spain
 *
 * This software was partially supported by the EuroHPC-funded project ADMIRE
 *   (Project ID: 956748, https://www.admire-eurohpc.eu).
 *
 * This file is part of scord.
 *
 * scord 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 of the License, or
 * (at your option) any later version.
 *
 * scord 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 scord.  If not, see <https://www.gnu.org/licenses/>.
 *
 * SPDX-License-Identifier: GPL-3.0-or-later
 *****************************************************************************/

#ifndef SCORD_CTL_COMMAND_HPP
#define SCORD_CTL_COMMAND_HPP

#include <string>
#include <vector>
#include <unordered_map>
#include <optional>
#include <filesystem>
#include <fmt/format.h>

namespace scord_ctl {

/**
 * @brief A class representing the environment variables that
 * should be set when running a command.
 */
class environment {

public:
    /**
     * @brief Set an environment variable.
     *
     * @param key The name of the environment variable.
     * @param value The value of the environment variable.
     */
    void
    set(const std::string& key, const std::string& value);

    /**
     * @brief Get the value of an environment variable.
     *
     * @param key The name of the environment variable.
     * @return The value of the environment variable if it exists, an empty
     * string otherwise.
     */
    std::string
    get(const std::string& key) const;

    /**
     * @brief Get the environment variables as a vector of strings.
     * Each string is of the form `key=value`.
     *
     * @return The environment variables as a vector of strings.
     */
    std::vector<std::string>
    as_vector() const;

    /**
     * @brief Get the number of environment variables.
     *
     * @return The number of environment variables.
     */
    std::size_t
    size() const;

    /**
     * @brief Get an iterator to the beginning of the environment variables.
     *
     * @return An iterator to the beginning of the environment variables.
     */
    std::unordered_map<std::string, std::string>::const_iterator
    begin() const;

    /**
     * @brief Get an iterator to the end of the environment variables.
     *
     * @return An iterator to the end of the environment variables.
     */
    std::unordered_map<std::string, std::string>::const_iterator
    end() const;

private:
    std::unordered_map<std::string, std::string> m_env;
};

/**
 * @brief A class representing a command to be executed.
 */
class command {
public:
    /**
     * @brief Keywords that can be used in the command line and
     * will be expanded with appropriate values when calling `eval()`.
     */
    static constexpr std::array<std::string_view, 3> keywords = {
            "{ADHOC_ID}", "{ADHOC_DIRECTORY}", "{ADHOC_NODES}"};

    /**
     * @brief Construct a command.
     *
     * @param cmdline The command line to be executed.
     * @param env The environment variables to be set when executing the
     * command.
     */
    explicit command(std::string cmdline,
                     std::optional<environment> env = std::nullopt);

    /**
     * @brief Get the template command line to be executed (i.e. without having
     * keywords expanded).
     *
     * @return The command line to be executed.
     */
    const std::string&
    cmdline() const;

    /**
     * @brief Get the environment variables to be set when executing the
     * command.
     *
     * @return The environment variables to be set when executing the command.
     */
    const std::optional<environment>&
    env() const;

    /**
     * @brief Return a copy of the current `command` where all the keywords in
     * its command line template have been replaced with string
     * representations of the arguments provided.
     *
     * @param adhoc_id The ID of the adhoc storage system.
     * @param adhoc_directory The directory where the adhoc storage will run.
     * @param adhoc_nodes The nodes where the adhoc storage will run.
     * @return The evaluated command.
     */
    command
    eval(const std::string& adhoc_id,
         const std::filesystem::path& adhoc_directory,
         const std::vector<std::string>& adhoc_nodes) const;

    /**
     * @brief Get the command line to be executed as a vector of strings. The
     * command line is split on spaces with each string in the resulting
     * vector being a token in the command line.
     *
     * @return The command line to be executed as a vector of strings.
     */
    std::vector<std::string>
    as_vector() const;


private:
    std::string m_cmdline;
    std::optional<environment> m_env;
};

} // namespace scord_ctl

/**
 * @brief Formatter for `scord_ctl::config::command`.
 */
template <>
struct fmt::formatter<scord_ctl::command> {
    template <typename ParseContext>
    constexpr auto
    parse(ParseContext& ctx) {
        return ctx.begin();
    }

    template <typename FormatContext>
    auto
    format(const scord_ctl::command& cmd, FormatContext& ctx) {
        return fmt::format_to(ctx.out(), "{}", cmd.cmdline());
    }
};

#endif // SCORD_CTL_COMMAND_HPP
+7 −123
Original line number Diff line number Diff line
@@ -26,8 +26,6 @@
#include <ryml.hpp>
#include <fmt/ostream.h>
#include <fstream>
#include <regex>
#include <ranges>
#include "config_file.hpp"

namespace {
@@ -90,10 +88,10 @@ to_adhoc_storage_type(const ryml::csubstr& type) {
 *
 * @return The parsed `scord_ctl::config::environment` object.
 */
scord_ctl::config::environment
scord_ctl::environment
parse_environment_node(const ryml::ConstNodeRef& node) {

    scord_ctl::config::environment env;
    scord_ctl::environment env;

    for(const auto& child : node) {
        if(!child.has_key()) {
@@ -119,11 +117,11 @@ parse_environment_node(const ryml::ConstNodeRef& node) {
 *
 * @return The parsed `scord_ctl::config::command` object.
 */
command
scord_ctl::command
parse_command_node(const ryml::ConstNodeRef& node) {

    std::string cmdline;
    std::optional<environment> env;
    std::optional<scord_ctl::environment> env;

    for(const auto& child : node) {
        if(!child.has_key()) {
@@ -147,7 +145,7 @@ parse_command_node(const ryml::ConstNodeRef& node) {
        throw std::runtime_error{"missing required `command` key"};
    }

    return command{cmdline, env};
    return scord_ctl::command{cmdline, env};
}

/**
@@ -177,8 +175,8 @@ adhoc_storage_config
parse_adhoc_config_node(const ryml::ConstNodeRef& node) {

    std::filesystem::path working_directory;
    std::optional<command> startup_command;
    std::optional<command> shutdown_command;
    std::optional<scord_ctl::command> startup_command;
    std::optional<scord_ctl::command> shutdown_command;

    for(const auto& child : node) {

@@ -284,120 +282,6 @@ parse_config_node(const ryml::ConstNodeRef& node) {

namespace scord_ctl::config {

void
environment::set(const std::string& key, const std::string& value) {
    m_env[key] = value;
}

std::string
environment::get(const std::string& key) const {
    return m_env.count(key) == 0 ? std::string{} : m_env.at(key);
}

std::vector<std::string>
environment::as_vector() const {

    std::vector<std::string> tmp;
    tmp.reserve(m_env.size());
    for(const auto& [key, value] : m_env) {
        tmp.emplace_back(fmt::format("{}={}", key, value));
    }

    return tmp;
}

std::size_t
environment::size() const {
    return m_env.size();
}

std::unordered_map<std::string, std::string>::const_iterator
environment::begin() const {
    return m_env.begin();
}

std::unordered_map<std::string, std::string>::const_iterator
environment::end() const {
    return m_env.end();
}

command::command(std::string cmdline, std::optional<environment> env)
    : m_cmdline(std::move(cmdline)), m_env(std::move(env)) {}

const std::string&
command::cmdline() const {
    return m_cmdline;
}

const std::optional<environment>&
command::env() const {
    return m_env;
}

command
command::eval(const std::string& adhoc_id,
              const std::filesystem::path& adhoc_directory,
              const std::vector<std::string>& adhoc_nodes) const {

    // generate a regex from a map of key/value pairs
    constexpr auto regex_from_map =
            [](const std::map<std::string, std::string>& m) -> std::regex {
        std::string result;
        for(const auto& [key, value] : m) {
            const auto escaped_key =
                    std::regex_replace(key, std::regex{R"([{}])"}, R"(\$&)");
            result += fmt::format("{}|", escaped_key);
        }
        result.pop_back();
        return std::regex{result};
    };

    const std::map<std::string, std::string> replacements{
            {std::string{keywords.at(0)}, adhoc_id},
            {std::string{keywords.at(1)}, adhoc_directory.string()},
            {std::string{keywords.at(2)},
             fmt::format("\"{}\"", fmt::join(adhoc_nodes, ","))}};

    // make sure that we fail if we ever add a new keyword and forget to add
    // a replacement for it
    assert(replacements.size() == keywords.size());

    std::string result;

    const auto re = regex_from_map(replacements);
    auto it = std::sregex_iterator(m_cmdline.begin(), m_cmdline.end(), re);
    auto end = std::sregex_iterator{};

    std::string::size_type last_pos = 0;

    for(; it != end; ++it) {
        const auto& match = *it;
        result += m_cmdline.substr(last_pos, match.position() - last_pos);
        result += replacements.at(match.str());
        last_pos = match.position() + match.length();
    }

    result += m_cmdline.substr(last_pos, m_cmdline.length() - last_pos);

    return command{result, m_env};
}

std::vector<std::string>
command::as_vector() const {
    std::vector<std::string> tmp;

    for(auto&& r : std::views::split(m_cmdline, ' ') |
                           std::views::transform([](auto&& v) -> std::string {
                               auto c = v | std::views::common;
                               return std::string{c.begin(), c.end()};
                           })) {
        tmp.emplace_back(std::move(r));
    }

    return tmp;
}


adhoc_storage_config::adhoc_storage_config(
        std::filesystem::path working_directory, command startup_command,
        command shutdown_command)
+1 −153
Original line number Diff line number Diff line
@@ -28,143 +28,10 @@

#include <scord/types.hpp>
#include <filesystem>
#include "command.hpp"

namespace scord_ctl::config {

/**
 * @brief A class representing the environment variables that
 * should be set when running a command.
 */
class environment {

public:
    /**
     * @brief Set an environment variable.
     *
     * @param key The name of the environment variable.
     * @param value The value of the environment variable.
     */
    void
    set(const std::string& key, const std::string& value);

    /**
     * @brief Get the value of an environment variable.
     *
     * @param key The name of the environment variable.
     * @return The value of the environment variable if it exists, an empty
     * string otherwise.
     */
    std::string
    get(const std::string& key) const;

    /**
     * @brief Get the environment variables as a vector of strings.
     * Each string is of the form `key=value`.
     *
     * @return The environment variables as a vector of strings.
     */
    std::vector<std::string>
    as_vector() const;

    /**
     * @brief Get the number of environment variables.
     *
     * @return The number of environment variables.
     */
    std::size_t
    size() const;

    /**
     * @brief Get an iterator to the beginning of the environment variables.
     *
     * @return An iterator to the beginning of the environment variables.
     */
    std::unordered_map<std::string, std::string>::const_iterator
    begin() const;

    /**
     * @brief Get an iterator to the end of the environment variables.
     *
     * @return An iterator to the end of the environment variables.
     */
    std::unordered_map<std::string, std::string>::const_iterator
    end() const;

private:
    std::unordered_map<std::string, std::string> m_env;
};

/**
 * @brief A class representing a command to be executed.
 */
class command {
public:
    /**
     * @brief Keywords that can be used in the command line and
     * will be expanded with appropriate values when calling `eval()`.
     */
    static constexpr std::array<std::string_view, 3> keywords = {
            "{ADHOC_ID}", "{ADHOC_DIRECTORY}", "{ADHOC_NODES}"};

    /**
     * @brief Construct a command.
     *
     * @param cmdline The command line to be executed.
     * @param env The environment variables to be set when executing the
     * command.
     */
    explicit command(std::string cmdline,
                     std::optional<environment> env = std::nullopt);

    /**
     * @brief Get the template command line to be executed (i.e. without having
     * keywords expanded).
     *
     * @return The command line to be executed.
     */
    const std::string&
    cmdline() const;

    /**
     * @brief Get the environment variables to be set when executing the
     * command.
     *
     * @return The environment variables to be set when executing the command.
     */
    const std::optional<environment>&
    env() const;

    /**
     * @brief Return a copy of the current `command` where all the keywords in
     * its command line template have been replaced with string
     * representations of the arguments provided.
     *
     * @param adhoc_id The ID of the adhoc storage system.
     * @param adhoc_directory The directory where the adhoc storage will run.
     * @param adhoc_nodes The nodes where the adhoc storage will run.
     * @return The evaluated command.
     */
    command
    eval(const std::string& adhoc_id,
         const std::filesystem::path& adhoc_directory,
         const std::vector<std::string>& adhoc_nodes) const;

    /**
     * @brief Get the command line to be executed as a vector of strings. The
     * command line is split on spaces with each string in the resulting
     * vector being a token in the command line.
     *
     * @return The command line to be executed as a vector of strings.
     */
    std::vector<std::string>
    as_vector() const;


private:
    std::string m_cmdline;
    std::optional<environment> m_env;
};

/**
 * @brief A class representing the configuration of an adhoc storage system.
 */
@@ -247,23 +114,4 @@ private:

} // namespace scord_ctl::config

/**
 * @brief Formatter for `scord_ctl::config::command`.
 */
template <>
struct fmt::formatter<scord_ctl::config::command> {
    template <typename ParseContext>
    constexpr auto
    parse(ParseContext& ctx) {
        return ctx.begin();
    }

    template <typename FormatContext>
    auto
    format(const scord_ctl::config::command& cmd, FormatContext& ctx) {
        return fmt::format_to(ctx.out(), "{}", cmd.cmdline());
    }
};


#endif // SCORD_CONFIG_HPP