@@ -62,25 +62,37 @@ static void get_optional(const TomlBasicValue &v, const toml::key &ky, T &destin
6262 }
6363}
6464
65+ // TODO: construct this from a helper class with state so all the checking can be done implicitly
6566class TomlChecker {
6667 const TomlBasicValue &m_v;
6768 tsl::ordered_set<toml::key> m_visited;
68- bool m_checked = false ;
69+ tsl::ordered_set<toml::key> m_conditionVisited ;
6970
7071 public:
7172 TomlChecker (const TomlBasicValue &v, const toml::key &ky) : m_v(toml::find(v, ky)) {}
7273 TomlChecker (const TomlBasicValue &v) : m_v(v) {}
7374 TomlChecker (const TomlChecker &) = delete ;
7475
75- ~TomlChecker () noexcept (false ) {
76- if (!m_checked) {
77- throw std::runtime_error (" TomlChecker::check() not called" );
76+ ~TomlChecker () noexcept (false ) {}
77+
78+ // TOOD: check if the condition is valid during the parsing stage to print better errors!
79+ template <typename T>
80+ void optional (const toml::key &ky, Condition<T> &destination) {
81+ get_optional (m_v, ky, destination);
82+ for (const auto &itr : destination) {
83+ if (!itr.first .empty ()) {
84+ m_conditionVisited.emplace (itr.first );
85+ }
7886 }
87+ visit (ky);
7988 }
8089
8190 template <typename T>
8291 void optional (const toml::key &ky, T &destination) {
83- get_optional (m_v, ky, destination);
92+ // TODO: this currently doesn't allow you to get an optional map<string, X>
93+ if (m_v.contains (ky)) {
94+ destination = toml::find<T>(m_v, ky);
95+ }
8496 visit (ky);
8597 }
8698
@@ -92,13 +104,48 @@ class TomlChecker {
92104
93105 void visit (const toml::key &ky) { m_visited.insert (ky); }
94106
107+ std::string format_unknown_key (const toml::key &ky, const TomlBasicValue &value) {
108+ auto loc = value.location ();
109+ auto line_number_str = std::to_string (loc.line ());
110+ auto line_width = line_number_str.length ();
111+ auto line_str = loc.line_str ();
112+
113+ std::ostringstream oss;
114+ oss << " [error] Unknown key: " << ky << ' \n ' ;
115+ oss << " --> " << loc.file_name () << ' \n ' ;
116+
117+ oss << std::string (line_width + 2 , ' ' ) << " |\n " ;
118+ oss << ' ' << line_number_str << " | " << line_str << ' \n ' ;
119+
120+ oss << std::string (line_width + 2 , ' ' ) << ' |' ;
121+ auto key_start = line_str.find_last_of (ky, loc.column ());
122+ if (key_start != std::string::npos) {
123+ oss << std::string (key_start - ky.length () + 2 , ' ' ) << std::string (ky.length (), ' ~' );
124+ }
125+ oss << ' \n ' ;
126+
127+ return oss.str ();
128+ }
129+
95130 void check () {
96- m_checked = true ;
97131 for (const auto &itr : m_v.as_table ()) {
98132 const auto &ky = itr.first ;
99- if (m_visited.count (ky) == 0 ) {
100- // TODO: nice error messages
101- throw std::runtime_error (" Unknown key '" + ky + " '" );
133+ if (m_conditionVisited.count (ky)) {
134+ // TODO: check if condition (ky) exists
135+ for (const auto &jtr : itr.second .as_table ()) {
136+ if (m_visited.count (jtr.first ) == 0 ) {
137+ throw std::runtime_error (format_unknown_key (jtr.first , jtr.second ));
138+ }
139+ }
140+ } else if (m_visited.count (ky) == 0 ) {
141+ if (itr.second .is_table ()) {
142+ for (const auto &jtr : itr.second .as_table ()) {
143+ if (m_visited.count (jtr.first ) == 0 ) {
144+ throw std::runtime_error (format_unknown_key (jtr.first , jtr.second ));
145+ }
146+ }
147+ }
148+ throw std::runtime_error (format_unknown_key (ky, itr.second ));
102149 }
103150 }
104151 }
@@ -126,6 +173,30 @@ Project::Project(const Project *parent, const std::string &path, bool build) {
126173 get_optional (cmake, " allow-in-tree" , allow_in_tree);
127174 }
128175 } else {
176+ // Reasonable default conditions (you can override these if you desire)
177+ if (parent == nullptr ) {
178+ conditions[" windows" ] = R"cmake( WIN32)cmake" ;
179+ conditions[" macos" ] = R"cmake( CMAKE_SYSTEM_NAME MATCHES "Darwin")cmake" ;
180+ conditions[" unix" ] = R"cmake( UNIX)cmake" ;
181+ conditions[" bsd" ] = R"cmake( CMAKE_SYSTEM_NAME MATCHES "BSD")cmake" ;
182+ conditions[" linux" ] = conditions[" lunix" ] = R"cmake( CMAKE_SYSTEM_NAME MATCHES "Linux")cmake" ;
183+ conditions[" gcc" ] = R"cmake( CMAKE_CXX_COMPILER_ID STREQUAL "GNU" OR CMAKE_C_COMPILER_ID STREQUAL "GNU")cmake" ;
184+ conditions[" clang" ] = R"cmake( CMAKE_CXX_COMPILER_ID MATCHES "Clang" OR CMAKE_C_COMPILER_ID MATCHES "Clang")cmake" ;
185+ conditions[" msvc" ] = R"cmake( MSVC)cmake" ;
186+ } else {
187+ conditions = parent->conditions ;
188+ }
189+
190+ if (toml.contains (" conditions" )) {
191+ auto conds = toml::find<decltype (conditions)>(toml, " conditions" );
192+ for (const auto &cond : conds) {
193+ conditions[cond.first ] = cond.second ;
194+ }
195+ }
196+
197+ // TODO: make TomlCheckerFactory
198+ // .check() only once (at the end)
199+
129200 if (toml.contains (" cmake" )) {
130201 const auto &cmake = toml::find (toml, " cmake" );
131202 cmake_version = toml::find (cmake, " version" ).as_string ();
@@ -165,7 +236,6 @@ Project::Project(const Project *parent, const std::string &path, bool build) {
165236
166237 TomlChecker sub (itr.second );
167238 sub.optional (" condition" , subdir.condition );
168- sub.optional (" condition" , subdir.condition );
169239 sub.optional (" cmake-before" , subdir.cmake_before );
170240 sub.optional (" cmake-after" , subdir.cmake_after );
171241 sub.optional (" include-before" , subdir.include_before );
@@ -409,27 +479,6 @@ Project::Project(const Project *parent, const std::string &path, bool build) {
409479 v.required (" packages" , vcpkg.packages );
410480 v.check ();
411481 }
412-
413- // Reasonable default conditions (you can override these if you desire)
414- if (parent == nullptr ) {
415- conditions[" windows" ] = R"cmake( WIN32)cmake" ;
416- conditions[" macos" ] = R"cmake( CMAKE_SYSTEM_NAME MATCHES "Darwin")cmake" ;
417- conditions[" unix" ] = R"cmake( UNIX)cmake" ;
418- conditions[" bsd" ] = R"cmake( CMAKE_SYSTEM_NAME MATCHES "BSD")cmake" ;
419- conditions[" linux" ] = conditions[" lunix" ] = R"cmake( CMAKE_SYSTEM_NAME MATCHES "Linux")cmake" ;
420- conditions[" gcc" ] = R"cmake( CMAKE_CXX_COMPILER_ID STREQUAL "GNU" OR CMAKE_C_COMPILER_ID STREQUAL "GNU")cmake" ;
421- conditions[" clang" ] = R"cmake( CMAKE_CXX_COMPILER_ID MATCHES "Clang" OR CMAKE_C_COMPILER_ID MATCHES "Clang")cmake" ;
422- conditions[" msvc" ] = R"cmake( MSVC)cmake" ;
423- } else {
424- conditions = parent->conditions ;
425- }
426-
427- if (toml.contains (" conditions" )) {
428- auto conds = toml::find<decltype (conditions)>(toml, " conditions" );
429- for (const auto &cond : conds) {
430- conditions[cond.first ] = cond.second ;
431- }
432- }
433482 }
434483}
435484
0 commit comments