| 
14 | 14 | #include <mrdocs/Support/Path.hpp>  | 
15 | 15 | #include <llvm/Support/FileSystem.h>  | 
16 | 16 | #include <ranges>  | 
 | 17 | +#include <thread>  | 
17 | 18 | 
 
  | 
18 | 19 | namespace clang {  | 
19 | 20 | namespace mrdocs {  | 
@@ -70,23 +71,207 @@ load_file(  | 
70 | 71 |     return {};  | 
71 | 72 | }  | 
72 | 73 | 
 
  | 
73 |  | -Expected<void>  | 
74 |  | -Config::Settings::  | 
75 |  | -normalize(ReferenceDirectories const& dirs)  | 
76 |  | -{  | 
77 |  | -    auto exp = PublicSettings::normalize(*this, dirs);  | 
78 |  | -    if (!exp)  | 
 | 74 | +struct PublicSettingsVisitor {  | 
 | 75 | +    template <class T>  | 
 | 76 | +    Expected<void>  | 
 | 77 | +    operator()(  | 
 | 78 | +        PublicSettings& self,  | 
 | 79 | +        std::string_view name,  | 
 | 80 | +        T& value,  | 
 | 81 | +        ReferenceDirectories const& dirs,  | 
 | 82 | +        PublicSettings::OptionProperties const& opts)  | 
 | 83 | +    {  | 
 | 84 | +        using DT = std::decay_t<T>;  | 
 | 85 | +        if constexpr (std::ranges::range<T>)  | 
 | 86 | +        {  | 
 | 87 | +            bool const useDefault = value.empty() && std::holds_alternative<DT>(opts.defaultValue);  | 
 | 88 | +            if (useDefault) {  | 
 | 89 | +                value = std::get<DT>(opts.defaultValue);  | 
 | 90 | +            }  | 
 | 91 | +            if (value.empty() && opts.required) {  | 
 | 92 | +                return formatError("`{}` option is required", name);  | 
 | 93 | +            }  | 
 | 94 | +            if constexpr (std::same_as<DT, std::string>) {  | 
 | 95 | +                if (!value.empty() &&  | 
 | 96 | +                    (opts.type == PublicSettings::OptionType::Path ||  | 
 | 97 | +                    opts.type == PublicSettings::OptionType::DirPath ||  | 
 | 98 | +                    opts.type == PublicSettings::OptionType::FilePath))  | 
 | 99 | +                {  | 
 | 100 | +                    // If the path is not absolute, we need to expand it  | 
 | 101 | +                    if (!files::isAbsolute(value)) {  | 
 | 102 | +                        auto exp = getBaseDir(value, dirs, useDefault, opts);  | 
 | 103 | +                        if (!exp)  | 
 | 104 | +                        {  | 
 | 105 | +                            MRDOCS_TRY(value, files::makeAbsolute(value));  | 
 | 106 | +                        }  | 
 | 107 | +                        else  | 
 | 108 | +                        {  | 
 | 109 | +                            std::string_view baseDir = *exp;  | 
 | 110 | +                            value = files::makeAbsolute(value, baseDir);  | 
 | 111 | +                        }  | 
 | 112 | +                    }  | 
 | 113 | +                    if (opts.mustExist &&  | 
 | 114 | +                        !files::exists(value))  | 
 | 115 | +                    {  | 
 | 116 | +                        return Unexpected(formatError("`{}` option: path does not exist: {}", name, value));  | 
 | 117 | +                    }  | 
 | 118 | +                    if (opts.type == PublicSettings::OptionType::DirPath &&  | 
 | 119 | +                        !files::isDirectory(value))  | 
 | 120 | +                    {  | 
 | 121 | +                        return Unexpected(formatError("`{}` option: path should be a directory: {}", name, value));  | 
 | 122 | +                    }  | 
 | 123 | +                    if (opts.type == PublicSettings::OptionType::FilePath &&  | 
 | 124 | +                        files::isDirectory(value))  | 
 | 125 | +                    {  | 
 | 126 | +                        return Unexpected(formatError("`{}` option: path should be a regular file: {}", name, value));  | 
 | 127 | +                    }  | 
 | 128 | +                }  | 
 | 129 | +                else if (opts.type == PublicSettings::OptionType::String) {  | 
 | 130 | +                    if (name == "base-url")  | 
 | 131 | +                    {  | 
 | 132 | +                        if (!value.empty() && value.back() != '/') {  | 
 | 133 | +                            value.push_back('/');  | 
 | 134 | +                        }  | 
 | 135 | +                    }  | 
 | 136 | +                }  | 
 | 137 | +            }  | 
 | 138 | +            else if constexpr (std::same_as<DT, std::vector<std::string>>) {  | 
 | 139 | +                if (opts.type == PublicSettings::OptionType::ListPath) {  | 
 | 140 | +                    for (auto& v : value) {  | 
 | 141 | +                        if (!files::isAbsolute(v))  | 
 | 142 | +                        {  | 
 | 143 | +                            auto exp = getBaseDir(v, dirs, useDefault, opts);  | 
 | 144 | +                            if (!exp)  | 
 | 145 | +                            {  | 
 | 146 | +                                MRDOCS_TRY(v, files::makeAbsolute(v));  | 
 | 147 | +                            }  | 
 | 148 | +                            else  | 
 | 149 | +                            {  | 
 | 150 | +                                std::string_view baseDir = *exp;  | 
 | 151 | +                                v = files::makeAbsolute(v, baseDir);  | 
 | 152 | +                            }  | 
 | 153 | +                        }  | 
 | 154 | +                        if (opts.mustExist && !files::exists(v))  | 
 | 155 | +                        {  | 
 | 156 | +                            return Unexpected(formatError("`{}` option: path does not exist: {}", name, v));  | 
 | 157 | +                        }  | 
 | 158 | +                        if (opts.commandLineSink && opts.filenameMapping.has_value())  | 
 | 159 | +                        {  | 
 | 160 | +                            auto const& map = opts.filenameMapping.value();  | 
 | 161 | +                            for (auto& [from, to] : map) {  | 
 | 162 | +                                auto f = files::getFileName(v);  | 
 | 163 | +                                if (f == from)  | 
 | 164 | +                                {  | 
 | 165 | +                                    auto* dest = fileMapDest(self, to);  | 
 | 166 | +                                    if (dest) {  | 
 | 167 | +                                        *dest = v;  | 
 | 168 | +                                    }  | 
 | 169 | +                                }  | 
 | 170 | +                            }  | 
 | 171 | +                        }  | 
 | 172 | +                    }  | 
 | 173 | +                }  | 
 | 174 | +            }  | 
 | 175 | +        }  | 
 | 176 | +        else if constexpr (std::same_as<DT, int> || std::same_as<DT, unsigned>) {  | 
 | 177 | +            if (name == "concurrency" && std::cmp_equal(value, 0))  | 
 | 178 | +            {  | 
 | 179 | +                value = std::thread::hardware_concurrency();  | 
 | 180 | +            }  | 
 | 181 | +            if (opts.minValue && std::cmp_less(value, *opts.minValue))  | 
 | 182 | +            {  | 
 | 183 | +                return Unexpected(formatError("`{}` option: value {} is less than minimum: {}", name, value, *opts.minValue));  | 
 | 184 | +            }  | 
 | 185 | +            if (opts.maxValue && std::cmp_greater(value, *opts.maxValue))  | 
 | 186 | +            {  | 
 | 187 | +                return Unexpected(formatError("`{}` option: value {} is greater than maximum: {}", name, value, *opts.maxValue));  | 
 | 188 | +            }  | 
 | 189 | +        }  | 
 | 190 | + | 
 | 191 | +        // Booleans should already be validated because the struct  | 
 | 192 | +        // already has their default values  | 
 | 193 | +        return {};  | 
 | 194 | +    }  | 
 | 195 | + | 
 | 196 | +    static  | 
 | 197 | +    std::string*  | 
 | 198 | +    fileMapDest(PublicSettings& self, std::string_view mapDest)  | 
79 | 199 |     {  | 
80 |  | -        return exp.error();  | 
 | 200 | +        if (mapDest == "config") {  | 
 | 201 | +            return &self.config;  | 
 | 202 | +        }  | 
 | 203 | +        if (mapDest == "compilationDatabase") {  | 
 | 204 | +            return &self.compilationDatabase;  | 
 | 205 | +        }  | 
 | 206 | +        return nullptr;  | 
81 | 207 |     }  | 
82 | 208 | 
 
  | 
83 |  | -    // Base-URL has to be dirsy with forward slash style  | 
84 |  | -    if (!baseUrl.empty() && baseUrl.back() != '/')  | 
 | 209 | +    Expected<std::string_view>  | 
 | 210 | +    getBaseDir(  | 
 | 211 | +        std::string_view referenceDirKey,  | 
 | 212 | +        ReferenceDirectories const& dirs)  | 
85 | 213 |     {  | 
86 |  | -        baseUrl.push_back('/');  | 
 | 214 | +        if (referenceDirKey == "config-dir") {  | 
 | 215 | +            return dirs.configDir;  | 
 | 216 | +        }  | 
 | 217 | +        else if (referenceDirKey == "cwd") {  | 
 | 218 | +            return dirs.cwd;  | 
 | 219 | +        }  | 
 | 220 | +        else if (referenceDirKey == "mrdocs-root") {  | 
 | 221 | +            return dirs.mrdocsRoot;  | 
 | 222 | +        }  | 
 | 223 | +        return Unexpected(formatError("unknown relative-to value: \"{}\"", referenceDirKey));  | 
87 | 224 |     }  | 
88 | 225 | 
 
  | 
89 |  | -    return {};  | 
 | 226 | +    static  | 
 | 227 | +    std::string_view  | 
 | 228 | +    trimBaseDirReference(std::string_view s)  | 
 | 229 | +    {  | 
 | 230 | +        if (s.size() > 2 &&  | 
 | 231 | +            s.front() == '<' &&  | 
 | 232 | +            s.back() == '>') {  | 
 | 233 | +            s.remove_prefix(1);  | 
 | 234 | +            s.remove_suffix(1);  | 
 | 235 | +        }  | 
 | 236 | +        return s;  | 
 | 237 | +    };  | 
 | 238 | + | 
 | 239 | +    Expected<std::string_view>  | 
 | 240 | +    getBaseDir(  | 
 | 241 | +        std::string& value,  | 
 | 242 | +        ReferenceDirectories const& dirs,  | 
 | 243 | +        bool useDefault,  | 
 | 244 | +        PublicSettings::OptionProperties const& opts)  | 
 | 245 | +    {  | 
 | 246 | +        if (!useDefault) {  | 
 | 247 | +            // If we did not use the default value, we use "relativeto"  | 
 | 248 | +            // as the base path  | 
 | 249 | +            std::string_view relativeTo = opts.relativeto;  | 
 | 250 | +            relativeTo = trimBaseDirReference(relativeTo);  | 
 | 251 | +            return getBaseDir(relativeTo, dirs);  | 
 | 252 | +        }  | 
 | 253 | + | 
 | 254 | +        // If we used the default value, the base dir comes from  | 
 | 255 | +        // the first path segment of the value  | 
 | 256 | +        std::string_view referenceDirKey = value;  | 
 | 257 | +        auto pos = referenceDirKey.find('/');  | 
 | 258 | +        if (pos != std::string::npos) {  | 
 | 259 | +            referenceDirKey = referenceDirKey.substr(0, pos);  | 
 | 260 | +        }  | 
 | 261 | +        referenceDirKey = trimBaseDirReference(referenceDirKey);  | 
 | 262 | +        MRDOCS_TRY(std::string_view baseDir, getBaseDir(referenceDirKey, dirs));  | 
 | 263 | +        if (pos != std::string::npos) {  | 
 | 264 | +            value = value.substr(pos + 1);  | 
 | 265 | +        }  | 
 | 266 | +        return baseDir;  | 
 | 267 | +    }  | 
 | 268 | +};  | 
 | 269 | + | 
 | 270 | +Expected<void>  | 
 | 271 | +Config::Settings::  | 
 | 272 | +normalize(ReferenceDirectories const& dirs)  | 
 | 273 | +{  | 
 | 274 | +    return PublicSettings::normalize(dirs, PublicSettingsVisitor{});  | 
90 | 275 | }  | 
91 | 276 | 
 
  | 
92 | 277 | } // mrdocs  | 
 | 
0 commit comments