Line data Source code
1 : // Copyright (C) 2022-2023 by Camden Mannett. 2 : // Distributed under the Boost Software License, Version 1.0. 3 : // (See accompanying file LICENSE or copy at https://www.boost.org/LICENSE_1_0.txt) 4 : 5 : #pragma once 6 : 7 : #include "arg_router/multi_lang/translation.hpp" 8 : #include "arg_router/parsing/token_type.hpp" 9 : #include "arg_router/utility/tuple_iterator.hpp" 10 : 11 : #include <variant> 12 : 13 : namespace arg_router::multi_lang 14 : { 15 : /** Provides multi-language support by instantiating an arg_router::root_t for a given language ID. 16 : * 17 : * This relies on the use of multi_lang::translation to provide compile-time selection of language 18 : * variants for strings. The user needs to specialise multi_lang::translation for DefaultLanguageID 19 : * and each supported ID in SupportedLanguageIDs. 20 : * 21 : * @code 22 : * namespace ar = arg_router; 23 : * namespace arp = ar::policy; 24 : * 25 : * namespace ar::multi_lang 26 : * { 27 : * template <> 28 : * class translation<S_("en_GB")> 29 : * { 30 : * public: 31 : * using force = S_("force"); 32 : * using force_description = S_("Force overwrite existing files"); 33 : * ... 34 : * }; 35 : * 36 : * template <> 37 : * class translation<S_("fr")> 38 : * { 39 : * public: 40 : * using force = S_("forcer"); 41 : * using force_description = S_("Forcer l'écrasement des fichiers existants"); 42 : * ... 43 : * }; 44 : * 45 : * template <> 46 : * class translation<S_("ja")> 47 : * { 48 : * public: 49 : * using force = S_("強制"); 50 : * using force_description = S_("既存のファイルを強制的に上書きする"); 51 : * ... 52 : * }; 53 : * } // namespace ar::multi_lang 54 : * 55 : * ar::multi_lang::root<S_("en_GB"), S_("fr"), S_("ja")>( // 56 : * ar::multi_lang::iso_locale(std::locale("").name()), 57 : * [&](auto tr_) { 58 : * using tr = decltype(tr_); 59 : * 60 : * const auto common_args = ar::list{ 61 : * ar::flag(arp::long_name<typename tr::force>, 62 : * arp::short_name<'f'>, 63 : * arp::description<typename tr::force_description>), 64 : * ... 65 : * @endcode 66 : * As shown by the above example, the supported translation object is given to the function object 67 : * that returns the root instance. This means that each use of translation specialisation must have 68 : * the same number of language-specific compile-time strings. 69 : * 70 : * @a RootFactory is a function object used to return a given root type from a supported language 71 : * ID, its signature must be equivalent to: 72 : * @code 73 : * template <typename ID> 74 : * auto operator()(multi_lang::translation<ID> I) { 75 : * ... 76 : * }; 77 : * @endcode 78 : * 79 : * The DefaultLanguageID and SupportedLanguageIDs parameters can be any set of unique compile-time 80 : * strings, but as they are compared against an input that would @em usually come from 81 : * <TT>std::locale()</TT>, then ISO language codes are least troublesome and easiest to read. 82 : * 83 : * @tparam RootFactory Function object type that accepts a multi_lang::translation specialisation 84 : * and returns a root instance 85 : * @tparam DefaultLanguageID The default language ID as a compile time string, this is used if the 86 : * runtime input code does not match this or any of SupportedLanguageIDs 87 : * @tparam SupportedLanguageIDs The supported language IDs as compile time strings 88 : */ 89 : template <typename RootFactory, typename DefaultLanguageID, typename... SupportedLanguageIDs> 90 : class root_t 91 : { 92 : using supported_language_ids = std::tuple<DefaultLanguageID, SupportedLanguageIDs...>; 93 : constexpr static auto num_supported_ids = std::tuple_size_v<supported_language_ids>; 94 : 95 : static_assert(num_supported_ids > 1, "Must be more than one language supported"); 96 : static_assert(num_supported_ids == 97 : std::tuple_size_v<boost::mp11::mp_unique<supported_language_ids>>, 98 : "Supported languages must be unique"); 99 : 100 : using root_variant_t = 101 : std::variant<std::decay_t<decltype(std::declval<RootFactory>()( 102 : std::declval<translation<DefaultLanguageID>>()))>, 103 : std::decay_t<decltype(std::declval<RootFactory>()( 104 : std::declval<translation<SupportedLanguageIDs>>()))>...>; 105 : 106 : public: 107 : /** Constructor 108 : * 109 : * @param language_id The runtime language selection, if it doesn't match any of the 110 : * SupportedLanguageIDs, then DefaultLanguageID is used 111 : * @param f Function object that returns the root instance for a given supported language 112 : */ 113 16 : explicit root_t(std::string_view language_id, const RootFactory& f) 114 : { 115 64 : utility::tuple_type_iterator<supported_language_ids>([&](auto I) { 116 : using id = std::tuple_element_t<I, supported_language_ids>; 117 : 118 48 : if (root_ || (language_id != id::get())) { 119 39 : return; 120 : } 121 : 122 9 : root_.emplace(root_variant_t{f(translation<id>{})}); 123 48 : }); 124 : 125 : // No match, so fall back to the default (the first language) 126 16 : if (!root_) { 127 7 : root_.emplace(root_variant_t{f(translation<DefaultLanguageID>{})}); 128 7 : } 129 16 : } 130 : 131 : /** Calls the parse method on the selected root. 132 : * 133 : * The first element is @em not expected to be the executable name. 134 : * @param args Vector of tokens 135 : * @exception parse_exception Thrown if parsing has failed 136 : */ 137 6 : void parse(vector<parsing::token_type> args) const 138 : { 139 12 : std::visit([&](const auto& root) { root.parse(std::move(args)); }, *root_); 140 6 : } 141 : 142 : /** Calls the parse method on the selected root. 143 : * 144 : * The first element is @em not expected to be the executable name. 145 : * @note The strings must out live the parse process as they are not copied. 146 : * @tparam Iter Iterator type to <TT>std::string_view</TT> convertible elements 147 : * @param begin Iterator to the first element 148 : * @param end Iterator to the one-past-the-end element 149 : */ 150 : template <typename Iter, typename = std::enable_if_t<!std::is_same_v<std::decay_t<Iter>, int>>> 151 6 : void parse(Iter begin, Iter end) const 152 : { 153 12 : std::visit([&](const auto& root) { root.parse(begin, end); }, *root_); 154 6 : } 155 : 156 : /** Calls the parse method on the selected root. 157 : * 158 : * The first element is @em not expected to be the executable name. 159 : * @note This does not take part in overload resolution if @a c is a 160 : * <TT>vector\<parsing::token_type\></TT> 161 : * @tparam Container 162 : * @param c Elements to parse 163 : */ 164 : template <typename Container, 165 : typename = std::enable_if_t< 166 : !std::is_same_v<std::decay_t<Container>, vector<parsing::token_type>>>> 167 12 : void parse(const Container& c) const 168 : { 169 24 : std::visit([&](const auto& root) { root.parse(c); }, *root_); 170 12 : } 171 : 172 : /** Calls the parse method on the selected root. 173 : * 174 : * @param argc Number of arguments 175 : * @param argv Array of char pointers to the command line tokens 176 : */ 177 12 : void parse(int argc, char** argv) const 178 : { 179 24 : std::visit([&](const auto& root) { root.parse(argc, argv); }, *root_); 180 12 : } 181 : 182 : /** Calls the help method on the selected root. 183 : * 184 : * @param stream Output stream to write into 185 : */ 186 4 : void help(std::ostream& stream) const 187 : { 188 8 : std::visit([&](const auto& root) { root.help(stream); }, *root_); 189 4 : } 190 : 191 : /** Calls the help method on the selected root. 192 : * 193 : * @return String holding the help output 194 : */ 195 : [[nodiscard]] string help() const 196 : { 197 : return std::visit([](const auto& root) { root.help(); }, *root_); 198 : } 199 : 200 : private: 201 : // Only optional due to the delayed initialisation 202 : std::optional<root_variant_t> root_; 203 : }; 204 : 205 : /** Convenience function that returns a root_t. 206 : * 207 : * Allows the user to define the supported languages IDs in the template parameters but has @a Fn 208 : * deduced from the input. 209 : * 210 : * @tparam DefaultLanguageID The default language ID as a compile time string, this is used if the 211 : * runtime input code does not match this or any of SupportedLanguageIDs 212 : * @tparam SupportedLanguageIDs The supported language IDs as compile time strings 213 : * @tparam RootFactory Function object type that accepts a multi_lang::translation specialisation 214 : * and returns a root instance 215 : * @param language_id The runtime language selection, if it doesn't match any of the 216 : * SupportedLanguageIDs, then DefaultLanguageID is used 217 : * @param f Function object that returns the root instance for a given supported language 218 : * @return root_t instance 219 : */ 220 : template <typename DefaultLanguageID, typename... SupportedLanguageIDs, typename RootFactory> 221 16 : auto root(std::string_view language_id, const RootFactory& f) 222 : { 223 16 : return root_t<RootFactory, DefaultLanguageID, SupportedLanguageIDs...>{language_id, f}; 224 : } 225 : } // namespace arg_router::multi_lang