Skip to content

Commit

Permalink
Merge pull request #24 from PowerGridModel/feature/example-cli
Browse files Browse the repository at this point in the history
add CLI
  • Loading branch information
mgovers authored Jan 10, 2025
2 parents dbe9100 + 3ddaac8 commit d1c0390
Show file tree
Hide file tree
Showing 11 changed files with 273 additions and 14 deletions.
2 changes: 2 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,8 @@ if(${PGM_IO_ENABLE_DEV_BUILD})
enable_testing()
# get tests
add_subdirectory("tests")
# get examples
add_subdirectory("examples")
endif()

# export the power grid model io native
Expand Down
5 changes: 5 additions & 0 deletions examples/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# SPDX-FileCopyrightText: Contributors to the Power Grid Model project <[email protected]>
#
# SPDX-License-Identifier: MPL-2.0

add_subdirectory("convert_vnf_to_pgm")
14 changes: 14 additions & 0 deletions examples/convert_vnf_to_pgm/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
# SPDX-FileCopyrightText: Contributors to the Power Grid Model project <[email protected]>
#
# SPDX-License-Identifier: MPL-2.0

add_executable(convert_vnf_to_pgm "src/main.cpp")

target_link_libraries(convert_vnf_to_pgm
PRIVATE power_grid_model_io_native_c
)

install(TARGETS convert_vnf_to_pgm
EXPORT power_grid_model_io_nativeTargets
COMPONENT power_grid_model_io_native
)
File renamed without changes.
File renamed without changes.
158 changes: 158 additions & 0 deletions examples/convert_vnf_to_pgm/src/main.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
// SPDX-FileCopyrightText: Contributors to the Power Grid Model project <[email protected]>
//
// SPDX-License-Identifier: MPL-2.0

#include <power_grid_model_io_native_c/basics.h>
#include <power_grid_model_io_native_c/handle.h>
#include <power_grid_model_io_native_c/pgm_vnf_converter.h>

#include <algorithm>
#include <filesystem>
#include <fstream>
#include <iostream>
#include <span>
#include <sstream>
#include <string>

namespace {
using Idx = PGM_IO_Idx;

enum class ErrorCode : int {
no_error = 0,
regular_error = 1,
invalid_arguments = 2,
io_error = 3,
unknown_error = 127,
};

// custom deleter
template <auto func> struct DeleterFunctor {
template <typename T> void operator()(T* arg) const { func(arg); }
};

// memory safety: cleanup is automatically handled by unique_ptr, even if exceptions are thrown
using VNFConverter = std::unique_ptr<PGM_IO_PgmVnfConverter, DeleterFunctor<&PGM_IO_destroy_pgm_vnf_converter>>;
using Handle = std::unique_ptr<PGM_IO_Handle, DeleterFunctor<&PGM_IO_destroy_handle>>;

class PGMIOException : public std::exception {
public:
PGMIOException(ErrorCode error_code, std::string msg) : error_code_{error_code}, msg_{std::move(msg)} {}
char const* what() const noexcept final { return msg_.c_str(); }
ErrorCode error_code() const noexcept { return error_code_; }

private:
ErrorCode error_code_;
std::string msg_;
};

void check_error(Handle const& handle) {
if (auto const error_code = PGM_IO_error_code(handle.get()); error_code != PGM_IO_no_error) {
std::string error_message = PGM_IO_error_message(handle.get());
PGM_IO_clear_error(handle.get());
throw PGMIOException{ErrorCode::regular_error, std::move(error_message)};
}
}

auto read_file(std::filesystem::path const& path) {
std::ifstream const f{path};
if (!f) {
using namespace std::string_literals;
throw PGMIOException{ErrorCode::io_error, "Could not open file for reading: "s + path.string()};
}

std::ostringstream buffer;
buffer << f.rdbuf();
return buffer.str();
}

auto write_file(std::filesystem::path const& path, std::string_view contents) {
std::ofstream f{path};
if (!f) {
using namespace std::string_literals;
throw PGMIOException{ErrorCode::io_error, "Could not open file for writing: "s + path.string()};
}

f << contents;
std::cout << "Wrote file: " << path << "\n";
}

void run(std::filesystem::path const& vnf_file, std::filesystem::path const& pgm_json_file) {
constexpr auto experimental_feature_flag = PGM_IO_experimental_features_enabled;

auto const input_file_contents = read_file(vnf_file);

Handle handle{PGM_IO_create_handle()};
check_error(handle);

VNFConverter converter{
PGM_IO_create_pgm_vnf_converter(handle.get(), input_file_contents.c_str(), experimental_feature_flag)};
check_error(handle);

auto const* const json_result = PGM_IO_pgm_vnf_converter_get_input_data(handle.get(), converter.get());
check_error(handle);
if (json_result == nullptr) {
throw PGMIOException{ErrorCode::regular_error, "Conversion failed for an unknown reason"};
}

write_file(pgm_json_file, json_result);

// Cleanup of converter and handle is automatically handled by the wrapper types, even if exceptions are thrown.
// No need to call PGM_IO_destroy_pgm_vnf_converter(converter.get()) or PGM_IO_destroy_handle(handle.get()).
// NOTE: If you do not use the wrapper types, you must always call the destroy functions manually.
}

struct Arguments {
std::filesystem::path vnf_file;
std::filesystem::path pgm_json_file;
};

Arguments parse_arguments(std::span<char const* const> const& args) {
using namespace std::string_literals;

auto program = args.empty() ? "<program>"s : std::filesystem::path{args[0]}.filename().string();
auto const help_str = "Usage: "s + program + " [-h/--help] <vnf_file> <pgm_json_file>"s;
if (std::ranges::find_if(args, [](auto const& arg) { return arg == "-h"s || arg == "--help"s; }) != args.end()) {
throw PGMIOException{ErrorCode::no_error, help_str}; // early exit but not an error
}
if (args.size() != 3) {
throw PGMIOException{ErrorCode::invalid_arguments, help_str};
}

return Arguments{args[1], args[2]};
}
Arguments parse_arguments(int argc, char const* const argv[]) {
return parse_arguments({argv, static_cast<std::size_t>(argc)});
}

// handle errors and exceptions using Lippincott pattern
ErrorCode handle_errors() {
try {
std::rethrow_exception(std::current_exception());
} catch (PGMIOException const& e) {
if (e.error_code() == ErrorCode::no_error) {
std::cout << e.what() << "\n";
} else {
std::cerr << e.what() << "\n";
}
return e.error_code();
} catch (std::exception const& e) {
std::cerr << "An unknown error occurred:\n" << e.what() << "\n";
return ErrorCode::unknown_error;
}
return ErrorCode::unknown_error;
}

} // namespace

