from __future__ import annotations import os import argparse from pathlib import Path from collections import OrderedDict import pandas as pd # ------------------------------------------------------------ # Cross-platform root resolver (Windows / Linux / macOS) # ------------------------------------------------------------ def resolve_data_root() -> Path: """ Priority: 1) Env var IDRAC_DATA_DIR (absolute/relative OK) 2) nearest parent of this file that contains a 'data' folder 3) ./data under current working directory """ env = os.getenv("IDRAC_DATA_DIR") if env: return Path(env).expanduser().resolve() here = Path(__file__).resolve() for p in [here] + list(here.parents): if (p / "data").is_dir(): return (p / "data").resolve() return (Path.cwd() / "data").resolve() DATA_ROOT = resolve_data_root() # ------------------------------------------------------------ # Utilities # ------------------------------------------------------------ def read_lines_any_encoding(path: Path) -> list[str]: """Read text file trying common encodings (utf-8/utf-8-sig/cp949/euc-kr/latin-1).""" encodings = ["utf-8-sig", "utf-8", "cp949", "euc-kr", "latin-1"] for enc in encodings: try: with path.open("r", encoding=enc, errors="strict") as f: return f.read().splitlines() except Exception: continue # last resort with replacement with path.open("r", encoding="utf-8", errors="replace") as f: return f.read().splitlines() def parse_txt_with_st(file_path: Path) -> dict: """ Parse a .txt file: - First line becomes 'S/T' - Remaining lines in 'Key: Value' form Keeps insertion order. """ lines = read_lines_any_encoding(file_path) if not lines: return {} data = OrderedDict() data["S/T"] = lines[0].strip() for raw in lines[1:]: line = raw.strip() if not line or ":" not in line: continue key, value = line.split(":", 1) data[key.strip()] = value.strip() return dict(data) def collect_file_list(input_dir: Path, list_file: Path | None) -> list[Path]: """ 1) list_file가 주어지고 존재하면: 그 목록 순서대로 .txt를 input_dir에서 찾음 2) 없으면: input_dir 안의 *.txt 전체를 파일명 오름차순으로 사용 """ files: list[Path] = [] if list_file and list_file.is_file(): names = [x.strip() for x in read_lines_any_encoding(list_file) if x.strip()] for name in names: p = input_dir / f"{name}.txt" if p.is_file(): files.append(p) else: print(f"[WARN] 파일을 찾을 수 없습니다: {p.name}") return files # fallback: 디렉토리 스캔 files = sorted(input_dir.glob("*.txt")) if not files: print(f"[WARN] 입력 폴더에 .txt 파일이 없습니다: {input_dir}") return files def main(): parser = argparse.ArgumentParser( description="GUID/GPU 시리얼 텍스트들을 하나의 Excel로 병합" ) parser.add_argument( "--preset", choices=["guid", "gpu"], default="guid", help="경로 프리셋 선택 (guid: 기존 GUID 경로, gpu: gpu_serial 폴더)" ) parser.add_argument( "--input-dir", type=Path, default=None, help="입력 텍스트 폴더(기본: preset에 따름)" ) parser.add_argument( "--list-file", type=Path, default=None, help="처리할 파일명 목록(txt). 없으면 폴더 내 *.txt 전체 처리" ) parser.add_argument( "--output-xlsx", type=Path, default=None, help="출력 엑셀 경로(기본: preset에 따름)" ) args = parser.parse_args() # ---- Preset 기본값 설정 ---- if args.preset == "guid": default_input_dir = Path(os.getenv("GUID_TXT_DIR", DATA_ROOT / "guid_file")) default_list_file = Path(os.getenv("GUID_LIST_FILE", DATA_ROOT / "server_list" / "guid_list.txt")) default_output = Path(os.getenv("GUID_OUTPUT_XLSX", DATA_ROOT / "idrac_info" / "XE9680_GUID.xlsx")) else: # gpu default_input_dir = Path(os.getenv("GPU_TXT_DIR", DATA_ROOT / "gpu_serial")) default_list_file = Path(os.getenv("GPU_LIST_FILE", DATA_ROOT / "server_list" / "gpu_serial_list.txt")) default_output = Path(os.getenv("GPU_OUTPUT_XLSX", DATA_ROOT / "idrac_info" / "GPU_SERIALS.xlsx")) input_dir: Path = args.input_dir or default_input_dir list_file: Path | None = args.list_file or (default_list_file if default_list_file.is_file() else None) output_xlsx: Path = args.output_xlsx or default_output # 출력 폴더 보장 output_xlsx.parent.mkdir(parents=True, exist_ok=True) if not input_dir.is_dir(): raise FileNotFoundError(f"입력 폴더가 없습니다: {input_dir}") # 파일 목록 수집 txt_files = collect_file_list(input_dir, list_file) # 데이터 누적 rows: list[dict] = [] for txt_path in txt_files: rows.append(parse_txt_with_st(txt_path)) if not rows: print("[INFO] 병합할 데이터가 없습니다.") return # DataFrame (모든 키의 합집합 컬럼 생성) df = pd.DataFrame(rows) # No 열 선두 삽입 df.insert(0, "No", range(1, len(df) + 1)) # 저장 df.to_excel(output_xlsx, index=False) print(f"엑셀 파일이 생성되었습니다: {output_xlsx}") if __name__ == "__main__": main()