aboutsummaryrefslogtreecommitdiff
path: root/lib/configuration/configuration.h
blob: af55f6210634f1d8f0c22d8bce26947011de6ee0 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
/*
 * This file is part of smolbote. It's copyrighted by the contributors recorded
 * in the version control history of the file, available from its original
 * location: https://neueland.iserlohn-fortress.net/gitea/aqua/smolbote
 *
 * SPDX-License-Identifier: GPL-3.0
 */

#ifndef SMOLBOTE_CONFIGURATION_H
#define SMOLBOTE_CONFIGURATION_H

#include <initializer_list>
#include <memory>
#include <optional>
#include <string>
#include <type_traits>
#include <unordered_map>
#include <variant>

#if defined(__clang__)
#define consumable(X) [[clang::consumable(X)]]
#define return_typestate(X) [[clang::return_typestate(X)]]
#define callable_when(X) [[clang::callable_when(X)]]
#define param_typestate(X) [[clang::param_typestate(X)]]
#else
#define consumable(X)
#define return_typestate(X)
#define callable_when(X)
#define param_typestate(X)
#endif

typedef std::variant<std::string, int, bool> conf_value_t;

template <typename T>
concept concept_value_t = std::is_arithmetic<T>::value || std::is_same<T, bool>::value || std::is_constructible<T, std::string>::value;

class consumable(unconsumed) Configuration : private std::unordered_map<std::string, conf_value_t>
{
    friend std::ostream &operator<<(std::ostream &out, const Configuration &obj);

public:
    return_typestate(unconsumed) Configuration();
    return_typestate(unconsumed) Configuration(const std::initializer_list<std::pair<std::string, conf_value_t>> &list) noexcept;

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

    return_typestate(unconsumed) Configuration(Configuration && other param_typestate(unconsumed)) = default;
    Configuration &operator=(Configuration &&) = delete;

    ~Configuration() = default;

    callable_when(unconsumed) void read_file(const std::string &location);
    callable_when(unconsumed) void read(std::basic_istream<char> & input);

    template <typename T>
    callable_when(unconsumed) [[nodiscard]] std::optional<T> value(const char *path) const
    {
        if(use_global) {
            return instance()->value<T>(path);
        }

        if(count(path) == 0) {
            return std::nullopt;
        }

        return std::get<T>(at(path));
    }

    template <concept_value_t T>
    callable_when(unconsumed) [[nodiscard]] std::optional<T> value(const char *path) const
    {
        if(use_global) {
            return instance()->value<T>(path);
        }

        if(this->count(path) == 0) {
            return std::nullopt;
        }

        // path is guaranteed to exist
        const auto value = at(path);

        if(std::holds_alternative<int>(value)) {
            if constexpr(std::is_arithmetic<T>::value) {
                return static_cast<T>(std::get<int>(value));
            } else if constexpr(std::is_constructible<T, std::string>::value) {
                return T{ std::to_string(std::get<int>(value)) };
            }

        } else if(std::holds_alternative<bool>(value)) {
            if constexpr(std::is_constructible<T, bool>::value) {
                return std::get<bool>(value);
            } else if constexpr(std::is_constructible<T, const char *>::value) {
                return std::get<bool>(value) ? T{ "true" } : T{ "false" };
            }

        } else if(std::holds_alternative<std::string>(value)) {
            auto str = std::get<std::string>(value);

            try {
                if constexpr(std::is_floating_point<T>::value) {
                    return static_cast<T>(std::stod(str));
                } else if constexpr(std::is_arithmetic<T>::value) {
                    return static_cast<T>(std::stol(str));
                }
            } catch(std::invalid_argument &) {
                return std::nullopt;
            }

            if(str.front() == '~') {
                str.replace(0, 1, m_homePath);
            }

            if constexpr(std::is_constructible<T, std::string>::value) {
                return T{ str };
            }
        }

        return std::nullopt;
    }

    template <typename T>
    callable_when(unconsumed) T &shortcut(T & /* unused */, const char * /* unused */) const
    {
        return T{};
    }

    callable_when(unconsumed) void setValue(const std::string &key, const std::string &value);

    bool make_global();

private:
    static Configuration *instance();

    const std::string m_homePath;
    const bool use_global = false;
};

#ifndef NO_QT_SPEC
#include "qt_specialization.h"
#endif

std::ostream &operator<<(std::ostream &out, const Configuration &obj);

#endif // SMOLBOTE_CONFIGURATION_H