Program Listing for File sections.hpp

Return to documentation for file (mcfp/sections.hpp)

/*-
 * SPDX-License-Identifier: BSD-2-Clause
 *
 * Copyright (c) 2025 Maarten L. Hekkelman
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 * 1. Redistributions of source code must retain the above copyright notice, this
 *    list of conditions and the following disclaimer
 * 2. Redistributions in binary form must reproduce the above copyright notice,
 *    this list of conditions and the following disclaimer in the documentation
 *    and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
 * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

#pragma once

#include "mcfp/options.hpp"

#include <iostream>
#include <iomanip>
#include <memory>
#include <string>
#include <string_view>
#include <type_traits>
#include <utility>

// --------------------------------------------------------------------


namespace mcfp
{

class section
{
  public:
    template <typename... Options>
        requires(not(std::is_reference_v<Options> and ...))
    explicit section(std::string name, Options const &...options)
        : m_name(std::move(name))
        , m_impl(new config_impl<Options...>(options...))
    {
    }

    template <typename... Options>
        requires(std::is_rvalue_reference_v<Options> and ...)
    explicit section(std::string name, Options &&...options)
        : m_name(std::move(name))
        , m_impl(new config_impl<Options...>(std::forward<Options>(options)...))
    {
    }

    [[nodiscard]] const std::string &name() const noexcept { return m_name; }

    void write(std::ostream &os, size_t indent, size_t output_width) const
    {
        os << '\n';
        if (not m_name.empty())
            os << "section " << std::quoted(m_name) << "\n\n";

        m_impl->write(os, m_name, indent, output_width);
    }

    [[nodiscard]] option_base *get_option(std::string_view name) const
    {
        return m_impl->get_option(name);
    }

    [[nodiscard]] option_base *get_option(char short_name) const
    {
        return m_impl->get_option(short_name);
    }

    [[nodiscard]] size_t get_option_width() const
    {
        return m_impl->get_option_width(m_name);
    }

  private:
    // --------------------------------------------------------------------

    struct config_impl_base
    {
        virtual ~config_impl_base() = default;

        [[nodiscard]] virtual option_base *get_option(std::string_view name) = 0;
        [[nodiscard]] virtual option_base *get_option(char short_name) = 0;

        [[nodiscard]] virtual size_t get_option_width(std::string_view section_name) const = 0;
        virtual void write(std::ostream &os, std::string_view section_name, size_t wrap_width, size_t output_width) const = 0;

        [[nodiscard]] virtual config_impl_base *next() const noexcept { return nullptr; }
    };

    template <typename... Options>
    struct config_impl : public config_impl_base
    {
        static constexpr size_t N = sizeof...(Options);

        explicit config_impl(Options const &...options)
            requires(sizeof...(Options) > 0)
            : m_options(options...)
        {
        }

        explicit config_impl(Options &&...options)
            : m_options(std::forward<Options>(options)...)
        {
        }

        option_base *get_option(std::string_view name) override
        {
            return get_option_by_nr<0>(name);
        }

        template <size_t Ix>
        option_base *get_option_by_nr([[maybe_unused]] std::string_view name)
        {
            if constexpr (Ix == N)
                return nullptr;
            else
            {
                option_base &opt = std::get<Ix>(m_options);
                return (opt.m_name == name) ? &opt : get_option_by_nr<Ix + 1>(name);
            }
        }

        option_base *get_option(char short_name) override
        {
            return get_option_by_nr<0>(short_name);
        }

        template <size_t Ix>
        option_base *get_option_by_nr([[maybe_unused]] char short_name)
        {
            if constexpr (Ix == N)
                return nullptr;
            else
            {
                option_base &opt = std::get<Ix>(m_options);
                return (opt.m_short_name == short_name) ? &opt : get_option_by_nr<Ix + 1>(short_name);
            }
        }

        [[nodiscard]] size_t get_option_width(std::string_view section_name) const override
        {
            return std::apply([section_name](Options const &...opts)
                {
                size_t width = 0;
                ((width = std::max(width, opts.width(section_name))), ...);
                return width; }, m_options);
        }

        void write(std::ostream &os, std::string_view section_name, size_t wrap_width, size_t output_width) const override
        {
            std::apply([&os, section_name, wrap_width, output_width](auto &&...opts)
                { (opts.write(os, section_name, wrap_width, output_width), ...); }, m_options);
        }

        std::tuple<Options...> m_options;
    };

    template <typename... Options>
    struct lib_config_impl : public config_impl<Options...>
    {
        explicit lib_config_impl(std::string lib_name, Options &&...options)
            : config_impl<Options...>(std::forward<Options>(options)...)
            , m_lib_name(std::move(lib_name))
        {
        }

        [[nodiscard]] config_impl_base *next() const noexcept override { return m_next; }

        std::string m_lib_name;
        config_impl_base *m_next = nullptr;
    };

    std::string m_name;
    std::unique_ptr<config_impl_base> m_impl;
};

} // namespace mcfp