import logging import re import sys from config.redmine import REDMINE_ISSUE_URL_TEMPLATE class ConsoleFormatter(logging.Formatter): """Форматирует консольные логи и делает номера задач Redmine кликабельными.""" ISSUE_PATTERN = re.compile(r"#(?P\d+)") def format(self, record): message = super().format(record) return self.ISSUE_PATTERN.sub(self._format_issue_link, message) def _format_issue_link(self, match): issue_id = match.group("issue_id") issue_text = match.group(0) issue_url = REDMINE_ISSUE_URL_TEMPLATE.format(issue_id=issue_id) return f"\033]8;;{issue_url}\033\\{issue_text}\033]8;;\033\\" def configure_logging(level: int = logging.INFO): """Настраивает короткий консольный лог и подробный файловый лог.""" file_handler = logging.FileHandler('app.log', encoding='utf-8') file_handler.setFormatter( logging.Formatter('%(asctime)s %(name)s:%(lineno)d %(levelname)s %(message)s') ) file_handler.setLevel(level) console_handler = logging.StreamHandler(sys.stdout) console_handler.setFormatter(ConsoleFormatter('%(message)s')) console_handler.setLevel(level) logging.basicConfig( level=level, handlers=[file_handler, console_handler], force=True, ) from api.clockify import ClockifyAPI # noqa: E402 from api.redmine import RedmineAPI # noqa: E402 from config import arg_parser, config # noqa: E402 from db.db import Database # noqa: E402 from services.tracking import TrackingService # noqa: E402 from utils import check_envs, db_update, get_start_end_dates # noqa: E402 def main(): """Запускает CLI-сценарий переноса трудочасов из Clockify в Redmine.""" args = arg_parser.parse_args() log_level = logging.DEBUG if args.debug else logging.INFO configure_logging(log_level) if is_debug := args.debug: config.MODE = "debug" if is_dry_run := args.dry_run: config.MODE = "debug" logger = logging.getLogger(__name__) db = Database(config.DB_NAME, config.SQL_PATH) red = RedmineAPI(config.REDMINE_TOKEN) clock = ClockifyAPI( token=config.CLOCKIFY_TOKEN, workspace_id=config.WORKSPACE_ID, user_id=config.USER_ID, db=db, redmine_api=red, ) start, end = get_start_end_dates(args) if not check_envs(clock): return if args.init_db: db.run_migrations(config.MIGRATIONS_PATH) db_update(clock, db, red) return try: TrackingService( clock=clock, red=red, db=db, is_debug=is_debug, is_dry_run=is_dry_run, ).track_period(start=start, end=end, page_size=args.page_size) except Exception as e: logger.error(e) if __name__ == "__main__": main()