From b232df9197a19e78d0e2a751e56e0e62547354ec Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka Date: Tue, 30 Oct 2018 13:22:42 +0200 Subject: [PATCH] bpo-31680: Add curses.ncurses_version. (GH-4217) Use curses.ncurses_version for conditionally skipping a test. --- Doc/library/curses.rst | 13 ++++ Doc/whatsnew/3.8.rst | 9 +++ Lib/test/test_curses.py | 22 +++++- .../2017-11-01-15-44-48.bpo-31680.yO6oSC.rst | 1 + Modules/_cursesmodule.c | 77 +++++++++++++++++++ 5 files changed, 119 insertions(+), 3 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2017-11-01-15-44-48.bpo-31680.yO6oSC.rst diff --git a/Doc/library/curses.rst b/Doc/library/curses.rst index 2a2ee2be84a..2a4d9ce8a35 100644 --- a/Doc/library/curses.rst +++ b/Doc/library/curses.rst @@ -1291,6 +1291,19 @@ The :mod:`curses` module defines the following data members: A bytes object representing the current version of the module. Also available as :const:`__version__`. + +.. data:: ncurses_version + + A named tuple containing the three components of the ncurses library + version: *major*, *minor*, and *patch*. All values are integers. The + components can also be accessed by name, so ``curses.ncurses_version[0]`` + is equivalent to ``curses.ncurses_version.major`` and so on. + + Availability: if the ncurses library is used. + + .. versionadded:: 3.8 + + Some constants are available to specify character cell attributes. The exact constants available are system dependent. diff --git a/Doc/whatsnew/3.8.rst b/Doc/whatsnew/3.8.rst index 758d32e6e55..02391de0dbc 100644 --- a/Doc/whatsnew/3.8.rst +++ b/Doc/whatsnew/3.8.rst @@ -152,6 +152,15 @@ now return ``False`` instead of raising :exc:`ValueError` or its subclasses characters or bytes unrepresentable at the OS level. (Contributed by Serhiy Storchaka in :issue:`33721`.) + +ncurses +------- + +Added a new variable holding structured version information for the +underlying ncurses library: :data:`~curses.ncurses_version`. +(Contributed by Serhiy Storchaka in :issue:`31680`.) + + pathlib ------- diff --git a/Lib/test/test_curses.py b/Lib/test/test_curses.py index 3b442fe6a4b..09738c8a41c 100644 --- a/Lib/test/test_curses.py +++ b/Lib/test/test_curses.py @@ -368,9 +368,8 @@ class TestCurses(unittest.TestCase): self.stdscr.getkey() @requires_curses_func('unget_wch') - # XXX Remove the decorator when ncurses on OpenBSD be updated - @unittest.skipIf(sys.platform.startswith("openbsd"), - "OpenBSD's curses (v.5.7) has bugs") + @unittest.skipIf(getattr(curses, 'ncurses_version', (99,)) < (5, 8), + "unget_wch is broken in ncurses 5.7 and earlier") def test_unget_wch(self): stdscr = self.stdscr encoding = stdscr.encoding @@ -456,6 +455,23 @@ class MiscTests(unittest.TestCase): # can be called. curses.update_lines_cols() + @requires_curses_func('ncurses_version') + def test_ncurses_version(self): + v = curses.ncurses_version + self.assertIsInstance(v[:], tuple) + self.assertEqual(len(v), 3) + self.assertIsInstance(v[0], int) + self.assertIsInstance(v[1], int) + self.assertIsInstance(v[2], int) + self.assertIsInstance(v.major, int) + self.assertIsInstance(v.minor, int) + self.assertIsInstance(v.patch, int) + self.assertEqual(v[0], v.major) + self.assertEqual(v[1], v.minor) + self.assertEqual(v[2], v.patch) + self.assertGreaterEqual(v.major, 0) + self.assertGreaterEqual(v.minor, 0) + self.assertGreaterEqual(v.patch, 0) class TestAscii(unittest.TestCase): diff --git a/Misc/NEWS.d/next/Library/2017-11-01-15-44-48.bpo-31680.yO6oSC.rst b/Misc/NEWS.d/next/Library/2017-11-01-15-44-48.bpo-31680.yO6oSC.rst new file mode 100644 index 00000000000..3cf33ac9bd6 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2017-11-01-15-44-48.bpo-31680.yO6oSC.rst @@ -0,0 +1 @@ +Added :data:`curses.ncurses_version`. diff --git a/Modules/_cursesmodule.c b/Modules/_cursesmodule.c index a728a24f6ca..c8f564a31db 100644 --- a/Modules/_cursesmodule.c +++ b/Modules/_cursesmodule.c @@ -4289,6 +4289,59 @@ _curses_use_default_colors_impl(PyObject *module) } #endif /* STRICT_SYSV_CURSES */ + +#ifdef NCURSES_VERSION + +PyDoc_STRVAR(ncurses_version__doc__, +"curses.ncurses_version\n\ +\n\ +Ncurses version information as a named tuple."); + +static PyTypeObject NcursesVersionType; + +static PyStructSequence_Field ncurses_version_fields[] = { + {"major", "Major release number"}, + {"minor", "Minor release number"}, + {"patch", "Patch release number"}, + {0} +}; + +static PyStructSequence_Desc ncurses_version_desc = { + "curses.ncurses_version", /* name */ + ncurses_version__doc__, /* doc */ + ncurses_version_fields, /* fields */ + 3 +}; + +static PyObject * +make_ncurses_version(void) +{ + PyObject *ncurses_version; + int pos = 0; + + ncurses_version = PyStructSequence_New(&NcursesVersionType); + if (ncurses_version == NULL) { + return NULL; + } + +#define SetIntItem(flag) \ + PyStructSequence_SET_ITEM(ncurses_version, pos++, PyLong_FromLong(flag)); \ + if (PyErr_Occurred()) { \ + Py_CLEAR(ncurses_version); \ + return NULL; \ + } + + SetIntItem(NCURSES_VERSION_MAJOR) + SetIntItem(NCURSES_VERSION_MINOR) + SetIntItem(NCURSES_VERSION_PATCH) +#undef SetIntItem + + return ncurses_version; +} + +#endif /* NCURSES_VERSION */ + + /* List of functions defined in the module */ static PyMethodDef PyCurses_methods[] = { @@ -4426,6 +4479,30 @@ PyInit__curses(void) PyDict_SetItemString(d, "__version__", v); Py_DECREF(v); +#ifdef NCURSES_VERSION + /* ncurses_version */ + if (NcursesVersionType.tp_name == NULL) { + if (PyStructSequence_InitType2(&NcursesVersionType, + &ncurses_version_desc) < 0) + return NULL; + } + v = make_ncurses_version(); + if (v == NULL) { + return NULL; + } + PyDict_SetItemString(d, "ncurses_version", v); + Py_DECREF(v); + + /* prevent user from creating new instances */ + NcursesVersionType.tp_init = NULL; + NcursesVersionType.tp_new = NULL; + if (PyDict_DelItemString(NcursesVersionType.tp_dict, "__new__") < 0 && + PyErr_ExceptionMatches(PyExc_KeyError)) + { + PyErr_Clear(); + } +#endif /* NCURSES_VERSION */ + SetDictInt("ERR", ERR); SetDictInt("OK", OK);