from __future__ import annotations import os 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() # ------------------------------------------------------------ # Paths (can be overridden with env vars if needed) # ------------------------------------------------------------ SERVER_LIST_DIR = Path(os.getenv("GUID_SERVER_LIST_DIR", DATA_ROOT / "server_list")) SERVER_LIST_FILE = Path(os.getenv("GUID_LIST_FILE", SERVER_LIST_DIR / "guid_list.txt")) GUID_TXT_DIR = Path(os.getenv("GUID_TXT_DIR", DATA_ROOT / "guid_file")) OUTPUT_XLSX = Path( os.getenv("GUID_OUTPUT_XLSX", DATA_ROOT / "idrac_info" / "XE9680_GUID.xlsx") ) # Make sure output directory exists OUTPUT_XLSX.parent.mkdir(parents=True, exist_ok=True) # ------------------------------------------------------------ # 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 GUID .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) # ------------------------------------------------------------ # 슬롯 우선순위 설정 # ------------------------------------------------------------ # 환경변수에서 슬롯 우선순위 읽기 (예: "38,39,37,36,32,33,34,35,31,40") slot_priority_str = os.getenv("GUID_SLOT_PRIORITY", "") if slot_priority_str: SLOT_PRIORITY = [s.strip() for s in slot_priority_str.split(",") if s.strip()] print(f"[INFO] 사용자 지정 슬롯 우선순위: {SLOT_PRIORITY}") else: # 기본 우선순위 (10개) SLOT_PRIORITY = ['38', '39', '37', '36', '32', '33', '34', '35', '31', '40'] print(f"[INFO] 기본 슬롯 우선순위 사용: {SLOT_PRIORITY}") # ------------------------------------------------------------ # Load list of file basenames from guid_list.txt # ------------------------------------------------------------ if not SERVER_LIST_FILE.is_file(): raise FileNotFoundError(f"guid_list.txt not found: {SERVER_LIST_FILE}") file_names = [x.strip() for x in read_lines_any_encoding(SERVER_LIST_FILE) if x.strip()] # ------------------------------------------------------------ # Collect rows # ------------------------------------------------------------ rows: list[dict] = [] for name in file_names: txt_path = GUID_TXT_DIR / f"{name}.txt" if not txt_path.is_file(): print(f"[WARN] 파일을 찾을 수 없습니다: {txt_path.name}") # still append at least S/T if you want a row placeholder # rows.append({"S/T": name}) continue parsed_data = parse_txt_with_st(txt_path) # 슬롯 우선순위에 따라 데이터 재정렬 reordered_data = OrderedDict() reordered_data["S/T"] = parsed_data.get("S/T", "") # 슬롯 데이터를 우선순위 순서대로 추가 # 슬롯 데이터를 우선순위 순서대로 추가하며 GUID 문자열 재구성 new_guid_list = [] for slot_num in SLOT_PRIORITY: slot_key = f"Slot.{slot_num}" val = parsed_data.get(slot_key) # 데이터가 있으면 컬럼 추가 if val: reordered_data[slot_key] = val # GUID 재구성을 위한 수집 (Not Found 제외, 포맷 확인) if val != "Not Found" and ":" in val: # 예: 3825:F303:0085:07A6 -> 0x3825F303008507A6 clean_hex = val.replace(":", "").upper() new_guid_list.append(f"0x{clean_hex}") # 1순위: 재구성된 GUID (사용자가 지정한 슬롯 순서대로) # 2순위: 파일에 있던 원본 GUID if new_guid_list: reordered_data["GUID"] = ";".join(new_guid_list) elif "GUID" in parsed_data: reordered_data["GUID"] = parsed_data["GUID"] # 나머지 필드들 추가 (슬롯이 아닌 것들) for key, value in parsed_data.items(): if key not in reordered_data: reordered_data[key] = value rows.append(dict(reordered_data)) # Build DataFrame (union of keys across all rows) df = pd.DataFrame(rows) # Prepend No column (1..N) df.insert(0, "No", range(1, len(df) + 1)) # Save to Excel df.to_excel(OUTPUT_XLSX, index=False) print(f"엑셀 파일이 생성되었습니다: {OUTPUT_XLSX}")