Line data Source code
1 1 : // Distributed under the MIT License.
2 : // See LICENSE.txt for details.
3 :
4 : /// \file
5 : /// Defines class template Factory.
6 :
7 : #pragma once
8 :
9 : #include <algorithm>
10 : #include <iomanip>
11 : #include <memory>
12 : #include <sstream>
13 : #include <string>
14 : #include <utility>
15 : #include <yaml-cpp/yaml.h>
16 :
17 : #include "Options/Options.hpp"
18 : #include "Options/ParseError.hpp"
19 : #include "Options/Protocols/FactoryCreation.hpp"
20 : #include "Utilities/ErrorHandling/Assert.hpp"
21 : #include "Utilities/PrettyType.hpp"
22 : #include "Utilities/ProtocolHelpers.hpp"
23 : #include "Utilities/StdHelpers.hpp"
24 : #include "Utilities/TMPL.hpp"
25 : #include "Utilities/TypeTraits/CreateGetStaticMemberVariableOrDefault.hpp"
26 :
27 : namespace Options {
28 : namespace Factory_detail {
29 : struct print_derived {
30 : // Not a stream because brigand requires the functor to be copyable.
31 : std::string value;
32 : template <typename T>
33 : void operator()(tmpl::type_<T> /*meta*/) {
34 : // These are zero-based
35 : const size_t name_col = 2;
36 : const size_t help_col = 22;
37 : const size_t end_col = 80;
38 :
39 : std::ostringstream ss;
40 : ss << std::left << std::setw(name_col) << ""
41 : << std::setw(help_col - name_col - 1) << pretty_type::name<T>();
42 : if (ss.str().size() >= help_col) {
43 : ss << "\n" << std::setw(help_col - 1) << "";
44 : }
45 :
46 : std::string help_snippet(T::help);
47 : if (help_snippet.size() > end_col - help_col) {
48 : help_snippet.resize(end_col - help_col - 3);
49 : help_snippet += "...";
50 : }
51 : std::replace(help_snippet.begin(), help_snippet.end(), '\n', ' ');
52 : ss << " " << help_snippet << "\n";
53 :
54 : value += ss.str();
55 : }
56 : };
57 :
58 : template <typename CreatableClasses>
59 : std::string help_derived() {
60 : return "Known Ids:\n" +
61 : tmpl::for_each<CreatableClasses>(
62 : Factory_detail::print_derived{})
63 : .value;
64 : }
65 :
66 : // This is for handling legacy code that still uses creatable_classes.
67 : // It should be inlined once everything is converted.
68 : template <typename BaseClass, typename Metavariables, typename = std::void_t<>>
69 : struct get_creatable_classes {
70 : using factory_creation = typename Metavariables::factory_creation;
71 : // This assertion is normally done in tests, but the executable
72 : // metavariables don't have tests, so we do it here.
73 : static_assert(tt::assert_conforms_to_v<factory_creation,
74 : Options::protocols::FactoryCreation>);
75 : using type = tmpl::at<typename factory_creation::factory_classes, BaseClass>;
76 : };
77 :
78 : template <typename BaseClass, typename Metavariables>
79 : struct get_creatable_classes<
80 : BaseClass, Metavariables,
81 : std::void_t<typename BaseClass::creatable_classes>> {
82 : using type = typename BaseClass::creatable_classes;
83 : };
84 :
85 : CREATE_GET_STATIC_MEMBER_VARIABLE_OR_DEFAULT(factory_creatable)
86 :
87 : template <typename T>
88 : struct is_factory_creatable
89 : : std::bool_constant<get_factory_creatable_or_default_v<T, true>> {};
90 :
91 : template <typename BaseClass, typename Metavariables>
92 : std::unique_ptr<BaseClass> create(const Option& options) {
93 : using all_creatable_classes =
94 : typename get_creatable_classes<BaseClass, Metavariables>::type;
95 : static_assert(not std::is_same_v<all_creatable_classes, tmpl::no_such_type_>,
96 : "List of creatable derived types for this class is missing "
97 : "from Metavariables::factory_classes.");
98 : using creatable_classes =
99 : tmpl::filter<all_creatable_classes, is_factory_creatable<tmpl::_1>>;
100 :
101 : const auto& node = options.node();
102 : Option derived_opts(options.context());
103 : derived_opts.append_context("While operating factory for " +
104 : pretty_type::name<BaseClass>());
105 : std::string id;
106 : if (node.IsScalar()) {
107 : id = node.as<std::string>();
108 : } else if (node.IsMap()) {
109 : if (node.size() != 1) {
110 : PARSE_ERROR(derived_opts.context(),
111 : "Expected a class name (and possibly options), got multiple "
112 : "key-value pairs:\n" << node);
113 : }
114 : id = node.begin()->first.as<std::string>();
115 : derived_opts.set_node(node.begin()->second);
116 : } else if (node.IsNull()) {
117 : PARSE_ERROR(derived_opts.context(),
118 : "Expected a class name (and possibly options):\n"
119 : << help_derived<creatable_classes>());
120 : } else {
121 : PARSE_ERROR(derived_opts.context(),
122 : "Expected a class or a class with options, got:\n"
123 : << node);
124 : }
125 :
126 : std::unique_ptr<BaseClass> result;
127 : tmpl::for_each<creatable_classes>(
128 : [&id, &derived_opts, &result](auto derived_v) {
129 : using Derived = tmpl::type_from<decltype(derived_v)>;
130 : if (pretty_type::name<Derived>() == id) {
131 : ASSERT(result == nullptr, "Duplicate factory id: " << id);
132 : result = std::make_unique<Derived>(
133 : derived_opts.parse_as<Derived, Metavariables>());
134 : }
135 : });
136 : if (result != nullptr) {
137 : return result;
138 : }
139 : PARSE_ERROR(derived_opts.context(),
140 : "Unknown Id '" << id << "'\n"
141 : << help_derived<creatable_classes>());
142 : }
143 : } // namespace Factory_detail
144 :
145 : template <typename T>
146 : struct create_from_yaml<std::unique_ptr<T>> {
147 : template <typename Metavariables>
148 : static std::unique_ptr<T> create(const Option& options) {
149 : return Factory_detail::create<T, Metavariables>(options);
150 : }
151 : };
152 : } // namespace Options
|