From 4bc211d5e3aa8c268937cdee5003b39e66de88b4 Mon Sep 17 00:00:00 2001 From: Bruno Cauet Date: Tue, 25 Nov 2014 11:44:40 +0100 Subject: [PATCH] Add readline.append_history_file function append_history_file(nelements, [filename]) behaves like append_history from readline/history.h Add a test for this function and for {write,read}_history_file as well. Add an example using readline.append_history_file --- Doc/library/readline.rst | 27 ++++++++++++++++++++++++++- Lib/test/test_readline.py | 40 ++++++++++++++++++++++++++++++++++++++++ Modules/readline.c | 37 +++++++++++++++++++++++++++++++++++++ 3 files changed, 103 insertions(+), 1 deletion(-) diff --git a/Doc/library/readline.rst b/Doc/library/readline.rst index 692310b..3061a52 100644 --- a/Doc/library/readline.rst +++ b/Doc/library/readline.rst @@ -59,6 +59,12 @@ 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]) + + Save the last nelements of a readline history file. The default filename is + :file:`~/.history`. The file must already exist. + + .. function:: clear_history() Clear the current history. (Note: this function is not available if the @@ -209,6 +215,26 @@ from the user's :envvar:`PYTHONSTARTUP` file. :: This code is actually automatically run when Python is run in :ref:`interactive mode ` (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, 'w').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 +260,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..3886397 100644 --- a/Lib/test/test_readline.py +++ b/Lib/test/test_readline.py @@ -1,6 +1,7 @@ """ Very minimal unittests for parts of the readline module. """ +import tempfile import os import unittest from test.support import run_unittest, import_module @@ -42,6 +43,45 @@ 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 + + # 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) + + os.unlink(hfilename) + class TestReadline(unittest.TestCase): diff --git a/Modules/readline.c b/Modules/readline.c index f349d3b..ef159ec 100644 --- a/Modules/readline.c +++ b/Modules/readline.c @@ -236,6 +236,41 @@ PyDoc_STRVAR(doc_write_history_file, 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 filename.\n\ +The default filename is ~/.history."); + + /* Set history length */ @@ -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, -- 2.1.3