mirror of
https://github.com/ModOrganizer2/modorganizer-plugin_python.git
synced 2026-07-27 14:03:33 -07:00
Add bindings for IFileTree glob() and walk() generators (#142)
* Adding binding for new IFileTree glob and walk. * Fix issues with Version binding.
This commit is contained in:
@@ -13,6 +13,7 @@
|
||||
#include "pybind11_qt/pybind11_qt.h"
|
||||
|
||||
#include "pybind11_utils/functional.h"
|
||||
#include "pybind11_utils/generator.h"
|
||||
#include "pybind11_utils/shared_cpp_owner.h"
|
||||
#include "pybind11_utils/smart_variant_wrapper.h"
|
||||
|
||||
|
||||
@@ -54,7 +54,24 @@ namespace mo2::python {
|
||||
.value("NO_METADATA", Version::FormatMode::NoMetadata)
|
||||
.value("CONDENSED",
|
||||
static_cast<Version::FormatMode>(Version::FormatCondensed.toInt()))
|
||||
.export_values();
|
||||
.export_values()
|
||||
.def("__xor__",
|
||||
py::overload_cast<Version::FormatMode, Version::FormatModes>(
|
||||
&operator^))
|
||||
.def("__and__",
|
||||
py::overload_cast<Version::FormatMode, Version::FormatModes>(
|
||||
&operator&))
|
||||
.def("__or__", py::overload_cast<Version::FormatMode, Version::FormatModes>(
|
||||
&operator|))
|
||||
.def("__rxor__",
|
||||
py::overload_cast<Version::FormatMode, Version::FormatModes>(
|
||||
&operator^))
|
||||
.def("__rand__",
|
||||
py::overload_cast<Version::FormatMode, Version::FormatModes>(
|
||||
&operator&))
|
||||
.def("__ror__",
|
||||
py::overload_cast<Version::FormatMode, Version::FormatModes>(
|
||||
&operator|));
|
||||
|
||||
pyVersion
|
||||
.def_static("parse", &Version::parse, "value"_a,
|
||||
@@ -86,8 +103,11 @@ namespace mo2::python {
|
||||
.def_property_readonly("subpatch", &Version::subpatch)
|
||||
.def_property_readonly("prereleases", &Version::preReleases)
|
||||
.def_property_readonly("build_metadata", &Version::buildMetadata)
|
||||
.def("string", &Version::string, "mode"_a = Version::FormatCondensed)
|
||||
.def("__str__", &Version::string)
|
||||
.def("string", &Version::string, "mode"_a = Version::FormatModes{})
|
||||
.def("__str__",
|
||||
[](Version const& version) {
|
||||
return version.string(Version::FormatCondensed);
|
||||
})
|
||||
.def(py::self < py::self)
|
||||
.def(py::self > py::self)
|
||||
.def(py::self <= py::self)
|
||||
|
||||
@@ -49,7 +49,7 @@ namespace mo2::detail {
|
||||
return std::make_shared<PyFileTree>(parent, name, m_Callback);
|
||||
}
|
||||
|
||||
bool doPopulate(std::shared_ptr<const IFileTree> parent,
|
||||
bool doPopulate([[maybe_unused]] std::shared_ptr<const IFileTree> parent,
|
||||
std::vector<std::shared_ptr<FileTreeEntry>>&) const override
|
||||
{
|
||||
return true;
|
||||
@@ -83,7 +83,6 @@ namespace mo2::python {
|
||||
|
||||
void add_ifiletree_bindings(pybind11::module_& m)
|
||||
{
|
||||
|
||||
// FileTreeEntry class:
|
||||
auto fileTreeEntryClass =
|
||||
py::class_<FileTreeEntry, std::shared_ptr<FileTreeEntry>>(m,
|
||||
@@ -164,6 +163,11 @@ namespace mo2::python {
|
||||
.value("SKIP", IFileTree::WalkReturn::SKIP)
|
||||
.export_values();
|
||||
|
||||
py::enum_<IFileTree::GlobPatternType>(iFileTreeClass, "GlobPatternType")
|
||||
.value("GLOB", IFileTree::GlobPatternType::GLOB)
|
||||
.value("REGEX", IFileTree::GlobPatternType::REGEX)
|
||||
.export_values();
|
||||
|
||||
// Non-mutable operations:
|
||||
iFileTreeClass.def("exists",
|
||||
py::overload_cast<QString, IFileTree::FileTypes>(
|
||||
@@ -175,10 +179,25 @@ namespace mo2::python {
|
||||
iFileTreeClass.def("pathTo", &IFileTree::pathTo, py::arg("entry"),
|
||||
py::arg("sep") = "\\");
|
||||
|
||||
// Note: walk() would probably be better as a generator in python, but
|
||||
// it is likely impossible to construct from the C++ walk() method.
|
||||
iFileTreeClass.def("walk", &IFileTree::walk, py::arg("callback"),
|
||||
py::arg("sep") = "\\");
|
||||
iFileTreeClass.def(
|
||||
"walk",
|
||||
py::overload_cast<
|
||||
std::function<IFileTree::WalkReturn(
|
||||
QString const&, std::shared_ptr<const FileTreeEntry>)>,
|
||||
QString>(&IFileTree::walk, py::const_),
|
||||
py::arg("callback"), py::arg("sep") = "\\");
|
||||
|
||||
iFileTreeClass.def("walk", [](IFileTree const* tree) {
|
||||
return make_generator(tree->walk());
|
||||
});
|
||||
|
||||
iFileTreeClass.def(
|
||||
"glob", // &IFileTree::glob,
|
||||
[](IFileTree const* tree, QString pattern,
|
||||
IFileTree::GlobPatternType patternType) {
|
||||
return make_generator(tree->glob(pattern, patternType));
|
||||
},
|
||||
py::arg("pattern"), py::arg("type") = IFileTree::GlobPatternType::GLOB);
|
||||
|
||||
// Kind-of-static operations:
|
||||
iFileTreeClass.def("createOrphanTree", &IFileTree::createOrphanTree,
|
||||
|
||||
@@ -2,6 +2,7 @@ cmake_minimum_required(VERSION 3.16)
|
||||
|
||||
add_library(pybind11-utils STATIC
|
||||
./include/pybind11_utils/functional.h
|
||||
./include/pybind11_utils/generator.h
|
||||
./include/pybind11_utils/shared_cpp_owner.h
|
||||
./include/pybind11_utils/smart_variant_wrapper.h
|
||||
./include/pybind11_utils/smart_variant.h
|
||||
|
||||
@@ -0,0 +1,54 @@
|
||||
#ifndef PYTHON_PYBIND11_GENERATOR_H
|
||||
#define PYTHON_PYBIND11_GENERATOR_H
|
||||
|
||||
#include <generator>
|
||||
|
||||
#include <pybind11/pybind11.h>
|
||||
|
||||
namespace mo2::python {
|
||||
|
||||
// the code here is mostly taken from pybind11 itself, and relies on some pybind11
|
||||
// internals so might be subject to change when upgrading pybind11 versions
|
||||
|
||||
namespace detail {
|
||||
template <typename T>
|
||||
struct generator_state {
|
||||
std::generator<T> g;
|
||||
decltype(g.begin()) it;
|
||||
|
||||
generator_state(std::generator<T> gen) : g(std::move(gen)), it(g.begin()) {}
|
||||
};
|
||||
} // namespace detail
|
||||
|
||||
// create a Python generator from a C++ generator
|
||||
//
|
||||
template <typename T>
|
||||
auto make_generator(std::generator<T> g)
|
||||
{
|
||||
using state = detail::generator_state<T>;
|
||||
|
||||
namespace py = pybind11;
|
||||
if (!py::detail::get_type_info(typeid(state), false)) {
|
||||
py::class_<state>(py::handle(), "iterator", pybind11::module_local())
|
||||
.def("__iter__",
|
||||
[](state& s) -> state& {
|
||||
return s;
|
||||
})
|
||||
.def("__next__", [](state& s) {
|
||||
if (s.it != s.g.end()) {
|
||||
const auto v = *s.it;
|
||||
s.it++;
|
||||
return v;
|
||||
}
|
||||
else {
|
||||
throw py::stop_iteration();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return py::cast(state{std::move(g)});
|
||||
}
|
||||
|
||||
} // namespace mo2::python
|
||||
|
||||
#endif
|
||||
@@ -53,7 +53,7 @@ foreach (test_file ${test_files})
|
||||
pybind11_add_module(${target} EXCLUDE_FROM_ALL THIN_LTO ${test_file})
|
||||
set_target_properties(${target}
|
||||
PROPERTIES
|
||||
CXX_STANDARD 20
|
||||
CXX_STANDARD 23
|
||||
OUTPUT_NAME ${pymodule}
|
||||
FOLDER tests/python
|
||||
LIBRARY_OUTPUT_DIRECTORY "${PYLIB_DIR}/mobase_tests")
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
from typing import TypeAlias, cast
|
||||
|
||||
import mobase
|
||||
|
||||
import mobase_tests.filetree as m
|
||||
@@ -34,3 +36,51 @@ def test_filetype():
|
||||
|
||||
assert m.is_file(FT.FILE_OR_DIRECTORY & ~FT.DIRECTORY)
|
||||
assert not m.is_directory(FT.FILE_OR_DIRECTORY & ~FT.DIRECTORY)
|
||||
|
||||
|
||||
_tree_values: TypeAlias = list["str | tuple[str, _tree_values]"]
|
||||
|
||||
|
||||
def make_tree(
|
||||
values: _tree_values, root: mobase.IFileTree | None = None
|
||||
) -> mobase.IFileTree:
|
||||
if root is None:
|
||||
root = cast(mobase.IFileTree, mobase.private.makeTree()) # type: ignore
|
||||
|
||||
for value in values:
|
||||
if isinstance(value, str):
|
||||
root.addFile(value)
|
||||
else:
|
||||
sub_tree = root.addDirectory(value[0])
|
||||
make_tree(value[1], sub_tree)
|
||||
|
||||
return root
|
||||
|
||||
|
||||
def test_walk():
|
||||
tree = make_tree(
|
||||
[("a", []), ("b", ["u", "v"]), "c.x", "d.y", ("e", [("q", ["c.t", ("p", [])])])]
|
||||
)
|
||||
|
||||
assert {"a", "b", "b/u", "b/v", "c.x", "d.y", "e", "e/q", "e/q/c.t", "e/q/p"} == {
|
||||
e.path("/") for e in tree.walk()
|
||||
}
|
||||
|
||||
entries: list[str] = []
|
||||
for e in tree.walk():
|
||||
if e.name() == "e":
|
||||
break
|
||||
entries.append(e.path("/"))
|
||||
assert {"a", "b", "b/u", "b/v"} == set(entries)
|
||||
|
||||
|
||||
def test_glob():
|
||||
tree = make_tree(
|
||||
[("a", []), ("b", ["u", "v"]), "c.x", "d.y", ("e", [("q", ["c.t", ("p", [])])])]
|
||||
)
|
||||
|
||||
assert {"a", "b", "b/u", "b/v", "c.x", "d.y", "e", "e/q", "e/q/c.t", "e/q/p"} == {
|
||||
e.path("/") for e in tree.glob("**/*")
|
||||
}
|
||||
|
||||
assert {"d.y"} == {e.path("/") for e in tree.glob("**/*.y")}
|
||||
|
||||
Reference in New Issue
Block a user