diff --git a/.gitignore b/.gitignore index dd5859d..b5ad1dc 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ *.csv *.code-workspace +build diff --git a/build/lib/pen_tracker/__init__.py b/build/lib/pen_tracker/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/build/lib/pen_tracker/cli.py b/build/lib/pen_tracker/cli.py deleted file mode 100644 index a73c052..0000000 --- a/build/lib/pen_tracker/cli.py +++ /dev/null @@ -1,364 +0,0 @@ -import os -import csv -import argparse -import json -from datetime import datetime -from .engine import PenTracker, Pen, InkTracker, Ink - -def validate_date(date_str: str) -> bool: - """Validate date in YYYY-MM-DD format.""" - if date_str == "N/A": - return True - try: - datetime.strptime(date_str, '%Y-%m-%d') - return True - except ValueError: - return False - -class CLITracker(PenTracker): - - def __init__(self, storage_file=None): - super().__init__(storage_file) - self.ink_tracker = InkTracker() - - def add_pen(self): - print("\n--- Add New Fountain Pen ---") - new_pen_data = {} - fields = [ - ("Make", "Make"), ("Model", "Model"), ("Date Purchased (YYYY-MM-DD)", "Date-Purchased"), - ("Vendor", "Vendor"), ("Nib Size", "Nib"), ("Nib Material", "Nib-Material"), - ("Body Material", "Body"), ("Cap Material", "Cap"), ("Postable", "Post"), - ("Current Ink", "Current-Ink"), ("Inked Date (YYYY-MM-DD)", "Inked-date"), ("Notes", "Notes") - ] - - for label, key in fields: - while True: - if key == 'Current-Ink': - value = self.select_ink() - break - value = input(f"Enter {label}: ").strip() - if not value: - value = "N/A" - break - if key in ['Date-Purchased', 'Inked-date']: - if validate_date(value): - break - else: - print("Invalid date format. Use YYYY-MM-DD.") - else: - break - new_pen_data[key] = value - - # Map to Pen fields - mapped_data = {self.key_map.get(k, k): v for k, v in new_pen_data.items()} - new_pen = Pen(**mapped_data) - self.pens.append(new_pen) - self.save_data() - print("\n[✔] Pen added successfully to Pens.csv!") - - def edit_pen(self): - """Allows the user to select a pen and modify specific fields.""" - if not self.pens: - print("\n[!] No pens available to edit.") - return - - # Show summary so user knows which ID to pick - print("\n--- Select Pen to Edit ---") - self.show_summary_list() - - try: - idx = int(input("\nEnter the ID of the pen to edit: ")) - pen = self.pens[idx] - except (ValueError, IndexError): - print("[!] Invalid ID.") - return - - print("\n--- Editing Pen Details ---") - print("(Press ENTER without typing to keep the current value)\n") - - # We iterate through headers so we don't miss any column - for header in self.headers: - field = self.key_map.get(header, header) - current_val = getattr(pen, field) - while True: - if header == 'Current-Ink': - print(f"Current Ink: {current_val}") - change_ink = input("Change ink? (y/n): ").strip().lower() - if change_ink == 'y': - new_val = self.select_ink() - break - else: - new_val = "" - break - new_val = input(f"{header} [{current_val}]: ").strip() - if not new_val: - break - if header in ['Date-Purchased', 'Inked-date']: - if validate_date(new_val): - break - else: - print("Invalid date format. Use YYYY-MM-DD.") - else: - break - if new_val: # If the user actually typed something new - setattr(pen, field, new_val) - - self.save_data() - print("\n[✔] Pen updated successfully!") - - def show_summary_list(self): - """Helper to print a list without the interactive menu logic.""" - print(f"{'ID':<4} | {'MAKE':<12} | {'MODEL':<12} | {'INK':<15}") - print("-" * 55) - for idx, pen in enumerate(self.pens): - make = pen.Make[:12] - model = pen.Model[:12] - ink = pen.Current_Ink[:15] - print(f"{idx:<4} | {make:<12} | {model:<12} | {ink:<15}") - - def view_all_pens(self): - if not self.pens: - print("\n[!] Your collection is currently empty.") - return - - print("\n" + "="*85) - print(f"{'ID':<4} | {'MAKE':<12} | {'MODEL':<12} | {'INK':<15} | {'INKED DATE':<12}") - print("-" * 85) - - for idx, pen in enumerate(self.pens): - make = pen.Make[:12] - model = pen.Model[:12] - ink = pen.Current_Ink[:15] - inkdate = pen.Inked_date[:12] - print(f"{idx:<4} | {make:<12} | {model:<12} | {ink:<15} | {inkdate:<12}") - - print("="*85) - - choice = input("\nEnter ID to see full details (or 'b' to go back): ") - if choice.lower() != 'b': - try: - self.view_pen_details(int(choice)) - except (ValueError, IndexError): - print("[!] Invalid ID.") - - def view_pen_details(self, index): - pen = self.pens[index] - print("\n" + "═"*45) - print(f"{' FOUNTAIN PEN DETAILS ':=^45}") - for header in self.headers: - field = self.key_map.get(header, header) - value = getattr(pen, field) - print(f"{header:<20}: {value}") - print("═"*45) - input("\nPress Enter to return to menu...") - - def delete_pen(self): - if not self.pens: - print("\n[!] Nothing to delete.") - return - self.show_summary_list() - try: - idx = int(input("\nEnter the ID of the pen to delete: ")) - removed = self.pens.pop(idx) - self.save_data() - print(f"\n[!] Removed: {removed.Make} {removed.Model}") - except (ValueError, IndexError): - print("[!] Invalid ID.") - - def add_ink(self): - print("\n--- Add New Ink ---") - new_ink_data = {} - fields = [ - ("Vendor", "Vendor"), ("Name", "Name"), ("Color", "Color"), - ("Purchased (YYYY-MM-DD)", "Purchased"), ("Size", "Size"), ("Notes", "Notes") - ] - - for label, key in fields: - while True: - value = input(f"Enter {label}: ").strip() - if not value: - value = "N/A" - break - if key == 'Purchased': - if validate_date(value): - break - else: - print("Invalid date format. Use YYYY-MM-DD.") - else: - break - new_ink_data[key] = value - - new_ink = Ink(**new_ink_data) - self.ink_tracker.inks.append(new_ink) - self.ink_tracker.save_data() - print("\n[✔] Ink added successfully to inks.csv!") - - def view_all_inks(self): - if not self.ink_tracker.inks: - print("\n[!] Your ink collection is currently empty.") - return - - print("\n" + "="*80) - print(f"{'ID':<4} | {'VENDOR':<12} | {'NAME':<20} | {'COLOR':<15} | {'SIZE':<10}") - print("-" * 80) - - for idx, ink in enumerate(self.ink_tracker.inks): - vendor = ink.Vendor[:12] - name = ink.Name[:20] - color = ink.Color[:15] - size = ink.Size[:10] - print(f"{idx:<4} | {vendor:<12} | {name:<20} | {color:<15} | {size:<10}") - - print("="*80) - - def select_ink(self): - """Helper method to select an ink from the list.""" - if not self.ink_tracker.inks: - print("[!] No inks available. Add some inks first.") - return "N/A" - - self.view_all_inks() - try: - idx = int(input("\nEnter the ID of the ink to select (or -1 for N/A): ")) - if idx == -1: - return "N/A" - ink = self.ink_tracker.inks[idx] - return ink.Name - except (ValueError, IndexError): - print("[!] Invalid ID.") - return "N/A" - -def clear_screen(): - os.system('cls' if os.name == 'nt' else 'clear') - -def main(): - # This is the entry point defined in pyproject.toml - parser = argparse.ArgumentParser(description="Fountain Pen Tracker") - parser.add_argument('--csv', default=None, help='Path to CSV file') - subparsers = parser.add_subparsers(dest='command', help='Commands') - - # Add command - add_parser = subparsers.add_parser('add', help='Add a new pen') - add_parser.add_argument('--make', required=True) - add_parser.add_argument('--model', required=True) - add_parser.add_argument('--date-purchased') - add_parser.add_argument('--vendor') - add_parser.add_argument('--nib') - add_parser.add_argument('--nib-material') - add_parser.add_argument('--body') - add_parser.add_argument('--cap') - add_parser.add_argument('--post') - add_parser.add_argument('--current-ink') - add_parser.add_argument('--inked-date') - add_parser.add_argument('--notes') - - # List command - list_parser = subparsers.add_parser('list', help='List all pens') - - # Export command - export_parser = subparsers.add_parser('export', help='Export to JSON') - export_parser.add_argument('--output', default='pens.json', help='Output file') - - # Ink commands - add_ink_parser = subparsers.add_parser('add-ink', help='Add a new ink') - add_ink_parser.add_argument('--vendor', required=True) - add_ink_parser.add_argument('--name', required=True) - add_ink_parser.add_argument('--color') - add_ink_parser.add_argument('--purchased') - add_ink_parser.add_argument('--size') - add_ink_parser.add_argument('--notes') - - list_inks_parser = subparsers.add_parser('list-inks', help='List all inks') - - args = parser.parse_args() - - tracker = CLITracker(args.csv) - - if args.command == 'add': - # Validate dates - if args.date_purchased and not validate_date(args.date_purchased): - print("Invalid date-purchased format.") - return - if args.inked_date and not validate_date(args.inked_date): - print("Invalid inked-date format.") - return - pen_data = { - 'Make': args.make, - 'Model': args.model, - 'Date-Purchased': args.date_purchased or 'N/A', - 'Vendor': args.vendor or 'N/A', - 'Nib': args.nib or 'N/A', - 'Nib-Material': args.nib_material or 'N/A', - 'Body': args.body or 'N/A', - 'Cap': args.cap or 'N/A', - 'Post': args.post or 'N/A', - 'Current-Ink': args.current_ink or 'N/A', - 'Inked-date': args.inked_date or 'N/A', - 'Notes': args.notes or 'N/A' - } - mapped_data = {tracker.key_map.get(k, k): v for k, v in pen_data.items()} - new_pen = Pen(**mapped_data) - tracker.pens.append(new_pen) - tracker.save_data() - print("Pen added successfully.") - elif args.command == 'list': - tracker.view_all_pens() - elif args.command == 'export': - with open(args.output, 'w') as f: - json.dump([asdict(p) for p in tracker.pens], f, indent=2) - print(f"Exported to {args.output}") - elif args.command == 'add-ink': - if args.purchased and not validate_date(args.purchased): - print("Invalid purchased date format.") - return - ink_data = { - 'Vendor': args.vendor, - 'Name': args.name, - 'Color': args.color or 'N/A', - 'Purchased': args.purchased or 'N/A', - 'Size': args.size or 'N/A', - 'Notes': args.notes or 'N/A' - } - new_ink = Ink(**ink_data) - tracker.ink_tracker.inks.append(new_ink) - tracker.ink_tracker.save_data() - print("Ink added successfully.") - elif args.command == 'list-inks': - tracker.view_all_inks() - else: - # Interactive mode - while True: - print("\n🖋️ FOUNTAIN PEN TRACKER (CSV Edition)") - print("1. View Collection Summary") - print("2. Add New Pen") - print("3. Edit a Pen") - print("4. Delete a Pen") - print("5. View Ink Collection") - print("6. Add New Ink") - print("7. Exit") - - choice = input("\nSelect an option: ") - - if choice == '1': - clear_screen() - tracker.view_all_pens() - elif choice == '2': - clear_screen() - tracker.add_pen() - elif choice == '3': - clear_screen() - tracker.edit_pen() - elif choice == '4': - clear_screen() - tracker.delete_pen() - elif choice == '5': - clear_screen() - tracker.view_all_inks() - elif choice == '6': - clear_screen() - tracker.add_ink() - elif choice == '7': - print("Goodbye! Happy writing! ✒️") - break - else: - print("[!] Invalid selection. Please try again.") diff --git a/build/lib/pen_tracker/engine.py b/build/lib/pen_tracker/engine.py deleted file mode 100644 index d3117c2..0000000 --- a/build/lib/pen_tracker/engine.py +++ /dev/null @@ -1,163 +0,0 @@ -import csv -import os -import logging -from dataclasses import dataclass, asdict -from typing import List - -logger = logging.getLogger(__name__) - -@dataclass -class Ink: - Vendor: str = "N/A" - Name: str = "N/A" - Color: str = "N/A" - Purchased: str = "N/A" - Size: str = "N/A" - Notes: str = "N/A" - -@dataclass -class Pen: - Make: str = "N/A" - Model: str = "N/A" - Date_Purchased: str = "N/A" - Vendor: str = "N/A" - Nib: str = "N/A" - Nib_Material: str = "N/A" - Body: str = "N/A" - Cap: str = "N/A" - Post: str = "N/A" - Current_Ink: str = "N/A" - Inked_date: str = "N/A" - Notes: str = "N/A" - -class InkTracker: - def __init__(self, storage_file: str = None): - if storage_file is None: - storage_file = os.getenv('INK_TRACKER_CSV') - if storage_file is None: - data_home = os.getenv('XDG_DATA_HOME', os.path.expanduser('~/.local/share')) - app_data_dir = os.path.join(data_home, 'pen-tracker') - os.makedirs(app_data_dir, exist_ok=True) - storage_file = os.path.join(app_data_dir, 'inks.csv') - self.storage_file = storage_file - self.headers = ['Vendor', 'Name', 'Color', 'Purchased', 'Size', 'Notes'] - self.inks: List[Ink] = self.load_data() - - def _sort_inks(self): - """Sorts the inks list by Vendor, then by Name alphabetically.""" - self.inks.sort(key=lambda x: (x.Vendor.lower(), x.Name.lower())) - - def load_data(self) -> List[Ink]: - """Loads data from the CSV file.""" - if not os.path.exists(self.storage_file): - self._create_empty_csv() - return [] - - inks = [] - try: - with open(self.storage_file, mode='r', encoding='utf-8-sig') as f: - reader = csv.DictReader(f) - if reader.fieldnames: - reader.fieldnames = [h.strip() for h in reader.fieldnames] - for row in reader: - if None in row: - extras = row.pop(None) - if extras: - extra_text = ",".join(extras).strip() - if extra_text: - row['Notes'] = (row.get('Notes') or '') - if row['Notes']: - row['Notes'] += ", " - row['Notes'] += extra_text - clean_row = { - k.strip(): (v.strip() if v else "N/A") - for k, v in row.items() if k is not None - } - inks.append(Ink(**clean_row)) - inks.sort(key=lambda x: (x.Vendor.lower(), x.Name.lower())) - except Exception as e: - logger.error(f"Error loading inks CSV: {e}") - return inks - - def _create_empty_csv(self): - """Creates the CSV file with headers if it is missing.""" - with open(self.storage_file, mode='w', newline='', encoding='utf-8') as f: - writer = csv.DictWriter(f, fieldnames=self.headers) - writer.writeheader() - - def save_data(self): - """Sorts the data before writing to ensure persistent alphabetical order.""" - self._sort_inks() - with open(self.storage_file, mode='w', newline='', encoding='utf-8') as f: - writer = csv.DictWriter(f, fieldnames=self.headers) - writer.writeheader() - for ink in self.inks: - writer.writerow(asdict(ink)) - -class PenTracker: - def __init__(self, storage_file: str = None): - if storage_file is None: - storage_file = os.getenv('PEN_TRACKER_CSV') - if storage_file is None: - data_home = os.getenv('XDG_DATA_HOME', os.path.expanduser('~/.local/share')) - app_data_dir = os.path.join(data_home, 'pen-tracker') - os.makedirs(app_data_dir, exist_ok=True) - storage_file = os.path.join(app_data_dir, 'pens.csv') - self.storage_file = storage_file - self.headers = [ - 'Make', 'Model', 'Date-Purchased', 'Vendor', 'Nib', - 'Nib-Material', 'Body', 'Cap', 'Post', - 'Current-Ink', 'Inked-date', 'Notes' - ] - self.key_map = { - 'Date-Purchased': 'Date_Purchased', - 'Inked-date': 'Inked_date', - 'Nib-Material': 'Nib_Material', - 'Current-Ink': 'Current_Ink' - } - self.reverse_key_map = {v: k for k, v in self.key_map.items()} - self.pens: List[Pen] = self.load_data() - - def _sort_pens(self): - """Sorts the pens list by Make, then by Model alphabetically.""" - self.pens.sort(key=lambda x: (x.Make.lower(), x.Model.lower())) - - def load_data(self) -> List[Pen]: - """Loads data from the CSV file.""" - if not os.path.exists(self.storage_file): - self._create_empty_csv() - return [] - - pens = [] - try: - # Standard, clean way to open the file - with open(self.storage_file, mode='r', encoding='utf-8-sig') as f: - reader = csv.DictReader(f) - for row in reader: - # Strip whitespace from keys and values, handle empty values - clean_row = {k.strip(): (v.strip() if v else "N/A") for k, v in row.items()} - # Map keys to dataclass field names - mapped_row = {self.key_map.get(k, k): v for k, v in clean_row.items()} - pens.append(Pen(**mapped_row)) - pens.sort(key=lambda x: (x.Make.lower(), x.Model.lower())) - except Exception as e: - logger.error(f"Error loading CSV: {e}") - return pens - - def _create_empty_csv(self): - """Creates the CSV file with headers if it is missing.""" - with open(self.storage_file, mode='w', newline='', encoding='utf-8') as f: - writer = csv.DictWriter(f, fieldnames=self.headers) - writer.writeheader() - - def save_data(self): - """Sorts the data before writing to ensure persistent alphabetical order.""" - self._sort_pens() - with open(self.storage_file, mode='w', newline='', encoding='utf-8') as f: - writer = csv.DictWriter(f, fieldnames=self.headers) - writer.writeheader() - for pen in self.pens: - row = asdict(pen) - # Map back to CSV keys - csv_row = {k.replace('_', '-'): v for k, v in row.items()} - writer.writerow(csv_row) diff --git a/build/lib/pen_tracker/tui.py b/build/lib/pen_tracker/tui.py deleted file mode 100644 index ed32fb1..0000000 --- a/build/lib/pen_tracker/tui.py +++ /dev/null @@ -1,231 +0,0 @@ -from textual.app import App, ComposeResult -from textual.screen import Screen -from textual.widgets import Label, Input, Button, Header, Footer, DataTable -from textual.containers import Vertical, Horizontal -from textual.binding import Binding -from .engine import PenTracker, Pen, InkTracker, Ink -# --- TUI SCREENS --- - -class PenFormScreen(Screen): - """A screen for adding or editing a pen.""" - - def __init__(self, tracker, existing_pen=None): - super().__init__() - self.tracker = tracker - self.existing_pen = existing_pen - - def compose(self) -> ComposeResult: - with Vertical(id="form-container"): - yield Label("📝 NEW PEN" if not self.existing_pen else "✏️ EDIT PEN") - - for header in self.tracker.headers: - field = self.tracker.key_map.get(header, header) - val = getattr(self.existing_pen, field) if self.existing_pen else "" - yield Input(value=val, placeholder=header, id=header) - - with Horizontal(): - yield Button("Save", variant="success", id="save_cmd") - yield Button("Cancel", variant="error", id="cancel_cmd") - - def on_button_pressed(self, event: Button.Pressed) -> None: - if event.button.id == "cancel_cmd": - self.dismiss() - return - - new_data = {} - for header in self.tracker.headers: - try: - input_widget = self.query_one(f"#{header}", Input) - new_data[header] = input_widget.value if input_widget.value.strip() else "N/A" - except Exception: - new_data[header] = "N/A" - - # Map to Pen fields - mapped_data = {self.tracker.key_map.get(k, k): v for k, v in new_data.items()} - new_pen = Pen(**mapped_data) - - if self.existing_pen is not None and self.existing_pen in self.tracker.pens: - idx = self.tracker.pens.index(self.existing_pen) - self.tracker.pens[idx] = new_pen - else: - self.tracker.pens.append(new_pen) - - # save_data() handles the sorting internally - self.tracker.save_data() - self.dismiss(new_pen) - -class InkFormScreen(Screen): - """A screen for adding or editing an ink.""" - - def __init__(self, ink_tracker, existing_ink=None): - super().__init__() - self.ink_tracker = ink_tracker - self.existing_ink = existing_ink - - def compose(self) -> ComposeResult: - with Vertical(id="form-container"): - yield Label("🖋️ NEW INK" if not self.existing_ink else "✏️ EDIT INK") - - for header in self.ink_tracker.headers: - val = getattr(self.existing_ink, header) if self.existing_ink else "" - yield Input(value=val, placeholder=header, id=header) - - with Horizontal(): - yield Button("Save", variant="success", id="save_cmd") - yield Button("Cancel", variant="error", id="cancel_cmd") - - def on_button_pressed(self, event: Button.Pressed) -> None: - if event.button.id == "cancel_cmd": - self.dismiss() - return - - new_data = {} - for header in self.ink_tracker.headers: - try: - input_widget = self.query_one(f"#{header}", Input) - new_data[header] = input_widget.value if input_widget.value.strip() else "N/A" - except Exception: - new_data[header] = "N/A" - - new_ink = Ink(**new_data) - - if self.existing_ink is not None and self.existing_ink in self.ink_tracker.inks: - idx = self.ink_tracker.inks.index(self.existing_ink) - self.ink_tracker.inks[idx] = new_ink - else: - self.ink_tracker.inks.append(new_ink) - - self.ink_tracker.save_data() - self.dismiss(new_ink) - -class PenTrackerApp(App): - """The Main TUI Application.""" - - CSS = """ - #form-container { - width: 60%; - height: auto; - border: heavy white; - padding: 1 2; - margin: 5 10; - background: $panel; - } - Label { - width: 100%; - text-align: center; - text-style: bold; - margin-bottom: 1; - } - Input { - margin-bottom: 1; - } - Horizontal { - align: center middle; - height: auto; - } - Button { - margin: 0 1; - } - DataTable { - height: 1fr; - } - """ - - BINDINGS = [ - Binding("d", "delete_selected", "Delete Selected"), - Binding("a", "add_new", "Add New"), - Binding("i", "toggle_mode", "Toggle Pens/Inks"), - Binding("q", "quit", "Quit"), - ] - - def compose(self) -> ComposeResult: - yield Header() - yield DataTable(id="pen_table") - yield Footer() - - def on_mount(self) -> None: - self.tracker = PenTracker() - self.ink_tracker = InkTracker() - self.mode = "pens" # "pens" or "inks" - self._refresh_table() - - def _refresh_table(self): - table = self.query_one("#pen_table", DataTable) - table.clear(columns=True) - - if self.mode == "pens": - display_cols = ['Make', 'Model', 'Nib', 'Nib-Material', 'Body','Cap', 'Current-Ink', 'Inked-date', 'Notes'] - table.add_columns(*display_cols) - items = self.tracker.pens - else: # inks - display_cols = ['Vendor', 'Name', 'Color', 'Purchased', 'Size', 'Notes'] - table.add_columns(*display_cols) - items = self.ink_tracker.inks - - for idx, item in enumerate(items): - if self.mode == "pens": - row_values = [getattr(item, c.replace('-','_')) for c in display_cols] - else: - row_values = [getattr(item, c) for c in display_cols] - table.add_row(*row_values, key=str(idx)) - - def action_add_new(self) -> None: - if self.mode == "pens": - form = PenFormScreen(self.tracker) - else: - form = InkFormScreen(self.ink_tracker) - self.push_screen(form, self.handle_form_result) - - def action_edit_selected(self, index_str: str): - try: - idx = int(index_str) - if self.mode == "pens": - existing_item = self.tracker.pens[idx] - form = PenFormScreen(self.tracker, existing_item) - else: - existing_item = self.ink_tracker.inks[idx] - form = InkFormScreen(self.ink_tracker, existing_item) - self.push_screen(form, self.handle_form_result) - except (ValueError, IndexError): - pass - - def handle_form_result(self, updated_pen_data) -> None: - if updated_pen_data: - self._refresh_table() - self.notify("Collection Updated & Sorted!", title="Success") - - def action_delete_selected(self) -> None: - table = self.query_one("#pen_table", DataTable) - try: - table = self.query_one("#pen_table", DataTable) - row_node = table.get_row_at_cursor() - idx = int(row_node.key.value) - if self.mode == "pens": - removed = self.tracker.pens.pop(idx) - self.tracker.save_data() - self.notify(f"Deleted {removed.Make}", title="Removed") - else: - removed = self.ink_tracker.inks.pop(idx) - self.ink_tracker.save_data() - self.notify(f"Deleted {removed.Name}", title="Removed") - self._refresh_table() - except Exception: - self.notify("No item selected to delete", title="Error", severity="error") - - def action_toggle_mode(self) -> None: - self.mode = "inks" if self.mode == "pens" else "pens" - self._refresh_table() - mode_name = "Pens" if self.mode == "pens" else "Inks" - self.notify(f"Switched to {mode_name} mode", title="Mode Changed") - - def on_data_table_cell_selected(self, event: DataTable.CellSelected) -> None: - try: - idx = int(event.cell_key.row_key.value) - self.action_edit_selected(str(idx)) - except (AttributeError, ValueError): - self.notify("Error selecting row", title="Error", severity="error") - -def main(): - # This is the entry point defined in pyproject.toml - app = PenTrackerApp() - app.run() diff --git a/pyproject.toml b/pyproject.toml index 4167766..c2786e5 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "pen-tracker" -version = "0.4.1" +version = "0.4.2" authors = [ { name="Don Harper", email="don@donharper.org" }, ] diff --git a/src/pen_tracker.egg-info/PKG-INFO b/src/pen_tracker.egg-info/PKG-INFO index 1446e53..af55fcb 100644 --- a/src/pen_tracker.egg-info/PKG-INFO +++ b/src/pen_tracker.egg-info/PKG-INFO @@ -1,6 +1,6 @@ Metadata-Version: 2.4 Name: pen-tracker -Version: 0.4.1 +Version: 0.4.2 Summary: A fountain pen collection tracker. Author-email: Don Harper Requires-Python: >=3.8 diff --git a/src/pen_tracker/cli.py b/src/pen_tracker/cli.py index a73c052..f1e5987 100644 --- a/src/pen_tracker/cli.py +++ b/src/pen_tracker/cli.py @@ -108,31 +108,25 @@ class CLITracker(PenTracker): def show_summary_list(self): """Helper to print a list without the interactive menu logic.""" - print(f"{'ID':<4} | {'MAKE':<12} | {'MODEL':<12} | {'INK':<15}") - print("-" * 55) + print(f"{'ID':<4} | {'MAKE':<12} | {'MODEL':<12} | {'BODY':<12} | {'CAP':<12} | {'NIB':<3} | {'INK':<15} | {'INKED DATE':<12}") + print("-" * 102) for idx, pen in enumerate(self.pens): make = pen.Make[:12] model = pen.Model[:12] + body = pen.Body[:12] + cap = pen.Cap[:12] + nib = pen.Nib[:3] ink = pen.Current_Ink[:15] - print(f"{idx:<4} | {make:<12} | {model:<12} | {ink:<15}") + inkdate = pen.Inked_date[:12] + print(f"{idx:<4} | {make:<12} | {model:<12} | {body:<12} | {cap:<12} | {nib:<3} | {ink:<15} | {inkdate:<12}") def view_all_pens(self): if not self.pens: print("\n[!] Your collection is currently empty.") return - print("\n" + "="*85) - print(f"{'ID':<4} | {'MAKE':<12} | {'MODEL':<12} | {'INK':<15} | {'INKED DATE':<12}") - print("-" * 85) - - for idx, pen in enumerate(self.pens): - make = pen.Make[:12] - model = pen.Model[:12] - ink = pen.Current_Ink[:15] - inkdate = pen.Inked_date[:12] - print(f"{idx:<4} | {make:<12} | {model:<12} | {ink:<15} | {inkdate:<12}") - - print("="*85) + self.show_summary_list() + print("="*102) choice = input("\nEnter ID to see full details (or 'b' to go back): ") if choice.lower() != 'b': @@ -302,7 +296,7 @@ def main(): tracker.save_data() print("Pen added successfully.") elif args.command == 'list': - tracker.view_all_pens() + tracker.show_summary_list() elif args.command == 'export': with open(args.output, 'w') as f: json.dump([asdict(p) for p in tracker.pens], f, indent=2)