Skip to content

Commit

Permalink
Support os.PathLike for paths and filenames.
Browse files Browse the repository at this point in the history
  • Loading branch information
Hoikas committed Oct 2, 2019
1 parent a47f027 commit 8537737
Show file tree
Hide file tree
Showing 15 changed files with 108 additions and 50 deletions.
6 changes: 3 additions & 3 deletions Python/Debug/pyDebug.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -44,9 +44,9 @@ PY_METHOD_STATIC_VA(Debug, InitFile,
"Initialize the debug logger to a file output")
{
int level = plDebug::kDLWarning;
const char* filename = "Plasma.log";
if (!PyArg_ParseTuple(args, "|is", &level, &filename)) {
PyErr_SetString(PyExc_TypeError, "InitFile expects int, string");
ST::string filename = ST_LITERAL("Plasma.log");
if (!PyArg_ParseTuple(args, "|iO&", &level, PyAnyString_PathDecoder, &filename)) {
PyErr_SetString(PyExc_TypeError, "InitFile expects int, string (or an os.PathLike object)");
return nullptr;
}
plDebug::InitFile(level, filename);
Expand Down
2 changes: 1 addition & 1 deletion Python/PRP/Audio/pySoundBuffer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
PY_PLASMA_NEW(SoundBuffer, plSoundBuffer)

PY_PROPERTY_PROXY(plWAVHeader, SoundBuffer, header, getHeader)
PY_PROPERTY(ST::string, SoundBuffer, fileName, getFileName, setFileName)
PY_PROPERTY_PATHLIKE(SoundBuffer, fileName, getFileName, setFileName)
PY_PROPERTY(unsigned int, SoundBuffer, flags, getFlags, setFlags)
PY_PROPERTY(size_t, SoundBuffer, dataLength, getDataLength, setDataLength)

Expand Down
4 changes: 2 additions & 2 deletions Python/PRP/Avatar/pyAGAnimBink.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,8 @@

PY_PLASMA_NEW(AGAnimBink, plAGAnimBink)

PY_PROPERTY(ST::string, AGAnimBink, binkFilename, getBinkFilename, setBinkFilename)
PY_PROPERTY(ST::string, AGAnimBink,sgtFilename, getSgtFilename, setSgtFilename)
PY_PROPERTY_PATHLIKE(AGAnimBink, binkFilename, getBinkFilename, setBinkFilename)
PY_PROPERTY_PATHLIKE(AGAnimBink, sgtFilename, getSgtFilename, setSgtFilename)
PY_PROPERTY(ST::string, AGAnimBink,subtitleId, getSubtitleId, setSubtitleId)

static PyGetSetDef pyAGAnimBink_GetSet[] = {
Expand Down
2 changes: 1 addition & 1 deletion Python/PRP/Modifier/pyPythonFileMod.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,7 @@ PY_GETSET_GETTER_DECL(PythonFileMod, parameters)
PY_PROPERTY_SETTER_MSG(PythonFileMod, parameters, "To add parameters, use addParameter")
PY_PROPERTY_GETSET_DECL(PythonFileMod, parameters)

PY_PROPERTY(ST::string, PythonFileMod, filename, getFilename, setFilename)
PY_PROPERTY_PATHLIKE(PythonFileMod, filename, getFilename, setFilename)

static PyGetSetDef pyPythonFileMod_GetSet[] = {
pyPythonFileMod_filename_getset,
Expand Down
2 changes: 1 addition & 1 deletion Python/PRP/Surface/pyLayerMovie.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@

PY_PLASMA_NEW(LayerMovie, plLayerMovie)

PY_PROPERTY(ST::string, LayerMovie, movieName, getMovieName, setMovieName)
PY_PROPERTY_PATHLIKE(LayerMovie, movieName, getMovieName, setMovieName)

static PyGetSetDef pyLayerMovie_GetSet[] = {
pyLayerMovie_movieName_getset,
Expand Down
39 changes: 39 additions & 0 deletions Python/PyPlasma.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,25 @@ ST::string PyAnyString_AsSTString(PyObject* str)
}
}

int PyAnyString_PathDecoder(PyObject* obj, ST::string* str)
{
#if (PY_MAJOR_VERSION > 3) || ((PY_MAJOR_VERSION == 3) && (PY_MINOR_VERSION >= 2))
PyObject* fsConvert;
if (PyUnicode_FSDecoder(obj, &fsConvert)) {
*str = PyAnyString_AsSTString(fsConvert);
Py_DECREF(fsConvert);
return 1;
}
return 0;
#else
if (PyAnyString_Check(obj)) {
*str = PyAnyString_AsSTString(obj);
return 1;
}
return 0;
#endif
}

int PyType_CheckAndReady(PyTypeObject* type)
{
static std::unordered_set<PyTypeObject*> init_bases;
Expand All @@ -60,3 +79,23 @@ int PyType_CheckAndReady(PyTypeObject* type)
ST::printf(stderr, "WARN: Failed to ready {}\n", type->tp_name);
return result;
}

#if (PY_MAJOR_VERSION < 3) || ((PY_MAJOR_VERSION == 3) && (PY_MINOR_VERSION < 2))
int PyUnicode_FSDecoder(PyObject* value, void* result)
{
if (!value || value == Py_None)
return 0;

// This will rely on a future call to PyAnyString_AsSTString to properly decode the path
if (PyAnyString_Check(value)) {
*((PyObject**)result) = value;
Py_INCREF(value);

// Py_CLEANUP_SUPPORTED was not added until Python 3.1, so we will rely on the caller
// to clean up the reference.
return 1;
}

return 0;
}
#endif
21 changes: 20 additions & 1 deletion Python/PyPlasma.h
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,8 @@
PyObject* PyString_FromSTString(const ST::string& str);
PyObject* PyUnicode_FromSTString(const ST::string& str);
ST::string PyAnyString_AsSTString(PyObject* str);
#define PyAnyString_Check(ob) (PyUnicode_Check(value) || PyBytes_Check(value))
int PyAnyString_PathDecoder(PyObject* obj, ST::string* str);
#define PyAnyString_Check(ob) (PyUnicode_Check(ob) || PyBytes_Check(ob))

int PyType_CheckAndReady(PyTypeObject* type);

Expand Down Expand Up @@ -417,6 +418,19 @@ template <> inline size_t pyPlasma_get(PyObject* value) { return (size_t)(unsign
return 0; \
}

#define PY_PROPERTY_WRITE_PATHLIKE(pyType, name, setter) \
PY_GETSET_SETTER_DECL(pyType, name) \
{ \
PY_PROPERTY_CHECK_NULL(name) \
ST::string path; \
if (!PyAnyString_PathDecoder(value, &path)) { \
PyErr_SetString(PyExc_TypeError, #name " expected type string or os.PathLike object"); \
return -1; \
} \
self->fThis->setter(path); \
return 0; \
}

#define PY_PROPERTY_SETTER_MSG(pyType, name, message) \
PY_GETSET_SETTER_DECL(pyType, name) \
{ \
Expand All @@ -435,6 +449,11 @@ template <> inline size_t pyPlasma_get(PyObject* value) { return (size_t)(unsign
PY_PROPERTY_READ(pyType, name, getter) \
PY_PROPERTY_GETSET_RO_DECL(pyType, name)

#define PY_PROPERTY_PATHLIKE(pyType, name, getter, setter) \
PY_PROPERTY_READ(pyType, name, getter) \
PY_PROPERTY_WRITE_PATHLIKE(pyType, name, setter) \
PY_PROPERTY_GETSET_DECL(pyType, name)

/* Helpers for properties that have direct-access to a member, rather than
* using getter/setter functions */
#define PY_PROPERTY_MEMBER_READ(pyType, name, member) \
Expand Down
15 changes: 8 additions & 7 deletions Python/ResManager/pyAgeInfo.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -28,11 +28,12 @@ PY_METHOD_VA(AgeInfo, readFromFile,
"Params: filename\n"
"Reads the AgeInfo from a .age file")
{
const char* filename;
if (!PyArg_ParseTuple(args, "s", &filename)) {
PyErr_SetString(PyExc_TypeError, "readFromFile expects a string");
ST::string filename;
if (!PyArg_ParseTuple(args, "O&", PyAnyString_PathDecoder, &filename)) {
PyErr_SetString(PyExc_TypeError, "readFromFile expects a string or an os.PathLike object");
return nullptr;
}

try {
self->fThis->readFromFile(filename);
Py_RETURN_NONE;
Expand All @@ -59,10 +60,10 @@ PY_METHOD_VA(AgeInfo, writeToFile,
"Params: filename, version\n"
"Write the AgeInfo to a .age file")
{
const char* filename;
ST::string filename;
int version;
if (!PyArg_ParseTuple(args, "si", &filename, &version)) {
PyErr_SetString(PyExc_TypeError, "writeToFile expects string, int");
if (!PyArg_ParseTuple(args, "O&i", PyAnyString_PathDecoder, &filename, &version)) {
PyErr_SetString(PyExc_TypeError, "writeToFile expects string or an os.PathLike object, int");
return nullptr;
}
try {
Expand Down Expand Up @@ -258,7 +259,7 @@ static PyMethodDef pyAgeInfo_Methods[] = {
PY_METHOD_TERMINATOR
};

PY_PROPERTY(ST::string, AgeInfo, name, getAgeName, setAgeName)
PY_PROPERTY_PATHLIKE(AgeInfo, name, getAgeName, setAgeName)
PY_PROPERTY(unsigned int, AgeInfo, startDateTime, getStartDateTime, setStartDateTime)
PY_PROPERTY(float, AgeInfo, dayLength, getDayLength, setDayLength)
PY_PROPERTY(short, AgeInfo, maxCapacity, getMaxCapacity, setMaxCapacity)
Expand Down
4 changes: 2 additions & 2 deletions Python/ResManager/pyPageInfo.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -101,9 +101,9 @@ static PyMethodDef pyPageInfo_Methods[] = {
PY_METHOD_TERMINATOR
};

PY_PROPERTY(ST::string, PageInfo, age, getAge, setAge)
PY_PROPERTY_PATHLIKE(PageInfo, age, getAge, setAge)
PY_PROPERTY_RO(PageInfo, chapter, getChapter)
PY_PROPERTY(ST::string, PageInfo, page, getPage, setPage)
PY_PROPERTY_PATHLIKE(PageInfo, page, getPage, setPage)
PY_PROPERTY(unsigned int, PageInfo, releaseVersion, getReleaseVersion, setReleaseVersion)
PY_PROPERTY(unsigned int, PageInfo, flags, getFlags, setFlags)
PY_PROPERTY(plLocation, PageInfo, location, getLocation, setLocation)
Expand Down
30 changes: 15 additions & 15 deletions Python/ResManager/pyResManager.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -161,11 +161,11 @@ PY_METHOD_VA(ResManager, ReadPage,
"Params: filename, [stub]\n"
"Reads an entire PRP file and returns the plPageInfo for it")
{
const char* filename;
ST::string filename;
pyStream* prxStream;
pyStream* prmStream = nullptr;
int stub = false;
if (PyArg_ParseTuple(args, "s|i", &filename, &stub)) {
if (PyArg_ParseTuple(args, "O&|i", PyAnyString_PathDecoder, &filename, &stub)) {
try {
return pyPageInfo_FromPageInfo(self->fThis->ReadPage(filename, (stub != 0)));
} catch (...) {
Expand All @@ -174,7 +174,7 @@ PY_METHOD_VA(ResManager, ReadPage,
}
} else if (PyErr_Clear(), PyArg_ParseTuple(args, "O|Oi", &prxStream, &prmStream, &stub)) {
if (!pyStream_Check((PyObject*)prxStream) || (prmStream && !pyStream_Check((PyObject*)prmStream))) {
PyErr_SetString(PyExc_TypeError, "ReadPage expects a string or stream");
PyErr_SetString(PyExc_TypeError, "ReadPage expects a string, an hsStream, or an os.PathLike object");
return nullptr;
}

Expand All @@ -187,7 +187,7 @@ PY_METHOD_VA(ResManager, ReadPage,
return nullptr;
}
} else {
PyErr_SetString(PyExc_TypeError, "ReadPage expects a string or stream");
PyErr_SetString(PyExc_TypeError, "ReadPage expects a string, an hsStream, or an os.PathLike object");
return nullptr;
}
}
Expand All @@ -196,12 +196,12 @@ PY_METHOD_VA(ResManager, WritePage,
"Params: filename, page\n"
"Writes an entire page to a PRP file")
{
const char* filename;
ST::string filename;
pyStream* stream;
pyPageInfo* page;
if (PyArg_ParseTuple(args, "sO", &filename, &page)) {
if (PyArg_ParseTuple(args, "O&O", PyAnyString_PathDecoder, &filename, &page)) {
if (!pyPageInfo_Check((PyObject*)page)) {
PyErr_SetString(PyExc_TypeError, "WritePage expects string or hsStream, plPageInfo");
PyErr_SetString(PyExc_TypeError, "WritePage expects a string, hsStream, or os.PathLike object, and a plPageInfo");
return nullptr;
}

Expand All @@ -214,7 +214,7 @@ PY_METHOD_VA(ResManager, WritePage,
}
} else if (PyErr_Clear(), PyArg_ParseTuple(args, "OO", &stream, &page)) {
if (!pyPageInfo_Check((PyObject*)page) || !pyStream_Check((PyObject*)stream)) {
PyErr_SetString(PyExc_TypeError, "WritePage expects string or hsStream, plPageInfo");
PyErr_SetString(PyExc_TypeError, "WritePage expects a string, hsStream, or os.PathLike object, and a plPageInfo");
return nullptr;
}

Expand All @@ -226,7 +226,7 @@ PY_METHOD_VA(ResManager, WritePage,
return nullptr;
}
} else {
PyErr_SetString(PyExc_TypeError, "WritePage expects string or hsStream, plPageInfo");
PyErr_SetString(PyExc_TypeError, "WritePage expects a string, hsStream, or os.PathLike object, and a plPageInfo");
return nullptr;
}
}
Expand Down Expand Up @@ -275,9 +275,9 @@ PY_METHOD_VA(ResManager, ReadAge,
"Reads a .age file. If readPages is True, also loads all PRPs\n"
"identified in the .age file")
{
const char* filename;
ST::string filename;
char readPages;
if (!PyArg_ParseTuple(args, "sb", &filename, &readPages)) {
if (!PyArg_ParseTuple(args, "O&b", PyAnyString_PathDecoder, &filename, &readPages)) {
PyErr_SetString(PyExc_TypeError, "ReadAge expects string, bool");
return nullptr;
}
Expand All @@ -304,14 +304,14 @@ PY_METHOD_VA(ResManager, WriteAge,
"Writes a plAgeInfo to the specified file\n"
"Does NOT write any PRP files!")
{
const char* filename;
ST::string filename;
pyAgeInfo* age;
if (!PyArg_ParseTuple(args, "sO", &filename, &age)) {
PyErr_SetString(PyExc_TypeError, "WriteAge expects string, plAgeInfo");
if (!PyArg_ParseTuple(args, "O&O", PyAnyString_PathDecoder, &filename, &age)) {
PyErr_SetString(PyExc_TypeError, "WriteAge expects string or an os.PathLike object, plAgeInfo");
return nullptr;
}
if (!pyAgeInfo_Check((PyObject*)age)) {
PyErr_SetString(PyExc_TypeError, "WriteAge expects string, plAgeInfo");
PyErr_SetString(PyExc_TypeError, "WriteAge expects string or an os.PathLike object, plAgeInfo");
return nullptr;
}
try {
Expand Down
6 changes: 3 additions & 3 deletions Python/SDL/pySDLMgr.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,8 @@ PY_METHOD_VA(SDLMgr, readDescriptors,
"Params: filename or stream\n"
"Reads SDL Descriptors from the provided filename or stream")
{
const char* filename;
if (PyArg_ParseTuple(args, "s", &filename)) {
ST::string filename;
if (PyArg_ParseTuple(args, "O&", PyAnyString_PathDecoder, &filename)) {
try {
self->fThis->ReadDescriptors(filename);
} catch (const hsException& ex) {
Expand All @@ -49,7 +49,7 @@ PY_METHOD_VA(SDLMgr, readDescriptors,
Py_RETURN_NONE;
}

PyErr_SetString(PyExc_TypeError, "readDescriptors expects a string or an hsStream");
PyErr_SetString(PyExc_TypeError, "readDescriptors expects a string, an hsStream, or an os.PathLike object");
return nullptr;
}

Expand Down
17 changes: 8 additions & 9 deletions Python/Stream/pyEncryptedStream.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -26,14 +26,13 @@ PY_METHOD_VA(EncryptedStream, open,
"Mode is: fmRead, fmWrite, fmReadWrite, fmCreate\n"
"Encryption is: kEncNone, kEncXtea, kEncAES, kEncDroid, kEncAuto")
{
const char* filename;
ST::string filename;
pyStream* stream;
int mode, encryption;

if (PyArg_ParseTuple(args, "sii", &filename, &mode, &encryption)) {
if (PyArg_ParseTuple(args, "O&ii", PyAnyString_PathDecoder, &filename, &mode, &encryption)) {
try {
if (!self->fThis->open(filename, (FileMode)mode,
(plEncryptedStream::EncryptionType)encryption)) {
if (!self->fThis->open(filename, (FileMode)mode, (plEncryptedStream::EncryptionType)encryption)) {
PyErr_SetString(PyExc_IOError, "Error opening file");
return nullptr;
}
Expand All @@ -45,7 +44,7 @@ PY_METHOD_VA(EncryptedStream, open,
}
} else if (PyErr_Clear(), PyArg_ParseTuple(args, "Oii", &stream, &mode, &encryption)) {
if (!pyStream_Check((PyObject*)stream)) {
PyErr_SetString(PyExc_TypeError, "open expects string or stream, int, int");
PyErr_SetString(PyExc_TypeError, "open expects a string, hsStream stream, or an os.PathLike object, an int, and an int");
return nullptr;
}

Expand All @@ -62,7 +61,7 @@ PY_METHOD_VA(EncryptedStream, open,
return nullptr;
}
} else {
PyErr_SetString(PyExc_TypeError, "open expects string or stream, int, int");
PyErr_SetString(PyExc_TypeError, "open expects a string, hsStream stream, or an os.PathLike object, an int, and an int");
return nullptr;
}
}
Expand Down Expand Up @@ -108,9 +107,9 @@ PY_METHOD_STATIC_VA(EncryptedStream, IsFileEncrypted,
"Params: filename\n"
"Tests whether the specified file is encrypted")
{
const char* filename;
if (!PyArg_ParseTuple(args, "s", &filename)) {
PyErr_SetString(PyExc_TypeError, "IsFileEncrypted expects a string");
ST::string filename;
if (!PyArg_ParseTuple(args, "O&", PyAnyString_PathDecoder, &filename)) {
PyErr_SetString(PyExc_TypeError, "IsFileEncrypted expects a string or an os.PathLike object");
return nullptr;
}
return pyPlasma_convert(plEncryptedStream::IsFileEncrypted(filename));
Expand Down
6 changes: 3 additions & 3 deletions Python/Stream/pyFileStream.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -25,11 +25,11 @@ PY_METHOD_VA(FileStream, open,
"Opens the specified file.\n"
"Mode is: fmRead, fmWrite, fmReadWrite, fmCreate")
{
const char* filename;
ST::string filename;
int mode;

if (!PyArg_ParseTuple(args, "si", &filename, &mode)) {
PyErr_SetString(PyExc_TypeError, "open expects string, int");
if (!PyArg_ParseTuple(args, "O&i", PyAnyString_PathDecoder, &filename, &mode)) {
PyErr_SetString(PyExc_TypeError, "open expects string or an os.PathLike object, int");
return nullptr;
}
try {
Expand Down
2 changes: 1 addition & 1 deletion core/Debug/plDebug.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ void plDebug::Init(int level, hsStream* stream)
}
}

void plDebug::InitFile(int level, const char* filename)
void plDebug::InitFile(int level, const ST::string& filename)
{
DeInit();

Expand Down
2 changes: 1 addition & 1 deletion core/Debug/plDebug.h
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ class PLASMA_DLL plDebug

public:
static void Init(int level, hsStream* stream = nullptr);
static void InitFile(int level, const char* filename = "Plasma.log");
static void InitFile(int level, const ST::string& filename = ST_LITERAL("Plasma.log"));

static void Error(const char* line)
{
Expand Down

0 comments on commit 8537737

Please sign in to comment.