import sys import argparse from typing import List, Optional from redminelib.resources import Issue from .config import Config from .client import fetch_issues_with_spent_time from .formatter import format_compact, format_table from .formatter_odt import format_odt from .formatter_csv import format_csv def parse_date_range(date_arg: str) -> tuple[str, str]: if "--" not in date_arg: raise ValueError("Date range must be in format YYYY-MM-DD--YYYY-MM-DD") parts = date_arg.split("--", 1) if len(parts) != 2: raise ValueError("Invalid date range format") return parts[0].strip(), parts[1].strip() def main(argv: Optional[List[str]] = None) -> int: parser = argparse.ArgumentParser( prog="redmine-reporter", description="Generate Redmine issue report based on your time entries." ) parser.add_argument( "--date", default=Config.get_default_date_range(), # help="Date range in format YYYY-MM-DD--YYYY-MM-DD (default: %(default)s)" help="Date range in format YYYY-MM-DD--YYYY-MM-DD (default from .env or %(default)s)" ) parser.add_argument( "--compact", action="store_true", help="Use compact plain-text output instead of table" ) parser.add_argument( "--output", help="Path to output .odt file (e.g., report.odt). If omitted, prints to stdout." ) parser.add_argument( "--author", default="", help="Override author name from .env (REDMINE_AUTHOR)" ) parser.add_argument( "--no-time", action="store_true", help="Do not include spent time into table" ) args = parser.parse_args(argv) try: Config.validate() except ValueError as e: print(f"❌ Configuration error: {e}", file=sys.stderr) return 1 try: from_date, to_date = parse_date_range(args.date) except ValueError as e: print(f"❌ Date error: {e}", file=sys.stderr) return 1 try: issue_hours = fetch_issues_with_spent_time(from_date, to_date) except Exception as e: print(f"❌ Redmine API error: {e}", file=sys.stderr) return 1 if issue_hours is None: print("ℹ️ No time entries found in the given period.", file=sys.stderr) return 0 print(f"✅ Total issues: {len(issue_hours)} [{args.date}]") if args.output: if not (args.output.endswith(".odt") or args.output.endswith(".csv")): print("❌ Output file must end with .odt or .csv", file=sys.stderr) return 1 try: if args.output.endswith(".odt"): doc = format_odt( issue_hours, author=Config.get_author(args.author), from_date=from_date, to_date=to_date, fill_time=not args.no_time ) doc.save(args.output) elif args.output.endswith(".csv"): csv_content = format_csv(issue_hours, fill_time=not args.no_time) with open(args.output, "w", encoding="utf-8", newline="") as f: f.write(csv_content) print(f"✅ Report saved to {args.output}") except ImportError as e: if args.output.endswith(".odt"): print("❌ odfpy is not installed. Install with: pip install odfpy", file=sys.stderr) else: print(f"❌ Import error: {e}", file=sys.stderr) return 1 except Exception as e: fmt = "ODT" if args.output.endswith(".odt") else "CSV" print(f"❌ {fmt} export error: {e}", file=sys.stderr) return 1 else: try: if args.compact: output = format_compact(issue_hours, fill_time=not args.no_time) else: output = format_table(issue_hours, fill_time=not args.no_time) print(output) except Exception as e: print(f"❌ Formatting error: {e}", file=sys.stderr) return 1 return 0 if __name__ == "__main__": sys.exit(main())