feat(cli): dynamic default range, --version, --verbose, --debug, --url, --api-key, --config
- #17: default date range falls back to current month when .env dates are missing - #18: add --version, --verbose, --debug flags - #20: add --url, --api-key, --config CLI overrides - Config supports CLI overrides for URL/API key and explicit config file loading - Update README with new CLI options - Add tests for new flags and config overrides Closes #17, closes #18, closes #20
This commit is contained in:
@@ -1,10 +1,12 @@
|
||||
import argparse
|
||||
import logging
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
from datetime import datetime
|
||||
from typing import List, Optional
|
||||
|
||||
from . import __version__
|
||||
from .client import fetch_issues_with_spent_time
|
||||
from .config import Config
|
||||
from .formatters.factory import get_console_formatter, get_formatter_by_extension
|
||||
@@ -41,8 +43,7 @@ def main(argv: Optional[List[str]] = None) -> int:
|
||||
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)",
|
||||
help="Date range in format YYYY-MM-DD--YYYY-MM-DD (default: current month or from .env)",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--compact",
|
||||
@@ -59,8 +60,33 @@ def main(argv: Optional[List[str]] = None) -> int:
|
||||
parser.add_argument(
|
||||
"--no-time", action="store_true", help="Do not include spent time into table"
|
||||
)
|
||||
parser.add_argument("--url", help="Override Redmine URL from .env (REDMINE_URL)")
|
||||
parser.add_argument("--api-key", help="Override Redmine API key from .env (REDMINE_API_KEY)")
|
||||
parser.add_argument("--config", help="Path to .env config file")
|
||||
parser.add_argument("--verbose", action="store_true", help="Enable verbose output")
|
||||
parser.add_argument("--debug", action="store_true", help="Enable debug output")
|
||||
parser.add_argument(
|
||||
"--version",
|
||||
action="version",
|
||||
version=f"%(prog)s {__version__}",
|
||||
help="Show version and exit",
|
||||
)
|
||||
args = parser.parse_args(argv)
|
||||
|
||||
# CLI-переопределения имеют приоритет над .env/env.
|
||||
if args.config:
|
||||
Config.load_config(args.config)
|
||||
Config.set_redmine_url(args.url)
|
||||
Config.set_redmine_api_key(args.api_key)
|
||||
|
||||
# Настройка уровня логирования
|
||||
if args.debug:
|
||||
logging.basicConfig(level=logging.DEBUG, format="%(levelname)s: %(message)s")
|
||||
elif args.verbose:
|
||||
logging.basicConfig(level=logging.INFO, format="%(levelname)s: %(message)s")
|
||||
else:
|
||||
logging.basicConfig(level=logging.WARNING, format="%(levelname)s: %(message)s")
|
||||
|
||||
try:
|
||||
Config.validate()
|
||||
except ValueError as e:
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import os
|
||||
from datetime import date, timedelta
|
||||
from typing import Union
|
||||
|
||||
from dotenv import load_dotenv
|
||||
@@ -11,12 +12,32 @@ TRUE_VALUES = {"1", "true", "yes", "on"}
|
||||
|
||||
|
||||
class Config:
|
||||
_cli_url: str | None = None
|
||||
_cli_api_key: str | None = None
|
||||
|
||||
@classmethod
|
||||
def load_config(cls, path: str) -> None:
|
||||
"""Загружает переменные из указанного .env-файла с override."""
|
||||
load_dotenv(path, override=True)
|
||||
|
||||
@classmethod
|
||||
def set_redmine_url(cls, url: str) -> None:
|
||||
cls._cli_url = url.strip().rstrip("/") if url else None
|
||||
|
||||
@classmethod
|
||||
def set_redmine_api_key(cls, key: str) -> None:
|
||||
cls._cli_api_key = key.strip() if key else None
|
||||
|
||||
@classmethod
|
||||
def get_redmine_url(cls) -> str:
|
||||
if cls._cli_url is not None:
|
||||
return cls._cli_url
|
||||
return os.getenv("REDMINE_URL", "").strip().rstrip("/")
|
||||
|
||||
@classmethod
|
||||
def get_redmine_api_key(cls) -> str:
|
||||
if cls._cli_api_key is not None:
|
||||
return cls._cli_api_key
|
||||
return os.getenv("REDMINE_API_KEY", "").strip()
|
||||
|
||||
@classmethod
|
||||
@@ -53,8 +74,17 @@ class Config:
|
||||
default_to_date = os.getenv("DEFAULT_TO_DATE", "").strip()
|
||||
if default_from_date and default_to_date:
|
||||
return f"{default_from_date}--{default_to_date}"
|
||||
# fallback hardcoded
|
||||
return "2025-12-19--2026-01-31"
|
||||
|
||||
# fallback: текущий месяц
|
||||
today = date.today()
|
||||
start = today.replace(day=1)
|
||||
# последний день месяца: берём первое число следующего месяца и вычитаем день
|
||||
if today.month == 12:
|
||||
next_month = today.replace(year=today.year + 1, month=1, day=1)
|
||||
else:
|
||||
next_month = today.replace(month=today.month + 1, day=1)
|
||||
end = next_month - timedelta(days=1)
|
||||
return f"{start.isoformat()}--{end.isoformat()}"
|
||||
|
||||
@classmethod
|
||||
def validate(cls) -> None:
|
||||
|
||||
Reference in New Issue
Block a user