Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add CLI #24

Merged
merged 4 commits into from
Jan 10, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
)
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_;
};
figueroa1395 marked this conversation as resolved.
Show resolved Hide resolved

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[]) {
figueroa1395 marked this conversation as resolved.
Show resolved Hide resolved
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 {
figueroa1395 marked this conversation as resolved.
Show resolved Hide resolved
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");
}
figueroa1395 marked this conversation as resolved.
Show resolved Hide resolved

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"),
...);
}
};
figueroa1395 marked this conversation as resolved.
Show resolved Hide resolved

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))} {
figueroa1395 marked this conversation as resolved.
Show resolved Hide resolved
}
};

class ExperimentalFeature : public InvalidArguments {
using InvalidArguments::InvalidArguments;
};
figueroa1395 marked this conversation as resolved.
Show resolved Hide resolved

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 */
figueroa1395 marked this conversation as resolved.
Show resolved Hide resolved
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
Loading