Program Listing for File settings.h

Return to documentation for file (portal/application/settings.h)

//
// Copyright © 2025 Jonatan Nevo.
// Distributed under the MIT license (see LICENSE file).
//

#pragma once

#include <filesystem>
#include <ranges>

#include "portal/core/log.h"
#include "portal/core/concurrency/reentrant_spin_lock.h"
#include "portal/serialization/archive.h"

namespace portal
{
enum class SettingsArchiveType
{
    Json
};

class ProjectSettings final : public ArchiveObject
{
public:
    static ProjectSettings create_settings(SettingsArchiveType type, const std::filesystem::path& working_directory, const std::filesystem::path& settings_file_name);

    ~ProjectSettings() override;

    ProjectSettings(const ProjectSettings&) = delete;
    ProjectSettings& operator=(const ProjectSettings&) = delete;

    ProjectSettings(ProjectSettings&& other) noexcept;
    ProjectSettings& operator=(ProjectSettings&& other) noexcept;

    void load();
    void dump() const;

    template <typename T>
    T get_setting(const PropertyName& name, const T& default_value)
    {
        auto optional_t = get_setting<T>(name);
        if (!optional_t.has_value())
        {
            set_setting(name, default_value);
            return default_value;
        }
        return *optional_t;
    }

    template <typename T>
    std::optional<T> get_setting(const PropertyName& name) const
    {
        auto split_view = name | std::ranges::views::split('.');
        auto split_size = std::ranges::distance(split_view);

        ArchiveObject* current_node = root.get();
        for (auto part : split_view)
        {
            std::string_view part_str{part};


            if (--split_size == 0)
            {
                T out;
                if (current_node->get_property(part_str, out))
                    return out;
                return std::nullopt;
            }

            current_node = current_node->get_object(part_str);
            if (!current_node)
            {
                LOG_ERROR_TAG("Settings", "Failed to get setting \"{}\"", name);
                return std::nullopt;
            }
        }

        return std::nullopt;
    }

    template <typename T>
    void set_setting(const PropertyName& name, const T& value)
    {
        auto split_view = name | std::ranges::views::split('.');
        auto split_size = std::ranges::distance(split_view);

        ArchiveObject* current_node = root.get();
        for (auto part : split_view)
        {
            std::string_view part_str{part};

            if (--split_size == 0)
            {
                current_node->add_property(part_str, value);
                return;
            }

            auto* next_node = current_node->get_object(part_str);
            if (!next_node)
            {
                next_node = current_node->create_child(part_str);
            }
            current_node = next_node;
        }
    }

    void debug_print() const;

private:
    void debug_print(std::string base_name, const ArchiveObject& object) const;
    void debug_print_scalar(const std::string& name, const reflection::Property& prop) const;
    void debug_print_array(const std::string& name, const reflection::Property& prop) const;

    explicit ProjectSettings(SettingsArchiveType type, const std::filesystem::path& path);


protected:
    reflection::Property& get_property_from_map(PropertyName name) override;
    reflection::Property& add_property_to_map(PropertyName name, reflection::Property&& property) override;

private:
    ReentrantSpinLock<> lock;
    SettingsArchiveType type;

    std::filesystem::path settings_path;
    // Memory ownership of properties
    std::unique_ptr<ArchiveObject> root;
};
} // portal