LCOV - code coverage report
Current view: top level - arg_router/multi_lang - root.hpp Hit Total Coverage
Test: arg_router Unit Test Code Coverage Lines: 27 27 100.0 %
Date: 2023-08-08 16:25:45
Legend: Lines: hit not hit

          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

Generated by: LCOV version 1.14