mirror of
https://github.com/ModOrganizer2/modorganizer-plugin_python.git
synced 2026-07-27 14:03:33 -07:00
Better implementation for FileWrapper / DirectoryWrapper.
This commit is contained in:
@@ -44,3 +44,25 @@ sub-directories:
|
||||
- [`tests/runner`](tests/runner/) contains C++ tests, using GTest
|
||||
Tests in this project instantiate a Python runner and then use it to check that
|
||||
plugins implemented in Python can be used properly on the C++ side.
|
||||
|
||||
## Building & Running tests
|
||||
|
||||
Tests are not built by default with `mob`, so you will need to run `cmake` manually
|
||||
with the proper arguments.
|
||||
|
||||
You need to define `PLUGIN_PYTHON_TESTS` with `-DPLUGIN_PYTHON_TESTS` when running
|
||||
the configure step of cmake.
|
||||
|
||||
You can then build the tests
|
||||
|
||||
```bash
|
||||
# replace vsbuild with your build folder
|
||||
cmake --build vsbuild --config RelWithDebInfo --target "python-tests" "runner-tests"
|
||||
```
|
||||
|
||||
To run the tests, use `ctest`
|
||||
|
||||
```bash
|
||||
# replace vsbuild with your build folder
|
||||
ctest.exe --test-dir vsbuild -C RelWithDebInfo
|
||||
```
|
||||
|
||||
@@ -68,7 +68,6 @@ PYBIND11_MODULE(mobase, m)
|
||||
// typing stuff to be consistent with stubs and allow plugin developers to properly
|
||||
// type their code if they want
|
||||
{
|
||||
m.add_object("Path", py::module_::import("pathlib").attr("Path"));
|
||||
m.add_object("TypeVar", py::module_::import("typing").attr("TypeVar"));
|
||||
|
||||
auto s = m.attr("__dict__");
|
||||
@@ -83,11 +82,6 @@ PYBIND11_MODULE(mobase, m)
|
||||
"MoVariant",
|
||||
py::eval("None | bool | int | str | list[object] | dict[str, object]"));
|
||||
|
||||
// same things for FileWrapper and DirectoryWrapper
|
||||
//
|
||||
m.add_object("FileWrapper", py::eval("str | PyQt6.QtCore.QFileInfo | Path", s));
|
||||
m.add_object("DirectoryWrapper", py::eval("str | PyQt6.QtCore.QDir | Path", s));
|
||||
|
||||
// same thing for GameFeatureType
|
||||
//
|
||||
m.add_object("GameFeatureType", py::eval("TypeVar('GameFeatureType')", s));
|
||||
|
||||
+101
-49
@@ -12,65 +12,72 @@
|
||||
|
||||
#include "pybind11_qt/pybind11_qt.h"
|
||||
|
||||
#include "pybind11_utils/arg_wrapper.h"
|
||||
#include "pybind11_utils/functional.h"
|
||||
#include "pybind11_utils/shared_cpp_owner.h"
|
||||
#include "pybind11_utils/smart_variant_wrapper.h"
|
||||
|
||||
#include <isavegame.h>
|
||||
#include <pluginrequirements.h>
|
||||
|
||||
namespace mo2::python {
|
||||
|
||||
// struct to wrap "path" object between C++ and Python, allowing to mix
|
||||
// pathlib.Path, QString, QFileInfo and QDir when possible
|
||||
namespace detail {
|
||||
|
||||
template <>
|
||||
struct smart_variant_converter<QString> {
|
||||
|
||||
static QString from(std::filesystem::path const& path)
|
||||
{
|
||||
return QString::fromStdWString(path.native());
|
||||
}
|
||||
|
||||
static QString from(QFileInfo const& fileInfo)
|
||||
{
|
||||
return fileInfo.filePath();
|
||||
}
|
||||
|
||||
static QString from(QDir const& dir) { return dir.path(); }
|
||||
};
|
||||
|
||||
template <>
|
||||
struct smart_variant_converter<std::filesystem::path> {
|
||||
|
||||
static std::filesystem::path from(QString const& value)
|
||||
{
|
||||
return {value.toStdWString()};
|
||||
}
|
||||
|
||||
static std::filesystem::path from(QFileInfo const& fileInfo)
|
||||
{
|
||||
return fileInfo.filesystemFilePath();
|
||||
}
|
||||
|
||||
static std::filesystem::path from(QDir const& dir)
|
||||
{
|
||||
return dir.filesystemPath();
|
||||
}
|
||||
};
|
||||
|
||||
// we do not need specialization for QFileInfo and QDir because both of them can
|
||||
// be constructed from std::filesystem::path and QString already
|
||||
|
||||
} // namespace detail
|
||||
|
||||
using FileWrapper = smart_variant<QString, std::filesystem::path, QFileInfo>;
|
||||
using DirectoryWrapper = smart_variant<QString, std::filesystem::path, QDir>;
|
||||
|
||||
// wrap the given function to accept FileWrapper (str | PathLike | QFileInfo) at the
|
||||
// given argument positions (or any valid positions if Is... is empty)
|
||||
//
|
||||
class BasePathWrapper {
|
||||
protected:
|
||||
// we store a std::filesystem::path because it can be converted to most thing,
|
||||
// even though we lose basic functionality on QDir (name filter, etc.)
|
||||
std::filesystem::path path_;
|
||||
|
||||
public:
|
||||
BasePathWrapper() = default;
|
||||
BasePathWrapper(BasePathWrapper const&) = default;
|
||||
BasePathWrapper& operator=(BasePathWrapper const&) = default;
|
||||
BasePathWrapper(BasePathWrapper&&) = default;
|
||||
BasePathWrapper& operator=(BasePathWrapper&&) = default;
|
||||
|
||||
BasePathWrapper(std::filesystem::path const& path) : path_{path} {}
|
||||
BasePathWrapper(QString const& path) : path_{path.toStdWString()} {}
|
||||
|
||||
operator QString() const { return QString::fromStdWString(path_.native()); }
|
||||
operator std::filesystem::path() const { return path_; }
|
||||
};
|
||||
|
||||
class FileWrapper : public BasePathWrapper {
|
||||
public:
|
||||
using BasePathWrapper::BasePathWrapper;
|
||||
|
||||
FileWrapper(QFileInfo const& fileInfo)
|
||||
: BasePathWrapper(fileInfo.filesystemFilePath())
|
||||
{
|
||||
}
|
||||
|
||||
operator QFileInfo() const { return QFileInfo(path_); }
|
||||
};
|
||||
|
||||
class DirectoryWrapper : public BasePathWrapper {
|
||||
public:
|
||||
using BasePathWrapper::BasePathWrapper;
|
||||
|
||||
DirectoryWrapper(QDir const& dir) : BasePathWrapper(dir.filesystemPath()) {}
|
||||
|
||||
operator QDir() const { return QDir(path_); }
|
||||
};
|
||||
|
||||
template <std::size_t... Is, class Fn>
|
||||
auto wrap_for_filepath(Fn&& fn)
|
||||
{
|
||||
return mo2::python::wrap_arguments<FileWrapper, Is...>(std::forward<Fn>(fn));
|
||||
}
|
||||
|
||||
// wrap the given function to accept DirectoryWrapper (str | PathLike | QDir)
|
||||
// at the given argument positions (or any valid positions if Is... is empty)
|
||||
//
|
||||
template <std::size_t... Is, class Fn>
|
||||
auto wrap_for_directory(Fn&& fn)
|
||||
{
|
||||
@@ -78,12 +85,57 @@ namespace mo2::python {
|
||||
std::forward<Fn>(fn));
|
||||
}
|
||||
|
||||
} // namespace mo2::python
|
||||
// wrap a function-like object to return a FileWrapper instead of its return type,
|
||||
// useful to generate proper typing in Python
|
||||
//
|
||||
// note that QFileInfo has a __fspath__ in Python, so it is quite easy to convert
|
||||
// from "FileWrapper", a.k.a., str | os.PathLike | QFileInfo to Path by simply
|
||||
// calling Path() on the return type if necessary
|
||||
//
|
||||
// this should be combined with custom return-value in PYBIND11_OVERRIDE(_PURE), see
|
||||
// ISaveGame binding for an example
|
||||
//
|
||||
template <class Fn>
|
||||
auto wrap_return_for_filepath(Fn&& fn)
|
||||
{
|
||||
return mo2::python::wrap_return<FileWrapper>(std::forward<Fn>(fn));
|
||||
}
|
||||
|
||||
MO2_PYBIND11_WRAP_ARGUMENT_CASTER(mo2::python::FileWrapper, "FileWrapper", QFileInfo,
|
||||
std::filesystem::path, QString);
|
||||
MO2_PYBIND11_WRAP_ARGUMENT_CASTER(mo2::python::DirectoryWrapper, "DirectoryWrapper",
|
||||
QDir, std::filesystem::path, QString);
|
||||
// similar to wrap_return_for_filepath, except it returns a DirectoryWrapper instead
|
||||
// of its return type
|
||||
//
|
||||
// this is much less practical than wrap_return_for_filepath since QDir does not
|
||||
// expose __fspath__, so more complex things need to be done in Python, which is why
|
||||
// this should be used carefully (e.g., should not be used if the return type is
|
||||
// already QDir)
|
||||
//
|
||||
template <class Fn>
|
||||
auto wrap_return_for_directory(Fn&& fn)
|
||||
{
|
||||
return mo2::python::wrap_return<DirectoryWrapper>(std::forward<Fn>(fn));
|
||||
}
|
||||
|
||||
// convert a QList to QStringList - QString must be constructible from QString
|
||||
//
|
||||
template <class T>
|
||||
QStringList toQStringList(QList<T> const& list)
|
||||
{
|
||||
static_assert(std::is_constructible_v<QString, T>,
|
||||
"QString must be constructible from T.");
|
||||
return {list.begin(), list.end()};
|
||||
}
|
||||
|
||||
// convert a QStringList to a QList - T must be constructible from QString
|
||||
//
|
||||
template <class T>
|
||||
QList<T> toQList(QStringList const& list)
|
||||
{
|
||||
static_assert(std::is_constructible_v<T, QString>,
|
||||
"T must be constructible from QString.");
|
||||
return {list.begin(), list.end()};
|
||||
}
|
||||
|
||||
} // namespace mo2::python
|
||||
|
||||
MO2_PYBIND11_SHARED_CPP_HOLDER(MOBase::IPluginRequirement)
|
||||
MO2_PYBIND11_SHARED_CPP_HOLDER(MOBase::ISaveGame)
|
||||
|
||||
@@ -155,12 +155,12 @@ namespace mo2::python {
|
||||
public:
|
||||
QString BinaryName() const override
|
||||
{
|
||||
PYBIND11_OVERRIDE_PURE(FileWrapper, ScriptExtender, BinaryName, );
|
||||
PYBIND11_OVERRIDE_PURE(QString, ScriptExtender, binaryName, );
|
||||
}
|
||||
|
||||
QString PluginPath() const override
|
||||
{
|
||||
PYBIND11_OVERRIDE_PURE(DirectoryWrapper, ScriptExtender, PluginPath, );
|
||||
PYBIND11_OVERRIDE_PURE(DirectoryWrapper, ScriptExtender, pluginPath, );
|
||||
}
|
||||
|
||||
QString loaderName() const override
|
||||
@@ -210,11 +210,10 @@ namespace mo2::python {
|
||||
}
|
||||
QStringList secondaryFiles(const QString& modName) const override
|
||||
{
|
||||
auto result = [&] {
|
||||
return toQStringList([&] {
|
||||
PYBIND11_OVERRIDE_PURE(QList<FileWrapper>, UnmanagedMods,
|
||||
secondaryFiles, modName);
|
||||
}();
|
||||
return QList<QString>(result.begin(), result.end());
|
||||
}());
|
||||
}
|
||||
};
|
||||
|
||||
@@ -297,10 +296,10 @@ namespace mo2::python {
|
||||
|
||||
py::class_<ScriptExtender, PyScriptExtender>(m, "ScriptExtender")
|
||||
.def(py::init<>())
|
||||
.def("BinaryName", &ScriptExtender::BinaryName)
|
||||
.def("PluginPath", &ScriptExtender::PluginPath)
|
||||
.def("binaryName", &ScriptExtender::BinaryName)
|
||||
.def("pluginPath", wrap_return_for_directory(&ScriptExtender::PluginPath))
|
||||
.def("loaderName", &ScriptExtender::loaderName)
|
||||
.def("loaderPath", &ScriptExtender::loaderPath)
|
||||
.def("loaderPath", wrap_return_for_filepath(&ScriptExtender::loaderPath))
|
||||
.def("savegameExtension", &ScriptExtender::savegameExtension)
|
||||
.def("isInstalled", &ScriptExtender::isInstalled)
|
||||
.def("getExtenderVersion", &ScriptExtender::getExtenderVersion)
|
||||
@@ -312,8 +311,15 @@ namespace mo2::python {
|
||||
.def(py::init<>())
|
||||
.def("mods", &UnmanagedMods::mods, "official_only"_a)
|
||||
.def("displayName", &UnmanagedMods::displayName, "mod_name"_a)
|
||||
.def("referenceFile", &UnmanagedMods::referenceFile, "mod_name"_a)
|
||||
.def("secondaryFiles", &UnmanagedMods::secondaryFiles, "mod_name"_a);
|
||||
.def("referenceFile",
|
||||
wrap_return_for_filepath(&UnmanagedMods::referenceFile), "mod_name"_a)
|
||||
.def(
|
||||
"secondaryFiles",
|
||||
[](UnmanagedMods* m, const QString& modName) -> QList<FileWrapper> {
|
||||
auto result = m->secondaryFiles(modName);
|
||||
return {result.begin(), result.end()};
|
||||
},
|
||||
"mod_name"_a);
|
||||
}
|
||||
|
||||
} // namespace mo2::python
|
||||
|
||||
@@ -375,11 +375,11 @@ namespace mo2::python {
|
||||
}
|
||||
QDir gameDirectory() const override
|
||||
{
|
||||
PYBIND11_OVERRIDE_PURE(DirectoryWrapper, IPluginGame, gameDirectory, );
|
||||
PYBIND11_OVERRIDE_PURE(QDir, IPluginGame, gameDirectory, );
|
||||
}
|
||||
QDir dataDirectory() const override
|
||||
{
|
||||
PYBIND11_OVERRIDE_PURE(DirectoryWrapper, IPluginGame, dataDirectory, );
|
||||
PYBIND11_OVERRIDE_PURE(QDir, IPluginGame, dataDirectory, );
|
||||
}
|
||||
void setGamePath(const QString& path) override
|
||||
{
|
||||
@@ -387,11 +387,11 @@ namespace mo2::python {
|
||||
}
|
||||
QDir documentsDirectory() const override
|
||||
{
|
||||
PYBIND11_OVERRIDE_PURE(DirectoryWrapper, IPluginGame, documentsDirectory, );
|
||||
PYBIND11_OVERRIDE_PURE(QDir, IPluginGame, documentsDirectory, );
|
||||
}
|
||||
QDir savesDirectory() const override
|
||||
{
|
||||
PYBIND11_OVERRIDE_PURE(DirectoryWrapper, IPluginGame, savesDirectory, );
|
||||
PYBIND11_OVERRIDE_PURE(QDir, IPluginGame, savesDirectory, );
|
||||
}
|
||||
QList<ExecutableInfo> executables() const override
|
||||
{
|
||||
|
||||
@@ -54,7 +54,9 @@ namespace mo2::python {
|
||||
|
||||
QStringList allFiles() const override
|
||||
{
|
||||
PYBIND11_OVERRIDE_PURE(QStringList, ISaveGame, allFiles, );
|
||||
return toQStringList([&] {
|
||||
PYBIND11_OVERRIDE_PURE(QList<FileWrapper>, ISaveGame, allFiles, );
|
||||
}());
|
||||
}
|
||||
|
||||
~PySaveGame() { std::cout << "~PySaveGame()" << std::endl; }
|
||||
@@ -80,11 +82,14 @@ namespace mo2::python {
|
||||
|
||||
py::class_<ISaveGame, PySaveGame, std::shared_ptr<ISaveGame>>(m, "ISaveGame")
|
||||
.def(py::init<>())
|
||||
.def("getFilepath", &ISaveGame::getFilepath)
|
||||
.def("getFilepath", wrap_return_for_filepath(&ISaveGame::getFilepath))
|
||||
.def("getCreationTime", &ISaveGame::getCreationTime)
|
||||
.def("getName", &ISaveGame::getName)
|
||||
.def("getSaveGroupIdentifier", &ISaveGame::getSaveGroupIdentifier)
|
||||
.def("allFiles", &ISaveGame::allFiles);
|
||||
.def("allFiles", [](ISaveGame* s) -> QList<FileWrapper> {
|
||||
const auto result = s->allFiles();
|
||||
return {result.begin(), result.end()};
|
||||
});
|
||||
|
||||
// ISaveGameInfoWidget - custom holder to keep the Python object alive alongside
|
||||
// the widget
|
||||
|
||||
@@ -2,12 +2,12 @@
|
||||
|
||||
This library contains some utility stuff for `pybind11`
|
||||
|
||||
## arg_wrapper.h
|
||||
## smart_variant_wrapper.h
|
||||
|
||||
Expose a function `mo2::python::wrap_arguments` and a macro
|
||||
`MO2_PYBIND11_WRAP_ARGUMENT_CASTER`.
|
||||
These can be used to convert C++ function when exposing them to Python to accept more
|
||||
type than the C++ one.
|
||||
Expose a function `mo2::python::wrap_arguments` and a template
|
||||
`mo2::python::smart_variant` that can be used to expose more interesting types Python
|
||||
than the C++ one, e.g., accept `os.PathLike` and `QFileInfo` when a simple `QString` is
|
||||
expected.
|
||||
|
||||
A toy example can be found in the test folder at
|
||||
[`tests/python/test_argument_wrapper.cpp`](../../tests/python/test_argument_wrapper.cpp).
|
||||
|
||||
@@ -1,155 +0,0 @@
|
||||
#ifndef PYTHON_PYBIND11_UTILS_FUNCTION_WRAPPER_H
|
||||
#define PYTHON_PYBIND11_UTILS_FUNCTION_WRAPPER_H
|
||||
|
||||
#include <functional>
|
||||
#include <type_traits>
|
||||
|
||||
#include <pybind11/pybind11.h>
|
||||
|
||||
namespace mo2::python {
|
||||
|
||||
namespace detail {
|
||||
|
||||
// simple helper class that expose a ::type attribute which is U is I is in Is,
|
||||
// V otherwise
|
||||
template <class U, class V, std::size_t I, class Is>
|
||||
struct wrap_arg;
|
||||
|
||||
template <class U, class V, std::size_t I, std::size_t... Is>
|
||||
struct wrap_arg<U, V, I, std::index_sequence<Is...>> {
|
||||
using type =
|
||||
std::conditional_t<std::disjunction_v<std::bool_constant<I == Is>...>,
|
||||
U, V>;
|
||||
};
|
||||
|
||||
// helper type for wrap_arg
|
||||
template <class U, class A, std::size_t I, class Is>
|
||||
using wrap_arg_t = typename wrap_arg<U, A, I, Is>::type;
|
||||
|
||||
template <class T, std::size_t... Is, std::size_t... AIs, class Fn, class R,
|
||||
class... Args>
|
||||
auto wrap_fn_impl(std::index_sequence<Is...>, Fn&& fn, R (*)(Args...),
|
||||
std::index_sequence<AIs...>)
|
||||
{
|
||||
return [fn = std::forward<Fn>(fn)](
|
||||
wrap_arg_t<T, Args, AIs, std::index_sequence<Is...>>... args) {
|
||||
return std::invoke(fn, std::forward<decltype(args)>(args)...);
|
||||
};
|
||||
}
|
||||
template <class T, class... Args, std::size_t... Is>
|
||||
auto make_convertible_index_sequence(std::index_sequence<Is...>)
|
||||
{
|
||||
return std::index_sequence<(std::is_convertible_v<T, Args> ? Is : -1)...>{};
|
||||
}
|
||||
|
||||
template <class T, std::size_t... Is, class Fn, class R, class... Args>
|
||||
auto wrap_fn_impl(Fn&& fn, R (*sg)(Args...))
|
||||
{
|
||||
if constexpr (sizeof...(Is) == 0) {
|
||||
return wrap_fn_impl<T>(make_convertible_index_sequence<T, Args...>(
|
||||
std::make_index_sequence<sizeof...(Args)>{}),
|
||||
std::forward<Fn>(fn), sg,
|
||||
std::make_index_sequence<sizeof...(Args)>{});
|
||||
}
|
||||
else {
|
||||
return wrap_fn_impl<T>(std::index_sequence<Is...>{},
|
||||
std::forward<Fn>(fn), sg,
|
||||
std::make_index_sequence<sizeof...(Args)>{});
|
||||
}
|
||||
}
|
||||
|
||||
// list of wrap_fn_impl for possible function types, the set of overload is from
|
||||
// pybind11::cpp_function
|
||||
|
||||
template <class T, std::size_t... Is, class R, class... Args>
|
||||
auto wrap_fn_impl(R (*fn)(Args...))
|
||||
{
|
||||
return wrap_fn_impl<T, Is...>(fn, fn);
|
||||
}
|
||||
|
||||
template <class T, std::size_t... Is, class R, class C, class... Args>
|
||||
auto wrap_fn_impl(R (C::*fn)(Args...))
|
||||
{
|
||||
return wrap_fn_impl<T, Is...>(fn, (R(*)(C*, Args...)) nullptr);
|
||||
}
|
||||
|
||||
template <class T, std::size_t... Is, class R, class C, class... Args>
|
||||
auto wrap_fn_impl(R (C::*fn)(Args...) &)
|
||||
{
|
||||
return wrap_fn_impl<T, Is...>(fn, (R(*)(C*, Args...)) nullptr);
|
||||
}
|
||||
|
||||
template <class T, std::size_t... Is, class R, class C, class... Args>
|
||||
auto wrap_fn_impl(R (C::*fn)(Args...) const)
|
||||
{
|
||||
return wrap_fn_impl<T, Is...>(fn, (R(*)(const C*, Args...)) nullptr);
|
||||
}
|
||||
|
||||
template <class T, std::size_t... Is, class R, class C, class... Args>
|
||||
auto wrap_fn_impl(R (C::*fn)(Args...) const&)
|
||||
{
|
||||
return wrap_fn_impl<T, Is...>(fn, (R(*)(const C*, Args...)) nullptr);
|
||||
}
|
||||
|
||||
template <class T, std::size_t... Is, class Fn>
|
||||
auto wrap_fn_impl(Fn&& fn)
|
||||
{
|
||||
return wrap_fn_impl<T, Is...>(
|
||||
std::forward<Fn>(fn),
|
||||
(pybind11::detail::function_signature_t<Fn>*)nullptr);
|
||||
}
|
||||
|
||||
template <class Type, class... WrappedTypes>
|
||||
struct load_wrapped_argument_helper;
|
||||
|
||||
template <class Type>
|
||||
struct load_wrapped_argument_helper<Type> {
|
||||
static bool load(Type&, pybind11::handle, bool) { return false; }
|
||||
};
|
||||
|
||||
template <class Type, class WrappedType, class... WrappedTypes>
|
||||
struct load_wrapped_argument_helper<Type, WrappedType, WrappedTypes...> {
|
||||
static bool load(Type& value, pybind11::handle src, bool convert)
|
||||
{
|
||||
pybind11::detail::make_caster<WrappedType> caster;
|
||||
|
||||
if (caster.load(src, convert)) {
|
||||
value = Type{static_cast<WrappedType>(caster)};
|
||||
return true;
|
||||
}
|
||||
|
||||
return load_wrapped_argument_helper<Type, WrappedTypes...>::load(
|
||||
value, src, convert);
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace detail
|
||||
|
||||
// wrap the given function-like object to accept T instead of the specified
|
||||
// arguments at the specified positions
|
||||
//
|
||||
// if the list of positions is empty, replace all arguments that can be converted to
|
||||
// T
|
||||
//
|
||||
template <class T, std::size_t... Is, class Fn>
|
||||
auto wrap_arguments(Fn&& fn)
|
||||
{
|
||||
return detail::wrap_fn_impl<T, Is...>(std::forward<Fn>(fn));
|
||||
}
|
||||
|
||||
} // namespace mo2::python
|
||||
|
||||
#define MO2_PYBIND11_WRAP_ARGUMENT_CASTER(Type, Name, ...) \
|
||||
namespace pybind11::detail { \
|
||||
template <> \
|
||||
struct type_caster<Type> { \
|
||||
PYBIND11_TYPE_CASTER(Type, const_name(Name)); \
|
||||
bool load(handle src, bool convert) \
|
||||
{ \
|
||||
return mo2::python::detail::load_wrapped_argument_helper< \
|
||||
Type, __VA_ARGS__>::load(value, src, convert); \
|
||||
} \
|
||||
}; \
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,53 @@
|
||||
#ifndef PYTHON_PYBIND11_UTILS_SMART_VARIANT_H
|
||||
#define PYTHON_PYBIND11_UTILS_SMART_VARIANT_H
|
||||
|
||||
#include <variant>
|
||||
|
||||
namespace mo2::python {
|
||||
|
||||
namespace detail {
|
||||
|
||||
// simple template class that should be specialized to expose proper fromXXX
|
||||
// methods
|
||||
//
|
||||
template <class T>
|
||||
struct smart_variant_converter {
|
||||
template <class U>
|
||||
static T from(U&& u)
|
||||
{
|
||||
return T{std::forward<U>(u)};
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace detail
|
||||
|
||||
// a smart_variant is a std::variant that can be automatically converted to any of
|
||||
// its type via custom operator T()
|
||||
//
|
||||
// user should specialize detail::smart_variant_converter to provide proper
|
||||
// conversions
|
||||
//
|
||||
template <class... Args>
|
||||
struct smart_variant : std::variant<Args...> {
|
||||
using std::variant<Args...>::variant;
|
||||
|
||||
template <class T, std::enable_if_t<
|
||||
std::disjunction_v<std::is_same<T, Args>...>, int> = 0>
|
||||
operator T() const
|
||||
{
|
||||
return std::visit(
|
||||
[](auto const& t) -> T {
|
||||
if constexpr (std::is_same_v<std::decay_t<decltype(t)>, T>) {
|
||||
return t;
|
||||
}
|
||||
else {
|
||||
return detail::smart_variant_converter<T>::from(t);
|
||||
}
|
||||
},
|
||||
*this);
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace mo2::python
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,181 @@
|
||||
#ifndef PYTHON_PYBIND11_UTILS_SMART_VARIANT_WRAPPER_H
|
||||
#define PYTHON_PYBIND11_UTILS_SMART_VARIANT_WRAPPER_H
|
||||
|
||||
#include <functional>
|
||||
#include <type_traits>
|
||||
|
||||
#include <pybind11/pybind11.h>
|
||||
#include <pybind11/stl.h>
|
||||
|
||||
#include "smart_variant.h"
|
||||
|
||||
namespace mo2::python {
|
||||
|
||||
namespace detail {
|
||||
|
||||
// simple helper class that expose a ::type attribute which is U is I is in Is,
|
||||
// V otherwise
|
||||
template <class U, class V, std::size_t I, class Is>
|
||||
struct wrap_arg;
|
||||
|
||||
template <class U, class V, std::size_t I, std::size_t... Is>
|
||||
struct wrap_arg<U, V, I, std::index_sequence<Is...>> {
|
||||
using type =
|
||||
std::conditional_t<std::disjunction_v<std::bool_constant<I == Is>...>,
|
||||
U, V>;
|
||||
};
|
||||
|
||||
// helper type for wrap_arg
|
||||
template <class U, class A, std::size_t I, class Is>
|
||||
using wrap_arg_t = typename wrap_arg<U, A, I, Is>::type;
|
||||
|
||||
template <class T, std::size_t... Is, std::size_t... AIs, class Fn, class R,
|
||||
class... Args>
|
||||
auto wrap_arguments_impl(std::index_sequence<Is...>, Fn&& fn, R (*)(Args...),
|
||||
std::index_sequence<AIs...>)
|
||||
{
|
||||
return [fn = std::forward<Fn>(fn)](
|
||||
wrap_arg_t<T, Args, AIs, std::index_sequence<Is...>>... args) {
|
||||
return std::invoke(fn, std::forward<decltype(args)>(args)...);
|
||||
};
|
||||
}
|
||||
|
||||
template <class T, class... Args, std::size_t... Is>
|
||||
auto make_convertible_index_sequence(std::index_sequence<Is...>)
|
||||
{
|
||||
return std::index_sequence<(std::is_convertible_v<T, Args> ? Is : -1)...>{};
|
||||
}
|
||||
|
||||
template <class T, std::size_t... Is, class Fn, class R, class... Args>
|
||||
auto wrap_arguments_impl(Fn&& fn, R (*sg)(Args...))
|
||||
{
|
||||
if constexpr (sizeof...(Is) == 0) {
|
||||
return wrap_arguments_impl<T>(
|
||||
make_convertible_index_sequence<T, Args...>(
|
||||
std::make_index_sequence<sizeof...(Args)>{}),
|
||||
std::forward<Fn>(fn), sg,
|
||||
std::make_index_sequence<sizeof...(Args)>{});
|
||||
}
|
||||
else {
|
||||
return wrap_arguments_impl<T>(
|
||||
std::index_sequence<Is...>{}, std::forward<Fn>(fn), sg,
|
||||
std::make_index_sequence<sizeof...(Args)>{});
|
||||
}
|
||||
}
|
||||
|
||||
template <class T, class Fn, class R, class... Args>
|
||||
auto wrap_return_impl(Fn&& fn, R (*)(Args...))
|
||||
{
|
||||
return [fn = std::forward<Fn>(fn)](Args... args) {
|
||||
return T{std::invoke(fn, std::forward<decltype(args)>(args)...)};
|
||||
};
|
||||
}
|
||||
|
||||
// make_python_function_signature: return a null-pointer with the proper type
|
||||
// for the given function
|
||||
|
||||
template <class Fn>
|
||||
struct function_signature {
|
||||
using type =
|
||||
pybind11::detail::function_signature_t<std::remove_reference_t<Fn>>;
|
||||
};
|
||||
|
||||
template <class R, class... Args>
|
||||
struct function_signature<R (*)(Args...)> {
|
||||
using type = R(Args...);
|
||||
};
|
||||
|
||||
template <class R, class C, class... Args>
|
||||
struct function_signature<R (C::*)(Args...)> {
|
||||
using type = R(C*, Args...);
|
||||
};
|
||||
|
||||
template <class R, class C, class... Args>
|
||||
struct function_signature<R (C::*)(Args...)&> {
|
||||
using type = R(C*, Args...);
|
||||
};
|
||||
|
||||
template <class R, class C, class... Args>
|
||||
struct function_signature<R (C::*)(Args...) const> {
|
||||
using type = R(const C*, Args...);
|
||||
};
|
||||
|
||||
template <class R, class C, class... Args>
|
||||
struct function_signature<R (C::*)(Args...) const&> {
|
||||
using type = R(const C*, Args...);
|
||||
};
|
||||
|
||||
template <class Fn>
|
||||
using function_signature_t = typename function_signature<Fn>::type;
|
||||
|
||||
template <class Type, class... WrappedTypes>
|
||||
class wrap_type_caster {
|
||||
using variant_type = std::variant<WrappedTypes...>;
|
||||
using variant_caster = pybind11::detail::make_caster<variant_type>;
|
||||
|
||||
public:
|
||||
PYBIND11_TYPE_CASTER(Type, variant_caster::name);
|
||||
|
||||
bool load(pybind11::handle src, bool convert)
|
||||
{
|
||||
variant_caster caster;
|
||||
|
||||
if (!caster.load(src, convert)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
value = std::visit(
|
||||
[](auto const& fn) {
|
||||
return Type(fn);
|
||||
},
|
||||
static_cast<variant_type>(caster));
|
||||
return true;
|
||||
}
|
||||
|
||||
static pybind11::handle cast(const Type& src,
|
||||
pybind11::return_value_policy policy,
|
||||
pybind11::handle parent)
|
||||
{
|
||||
return variant_caster::cast(variant_type(std::in_place_index<0>, src),
|
||||
policy, parent);
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace detail
|
||||
|
||||
// wrap the given function-like object to accept T instead of the specified
|
||||
// arguments at the specified positions
|
||||
//
|
||||
// if the list of positions is empty, replace all arguments that can be converted to
|
||||
// T
|
||||
//
|
||||
template <class T, std::size_t... Is, class Fn>
|
||||
auto wrap_arguments(Fn&& fn)
|
||||
{
|
||||
return detail::wrap_arguments_impl<T, Is...>(
|
||||
std::forward<Fn>(fn),
|
||||
(mo2::python::detail::function_signature_t<Fn>*)nullptr);
|
||||
}
|
||||
|
||||
// wrap the given function-like object to return T instead of the specified type
|
||||
//
|
||||
template <class T, class Fn>
|
||||
auto wrap_return(Fn&& fn)
|
||||
{
|
||||
return detail::wrap_return_impl<T>(
|
||||
std::forward<Fn>(fn),
|
||||
(mo2::python::detail::function_signature_t<Fn>*)nullptr);
|
||||
}
|
||||
|
||||
} // namespace mo2::python
|
||||
|
||||
namespace pybind11::detail {
|
||||
|
||||
template <class... Args>
|
||||
struct type_caster<::mo2::python::smart_variant<Args...>>
|
||||
: variant_caster<::mo2::python::smart_variant<Args...>> {
|
||||
};
|
||||
|
||||
} // namespace pybind11::detail
|
||||
|
||||
#endif
|
||||
@@ -11,7 +11,7 @@ else()
|
||||
set(UIBASE_PATH "${MO2_INSTALL_PATH}/bin")
|
||||
endif()
|
||||
|
||||
add_custom_target(pytest-deps)
|
||||
add_custom_target(python-tests)
|
||||
|
||||
add_test(NAME pytest
|
||||
COMMAND ${CMAKE_CURRENT_BINARY_DIR}/pylibs/bin/pytest.exe ${CMAKE_CURRENT_SOURCE_DIR} -s
|
||||
@@ -20,7 +20,7 @@ add_test(NAME pytest
|
||||
set(extra_paths "${UIBASE_PATH}\\;${MO2_INSTALL_PATH}/bin/dlls")
|
||||
set_tests_properties(pytest
|
||||
PROPERTIES
|
||||
DEPENDS pytest-deps
|
||||
DEPENDS python-tests
|
||||
WORKING_DIRECTORY ${MO2_INSTALL_PATH}/bin
|
||||
ENVIRONMENT_MODIFICATION
|
||||
"PYTHONPATH=set:${PYLIB_DIR}\\;$<TARGET_FILE_DIR:mobase>;\
|
||||
@@ -28,10 +28,10 @@ UIBASE_PATH=set:${UIBASE_PATH};\
|
||||
QT_ROOT=set:${QT_ROOT}"
|
||||
)
|
||||
|
||||
mo2_python_pip_install(pytest-deps
|
||||
mo2_python_pip_install(python-tests
|
||||
DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/pylibs
|
||||
PACKAGES pytest PyQt6==6.3.0)
|
||||
add_dependencies(pytest-deps mobase)
|
||||
add_dependencies(python-tests mobase)
|
||||
|
||||
file(GLOB test_files CONFIGURE_DEPENDS "test_*.cpp")
|
||||
foreach (test_file ${test_files})
|
||||
@@ -57,5 +57,5 @@ foreach (test_file ${test_files})
|
||||
|
||||
target_include_directories(${target}
|
||||
PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/../mocks)
|
||||
add_dependencies(pytest-deps ${target})
|
||||
add_dependencies(python-tests ${target})
|
||||
endforeach()
|
||||
|
||||
@@ -1,34 +1,26 @@
|
||||
#include "pybind11_utils/arg_wrapper.h"
|
||||
#include "pybind11_utils/smart_variant_wrapper.h"
|
||||
|
||||
#include <pybind11/pybind11.h>
|
||||
#include <pybind11/stl.h>
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace mo2::python::detail {
|
||||
|
||||
template <>
|
||||
struct smart_variant_converter<std::string> {
|
||||
static std::string from(int const& value) { return std::to_string(value); }
|
||||
};
|
||||
|
||||
template <>
|
||||
struct smart_variant_converter<int> {
|
||||
static int from(std::string const& value) { return std::stoi(value); }
|
||||
};
|
||||
|
||||
} // namespace mo2::python::detail
|
||||
|
||||
// wrapper that can be constructed from
|
||||
class Wrapper {
|
||||
std::string value;
|
||||
|
||||
public:
|
||||
Wrapper() = default;
|
||||
|
||||
Wrapper(Wrapper const&) = default;
|
||||
Wrapper(Wrapper&&) = default;
|
||||
Wrapper& operator=(Wrapper const&) = default;
|
||||
Wrapper& operator=(Wrapper&&) = default;
|
||||
|
||||
template <class U, std::enable_if_t<std::is_convertible_v<U, std::string>, int> = 0>
|
||||
Wrapper(U&& u) : value{std::forward<U>(u)}
|
||||
{
|
||||
}
|
||||
|
||||
Wrapper(int u) : value{std::to_string(u)} {}
|
||||
|
||||
operator int() const { return std::stoi(value); }
|
||||
operator std::string() const { return value; }
|
||||
};
|
||||
|
||||
MO2_PYBIND11_WRAP_ARGUMENT_CASTER(Wrapper, "Wrapper", int, std::string);
|
||||
using Wrapper = mo2::python::smart_variant<int, std::string>;
|
||||
|
||||
template <std::size_t... Is, class Fn>
|
||||
auto wrap(Fn&& fn)
|
||||
|
||||
Reference in New Issue
Block a user