mirror of
https://github.com/xonsh/xonsh.git
synced 2025-03-04 08:24:40 +01:00
* Add `history delete` #4929 * Add news and satisfy linter. * Added `history delete` for sqlite backend * Format with black. * Use REGEXP instead of LIKE for sqlite backend. * History delete: iterate over entries for sqlite backend. * Use '='-operator instead of LIKE.
This commit is contained in:
parent
b74d33abed
commit
44b661dcc0
5 changed files with 117 additions and 1 deletions
23
news/add_history_delete.rst
Normal file
23
news/add_history_delete.rst
Normal file
|
@ -0,0 +1,23 @@
|
|||
**Added:**
|
||||
|
||||
* Added ``history delete`` command to both the JSON and SQLite history backends allowing users to delete commands from history that matches a pattern.
|
||||
|
||||
**Changed:**
|
||||
|
||||
* <news item>
|
||||
|
||||
**Deprecated:**
|
||||
|
||||
* <news item>
|
||||
|
||||
**Removed:**
|
||||
|
||||
* <news item>
|
||||
|
||||
**Fixed:**
|
||||
|
||||
* <news item>
|
||||
|
||||
**Security:**
|
||||
|
||||
* <news item>
|
|
@ -174,6 +174,22 @@ class History:
|
|||
"""
|
||||
pass
|
||||
|
||||
def delete(self, pattern):
|
||||
"""Deletes the history of the current session for commands that match
|
||||
a user-provided pattern.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
pattern: str
|
||||
The regex pattern to match commands against.
|
||||
|
||||
Returns
|
||||
-------
|
||||
int
|
||||
The number of commands deleted from history.
|
||||
"""
|
||||
pass
|
||||
|
||||
@functools.cached_property
|
||||
def ignore_regex(self):
|
||||
compiled_regex = None
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
import collections
|
||||
import collections.abc as cabc
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
import threading
|
||||
import time
|
||||
|
@ -10,9 +11,13 @@ from xonsh.built_ins import XSH
|
|||
|
||||
try:
|
||||
import ujson as json
|
||||
|
||||
JSONDecodeError = json.JSONDecodeError # type: ignore
|
||||
except ImportError:
|
||||
import json # type: ignore
|
||||
|
||||
JSONDecodeError = json.decoder.JSONDecodeError # type: ignore
|
||||
|
||||
import xonsh.lazyjson as xlj
|
||||
import xonsh.tools as xt
|
||||
import xonsh.xoreutils.uptime as uptime
|
||||
|
@ -549,7 +554,7 @@ class JsonHistory(History):
|
|||
continue
|
||||
try:
|
||||
commands = json_file.load()["cmds"]
|
||||
except (json.decoder.JSONDecodeError, ValueError):
|
||||
except (JSONDecodeError, ValueError):
|
||||
# file is corrupted somehow
|
||||
if XSH.env.get("XONSH_DEBUG") > 0:
|
||||
msg = "xonsh history file {0!r} is not valid JSON"
|
||||
|
@ -596,3 +601,43 @@ class JsonHistory(History):
|
|||
|
||||
# Flush empty history object to disk, overwriting previous data.
|
||||
self.flush()
|
||||
|
||||
def delete(self, pattern):
|
||||
"""Deletes all entries in history which matches a pattern."""
|
||||
pattern = re.compile(pattern)
|
||||
|
||||
deleted = 0
|
||||
# First, delete any matching commands in the in-memory buffer.
|
||||
for i, cmd in enumerate(self.buffer):
|
||||
if pattern.match(cmd["inp"]):
|
||||
del self.buffer[i]
|
||||
deleted += 1
|
||||
|
||||
# Then, delete any matching commands on disk.
|
||||
while self.gc and self.gc.is_alive():
|
||||
time.sleep(0.011) # gc sleeps for 0.01 secs, sleep a beat longer
|
||||
for f in _xhj_get_history_files():
|
||||
try:
|
||||
json_file = xlj.LazyJSON(f, reopen=False)
|
||||
except ValueError:
|
||||
# Invalid json file
|
||||
continue
|
||||
try:
|
||||
file_content = json_file.load()
|
||||
commands = file_content["cmds"]
|
||||
for i, c in enumerate(commands):
|
||||
if pattern.match(c["inp"]):
|
||||
del commands[i]
|
||||
deleted += 1
|
||||
|
||||
file_content["cmds"] = commands
|
||||
with open(f, "w") as fp:
|
||||
xlj.ljdump(file_content, fp)
|
||||
except (JSONDecodeError, ValueError):
|
||||
# file is corrupted somehow
|
||||
if XSH.env.get("XONSH_DEBUG") > 0:
|
||||
msg = "xonsh history file {0!r} is not valid JSON"
|
||||
print(msg.format(f), file=sys.stderr)
|
||||
continue
|
||||
|
||||
return deleted
|
||||
|
|
|
@ -339,6 +339,19 @@ class HistoryAlias(xcli.ArgParserAlias):
|
|||
hist.clear()
|
||||
print("History cleared", file=sys.stderr)
|
||||
|
||||
@staticmethod
|
||||
def delete(pattern):
|
||||
"""Delete all commands matching a pattern
|
||||
|
||||
Parameters
|
||||
----------
|
||||
pattern:
|
||||
regex pattern to match against command history
|
||||
"""
|
||||
hist = XSH.history
|
||||
deleted = hist.delete(pattern)
|
||||
print(f"Deleted {deleted} entries from history")
|
||||
|
||||
@staticmethod
|
||||
def file(_stdout):
|
||||
"""Display the current history filename"""
|
||||
|
@ -466,6 +479,7 @@ class HistoryAlias(xcli.ArgParserAlias):
|
|||
parser.add_command(self.off)
|
||||
parser.add_command(self.on)
|
||||
parser.add_command(self.clear)
|
||||
parser.add_command(self.delete)
|
||||
parser.add_command(self.gc)
|
||||
parser.add_command(self.transfer)
|
||||
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
import collections
|
||||
import json
|
||||
import os
|
||||
import re
|
||||
import sqlite3
|
||||
import sys
|
||||
import threading
|
||||
|
@ -220,6 +221,17 @@ def xh_sqlite_wipe_session(sessionid=None, filename=None):
|
|||
c.execute(sql, (str(sessionid),))
|
||||
|
||||
|
||||
def xh_sqlite_delete_input_matching(pattern, filename=None):
|
||||
"""Deletes entries from the database where the input matches a pattern."""
|
||||
with _xh_sqlite_get_conn(filename=filename) as conn:
|
||||
c = conn.cursor()
|
||||
_xh_sqlite_create_history_table(c)
|
||||
for inp, *_ in _xh_sqlite_get_records(c):
|
||||
if pattern.match(inp):
|
||||
sql = f"DELETE FROM xonsh_history WHERE inp = '{inp}'"
|
||||
c.execute(sql)
|
||||
|
||||
|
||||
class SqliteHistoryGC(threading.Thread):
|
||||
"""Shell history garbage collection."""
|
||||
|
||||
|
@ -385,3 +397,9 @@ class SqliteHistory(History):
|
|||
self.cwds = []
|
||||
|
||||
xh_sqlite_wipe_session(sessionid=self.sessionid, filename=self.filename)
|
||||
|
||||
def delete(self, pattern):
|
||||
"""Deletes all entries in the database where the input matches a pattern."""
|
||||
xh_sqlite_delete_input_matching(
|
||||
pattern=re.compile(pattern), filename=self.filename
|
||||
)
|
||||
|
|
Loading…
Add table
Reference in a new issue