From ce21888e59a69199b33982455b1f2368e5b41884 Mon Sep 17 00:00:00 2001 From: Kevin Ernst Date: Thu, 3 Mar 2022 15:58:34 -0500 Subject: [PATCH 1/2] Implement 'ipython history show [opts]' Includes: * a `-l` / `--limit` option which defaults to the last 1000 entries from the SQLite history database * a `-P` / `--pretty` option which pretty-prints the output with IPython.util.PyColorize * flags to specify a custom delimiter (defaults to tab) and add a header row (corresponding to the SQLite column names) * flags which control `--pretty`'s colorized output, defaulting to color only when stdout is a terminal --- IPython/core/historyapp.py | 116 ++++++++++++++++++++++++++++++++++++- 1 file changed, 115 insertions(+), 1 deletion(-) diff --git a/IPython/core/historyapp.py b/IPython/core/historyapp.py index 01a55343f8a..2fbda5f9237 100644 --- a/IPython/core/historyapp.py +++ b/IPython/core/historyapp.py @@ -10,9 +10,15 @@ from traitlets.config.application import Application from .application import BaseIPythonApplication -from traitlets import Bool, Int, Dict +from traitlets import Bool, Int, Dict, Unicode, CaselessStrEnum from ..utils.io import ask_yes_no +show_hist_help = """Show recent entries from the IPython history database. + +Limited to the last 1000 unless `--limit=N` is supplied; `--limit=0` or `-1` +prints all entries in the database. +""" + trim_hist_help = """Trim the IPython history database to the last 1000 entries. This actually copies the last 1000 entries to a new database, and then replaces @@ -29,6 +35,113 @@ This is an handy alias to `ipython history trim --keep=0` """ +class HistoryShow(BaseIPythonApplication): + description = show_hist_help + limit = Int(1000, + help="Number of recent history entries to show." + ).tag(config=True) + + delimiter = Unicode(u'\t', + help="Delimiter for separating the columns of history output." + ).tag(config=True) + + header = Bool(False, + help="Include a header row in the output." + ).tag(config=True) + + pretty = Bool(False, + help="Pretty-print the output using IPython.utils.PyColorize." + ).tag(config=True) + + colors = CaselessStrEnum(('Neutral', 'NoColor','LightBG','Linux'), + default_value='Neutral', + help="Set the color scheme (NoColor, Neutral, Linux, or LightBG)." + ).tag(config=True) + + color = CaselessStrEnum(('Never', 'Always', 'Auto'), + default_value='Auto', + help="Choose when to display color; requires `--pretty`." + ).tag(config=True) + + flags = Dict({ + 'L': ({'HistoryShow': {'limit': -1}}, + "No limit; show all history entries."), + 'pretty': ({'HistoryShow': {'pretty': True}}, pretty.help), + 'P': ({'HistoryShow': {'pretty': True}}, + "An alias for `--pretty`."), + 'header': ({'HistoryShow': {'header': True}}, header.help), + 'headers': ({'HistoryShow': {'header': True}}, + "An alias for `--header`."), + 'H': ({'HistoryShow': {'header': True}}, + "An alias for `--header`."), + 'color-always': ({'HistoryShow': {'color': 'Always'}}, + "Always output in color (e.g., when piping into `less -R`)."), + 'C': ({'HistoryShow': {'color': 'Always'}}, + "An alias for `--color-always`."), + 'color-never': ({'HistoryShow': {'color': 'Never'}}, + "Never show color, even if the output is a terminal.") + }) + + aliases=Dict(dict( + l = 'HistoryShow.limit', + limit = 'HistoryShow.limit', + d = 'HistoryShow.delimiter', + delimiter = 'HistoryShow.delimiter', + header = 'HistoryShow.header', + pretty = 'HistoryShow.pretty', + colors = 'HistoryShow.colors', + color = 'HistoryShow.color', + )) + + def start(self): + profile_dir = Path(self.profile_dir.location) + hist_file = profile_dir / "history.sqlite" + con = sqlite3.connect(hist_file) + + if not self.limit: + self.limit = -1 + + # Grab the recent history from the current database. + entries = list(con.execute( + """SELECT session, line, source, source_raw FROM + history ORDER BY session DESC, line DESC LIMIT ?""", + (self.limit,) + )) + + # Emulate shell `history` command; newest at bottom + entries.reverse() + + if self.pretty: + import sys + from IPython.utils import PyColorize + pyformat = PyColorize.Parser(style=self.colors, + parent=self).format + + for entry in entries: + header = f">>> # session {entry[0]}, line {entry[1]}\n" + entry = header + str(entry[3]) + + if (sys.stdout.isatty() or self.color == 'Always') \ + and self.color != 'Never': + print(pyformat(entry, out='str')) + else: + # could set self.colors='NoColor' and just use pyformat + # here, too, but this is probably marginally faster + print(entry, "\n") + + else: + if self.header: + print(self.delimiter.join(['session', 'line', 'source', + 'source_raw'])) + for entry in entries: + # FIXME: even if you specify `-d,` this is not very + # CSV-friendly, since the outer quote character (single or + # double) depends on whether the history entry itself has + # embedded strings + print(self.delimiter.join([repr(x) for x in entry])) + + con.close() + class HistoryTrim(BaseIPythonApplication): description = trim_hist_help @@ -145,6 +258,7 @@ class HistoryApp(Application): description = "Manage the IPython history database." subcommands = Dict(dict( + show = (HistoryShow, HistoryShow.description.splitlines()[0]), trim = (HistoryTrim, HistoryTrim.description.splitlines()[0]), clear = (HistoryClear, HistoryClear.description.splitlines()[0]), )) From 550b20382718f98ca11d698091ef42810110e742 Mon Sep 17 00:00:00 2001 From: Kevin Ernst Date: Thu, 3 Mar 2022 16:54:02 -0500 Subject: [PATCH 2/2] Formatting fixes to make `darker` happy --- IPython/core/historyapp.py | 138 ++++++++++++++++++++----------------- 1 file changed, 75 insertions(+), 63 deletions(-) diff --git a/IPython/core/historyapp.py b/IPython/core/historyapp.py index 2fbda5f9237..bc741db3bc6 100644 --- a/IPython/core/historyapp.py +++ b/IPython/core/historyapp.py @@ -35,63 +35,71 @@ This is an handy alias to `ipython history trim --keep=0` """ + class HistoryShow(BaseIPythonApplication): description = show_hist_help - limit = Int(1000, - help="Number of recent history entries to show." - ).tag(config=True) - - delimiter = Unicode(u'\t', - help="Delimiter for separating the columns of history output." - ).tag(config=True) + limit = Int(1000, help="Number of recent history entries to show.").tag(config=True) - header = Bool(False, - help="Include a header row in the output." - ).tag(config=True) + delimiter = Unicode( + "\t", help="Delimiter for separating the columns of history output." + ).tag(config=True) - pretty = Bool(False, - help="Pretty-print the output using IPython.utils.PyColorize." - ).tag(config=True) + header = Bool(False, help="Include a header row in the output.").tag(config=True) - colors = CaselessStrEnum(('Neutral', 'NoColor','LightBG','Linux'), - default_value='Neutral', - help="Set the color scheme (NoColor, Neutral, Linux, or LightBG)." + pretty = Bool( + False, help="Pretty-print the output using IPython.utils.PyColorize." ).tag(config=True) - color = CaselessStrEnum(('Never', 'Always', 'Auto'), - default_value='Auto', - help="Choose when to display color; requires `--pretty`." + colors = CaselessStrEnum( + ("Neutral", "NoColor", "LightBG", "Linux"), + default_value="Neutral", + help="Set the color scheme (NoColor, Neutral, Linux, or LightBG).", ).tag(config=True) - flags = Dict({ - 'L': ({'HistoryShow': {'limit': -1}}, - "No limit; show all history entries."), - 'pretty': ({'HistoryShow': {'pretty': True}}, pretty.help), - 'P': ({'HistoryShow': {'pretty': True}}, - "An alias for `--pretty`."), - 'header': ({'HistoryShow': {'header': True}}, header.help), - 'headers': ({'HistoryShow': {'header': True}}, - "An alias for `--header`."), - 'H': ({'HistoryShow': {'header': True}}, - "An alias for `--header`."), - 'color-always': ({'HistoryShow': {'color': 'Always'}}, - "Always output in color (e.g., when piping into `less -R`)."), - 'C': ({'HistoryShow': {'color': 'Always'}}, - "An alias for `--color-always`."), - 'color-never': ({'HistoryShow': {'color': 'Never'}}, - "Never show color, even if the output is a terminal.") - }) + color = CaselessStrEnum( + ("Never", "Always", "Auto"), + default_value="Auto", + help="Choose when to display color; requires `--pretty`.", + ).tag(config=True) - aliases=Dict(dict( - l = 'HistoryShow.limit', - limit = 'HistoryShow.limit', - d = 'HistoryShow.delimiter', - delimiter = 'HistoryShow.delimiter', - header = 'HistoryShow.header', - pretty = 'HistoryShow.pretty', - colors = 'HistoryShow.colors', - color = 'HistoryShow.color', - )) + flags = Dict( + { + "L": ( + {"HistoryShow": {"limit": -1}}, + "No limit; show all history entries.", + ), + "pretty": ({"HistoryShow": {"pretty": True}}, pretty.help), + "P": ({"HistoryShow": {"pretty": True}}, "An alias for `--pretty`."), + "header": ({"HistoryShow": {"header": True}}, header.help), + "headers": ({"HistoryShow": {"header": True}}, "An alias for `--header`."), + "H": ({"HistoryShow": {"header": True}}, "An alias for `--header`."), + "color-always": ( + {"HistoryShow": {"color": "Always"}}, + "Always output in color (e.g., when piping into `less -R`).", + ), + "C": ( + {"HistoryShow": {"color": "Always"}}, + "An alias for `--color-always`.", + ), + "color-never": ( + {"HistoryShow": {"color": "Never"}}, + "Never show color, even if the output is a terminal.", + ), + } + ) + + aliases = Dict( + dict( + l="HistoryShow.limit", + limit="HistoryShow.limit", + d="HistoryShow.delimiter", + delimiter="HistoryShow.delimiter", + header="HistoryShow.header", + pretty="HistoryShow.pretty", + colors="HistoryShow.colors", + color="HistoryShow.color", + ) + ) def start(self): profile_dir = Path(self.profile_dir.location) @@ -102,11 +110,13 @@ def start(self): self.limit = -1 # Grab the recent history from the current database. - entries = list(con.execute( - """SELECT session, line, source, source_raw FROM + entries = list( + con.execute( + """SELECT session, line, source, source_raw FROM history ORDER BY session DESC, line DESC LIMIT ?""", - (self.limit,) - )) + (self.limit,), + ) + ) # Emulate shell `history` command; newest at bottom entries.reverse() @@ -114,16 +124,17 @@ def start(self): if self.pretty: import sys from IPython.utils import PyColorize - pyformat = PyColorize.Parser(style=self.colors, - parent=self).format + + pyformat = PyColorize.Parser(style=self.colors, parent=self).format for entry in entries: header = f">>> # session {entry[0]}, line {entry[1]}\n" entry = header + str(entry[3]) - if (sys.stdout.isatty() or self.color == 'Always') \ - and self.color != 'Never': - print(pyformat(entry, out='str')) + if ( + sys.stdout.isatty() or self.color == "Always" + ) and self.color != "Never": + print(pyformat(entry, out="str")) else: # could set self.colors='NoColor' and just use pyformat # here, too, but this is probably marginally faster @@ -131,8 +142,7 @@ def start(self): else: if self.header: - print(self.delimiter.join(['session', 'line', 'source', - 'source_raw'])) + print(self.delimiter.join(["session", "line", "source", "source_raw"])) for entry in entries: # FIXME: even if you specify `-d,` this is not very # CSV-friendly, since the outer quote character (single or @@ -257,11 +267,13 @@ class HistoryApp(Application): name = u'ipython-history' description = "Manage the IPython history database." - subcommands = Dict(dict( - show = (HistoryShow, HistoryShow.description.splitlines()[0]), - trim = (HistoryTrim, HistoryTrim.description.splitlines()[0]), - clear = (HistoryClear, HistoryClear.description.splitlines()[0]), - )) + subcommands = Dict( + dict( + show=(HistoryShow, HistoryShow.description.splitlines()[0]), + trim=(HistoryTrim, HistoryTrim.description.splitlines()[0]), + clear=(HistoryClear, HistoryClear.description.splitlines()[0]), + ) + ) def start(self): if self.subapp is None: