Coverage for src / mysingle / cli / protos / utils.py: 0%

62 statements  

« prev     ^ index     » next       coverage.py v7.12.0, created at 2025-12-02 00:58 +0900

1""" 

2공통 유틸리티 함수 모듈. 

3 

4색상 출력, 로깅, 테이블 포맷팅 등의 공통 기능을 제공합니다. 

5""" 

6 

7from __future__ import annotations 

8 

9import os 

10import sys 

11from enum import Enum 

12 

13 

14class Color(str, Enum): 

15 """ANSI 색상 코드""" 

16 

17 RESET = "\033[0m" 

18 BOLD = "\033[1m" 

19 DIM = "\033[2m" 

20 

21 # 기본 색상 

22 RED = "\033[31m" 

23 GREEN = "\033[32m" 

24 YELLOW = "\033[33m" 

25 BLUE = "\033[34m" 

26 MAGENTA = "\033[35m" 

27 CYAN = "\033[36m" 

28 WHITE = "\033[37m" 

29 

30 # 밝은 색상 

31 BRIGHT_RED = "\033[91m" 

32 BRIGHT_GREEN = "\033[92m" 

33 BRIGHT_YELLOW = "\033[93m" 

34 BRIGHT_BLUE = "\033[94m" 

35 BRIGHT_MAGENTA = "\033[95m" 

36 BRIGHT_CYAN = "\033[96m" 

37 

38 

39class LogLevel(str, Enum): 

40 """로그 레벨""" 

41 

42 DEBUG = "DEBUG" 

43 INFO = "INFO" 

44 SUCCESS = "SUCCESS" 

45 WARNING = "WARNING" 

46 ERROR = "ERROR" 

47 STEP = "STEP" 

48 

49 

50def colorize(text: str, color: Color, bold: bool = False) -> str: 

51 """텍스트에 색상 적용""" 

52 # 터미널이 색상을 지원하지 않거나 파이프로 리다이렉트된 경우 색상 코드 생략 

53 if not sys.stdout.isatty() or os.environ.get("NO_COLOR"): 

54 return text 

55 prefix = f"{Color.BOLD.value}{color.value}" if bold else color.value 

56 return f"{prefix}{text}{Color.RESET.value}" 

57 

58 

59def log(msg: str, level: LogLevel = LogLevel.INFO) -> None: 

60 """로그 출력 (레벨별 색상 및 아이콘 적용)""" 

61 icons = { 

62 LogLevel.DEBUG: "🔍", 

63 LogLevel.INFO: "ℹ️ ", 

64 LogLevel.SUCCESS: "✅", 

65 LogLevel.WARNING: "⚠️ ", 

66 LogLevel.ERROR: "❌", 

67 LogLevel.STEP: "📋", 

68 } 

69 colors = { 

70 LogLevel.DEBUG: Color.DIM, 

71 LogLevel.INFO: Color.CYAN, 

72 LogLevel.SUCCESS: Color.GREEN, 

73 LogLevel.WARNING: Color.YELLOW, 

74 LogLevel.ERROR: Color.RED, 

75 LogLevel.STEP: Color.BRIGHT_BLUE, 

76 } 

77 icon = icons.get(level, " ") 

78 color = colors.get(level, Color.RESET) 

79 

80 if level == LogLevel.STEP: 

81 print(colorize(f"\n{icon} {msg}", color, bold=True), flush=True) 

82 else: 

83 print(f"{icon} {colorize(msg, color)}", flush=True) 

84 

85 

86def log_header(title: str) -> None: 

87 """섹션 헤더 출력""" 

88 border = "=" * 60 

89 print() 

90 print(colorize(border, Color.BRIGHT_CYAN, bold=True)) 

91 print(colorize(f" {title}", Color.BRIGHT_CYAN, bold=True)) 

92 print(colorize(border, Color.BRIGHT_CYAN, bold=True)) 

93 print() 

94 

95 

96def log_table(headers: list[str], rows: list[list[str]]) -> None: 

97 """테이블 형식으로 출력""" 

98 if not rows: 

99 return 

100 

101 # 각 컬럼의 최대 너비 계산 

102 col_widths = [len(h) for h in headers] 

103 for row in rows: 

104 for i, cell in enumerate(row): 

105 col_widths[i] = max(col_widths[i], len(str(cell))) 

106 

107 # 헤더 출력 

108 header_line = " ".join(h.ljust(col_widths[i]) for i, h in enumerate(headers)) 

109 print(colorize(header_line, Color.BRIGHT_CYAN, bold=True)) 

110 print(colorize("-" * len(header_line), Color.CYAN)) 

111 

112 # 데이터 행 출력 

113 for row in rows: 

114 row_line = " ".join( 

115 str(cell).ljust(col_widths[i]) for i, cell in enumerate(row) 

116 ) 

117 print(row_line) 

118 print()