From 718334a7f8208a0422046f24ed5bc78d883ee733 Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka Date: Wed, 1 Nov 2017 15:45:26 +0200 Subject: [PATCH 1/4] bpo-31680: Added curses.ncurses_version. --- Doc/library/curses.rst | 12 ++++ Lib/test/test_curses.py | 16 +++++ .../2017-11-01-15-44-48.bpo-31680.yO6oSC.rst | 1 + Modules/_cursesmodule.c | 69 +++++++++++++++++++ 4 files changed, 98 insertions(+) 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 f2341f4ec833ff..b6aabd0abb748b 100644 --- a/Doc/library/curses.rst +++ b/Doc/library/curses.rst @@ -1279,6 +1279,18 @@ 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. Available + only if the ncurses library is used. + + .. versionadded:: 3.7 + + Some constants are available to specify character cell attributes. The exact constants available are system dependent. diff --git a/Lib/test/test_curses.py b/Lib/test/test_curses.py index 514ed83167d794..ae61dba282e7a7 100644 --- a/Lib/test/test_curses.py +++ b/Lib/test/test_curses.py @@ -444,6 +444,22 @@ def test_update_lines_cols(self): # 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.assertTrue(v > (0, 0, 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 00000000000000..3cf33ac9bd63ab --- /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 7962936966906f..82e4196ef2debf 100644 --- a/Modules/_cursesmodule.c +++ b/Modules/_cursesmodule.c @@ -3187,6 +3187,53 @@ PyCurses_Use_Default_Colors(PyObject *self) } #endif /* STRICT_SYSV_CURSES */ +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; +} + /* List of functions defined in the module */ static PyMethodDef PyCurses_methods[] = { @@ -3353,6 +3400,28 @@ PyInit__curses(void) PyDict_SetItemString(d, "__version__", v); Py_DECREF(v); + /* 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(); + } + SetDictInt("ERR", ERR); SetDictInt("OK", OK); From 78f71d9f48c624122b3b01cf2ac3e9ea914124f7 Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka Date: Wed, 1 Nov 2017 16:11:40 +0200 Subject: [PATCH 2/4] Use curses.ncurses_version for conditionally skipping a test. --- Lib/test/test_curses.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/Lib/test/test_curses.py b/Lib/test/test_curses.py index eba170992b0b46..316d9a941de979 100644 --- a/Lib/test/test_curses.py +++ b/Lib/test/test_curses.py @@ -359,9 +359,8 @@ def test_issue6243(self): 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 From b2e41850353c2d73515c09e1120623406167170c Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka Date: Thu, 2 Nov 2017 16:50:41 +0200 Subject: [PATCH 3/4] Address review comments. --- Doc/library/curses.rst | 5 +++-- Lib/test/test_curses.py | 5 +++-- Modules/_cursesmodule.c | 8 ++++++++ 3 files changed, 14 insertions(+), 4 deletions(-) diff --git a/Doc/library/curses.rst b/Doc/library/curses.rst index b6aabd0abb748b..032c9159b5a86d 100644 --- a/Doc/library/curses.rst +++ b/Doc/library/curses.rst @@ -1285,8 +1285,9 @@ The :mod:`curses` module defines the following data members: 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. Available - only if the ncurses library is used. + is equivalent to ``curses.ncurses_version.major`` and so on. + + Availability: if the ncurses library is used. .. versionadded:: 3.7 diff --git a/Lib/test/test_curses.py b/Lib/test/test_curses.py index 316d9a941de979..76b1772b31cc43 100644 --- a/Lib/test/test_curses.py +++ b/Lib/test/test_curses.py @@ -460,8 +460,9 @@ def test_ncurses_version(self): self.assertEqual(v[0], v.major) self.assertEqual(v[1], v.minor) self.assertEqual(v[2], v.patch) - self.assertTrue(v > (0, 0, 0)) - + self.assertGreaterEqual(v.major, 0) + self.assertGreaterEqual(v.minor, 0) + self.assertGreaterEqual(v.patch, 0) class TestAscii(unittest.TestCase): diff --git a/Modules/_cursesmodule.c b/Modules/_cursesmodule.c index 8f9fa43c5009df..06aafc92977a13 100644 --- a/Modules/_cursesmodule.c +++ b/Modules/_cursesmodule.c @@ -3195,6 +3195,9 @@ PyCurses_Use_Default_Colors(PyObject *self) } #endif /* STRICT_SYSV_CURSES */ + +#ifdef NCURSES_VERSION + PyDoc_STRVAR(ncurses_version__doc__, "curses.ncurses_version\n\ \n\ @@ -3242,6 +3245,9 @@ make_ncurses_version(void) return ncurses_version; } +#endif /* NCURSES_VERSION */ + + /* List of functions defined in the module */ static PyMethodDef PyCurses_methods[] = { @@ -3408,6 +3414,7 @@ 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, @@ -3429,6 +3436,7 @@ PyInit__curses(void) { PyErr_Clear(); } +#endif /* NCURSES_VERSION */ SetDictInt("ERR", ERR); SetDictInt("OK", OK); From 0686f774b3f348aeb0c7524b841feb8e967fa2eb Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka Date: Mon, 29 Oct 2018 20:47:02 +0200 Subject: [PATCH 4/4] Add a What's New entry. --- Doc/library/curses.rst | 2 +- Doc/whatsnew/3.8.rst | 9 +++++++++ 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/Doc/library/curses.rst b/Doc/library/curses.rst index 5d0ab76dba2604..2a4d9ce8a35a4e 100644 --- a/Doc/library/curses.rst +++ b/Doc/library/curses.rst @@ -1301,7 +1301,7 @@ The :mod:`curses` module defines the following data members: Availability: if the ncurses library is used. - .. versionadded:: 3.7 + .. versionadded:: 3.8 Some constants are available to specify character cell attributes. diff --git a/Doc/whatsnew/3.8.rst b/Doc/whatsnew/3.8.rst index 758d32e6e55a43..02391de0dbc0c7 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 -------