int main(int argc, char const* const argv[]) noexcept {
try {
auto const arguments = parse_arguments(argc, argv);

run(arguments.vnf_file, arguments.pgm_json_file);

std::cout << "Conversion successful\n";
return static_cast<int>(ErrorCode::no_error);
} catch (...) {
return static_cast<int>(handle_errors());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
// SPDX-FileCopyrightText: Contributors to the Power Grid Model project <[email protected]>
//
// SPDX-License-Identifier: MPL-2.0

#pragma once

#include "common.hpp"
#include "enum.hpp"

#include <concepts>
#include <exception>
#include <sstream>
#include <string>

namespace power_grid_model_io_native {
namespace detail {
inline auto to_string(std::floating_point auto x) {
std::ostringstream sstr{}; // NOLINT(misc-const-correctness) // https://github.com/llvm/llvm-project/issues/57297
sstr << x;
return sstr.str();
}
inline auto to_string(std::integral auto x) { return std::to_string(x); }
} // namespace detail

class PGMIOError : public std::exception {
public:
void append_msg(std::string_view msg) { msg_ += msg; }
char const* what() const noexcept final { return msg_.c_str(); }

private:
std::string msg_;
};

class InvalidArguments : public PGMIOError {
public:
struct TypeValuePair {
std::string name;
std::string value;
};

template <std::same_as<TypeValuePair>... Options>
InvalidArguments(std::string const& method, std::string const& arguments) {
append_msg(method + " is not implemented for " + arguments + "!\n");
}

template <class... Options>
requires(std::same_as<std::remove_cvref_t<Options>, TypeValuePair> && ...)
InvalidArguments(std::string const& method, Options&&... options)
: InvalidArguments{method, "the following combination of options"} {
(append_msg(" " + std::forward<Options>(options).name + ": " + std::forward<Options>(options).value + "\n"),
...);
}
};

class MissingCaseForEnumError : public InvalidArguments {
public:
template <typename T>
MissingCaseForEnumError(std::string const& method, const T& value)
: InvalidArguments{method, std::string{typeid(T).name()} + " #" + detail::to_string(static_cast<IntS>(value))} {
}
};

class ExperimentalFeature : public InvalidArguments {
using InvalidArguments::InvalidArguments;
};

class NotImplementedError : public PGMIOError {
public:
NotImplementedError() { append_msg("Function not yet implemented"); }
};

class UnreachableHit : public PGMIOError {
public:
UnreachableHit(std::string const& method, std::string const& reason_for_assumption) {
append_msg("Unreachable code hit when executing " + method +
".\n The following assumption for unreachability was not met: " + reason_for_assumption +
".\n This may be a bug in the library\n");
}
};

} // namespace power_grid_model_io_native
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,11 @@

#include <power_grid_model_io_native/common/common.hpp>
#include <power_grid_model_io_native/common/enum.hpp>
#include <power_grid_model_io_native/common/exception.hpp>

#include <power_grid_model/auxiliary/dataset.hpp>
#include <power_grid_model/auxiliary/meta_data_gen.hpp>
#include <power_grid_model/auxiliary/serialization/serializer.hpp>
#include <power_grid_model/common/exception.hpp>
#include <power_grid_model/component/node.hpp>
#include <power_grid_model/container.hpp>

Expand Down Expand Up @@ -74,12 +74,11 @@ class PgmVnfConverter {
inline PgmVnfConverter::PgmVnfConverter(std::string_view buffer, ExperimentalFeatures experimental_feature_flag)
: buffer_(buffer) {
if (experimental_feature_flag == experimental_features_disabled) {
using pgm::ExperimentalFeature;
throw ExperimentalFeature{
"PGM_VNF_converter",
ExperimentalFeature::TypeValuePair{.name = "PGM_VNF_conversion",
.value = "PgmVnfConverter is still in an experimental phase, if you'd "
"like to use it, enable experimental features."}};
throw ExperimentalFeature{"PGM_VNF_converter",
ExperimentalFeature::TypeValuePair{
.name = "PGM_VNF_conversion",
.value = "PgmVnfConverter is still in an experimental phase, if you would "
"like to use it, enable experimental features."}};
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -87,10 +87,8 @@ typedef struct PGM_IO_Handle PGM_IO_Handle;
*
*/
enum PGM_IO_ErrorCode {
PGM_IO_no_error = 0, /**< no error occurred */
PGM_IO_regular_error = 1, /**< some error occurred which is not in the batch calculation */
PGM_IO_batch_error = 2, /**< some error occurred which is in the batch calculation */
PGM_IO_serialization_error = 3 /**< some error occurred which is in the (de)serialization process */
PGM_IO_no_error = 0, /**< no error occurred */
PGM_IO_regular_error = 1, /**< some error occurred during conversion */
};

/**
Expand Down
4 changes: 3 additions & 1 deletion tests/c_api_tests/test_c_api_pgm_vnf_converter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -204,9 +204,11 @@ TEST_CASE("Test PGM_IO_get_example_vnf_input_data") {
PGM_IO_ExperimentalFeatures const experimental_feature_flag = PGM_IO_experimental_features_enabled;

auto* converter = PGM_IO_create_pgm_vnf_converter(handle, basic_vision_9_7_vnf_file, experimental_feature_flag);
CHECK(converter != nullptr);
REQUIRE(PGM_IO_error_code(handle) == PGM_IO_no_error);
REQUIRE(converter != nullptr);

auto const* const json_result = PGM_IO_pgm_vnf_converter_get_input_data(handle, converter);
REQUIRE(PGM_IO_error_code(handle) == PGM_IO_no_error);
std::string_view const json_string =
R"({"version":"1.0","type":"input","is_batch":false,"attributes":{},"data":{"node":[{"id":0,"u_rated":11},{"id":1,"u_rated":11},{"id":2,"u_rated":0.4},{"id":3,"u_rated":11},{"id":4,"u_rated":0.4}]}})";
CHECK(json_string == json_result);
Expand Down
4 changes: 2 additions & 2 deletions tests/cpp_unit_tests/test_pgm_vnf_converter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,11 @@
// SPDX-License-Identifier: MPL-2.0

#include <power_grid_model_io_native/common/enum.hpp>
#include <power_grid_model_io_native/common/exception.hpp>
#include <power_grid_model_io_native/pgm_vnf_converter/pgm_vnf_converter.hpp>

#include <power_grid_model/auxiliary/dataset.hpp>
#include <power_grid_model/auxiliary/meta_data_gen.hpp>
#include <power_grid_model/common/exception.hpp>

#include <doctest/doctest.h>

Expand All @@ -27,7 +27,7 @@ std::string_view const empty_json_string =

TEST_CASE("Test converter constructor") {
SUBCASE("Without experimental features") {
CHECK_THROWS_AS(PgmVnfConverter("", experimental_features_disabled), pgm::ExperimentalFeature);
CHECK_THROWS_AS(PgmVnfConverter("", experimental_features_disabled), ExperimentalFeature);
}

SUBCASE("With experimental features") { CHECK_NOTHROW(PgmVnfConverter("", experimental_features_enabled)); }
Expand Down

0 comments on commit d1c0390

Please sign in to comment.