1 // Copyright (c) 2021 The Pybind Development Team.
2 // All rights reserved. Use of this source code is governed by a
3 // BSD-style license that can be found in the LICENSE file.
4 
5 #pragma once
6 
7 #include "../cast.h"
8 #include "../pybind11.h"
9 #include "../pytypes.h"
10 
11 #include "../detail/common.h"
12 #include "../detail/descr.h"
13 
14 #include <string>
15 
16 #ifdef __has_include
17 #  if defined(PYBIND11_CPP17) && __has_include(<filesystem>) && \
18       PY_VERSION_HEX >= 0x03060000
19 #    include <filesystem>
20 #    define PYBIND11_HAS_FILESYSTEM 1
21 #  endif
22 #endif
23 
24 #if !defined(PYBIND11_HAS_FILESYSTEM) && !defined(PYBIND11_HAS_FILESYSTEM_IS_OPTIONAL)
25 #    error                                                                                        \
26         "#include <filesystem> is not available. (Use -DPYBIND11_HAS_FILESYSTEM_IS_OPTIONAL to ignore.)"
27 #endif
28 
29 PYBIND11_NAMESPACE_BEGIN(PYBIND11_NAMESPACE)
30 PYBIND11_NAMESPACE_BEGIN(detail)
31 
32 #if defined(PYBIND11_HAS_FILESYSTEM)
33 template<typename T> struct path_caster {
34 
35 private:
unicode_from_fs_nativepath_caster36     static PyObject* unicode_from_fs_native(const std::string& w) {
37 #if !defined(PYPY_VERSION)
38         return PyUnicode_DecodeFSDefaultAndSize(w.c_str(), ssize_t(w.size()));
39 #else
40         // PyPy mistakenly declares the first parameter as non-const.
41         return PyUnicode_DecodeFSDefaultAndSize(
42             const_cast<char*>(w.c_str()), ssize_t(w.size()));
43 #endif
44     }
45 
unicode_from_fs_nativepath_caster46     static PyObject* unicode_from_fs_native(const std::wstring& w) {
47         return PyUnicode_FromWideChar(w.c_str(), ssize_t(w.size()));
48     }
49 
50 public:
castpath_caster51     static handle cast(const T& path, return_value_policy, handle) {
52         if (auto py_str = unicode_from_fs_native(path.native())) {
53             return module_::import("pathlib").attr("Path")(reinterpret_steal<object>(py_str))
54                    .release();
55         }
56         return nullptr;
57     }
58 
loadpath_caster59     bool load(handle handle, bool) {
60         // PyUnicode_FSConverter and PyUnicode_FSDecoder normally take care of
61         // calling PyOS_FSPath themselves, but that's broken on PyPy (PyPy
62         // issue #3168) so we do it ourselves instead.
63         PyObject* buf = PyOS_FSPath(handle.ptr());
64         if (!buf) {
65             PyErr_Clear();
66             return false;
67         }
68         PyObject* native = nullptr;
69         if constexpr (std::is_same_v<typename T::value_type, char>) {
70             if (PyUnicode_FSConverter(buf, &native) != 0) {
71                 if (auto c_str = PyBytes_AsString(native)) {
72                     // AsString returns a pointer to the internal buffer, which
73                     // must not be free'd.
74                     value = c_str;
75                 }
76             }
77         } else if constexpr (std::is_same_v<typename T::value_type, wchar_t>) {
78             if (PyUnicode_FSDecoder(buf, &native) != 0) {
79                 if (auto c_str = PyUnicode_AsWideCharString(native, nullptr)) {
80                     // AsWideCharString returns a new string that must be free'd.
81                     value = c_str;  // Copies the string.
82                     PyMem_Free(c_str);
83                 }
84             }
85         }
86         Py_XDECREF(native);
87         Py_DECREF(buf);
88         if (PyErr_Occurred()) {
89             PyErr_Clear();
90             return false;
91         }
92         return true;
93     }
94 
95     PYBIND11_TYPE_CASTER(T, const_name("os.PathLike"));
96 };
97 
98 template<> struct type_caster<std::filesystem::path>
99     : public path_caster<std::filesystem::path> {};
100 #endif // PYBIND11_HAS_FILESYSTEM
101 
102 PYBIND11_NAMESPACE_END(detail)
103 PYBIND11_NAMESPACE_END(PYBIND11_NAMESPACE)
104