diff --git a/dtool/src/dtoolutil/filename.I b/dtool/src/dtoolutil/filename.I index 9ab46bf501..9bc23dfca3 100644 --- a/dtool/src/dtoolutil/filename.I +++ b/dtool/src/dtoolutil/filename.I @@ -38,7 +38,6 @@ Filename(const char *filename) { (*this) = filename; } - /** * */ @@ -84,6 +83,20 @@ Filename(Filename &&from) NOEXCEPT : } #endif // USE_MOVE_SEMANTICS +/** + * Creates an empty Filename. + */ +INLINE Filename:: +Filename() : + _dirname_end(0), + _basename_start(0), + _basename_end(string::npos), + _extension_start(string::npos), + _hash_start(string::npos), + _hash_end(string::npos), + _flags(0) { +} + /** * */ @@ -155,14 +168,6 @@ pattern_filename(const string &filename) { return result; } -/** - * - */ -INLINE Filename:: -~Filename() { -} - - /** * */ @@ -233,7 +238,7 @@ operator = (string &&filename) NOEXCEPT { */ INLINE Filename &Filename:: operator = (Filename &&from) NOEXCEPT { - _filename = MOVE(from._filename); + _filename = move(from._filename); _dirname_end = from._dirname_end; _basename_start = from._basename_start; _basename_end = from._basename_end; diff --git a/dtool/src/dtoolutil/filename.h b/dtool/src/dtoolutil/filename.h index 378bf4fe1c..e0c4b8c414 100644 --- a/dtool/src/dtoolutil/filename.h +++ b/dtool/src/dtoolutil/filename.h @@ -55,20 +55,22 @@ public: }; INLINE Filename(const char *filename); - -PUBLISHED: - INLINE Filename(const string &filename = ""); + INLINE Filename(const string &filename); INLINE Filename(const wstring &filename); INLINE Filename(const Filename ©); - Filename(const Filename &dirname, const Filename &basename); - INLINE ~Filename(); #ifdef USE_MOVE_SEMANTICS INLINE Filename(string &&filename) NOEXCEPT; INLINE Filename(Filename &&from) NOEXCEPT; #endif +PUBLISHED: + INLINE Filename(); + Filename(const Filename &dirname, const Filename &basename); + #ifdef HAVE_PYTHON + EXTENSION(Filename(PyObject *path)); + EXTENSION(PyObject *__reduce__(PyObject *self) const); #endif @@ -118,6 +120,7 @@ PUBLISHED: INLINE char operator [] (size_t n) const; EXTENSION(PyObject *__repr__() const); + EXTENSION(PyObject *__fspath__() const); INLINE string substr(size_t begin) const; INLINE string substr(size_t begin, size_t end) const; diff --git a/panda/src/express/filename_ext.cxx b/panda/src/express/filename_ext.cxx index 70afd0d07d..1c9bf78e7e 100644 --- a/panda/src/express/filename_ext.cxx +++ b/panda/src/express/filename_ext.cxx @@ -14,6 +14,115 @@ #include "filename_ext.h" #ifdef HAVE_PYTHON + +#ifndef CPPPARSER +extern Dtool_PyTypedObject Dtool_Filename; +#endif // CPPPARSER + +/** + * Constructs a Filename object from a str, bytes object, or os.PathLike. + */ +void Extension:: +__init__(PyObject *path) { + nassertv(path != NULL); + nassertv(_this != NULL); + + Py_ssize_t length; + + if (PyUnicode_CheckExact(path)) { + wchar_t *data; +#if PY_VERSION_HEX >= 0x03020000 + data = PyUnicode_AsWideCharString(path, &length); +#else + length = PyUnicode_GET_SIZE(path); + data = (wchar_t *)alloca(sizeof(wchar_t) * (length + 1)); + PyUnicode_AsWideChar((PyUnicodeObject *)path, data, length); +#endif + (*_this) = wstring(data, length); + +#if PY_VERSION_HEX >= 0x03020000 + PyMem_Free(data); +#endif + return; + } + + if (PyBytes_CheckExact(path)) { + char *data; + PyBytes_AsStringAndSize(path, &data, &length); + (*_this) = string(data, length); + return; + } + + if (Py_TYPE(path) == &Dtool_Filename._PyType) { + // Copy constructor. + (*_this) = *((Filename *)((Dtool_PyInstDef *)path)->_ptr_to_object); + return; + } + + PyObject *path_str; + +#if PY_VERSION_HEX >= 0x03060000 + // It must be an os.PathLike object. Check for an __fspath__ method. + PyObject *fspath = PyObject_GetAttrString((PyObject *)Py_TYPE(path), "__fspath__"); + if (fspath == NULL) { + PyErr_Format(PyExc_TypeError, "expected str, bytes or os.PathLike object, not %s", Py_TYPE(path)->tp_name); + return; + } + + path_str = PyObject_CallFunctionObjArgs(fspath, path, NULL); + Py_DECREF(fspath); +#else + // There is no standard path protocol before Python 3.6, but let's try and + // support taking pathlib paths anyway. We don't version check this to + // allow people to use backports of the pathlib module. + if (PyObject_HasAttrString(path, "_format_parsed_parts")) { + path_str = PyObject_Str(path); + } else { +#if PY_VERSION_HEX >= 0x03040000 + PyErr_Format(PyExc_TypeError, "expected str, bytes, Path or Filename object, not %s", Py_TYPE(path)->tp_name); +#elif PY_MAJOR_VERSION >= 3 + PyErr_Format(PyExc_TypeError, "expected str, bytes or Filename object, not %s", Py_TYPE(path)->tp_name); +#else + PyErr_Format(PyExc_TypeError, "expected str or unicode object, not %s", Py_TYPE(path)->tp_name); +#endif + return; + } +#endif + + if (path_str == NULL) { + return; + } + + if (PyUnicode_CheckExact(path_str)) { + wchar_t *data; +#if PY_VERSION_HEX >= 0x03020000 + data = PyUnicode_AsWideCharString(path_str, &length); +#else + length = PyUnicode_GET_SIZE(path_str); + data = (wchar_t *)alloca(sizeof(wchar_t) * (length + 1)); + PyUnicode_AsWideChar((PyUnicodeObject *)path_str, data, length); +#endif + (*_this) = Filename::from_os_specific_w(wstring(data, length)); + +#if PY_VERSION_HEX >= 0x03020000 + PyMem_Free(data); +#endif + + } else if (PyBytes_CheckExact(path_str)) { + char *data; + PyBytes_AsStringAndSize(path_str, &data, &length); + (*_this) = Filename::from_os_specific(string(data, length)); + + } else { +#if PY_MAJOR_VERSION >= 3 + PyErr_Format(PyExc_TypeError, "expected str or bytes object, not %s", Py_TYPE(path_str)->tp_name); +#else + PyErr_Format(PyExc_TypeError, "expected str or unicode object, not %s", Py_TYPE(path_str)->tp_name); +#endif + } + Py_DECREF(path_str); +} + /** * This special Python method is implement to provide support for the pickle * module. @@ -62,6 +171,16 @@ __repr__() const { return result; } +/** + * Allows a Filename object to be passed to any Python function that accepts + * an os.PathLike object. + */ +PyObject *Extension:: +__fspath__() const { + wstring filename = _this->to_os_specific_w(); + return PyUnicode_FromWideChar(filename.data(), (Py_ssize_t)filename.size()); +} + /** * This variant on scan_directory returns a Python list of strings on success, * or None on failure. diff --git a/panda/src/express/filename_ext.h b/panda/src/express/filename_ext.h index c1d5869ec7..1ebeaacc52 100644 --- a/panda/src/express/filename_ext.h +++ b/panda/src/express/filename_ext.h @@ -29,8 +29,11 @@ template<> class Extension : public ExtensionBase { public: + void __init__(PyObject *path); + PyObject *__reduce__(PyObject *self) const; PyObject *__repr__() const; + PyObject *__fspath__() const; PyObject *scan_directory() const; };