diff options
-rw-r--r-- | Doc/library/readline.rst | 29 | ||||
-rw-r--r-- | Lib/test/test_readline.py | 40 | ||||
-rw-r--r-- | Misc/NEWS | 2 | ||||
-rw-r--r-- | Modules/readline.c | 37 |
4 files changed, 106 insertions, 2 deletions
diff --git a/Doc/library/readline.rst b/Doc/library/readline.rst index 692310b..3864f0d 100644 --- a/Doc/library/readline.rst +++ b/Doc/library/readline.rst @@ -59,6 +59,14 @@ The :mod:`readline` module defines the following functions: Save a readline history file. The default filename is :file:`~/.history`. +.. function:: append_history_file(nelements[, filename]) + + Append the last *nelements* of history to a file. The default filename is + :file:`~/.history`. The file must already exist. + + .. versionadded:: 3.5 + + .. function:: clear_history() Clear the current history. (Note: this function is not available if the @@ -209,6 +217,26 @@ from the user's :envvar:`PYTHONSTARTUP` file. :: This code is actually automatically run when Python is run in :ref:`interactive mode <tut-interactive>` (see :ref:`rlcompleter-config`). +The following example achieves the same goal but supports concurrent interactive +sessions, by only appending the new history. :: + + import atexit + import os + import realine + histfile = os.path.join(os.path.expanduser("~"), ".python_history") + + try: + readline.read_history_file(histfile) + h_len = readline.get_history_length() + except FileNotFoundError: + open(histfile, 'wb').close() + h_len = 0 + + def save(prev_h_len, histfile): + new_h_len = readline.get_history_length() + readline.append_history_file(new_h_len - prev_h_len, histfile) + atexit.register(save, h_len, histfile) + The following example extends the :class:`code.InteractiveConsole` class to support history save/restore. :: @@ -234,4 +262,3 @@ support history save/restore. :: def save_history(self, histfile): readline.write_history_file(histfile) - diff --git a/Lib/test/test_readline.py b/Lib/test/test_readline.py index d2a11f2..e5be02e 100644 --- a/Lib/test/test_readline.py +++ b/Lib/test/test_readline.py @@ -2,8 +2,9 @@ Very minimal unittests for parts of the readline module. """ import os +import tempfile import unittest -from test.support import run_unittest, import_module +from test.support import run_unittest, import_module, unlink from test.script_helper import assert_python_ok # Skip tests if there is no readline module @@ -42,6 +43,43 @@ class TestHistoryManipulation (unittest.TestCase): self.assertEqual(readline.get_current_history_length(), 1) + def test_write_read_append(self): + hfile = tempfile.NamedTemporaryFile(delete=False) + hfile.close() + hfilename = hfile.name + self.addCleanup(unlink, hfilename) + + # test write-clear-read == nop + readline.clear_history() + readline.add_history("first line") + readline.add_history("second line") + readline.write_history_file(hfilename) + + readline.clear_history() + self.assertEqual(readline.get_current_history_length(), 0) + + readline.read_history_file(hfilename) + self.assertEqual(readline.get_current_history_length(), 2) + self.assertEqual(readline.get_history_item(1), "first line") + self.assertEqual(readline.get_history_item(2), "second line") + + # test append + readline.append_history_file(1, hfilename) + readline.clear_history() + readline.read_history_file(hfilename) + self.assertEqual(readline.get_current_history_length(), 3) + self.assertEqual(readline.get_history_item(1), "first line") + self.assertEqual(readline.get_history_item(2), "second line") + self.assertEqual(readline.get_history_item(3), "second line") + + # test 'no such file' behaviour + os.unlink(hfilename) + with self.assertRaises(FileNotFoundError): + readline.append_history_file(1, hfilename) + + # write_history_file can create the target + readline.write_history_file(hfilename) + class TestReadline(unittest.TestCase): @@ -191,6 +191,8 @@ Core and Builtins Library ------- +- Issue #22940: Add readline.append_history_file. + - Issue #19676: Added the "namereplace" error handler. - Issue #22788: Add *context* parameter to logging.handlers.HTTPHandler. diff --git a/Modules/readline.c b/Modules/readline.c index f349d3b..b5a0eba 100644 --- a/Modules/readline.c +++ b/Modules/readline.c @@ -237,6 +237,41 @@ Save a readline history file.\n\ The default filename is ~/.history."); +/* Exported function to save part of a readline history file */ + +static PyObject * +append_history_file(PyObject *self, PyObject *args) +{ + int nelements; + PyObject *filename_obj = Py_None, *filename_bytes; + char *filename; + int err; + if (!PyArg_ParseTuple(args, "i|O:append_history_file", &nelements, &filename_obj)) + return NULL; + if (filename_obj != Py_None) { + if (!PyUnicode_FSConverter(filename_obj, &filename_bytes)) + return NULL; + filename = PyBytes_AsString(filename_bytes); + } else { + filename_bytes = NULL; + filename = NULL; + } + errno = err = append_history(nelements, filename); + if (!err && _history_length >= 0) + history_truncate_file(filename, _history_length); + Py_XDECREF(filename_bytes); + errno = err; + if (errno) + return PyErr_SetFromErrno(PyExc_IOError); + Py_RETURN_NONE; +} + +PyDoc_STRVAR(doc_append_history_file, +"append_history_file(nelements[, filename]) -> None\n\ +Append the last nelements of the history list to file.\n\ +The default filename is ~/.history."); + + /* Set history length */ static PyObject* @@ -747,6 +782,8 @@ static struct PyMethodDef readline_methods[] = METH_VARARGS, doc_read_history_file}, {"write_history_file", write_history_file, METH_VARARGS, doc_write_history_file}, + {"append_history_file", append_history_file, + METH_VARARGS, doc_append_history_file}, {"get_history_item", get_history_item, METH_VARARGS, doc_get_history_item}, {"get_current_history_length", (PyCFunction)get_current_history_length, |