Compare commits

...

3 commits

Author SHA1 Message Date
6f5610bff9
run mypy at build time & fix type errors 2023-12-10 14:55:58 -06:00
69da2a4df9
Improve searching
* previously, would crash if an invalid RE was entered
 * make a literal space search for anything-whitespace-anything as a
   cheap fuzzy search facility
2023-12-10 13:34:45 -06:00
5015d9b700
sort requirements 2023-12-10 09:37:42 -06:00
4 changed files with 40 additions and 6 deletions

View file

@ -32,3 +32,10 @@ repos:
args: [ --fix, --preview ]
# Run the formatter.
- id: ruff-format
- repo: local
hooks:
- id: mypy
name: Run the mypy type linter
language: system
types: [python]
entry: 'make mypy'

View file

@ -75,9 +75,16 @@ Your Operating System may report that `ttotp` "pasted from the clipboard".
This is because `ttotp` tries to only clear values that it set,
by checking that the current clipboard value is equal to the value it pasted earlier.
Search for a key by pressing "/" and then entering a case insensitive regular expression.
Search for a key by pressing "/" and then entering a modified case insensitive regular expression.
Press Ctrl+A to show all keys again.
In this type of regular expression, a space ` ` stands for "zero or more characters, followed by whitespace, followed by zero or more characters"; the sequence backslash-space stands for a literal space.
This makes it easy to search for e.g., "Jay Doe / example.com" by entering "ja d ex", while not requiring any sophisticated fuzzy search technology.
Due to the simple way this is implemented, a space character inside a character class does not function as expected.
Since complicated regular expressions are likely seldom used, this is not likely to be a huge limitation.
Exit the app with Ctrl+C.
# In-memory storage of TOTPs

View file

@ -2,8 +2,8 @@
#
# SPDX-License-Identifier: MIT
textual
pyperclip
pyotp
click
platformdirs
pyotp
pyperclip
textual

View file

@ -176,11 +176,12 @@ class SearchInput(Input, can_focus=False):
]
def on_focus(self) -> None:
self.placeholder = "Enter regular expression"
self.placeholder = "Enter search expression"
def on_blur(self) -> None:
self.placeholder = "Type / to search"
self.can_focus = False
self.remove_class("error")
class TOTPButton(Button, can_focus=False):
@ -243,6 +244,18 @@ class TOTPData:
)
def search_preprocess(s: str) -> str:
def replace_escape_sequence(m: re.Match[str]) -> str:
s = m.group(0)
if s == "\\ ":
return " "
if s == " ":
return r".*\s+.*"
return s
return re.sub(r"\\.| |[^\\ ]+", replace_escape_sequence, s)
class TTOTP(App[None]):
CSS = """
VerticalScroll { min-height: 1; }
@ -256,6 +269,7 @@ class TTOTP(App[None]):
Button { border: none; height: 1; width: 3; min-width: 4 }
Horizontal { height: 1; }
Input { border: none; height: 1; width: 1fr; }
Input.error { background: $error; }
"""
BINDINGS = [
@ -339,7 +353,13 @@ class TTOTP(App[None]):
self.screen.focus_next()
def on_input_changed(self, event: Input.Changed) -> None:
rx = re.compile(event.value or ".", re.I)
haystack = event.value.replace(" ", ".* .*")
try:
rx = re.compile(haystack, re.I)
except re.error:
self.search.add_class("error")
return
self.search.remove_class("error")
for otp in self.otp_data:
parent = otp.name_widget.parent
assert parent is not None