diff --git a/src/app/openemsh.cpp b/src/app/openemsh.cpp index a8ebb0a6..e58c0f52 100644 --- a/src/app/openemsh.cpp +++ b/src/app/openemsh.cpp @@ -10,6 +10,7 @@ #include "infra/serializers/serializer_to_plantuml.hpp" #include "infra/serializers/serializer_to_prettyprint.hpp" #include "utils/concepts.hpp" +#include "utils/expected_utils.hpp" #include "utils/unreachable.hpp" #include "openemsh.hpp" @@ -99,17 +100,32 @@ void OpenEMSH::set_output_format(Params::OutputFormat format) { } //****************************************************************************** -void OpenEMSH::parse() { +expected OpenEMSH::parse() { Caretaker::singleton().reset(); - board = ParserFromCsx::run(params.input, static_cast(params), params.override_from_cli); + UNWRAP( + ParserFromCsx::run(params.input, static_cast(params), params.override_from_cli), + [this](auto& value) { + board = value; + }); Caretaker::singleton().remember_current_timepoint(); + return {}; } //****************************************************************************** -void OpenEMSH::write() const { +bool OpenEMSH::is_about_overwriting() const { + return !params.force + && params.output_format == Params::OutputFormat::CSX + && (filesystem::exists(params.output) + || (params.input == params.output + && board + && board->global_params->get_current_state().has_grid_already)); +} + +//****************************************************************************** +expected OpenEMSH::write() const { switch(params.output_format) { case Params::OutputFormat::CSX: - SerializerToCsx::run(*board, params.input, params.output, static_cast(params)); + return SerializerToCsx::run(*board, params.input, params.output, static_cast(params)); break; case Params::OutputFormat::PLANTUML: { // SerializerToPlantuml::run(*board, static_cast(params)); @@ -122,6 +138,7 @@ void OpenEMSH::write() const { default: ::unreachable(); }; + return {}; } //****************************************************************************** diff --git a/src/app/openemsh.hpp b/src/app/openemsh.hpp index 1bd4dea2..a754ce20 100644 --- a/src/app/openemsh.hpp +++ b/src/app/openemsh.hpp @@ -6,6 +6,7 @@ #pragma once +#include #include #include #include @@ -55,15 +56,15 @@ class OpenEMSH { // TODO implement validation checks on params here. // void check_x(); - void parse(); + std::expected parse(); void run(std::set const& steps) const; void run_all_steps() const; void run_next_step() const; void run_from_step(Step step) const; void go_before(Step step) const; void go_before_previous_step() const; - void write() const; - + bool is_about_overwriting() const; + std::expected write() const; bool can_run_a_next_step() const; bool can_go_before() const; std::optional get_current_step() const; diff --git a/src/domain/global.hpp b/src/domain/global.hpp index e9ca3690..be533e25 100644 --- a/src/domain/global.hpp +++ b/src/domain/global.hpp @@ -14,6 +14,7 @@ namespace domain { //****************************************************************************** struct Params { + bool has_grid_already = false; // TODO would better fit in infra layer? double proximity_limit = 1; // TODO must be linked to initial d double smoothness = 2; std::size_t lmin = 10; diff --git a/src/infra/parsers/parser_from_csx.cpp b/src/infra/parsers/parser_from_csx.cpp index 62cb2c7d..9742836b 100644 --- a/src/infra/parsers/parser_from_csx.cpp +++ b/src/infra/parsers/parser_from_csx.cpp @@ -6,7 +6,7 @@ #include #include -#include +#include #include @@ -18,6 +18,7 @@ #include "csxcad_layer/point_3d.hpp" #include "parser_from_csx.hpp" +#include "utils/expected_utils.hpp" #include "utils/progress.hpp" #include "utils/unreachable.hpp" #include "utils/vector_utils.hpp" @@ -51,9 +52,12 @@ class ParserFromCsx::Pimpl { ParserFromCsx::Params const& params; std::map primitives_ids; Board::Builder board; + domain::Params domain_params; Pimpl(ParserFromCsx::Params const& params); + expected parse_grid(pugi::xml_node const& node); + shared_ptr parse_property(pugi::xml_node const& node); bool parse_primitive(pugi::xml_node const& node, shared_ptr const& material); @@ -68,6 +72,26 @@ ParserFromCsx::Pimpl::Pimpl(ParserFromCsx::Params const& params) : params(params) {} +//****************************************************************************** +expected ParserFromCsx::Pimpl::parse_grid(pugi::xml_node const& node) { + std::size_t coord_system = node.attribute("CoordSystem").as_uint(); + + if(coord_system == 0) { + // First step : into bool has_grid_already + pugi::xml_node grid = node.child("RectilinearGrid"); + string_view x_lines = grid.child_value("XLines"); + string_view y_lines = grid.child_value("YLines"); + string_view z_lines = grid.child_value("ZLines"); + domain_params.has_grid_already = !x_lines.empty() || !y_lines.empty() || !z_lines.empty(); + // TODO Second step : into fixed MLP + // TODO Third step : into vizualisable set of meshlines for comparison +// } else if(coord_system == 1) { + } else { + return unexpected("Unsupported CoordSystem"); + } + return {}; +} + //****************************************************************************** shared_ptr ParserFromCsx::Pimpl::parse_property(pugi::xml_node const& node) { string name(node.attribute("Name").as_string()); @@ -249,21 +273,21 @@ void ParserFromCsx::Pimpl::parse_primitive_linpoly(pugi::xml_node const& node, s } //****************************************************************************** -shared_ptr ParserFromCsx::run(std::filesystem::path const& input) { +expected, string> ParserFromCsx::run(std::filesystem::path const& input) { return ParserFromCsx::run(input, {}); } //****************************************************************************** -shared_ptr ParserFromCsx::run(std::filesystem::path const& input, Params params) { +expected, string> ParserFromCsx::run(std::filesystem::path const& input, Params params) { ParserFromCsx parser(input, std::move(params)); - parser.parse(); + TRY(parser.parse()); return parser.output(); } //****************************************************************************** -shared_ptr ParserFromCsx::run(std::filesystem::path const& input, Params params, std::function const& override_domain_params) { +expected, string> ParserFromCsx::run(std::filesystem::path const& input, Params params, std::function const& override_domain_params) { ParserFromCsx parser(input, std::move(params)); - parser.parse(); + TRY(parser.parse()); override_domain_params(parser.domain_params); return parser.output(); } @@ -285,17 +309,19 @@ ParserFromCsx::ParserFromCsx(filesystem::path const& input, Params params) ParserFromCsx::~ParserFromCsx() = default; //****************************************************************************** -void ParserFromCsx::parse() { +expected ParserFromCsx::parse() { pugi::xml_document doc; - pugi::xml_parse_result res = doc.load_file(input.native().c_str()); - if(res.status != pugi::status_ok) { - cerr << res.description() << endl; - return; + if(auto res = doc.load_file(input.native().c_str()) + ; res.status != pugi::status_ok) { + return unexpected(res.description()); } pugi::xpath_node fdtd = doc.select_node("/openEMS/FDTD"); + pugi::xpath_node csx = doc.select_node("/openEMS/ContinuousStructure"); + TRY(pimpl->parse_grid(csx.node())); + { // Primitives' IDs grow disregarding properties. size_t id = 0; @@ -324,6 +350,8 @@ void ParserFromCsx::parse() { } bar.complete(); + domain_params = std::move(pimpl->domain_params); + return {}; } //****************************************************************************** diff --git a/src/infra/parsers/parser_from_csx.hpp b/src/infra/parsers/parser_from_csx.hpp index af0f72a7..8f20bda6 100644 --- a/src/infra/parsers/parser_from_csx.hpp +++ b/src/infra/parsers/parser_from_csx.hpp @@ -6,6 +6,7 @@ #pragma once +#include #include #include #include @@ -31,15 +32,15 @@ class ParserFromCsx { ~ParserFromCsx(); - [[nodiscard]] static std::shared_ptr run(std::filesystem::path const& input); - [[nodiscard]] static std::shared_ptr run(std::filesystem::path const& input, Params params); - [[nodiscard]] static std::shared_ptr run(std::filesystem::path const& input, Params params, std::function const& override_domain_params); + [[nodiscard]] static std::expected, std::string> run(std::filesystem::path const& input); + [[nodiscard]] static std::expected, std::string> run(std::filesystem::path const& input, Params params); + [[nodiscard]] static std::expected, std::string> run(std::filesystem::path const& input, Params params, std::function const& override_domain_params); private: ParserFromCsx(std::filesystem::path const& input); ParserFromCsx(std::filesystem::path const& input, Params params); - void parse(); + std::expected parse(); [[nodiscard]] std::shared_ptr output(); std::filesystem::path const input; @@ -49,5 +50,4 @@ class ParserFromCsx { // std::vector> polygons; class Pimpl; std::unique_ptr pimpl; - }; diff --git a/src/infra/serializers/serializer_to_csx.cpp b/src/infra/serializers/serializer_to_csx.cpp index 319a730c..456901df 100644 --- a/src/infra/serializers/serializer_to_csx.cpp +++ b/src/infra/serializers/serializer_to_csx.cpp @@ -4,8 +4,6 @@ /// @author Thomas Lepoix ///***************************************************************************** -#include - #include #include "domain/mesh/meshline.hpp" @@ -28,16 +26,16 @@ string to_xml_node(Axis const axis) noexcept { } //****************************************************************************** -void SerializerToCsx::run( +expected SerializerToCsx::run( Board& board, filesystem::path const& input, filesystem::path const& output) { - SerializerToCsx::run(board, input, output, {}); + return SerializerToCsx::run(board, input, output, {}); } //****************************************************************************** -void SerializerToCsx::run( +expected SerializerToCsx::run( Board& board, filesystem::path const& input, filesystem::path const& output, @@ -45,6 +43,10 @@ void SerializerToCsx::run( SerializerToCsx serializer(input, output, std::move(params)); board.accept(serializer); + + if(serializer.error) + return unexpected(serializer.error.value()); + return {}; } //****************************************************************************** @@ -54,17 +56,36 @@ SerializerToCsx::SerializerToCsx(filesystem::path const& input, filesystem::path , output(output) {} +//****************************************************************************** +pugi::xml_node find_or_prepend_child(pugi::xml_node& node, char const* child) { + if(pugi::xml_node c = node.child(child); c.empty()) + return node.prepend_child(child); + else + return c; +} + +//****************************************************************************** +pugi::xml_node find_or_append_child(pugi::xml_node& node, char const* child) { + if(pugi::xml_node c = node.child(child); c.empty()) + return node.append_child(child); + else + return c; +} + //****************************************************************************** void SerializerToCsx::visit(Board& board) { pugi::xml_document doc; pugi::xml_parse_result const res = doc.load_file(input.native().c_str()); if(res.status != pugi::status_ok) { - cerr << res.description() << endl; + error = res.description(); return; } - pugi::xpath_node const grid = doc.select_node("/openEMS/ContinuousStructure/RectilinearGrid"); + pugi::xml_node oems = find_or_append_child(doc, "openEMS"); + pugi::xml_node csx = find_or_append_child(oems, "ContinuousStructure"); + pugi::xml_node grid = find_or_append_child(csx, "RectilinearGrid"); + grid.remove_children(); auto const add_meshlines_to_xml_doc = [this, &grid, &board](Axis const axis) { string out; @@ -78,9 +99,7 @@ void SerializerToCsx::visit(Board& board) { if(!out.empty()) out.pop_back(); - cerr << "lines: " << out << endl << endl; - grid.node().select_node(to_xml_node(axis).c_str()) - .node().text().set(out.c_str()); + grid.append_child(to_xml_node(axis).c_str()).text().set(out.c_str()); }; if(params.with_axis_x) @@ -90,6 +109,18 @@ void SerializerToCsx::visit(Board& board) { if(params.with_axis_z) add_meshlines_to_xml_doc(Z); - cerr << "output: " << output << endl; - doc.save_file(output.native().c_str()); + if(params.with_oemsh_params) { + auto const& p = board.global_params->get_current_state(); + pugi::xml_node oemsh = find_or_prepend_child(doc, "OpenEMSH"); + pugi::xml_node global_params = find_or_append_child(oemsh, "GlobalParams"); + global_params.remove_attributes(); + global_params.append_attribute("ProximityLimit").set_value(p.proximity_limit); + global_params.append_attribute("Smoothness").set_value(p.smoothness); + global_params.append_attribute("dmax").set_value(p.dmax); + global_params.append_attribute("lmin").set_value(p.lmin); + } else { + doc.remove_child("OpenEMSH"); + } + + doc.save_file(output.native().c_str(), " "); } diff --git a/src/infra/serializers/serializer_to_csx.hpp b/src/infra/serializers/serializer_to_csx.hpp index 7824e88c..f483d1c9 100644 --- a/src/infra/serializers/serializer_to_csx.hpp +++ b/src/infra/serializers/serializer_to_csx.hpp @@ -6,7 +6,9 @@ #pragma once +#include #include +#include #include #include "domain/utils/entity_visitor.hpp" @@ -22,13 +24,14 @@ class SerializerToCsx final : public domain::EntityVisitor { bool with_axis_x = true; bool with_axis_y = true; bool with_axis_z = true; + bool with_oemsh_params = false; }; - static void run( + static std::expected run( domain::Board& board, std::filesystem::path const& input, std::filesystem::path const& output); - static void run( + static std::expected run( domain::Board& board, std::filesystem::path const& input, std::filesystem::path const& output, @@ -47,4 +50,5 @@ class SerializerToCsx final : public domain::EntityVisitor { Params const params; std::filesystem::path const input; std::filesystem::path const output; + std::optional error; }; diff --git a/src/main.cpp b/src/main.cpp index e841145d..199515d6 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -7,6 +7,7 @@ #include #include +#include #include #include "app/openemsh.hpp" @@ -29,9 +30,40 @@ int main(int argc, char* argv[]) { }); if(!oemsh.get_params().gui) { - oemsh.parse(); + if(auto res = oemsh.parse(); !res.has_value()) { + std::cerr + << std::format( + "Error parsing file \"{}\" : {}", + oemsh.get_params().input.generic_string(), + res.error()) + << std::endl; + return EXIT_FAILURE; + } + if(oemsh.is_about_overwriting()) { + std::cerr + << std::format( + "Error: You are about overwriting the file \"{}\", " + "use --force if that is what you want.", + oemsh.get_params().output.generic_string()) + << std::endl; + return EXIT_FAILURE; + } oemsh.run_all_steps(); - oemsh.write(); + if(auto res = oemsh.write(); !res.has_value()) { + std::cerr + << std::format( + "Error saving file \"{}\" : {}", + oemsh.get_params().output.generic_string(), + res.error()) + << std::endl; + return EXIT_FAILURE; + } else if(oemsh.get_params().verbose) { + std::cerr + << std::format( + "Saved file \"{}\"", + oemsh.get_params().output.generic_string()) + << std::endl; + } } else { QApplication a(argc, argv); diff --git a/src/ui/cli/cli.cpp b/src/ui/cli/cli.cpp index 5b4c405c..eff48099 100644 --- a/src/ui/cli/cli.cpp +++ b/src/ui/cli/cli.cpp @@ -10,11 +10,13 @@ //#include #include +#include #include #include #include #include "utils/concepts.hpp" +#include "utils/map_utils.hpp" #include "utils/unreachable.hpp" #include "cli.hpp" @@ -127,7 +129,8 @@ app::OpenEMSH::Params cli(int const argc, char* argv[]) { auto* i = app.add_option("input,-i,--input", params.input, "Input CSX file.")->check(CLI::ExistingFile)->required(); g->trigger_on_parse()->check(JustDo([i]() { i->required(false); })); // app.add_option("-o,--output", params.output, "Output CSX file. If different from input, will copy and extend it.")->check((!CLI::ExistingFile)|FutureConditional(params.force,"Cannot overwrite a file without --force")); - app.add_option("-o,--output", params.output, "Output CSX file. If different from input, will copy and extend it.")->check(CLI::Validator((!CLI::ExistingFile)|FutureConditional(params.force,"Cannot overwrite a file without --force"), "FILE", "KO")); +// app.add_option("-o,--output", params.output, "Output CSX file. If different from input, will copy and extend it.")->check(CLI::Validator((!CLI::ExistingFile)|FutureConditional(params.force,"Cannot overwrite a file without --force"), "FILE", "KO")); + app.add_option("-o,--output", params.output, "Output CSX file. If different from input, will copy and extend it. (Defaults to input, if provided)")->type_name(format("{}:FILE", CLI::detail::type_name())); app.add_flag("-f,--force", params.force, "Allow overwriting a file.")->trigger_on_parse(); static std::map> const map { @@ -136,7 +139,7 @@ app::OpenEMSH::Params cli(int const argc, char* argv[]) { { "prettyprint", app::OpenEMSH::Params::OutputFormat::PRETTYPRINT } }; // https://github.com/CLIUtils/CLI11/issues/554#issuecomment-932782337 - app.add_option("--output-format", params.output_format, "Output format.")->transform(CLI::CheckedTransformer(map, CLI::ignore_case).description(CLI::detail::generate_map(CLI::detail::smart_deref(map), true))); + app.add_option("--output-format", params.output_format, "Output format.")->transform(CLI::CheckedTransformer(map, CLI::ignore_case).description(CLI::detail::generate_map(CLI::detail::smart_deref(map), true)))->default_str(reverse_kv(map).at(params.output_format)); app.add_flag("--no-yz", [¶ms](size_t) { params.with_yz = false; }, "Don't process YZ plane.")->group("Input options"); app.add_flag("--no-zx", [¶ms](size_t) { params.with_zx = false; }, "Don't process ZX plane.")->group("Input options"); @@ -165,15 +168,20 @@ app::OpenEMSH::Params cli(int const argc, char* argv[]) { app.add_flag("--no-x", [¶ms](size_t) { params.with_axis_x = false; }, "Don't include X axis meshlines in output.")->group("Output options"); app.add_flag("--no-y", [¶ms](size_t) { params.with_axis_y = false; }, "Don't include Y axis meshlines in output.")->group("Output options"); app.add_flag("--no-z", [¶ms](size_t) { params.with_axis_z = false; }, "Don't include Z axis meshlines in output.")->group("Output options"); - app.add_flag("--meshlines", params.with_meshlines, "Include regular meshlines in output.")->group("Output options")->default_str(to_string(params.with_meshlines)); - app.add_flag("--policy-lines", params.with_meshline_policies, "Include meshline policies in output.")->group("Output options")->default_str(to_string(params.with_meshline_policies)); -// app.add_flag("--policy-lines", params.with_meshline_policies, "Include meshline policies in output.")->group("Output options")->capture_default_str(); + app.add_flag("--save-oemsh-params", params.with_oemsh_params, "Include OpenEMSH parameters used for this mesh.")->group("Output options"); + app.add_option("--meshlines", params.with_meshlines, "Include regular meshlines in output.")->group("Output options")->default_str(to_string(params.with_meshlines)); + app.add_option("--policy-lines", params.with_meshline_policies, "Include meshline policies in output.")->group("Output options")->default_str(to_string(params.with_meshline_policies)); app.preparse_callback([g](size_t argc) { if(argc == 0) g->force_callback()->default_val(true); }); + app.final_callback([¶ms]() { + if(params.output.empty()) + params.output = params.input; + }); + try { app.parse(argc, argv); } catch(CLI::Success const& e) { diff --git a/src/ui/qt/main_window.cpp b/src/ui/qt/main_window.cpp index b5223575..9fd79a06 100644 --- a/src/ui/qt/main_window.cpp +++ b/src/ui/qt/main_window.cpp @@ -8,6 +8,7 @@ #include #include #include +#include #include #include @@ -78,17 +79,25 @@ MainWindow::MainWindow(app::OpenEMSH& oemsh, QWidget* parent) MainWindow::~MainWindow() = default; //****************************************************************************** -void MainWindow::parse_and_display() { +bool MainWindow::parse_and_display() { if(csx_file.isEmpty()) - return; + return false; QGuiApplication::setOverrideCursor(Qt::WaitCursor); + + if(auto res = oemsh.parse() + ; !res.has_value()) { + QGuiApplication::restoreOverrideCursor(); + ui->statusBar->showMessage("Error parsing file \"" + csx_file + "\"" + " : " + QString::fromStdString(res.error())); + return false; + } + update_title(); - oemsh.parse(); ui->structure_view->init(&oemsh.get_board()); ui->processing_view->init(&oemsh.get_board()); run(); QGuiApplication::restoreOverrideCursor(); + return true; } //****************************************************************************** @@ -112,6 +121,7 @@ void MainWindow::update_title() { void MainWindow::clear() { ui->structure_view->clear(); ui->processing_view->clear(); + ui->statusBar->clearMessage(); update_cell_number(true); } @@ -299,36 +309,65 @@ void MainWindow::on_a_file_open_triggered() { dialog.setNameFilter(format_filter_csx); dialog.setDirectory(csx_file.isEmpty() ? QString(".") : QFileInfo(csx_file).path()); if(dialog.exec()) { - QGuiApplication::setOverrideCursor(Qt::WaitCursor); csx_file = dialog.selectedFiles().first(); clear(); oemsh.set_input(csx_file.toStdString()); - parse_and_display(); + if(!parse_and_display()) + return; on_a_fit_triggered(); - QGuiApplication::restoreOverrideCursor(); } } //****************************************************************************** -void MainWindow::on_a_file_save_triggered() { - QGuiApplication::setOverrideCursor(Qt::WaitCursor); - - if(oemsh.get_params().output.empty()) - oemsh.set_output(csx_file.toStdString()); - - // TODO deduce from csx_file suffix +void MainWindow::save_csx_file() { oemsh.set_output_format(app::OpenEMSH::Params::OutputFormat::CSX); - // TODO warn for overwrite? - - // TODO be sure in this mode, the XML file is edited and stuff like comments won't be discarded - oemsh.write(); + if(oemsh.is_about_overwriting()) { + auto res = QMessageBox::warning(this, + "", + QString::fromStdString(std::format( + "You are about overwriting the file \"{}\", do you want to continue?", + oemsh.get_params().output.generic_string())), + QMessageBox::Cancel | QMessageBox::Save); + if(res != QMessageBox::Save) { + return; + } + } + QGuiApplication::setOverrideCursor(Qt::WaitCursor); + if(auto res = oemsh.write() + ; res.has_value()) + ui->statusBar->showMessage("Saved file \"" + csx_file + "\""); + else + ui->statusBar->showMessage("Failed to save file \"" + csx_file + "\"" + " : " + QString::fromStdString(res.error())); QGuiApplication::restoreOverrideCursor(); } +//****************************************************************************** +void MainWindow::on_a_file_save_triggered() { + if(oemsh.get_params().output.empty()) { + oemsh.set_output(csx_file.toStdString()); + } else if(csx_file != QString::fromStdString(oemsh.get_params().output.generic_string())) { + auto res = QMessageBox::question(this, + "", + QString::fromStdString(std::format( + "You are about saving to the file \"{}\" which is different from the input file \"{}\", do you want to continue?", + oemsh.get_params().output.generic_string(), + oemsh.get_params().input.generic_string())), + QMessageBox::Cancel | QMessageBox::Save); + if(res == QMessageBox::Save) { + csx_file = QString::fromStdString(oemsh.get_params().output.generic_string()); + } else { + return; + } + } + + // TODO deduce format from csx_file suffix + save_csx_file(); +} + //****************************************************************************** void MainWindow::on_a_file_save_as_triggered() { QFileDialog dialog(this, ui->a_file_save_as->toolTip()); @@ -338,17 +377,13 @@ void MainWindow::on_a_file_save_as_triggered() { dialog.setDefaultSuffix(".csx"); dialog.setDirectory(csx_file.isEmpty() ? QString(".") : QFileInfo(csx_file).path()); if(dialog.exec()) { - QGuiApplication::setOverrideCursor(Qt::WaitCursor); csx_file = dialog.selectedFiles().first(); update_title(); // TODO deduce from filter selected by user // dialog.selectedNameFilter(); // TODO check actual suffix with that ? - oemsh.set_output_format(app::OpenEMSH::Params::OutputFormat::CSX); oemsh.set_output(csx_file.toStdString()); - oemsh.write(); - - QGuiApplication::restoreOverrideCursor(); + save_csx_file(); } } diff --git a/src/ui/qt/main_window.hpp b/src/ui/qt/main_window.hpp index 27dc4ac8..26ae48a2 100644 --- a/src/ui/qt/main_window.hpp +++ b/src/ui/qt/main_window.hpp @@ -37,6 +37,7 @@ class MainWindow : public QMainWindow { void update_navigation_buttons_visibility(); void update_show_buttons_pressing(); void update_cell_number(bool reset = false); + void save_csx_file(); void go_to_current_state(); void make_current_state_view(); @@ -87,7 +88,7 @@ private slots: MainWindow(app::OpenEMSH& oemsh, QWidget* parent = nullptr); ~MainWindow() override; - void parse_and_display(); + bool parse_and_display(); void clear(); protected: diff --git a/src/utils/expected_utils.hpp b/src/utils/expected_utils.hpp new file mode 100644 index 00000000..b13f739d --- /dev/null +++ b/src/utils/expected_utils.hpp @@ -0,0 +1,27 @@ +///***************************************************************************** +/// @date Feb 2021 +/// @copyright GPL-3.0-or-later +/// @author Thomas Lepoix +///***************************************************************************** + +#pragma once + +#include + +// Example: TRY(func()); +//****************************************************************************** +#define TRY(x) \ + { /* TRY */ \ + if(auto MACRO_TRY_res = (x); !MACRO_TRY_res.has_value()) \ + return std::unexpected(std::move(MACRO_TRY_res.error())); \ + } + +// Example: UNWRAP(func(), [&](auto& val) { var = val; }); +//****************************************************************************** +#define UNWRAP(x, unwrap) \ + { /* UNWRAP */ \ + if(auto MACRO_UNWRAP_res = (x); MACRO_UNWRAP_res.has_value()) \ + unwrap(MACRO_UNWRAP_res.value()); \ + else \ + return std::unexpected(std::move(MACRO_UNWRAP_res.error())); \ + } diff --git a/src/utils/map_utils.hpp b/src/utils/map_utils.hpp new file mode 100644 index 00000000..b6c96b15 --- /dev/null +++ b/src/utils/map_utils.hpp @@ -0,0 +1,19 @@ +///***************************************************************************** +/// @date Feb 2021 +/// @copyright GPL-3.0-or-later +/// @author Thomas Lepoix +///***************************************************************************** + +#pragma once + +#include +#include + +//****************************************************************************** +template> +std::map reverse_kv(std::map const& in) { + std::map out; + for(auto const& [k, v] : in) + out.emplace(v, k); + return out; +} diff --git a/test/unit/CMakeLists.txt b/test/unit/CMakeLists.txt index 5b5db849..0f3f1f63 100644 --- a/test/unit/CMakeLists.txt +++ b/test/unit/CMakeLists.txt @@ -29,6 +29,7 @@ if( Catch2_FOUND ) "${CMAKE_CURRENT_SOURCE_DIR}/infra/serializers/test_serializer_to_plantuml.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/infra/utils/test_to_string.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/utils/test_down_up_cast.cpp" + "${CMAKE_CURRENT_SOURCE_DIR}/utils/test_map_utils.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/utils/test_signum.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/utils/test_vector_utils.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/utils/test_tree_node.cpp" diff --git a/test/unit/utils/test_map_utils.cpp b/test/unit/utils/test_map_utils.cpp new file mode 100644 index 00000000..a00ddc83 --- /dev/null +++ b/test/unit/utils/test_map_utils.cpp @@ -0,0 +1,42 @@ +///***************************************************************************** +/// @date Feb 2021 +/// @copyright GPL-3.0-or-later +/// @author Thomas Lepoix +///***************************************************************************** + +#include + +#include + +#include "utils/map_utils.hpp" + +/// @test template> +/// std::map reverse_kv(std::map const& in) +///***************************************************************************** + +//****************************************************************************** +SCENARIO("template> \ +std::map reverse_kv(std::map const& in)", "[utils][map_utils]") { + enum class Value { + ZERO, + ONE, + TWO, + THREE + }; + GIVEN("A map of string keys and enum values") { + std::map m { + {"zero", Value::ZERO }, + {"one", Value::ONE }, + {"two", Value::TWO }, + {"three", Value::THREE } + }; + THEN("Should return a map of enum keys and string values") { + std::map r = reverse_kv(m); + REQUIRE(r.size() == m.size()); + REQUIRE(r.at(Value::ZERO) == "zero"); + REQUIRE(r.at(Value::ONE) == "one"); + REQUIRE(r.at(Value::TWO) == "two"); + REQUIRE(r.at(Value::THREE) == "three"); + } + } +}