diff --git a/src/fosslight_util/cover.py b/src/fosslight_util/cover.py index 95ee8a5..cc8a12e 100644 --- a/src/fosslight_util/cover.py +++ b/src/fosslight_util/cover.py @@ -4,13 +4,22 @@ # SPDX-License-Identifier: Apache-2.0 import os +import re import sys +import yaml from fosslight_util.help import print_package_version +from fosslight_util.time import format_display_time, format_running_time + + +def dump_result_log(result_log): + log_text = yaml.safe_dump(result_log, allow_unicode=True, sort_keys=True) + log_text = re.sub(r"Running time: ['\"](.+?)['\"]\n", r"Running time: \1\n", log_text) + return log_text.strip() class CoverItem: tool_name_key = "Tool information" - start_time_key = "Start time" + start_time_key = "Running time" python_ver_key = "Python version" analyzed_path_key = "Analyzed path" excluded_path_key = "Excluded path" @@ -23,7 +32,8 @@ class CoverItem: "fosslight_binary" ] - def __init__(self, tool_name="", start_time="", input_path="", comment="", exclude_path=[], simple_mode=True): + def __init__(self, tool_name="", start_time="", input_path="", comment="", exclude_path=[], simple_mode=True, + finish_time=""): if simple_mode: self.tool_name = f'{tool_name} v{print_package_version(tool_name, "", False)}' else: @@ -34,11 +44,9 @@ def __init__(self, tool_name="", start_time="", input_path="", comment="", exclu ]) self.tool_name = f'{first_pkg} ({remaining_pkgs})' - if start_time: - date, time = start_time.split('_') - self.start_time = f'{date}, {time[0:2]}:{time[2:4]}' - else: - self.start_time = "" + self.start_time = start_time + self.finish_time = finish_time + self.running_time = self._format_running_time() self.input_path = os.path.abspath(input_path) self.exclude_path = ", ".join(exclude_path) self.comment = comment @@ -57,6 +65,19 @@ def get_sort_order(self): def __lt__(self, other): return self.get_sort_order() < other.get_sort_order() + def set_finish_time(self, finish_time): + self.finish_time = finish_time + self.running_time = self._format_running_time() + + def _format_running_time(self): + if not self.start_time: + return "" + + if not self.finish_time: + return format_display_time(self.start_time) + + return format_running_time(self.start_time, self.finish_time) + def create_merged_comment(self, cover_items): if not cover_items: return "" @@ -69,7 +90,7 @@ def create_merged_comment(self, cover_items): def get_print_json(self): json_item = {} json_item[self.tool_name_key] = self.tool_name - json_item[self.start_time_key] = self.start_time + json_item[self.start_time_key] = self.running_time json_item[self.python_ver_key] = self.python_version json_item[self.analyzed_path_key] = self.input_path json_item[self.excluded_path_key] = self.exclude_path diff --git a/src/fosslight_util/oss_item.py b/src/fosslight_util/oss_item.py index 354f2ba..c23bb27 100644 --- a/src/fosslight_util/oss_item.py +++ b/src/fosslight_util/oss_item.py @@ -8,6 +8,7 @@ import hashlib from fosslight_util.constant import LOGGER_NAME, FOSSLIGHT_SCANNER, COMMENT_DELIMITER from fosslight_util.cover import CoverItem +from fosslight_util.time import current_timestamp_utc from typing import List, Dict _logger = logging.getLogger(LOGGER_NAME) @@ -211,6 +212,11 @@ def set_cover_comment(self, value): else: self.cover.comment = value + def set_cover_finish_time(self, finish_time=""): + if not finish_time: + finish_time = current_timestamp_utc() + self.cover.set_finish_time(finish_time) + def get_cover_comment(self): return [item.strip() for item in self.cover.comment.split(COMMENT_DELIMITER)] diff --git a/src/fosslight_util/time.py b/src/fosslight_util/time.py new file mode 100644 index 0000000..8886029 --- /dev/null +++ b/src/fosslight_util/time.py @@ -0,0 +1,60 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# Copyright (c) 2024 LG Electronics Inc. +# SPDX-License-Identifier: Apache-2.0 + +from datetime import datetime, timezone, timedelta + +_TIMESTAMP_FMT = '%Y%m%d_%H%M%S' +_DISPLAY_TZ = timezone(timedelta(hours=9)) # KST (UTC+9) + + +def current_timestamp_utc() -> str: + """Return current time as UTC wall-clock string for internal storage.""" + return datetime.now(timezone.utc).strftime(_TIMESTAMP_FMT) + + +def timestamp_for_filename(utc_timestamp: str) -> str: + """Convert UTC storage timestamp to KST for file and directory names.""" + if not utc_timestamp: + return current_timestamp_for_filename() + return _parse_utc_timestamp(utc_timestamp).astimezone(_DISPLAY_TZ).strftime(_TIMESTAMP_FMT) + + +def current_timestamp_for_filename() -> str: + """Return current time as KST wall-clock string for file and directory names.""" + return timestamp_for_filename(current_timestamp_utc()) + + +def _parse_utc_timestamp(value: str) -> datetime: + return datetime.strptime(value, _TIMESTAMP_FMT).replace(tzinfo=timezone.utc) + + +def format_display_time(value: str) -> str: + return _parse_utc_timestamp(value).astimezone(_DISPLAY_TZ).strftime('%Y%m%d_%H:%M:%S') + + +def _format_duration_compact(total_seconds: int) -> str: + total_seconds = max(0, total_seconds) + hours, remaining_seconds = divmod(total_seconds, 3600) + minutes, seconds = divmod(remaining_seconds, 60) + + duration_items = [] + if hours > 0: + duration_items.append(f'{hours}h') + if minutes > 0: + duration_items.append(f'{minutes}m') + duration_items.append(f'{seconds}s') + return f'({" ".join(duration_items)})' + + +def format_running_time(start_time: str, finish_time: str) -> str: + start_utc = _parse_utc_timestamp(start_time) + finish_utc = _parse_utc_timestamp(finish_time) + total_seconds = int((finish_utc - start_utc).total_seconds()) + + start_display = start_utc.astimezone(_DISPLAY_TZ).strftime('%Y%m%d_%H:%M:%S') + finish_display = finish_utc.astimezone(_DISPLAY_TZ).strftime('%Y%m%d_%H:%M:%S') + duration = _format_duration_compact(total_seconds) + + return f'{start_display} ~ {finish_display} {duration}'