Rework cli to accept ranges
This commit is contained in:
parent
77ec5ffff1
commit
32007d5c36
@ -2,7 +2,7 @@ import argparse
|
|||||||
import sys
|
import sys
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
from .maildir import parse_maildir
|
from .maildir import MailDir, TopSender, parse_maildir
|
||||||
|
|
||||||
|
|
||||||
def parse_arguments() -> argparse.Namespace:
|
def parse_arguments() -> argparse.Namespace:
|
||||||
@ -43,30 +43,118 @@ def cli():
|
|||||||
if args.verbose:
|
if args.verbose:
|
||||||
print(f"Analyzing emails in {maildir_path}...")
|
print(f"Analyzing emails in {maildir_path}...")
|
||||||
|
|
||||||
|
run_loop(args, maildir_path)
|
||||||
|
|
||||||
|
return 0
|
||||||
|
|
||||||
|
|
||||||
|
def run_loop(args: argparse.Namespace, maildir_path: str | Path):
|
||||||
|
|
||||||
|
# Set up maildir
|
||||||
maildir = parse_maildir(maildir_path)
|
maildir = parse_maildir(maildir_path)
|
||||||
if args.verbose:
|
if args.verbose:
|
||||||
print(f"Found {len(maildir._df)} emails")
|
print(f"Found {len(maildir._df)} emails")
|
||||||
|
|
||||||
top_senders = maildir.get_top_n_senders(args.top)
|
# Main running loop
|
||||||
|
while True:
|
||||||
|
top_senders = maildir.get_top_n_senders(args.top)
|
||||||
|
|
||||||
if not top_senders:
|
if not top_senders:
|
||||||
print("No senders found in the maildir", file=sys.stderr)
|
print("No senders found in the maildir", file=sys.stderr)
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
result = []
|
result = []
|
||||||
for i, sender in enumerate(top_senders, 1):
|
for i, sender in enumerate(top_senders, 1):
|
||||||
names_str = ", ".join(sender.names[:5]) # Limit to first 5 names
|
names_str = ", ".join(sender.names[:5]) # Limit to first 5 names
|
||||||
if len(sender.names) > 5:
|
if len(sender.names) > 5:
|
||||||
names_str += f" and {len(sender.names) - 5} more"
|
names_str += f" and {len(sender.names) - 5} more"
|
||||||
|
|
||||||
result.append(
|
result.append(
|
||||||
f"{i}. {sender.email} - Email count: {sender.count} - Names used: {names_str}"
|
f"{i}. {sender.email} - Email count: {sender.count} - Names used: {names_str}"
|
||||||
|
)
|
||||||
|
|
||||||
|
output = "\n".join(
|
||||||
|
[f"Top {len(top_senders)} senders in {maildir_path}:", "=" * 40, *result]
|
||||||
)
|
)
|
||||||
|
|
||||||
output = "\n".join(
|
print(output)
|
||||||
[f"Top {len(top_senders)} senders in {maildir_path}:", "=" * 40, *result]
|
if not user_input_loop(top_senders, maildir):
|
||||||
)
|
break
|
||||||
|
|
||||||
print(output)
|
|
||||||
|
|
||||||
return 0
|
def user_input_loop(top_senders: list[TopSender], maildir: MailDir) -> bool:
|
||||||
|
user_input = input("> ").strip()
|
||||||
|
while handle_user_input(user_input, top_senders, maildir):
|
||||||
|
user_input = input("> ").strip()
|
||||||
|
|
||||||
|
|
||||||
|
def parse_selections(user_input, max_selection):
|
||||||
|
"""
|
||||||
|
Parse user input into a set of valid selections.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
user_input (str): User input string with numbers, comma-separated lists, or ranges
|
||||||
|
max_selection (int): Maximum allowed selection number
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
set: Set of valid selection numbers
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
ValueError: If any input is invalid or out of range
|
||||||
|
"""
|
||||||
|
selections = set()
|
||||||
|
|
||||||
|
# Clean up the input
|
||||||
|
user_input = user_input.strip()
|
||||||
|
|
||||||
|
# Split by comma
|
||||||
|
items = [item.strip() for item in user_input.split(",")]
|
||||||
|
|
||||||
|
for item in items:
|
||||||
|
if "-" in item:
|
||||||
|
# Handle range (e.g., "1-3" or "4 - 5")
|
||||||
|
range_parts = [part.strip() for part in item.split("-")]
|
||||||
|
if (
|
||||||
|
len(range_parts) != 2
|
||||||
|
or not range_parts[0].isdigit()
|
||||||
|
or not range_parts[1].isdigit()
|
||||||
|
):
|
||||||
|
raise ValueError(f"Invalid range format: {item}")
|
||||||
|
|
||||||
|
start = int(range_parts[0])
|
||||||
|
end = int(range_parts[1])
|
||||||
|
|
||||||
|
if start > end:
|
||||||
|
raise ValueError(
|
||||||
|
f"Invalid range: {item}. Start must be less than or equal to end."
|
||||||
|
)
|
||||||
|
|
||||||
|
selections.update(range(start, end + 1))
|
||||||
|
elif item.isdigit():
|
||||||
|
# Handle single number
|
||||||
|
selections.add(int(item))
|
||||||
|
else:
|
||||||
|
raise ValueError(f"Invalid input: {item}")
|
||||||
|
|
||||||
|
# Check if any selection is out of range
|
||||||
|
out_of_range = [s for s in selections if s < 1 or s > max_selection]
|
||||||
|
if out_of_range:
|
||||||
|
raise ValueError(
|
||||||
|
f"Selection(s) out of range: {', '.join(map(str, out_of_range))}. Valid range is 1-{max_selection}"
|
||||||
|
)
|
||||||
|
|
||||||
|
return selections
|
||||||
|
|
||||||
|
|
||||||
|
def handle_user_input(user_input, top_senders, maildir):
|
||||||
|
if user_input.lower() == "q":
|
||||||
|
return False
|
||||||
|
|
||||||
|
try:
|
||||||
|
selections = parse_selections(user_input, len(top_senders))
|
||||||
|
for selection in selections:
|
||||||
|
selected_sender = top_senders[selection - 1]
|
||||||
|
print(f"Selected {selected_sender.email}")
|
||||||
|
return True
|
||||||
|
except ValueError:
|
||||||
|
print("Please enter a valid number or 'q' to quit")
|
||||||
|
87
tests/test_cli.py
Normal file
87
tests/test_cli.py
Normal file
@ -0,0 +1,87 @@
|
|||||||
|
import pytest
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
# Import the function to test - assuming it's in a module called 'maildir_analyzer'
|
||||||
|
# Update this import to match your actual module structure
|
||||||
|
from maildirclean.cli import parse_selections
|
||||||
|
|
||||||
|
|
||||||
|
def test_single_number():
|
||||||
|
result = parse_selections("3", 5)
|
||||||
|
assert result == {3}
|
||||||
|
|
||||||
|
|
||||||
|
def test_comma_separated_list():
|
||||||
|
result = parse_selections("1,3,5", 5)
|
||||||
|
assert result == {1, 3, 5}
|
||||||
|
|
||||||
|
|
||||||
|
def test_range():
|
||||||
|
result = parse_selections("2-4", 5)
|
||||||
|
assert result == {2, 3, 4}
|
||||||
|
|
||||||
|
|
||||||
|
def test_range_with_spaces():
|
||||||
|
result = parse_selections("1 - 3", 5)
|
||||||
|
assert result == {1, 2, 3}
|
||||||
|
|
||||||
|
|
||||||
|
def test_combined_input():
|
||||||
|
result = parse_selections("1,3-5,7", 10)
|
||||||
|
assert result == {1, 3, 4, 5, 7}
|
||||||
|
|
||||||
|
|
||||||
|
def test_duplicate_numbers():
|
||||||
|
result = parse_selections("1,1,2,2-4,3", 5)
|
||||||
|
assert result == {1, 2, 3, 4} # Set removes duplicates
|
||||||
|
|
||||||
|
|
||||||
|
def test_invalid_format():
|
||||||
|
with pytest.raises(ValueError, match="Invalid input: abc"):
|
||||||
|
parse_selections("abc", 5)
|
||||||
|
|
||||||
|
|
||||||
|
def test_invalid_range_format():
|
||||||
|
with pytest.raises(ValueError, match="Invalid range format: 2-a"):
|
||||||
|
parse_selections("2-a", 5)
|
||||||
|
|
||||||
|
|
||||||
|
def test_inverted_range():
|
||||||
|
with pytest.raises(ValueError, match="Invalid range: 5-2"):
|
||||||
|
parse_selections("5-2", 5)
|
||||||
|
|
||||||
|
|
||||||
|
def test_out_of_range():
|
||||||
|
with pytest.raises(ValueError, match="Selection.*out of range"):
|
||||||
|
parse_selections("3,6,8", 5)
|
||||||
|
|
||||||
|
|
||||||
|
def test_mix_valid_and_invalid():
|
||||||
|
with pytest.raises(ValueError):
|
||||||
|
parse_selections("1,abc,3", 5)
|
||||||
|
|
||||||
|
|
||||||
|
def test_empty_input():
|
||||||
|
with pytest.raises(ValueError):
|
||||||
|
parse_selections("", 5)
|
||||||
|
|
||||||
|
|
||||||
|
def test_whitespace_only():
|
||||||
|
with pytest.raises(ValueError):
|
||||||
|
parse_selections(" ", 5)
|
||||||
|
|
||||||
|
|
||||||
|
def test_max_boundary():
|
||||||
|
# Test the boundary case
|
||||||
|
result = parse_selections("5", 5)
|
||||||
|
assert result == {5}
|
||||||
|
|
||||||
|
|
||||||
|
def test_complex_input():
|
||||||
|
result = parse_selections("1-2, 4, 6-8", 10)
|
||||||
|
assert result == {1, 2, 4, 6, 7, 8}
|
||||||
|
|
||||||
|
|
||||||
|
def test_negative_numbers():
|
||||||
|
with pytest.raises(ValueError, match="Invalid range format:*"):
|
||||||
|
parse_selections("-1,2", 5)
|
Loading…
x
Reference in New Issue
Block a user