Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions Doc/library/curses.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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.

Expand Down
9 changes: 9 additions & 0 deletions Doc/whatsnew/3.8.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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
-------

Expand Down
22 changes: 19 additions & 3 deletions Lib/test/test_curses.py
Original file line number Diff line number Diff line change
Expand Up @@ -368,9 +368,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
Expand Down Expand Up @@ -456,6 +455,23 @@ 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.assertGreaterEqual(v.major, 0)
self.assertGreaterEqual(v.minor, 0)
self.assertGreaterEqual(v.patch, 0)
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Perfect, thank you :-)


class TestAscii(unittest.TestCase):

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Added :data:`curses.ncurses_version`.
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You may add it to Doc/whatsnew/3.7.rst as well. It's up to you.

77 changes: 77 additions & 0 deletions Modules/_cursesmodule.c
Original file line number Diff line number Diff line change
Expand Up @@ -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[] = {
Expand Down Expand Up @@ -4426,6 +4479,30 @@ PyInit__curses(void)
PyDict_SetItemString(d, "__version__", v);
Py_DECREF(v);

#ifdef NCURSES_VERSION
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah ok, now it's more explicit :-)

/* 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);

Expand Down