Rework cli to accept ranges
This commit is contained in:
parent
77ec5ffff1
commit
32007d5c36
@ -2,7 +2,7 @@ import argparse
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
from .maildir import parse_maildir
|
||||
from .maildir import MailDir, TopSender, parse_maildir
|
||||
|
||||
|
||||
def parse_arguments() -> argparse.Namespace:
|
||||
@ -43,30 +43,118 @@ def cli():
|
||||
if args.verbose:
|
||||
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)
|
||||
if args.verbose:
|
||||
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:
|
||||
print("No senders found in the maildir", file=sys.stderr)
|
||||
return 0
|
||||
if not top_senders:
|
||||
print("No senders found in the maildir", file=sys.stderr)
|
||||
return 0
|
||||
|
||||
result = []
|
||||
for i, sender in enumerate(top_senders, 1):
|
||||
names_str = ", ".join(sender.names[:5]) # Limit to first 5 names
|
||||
if len(sender.names) > 5:
|
||||
names_str += f" and {len(sender.names) - 5} more"
|
||||
result = []
|
||||
for i, sender in enumerate(top_senders, 1):
|
||||
names_str = ", ".join(sender.names[:5]) # Limit to first 5 names
|
||||
if len(sender.names) > 5:
|
||||
names_str += f" and {len(sender.names) - 5} more"
|
||||
|
||||
result.append(
|
||||
f"{i}. {sender.email} - Email count: {sender.count} - Names used: {names_str}"
|
||||
result.append(
|
||||
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(
|
||||
[f"Top {len(top_senders)} senders in {maildir_path}:", "=" * 40, *result]
|
||||
)
|
||||
print(output)
|
||||
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