Initial project upload

This commit is contained in:
Kim.KANGHEE
2026-05-05 17:14:11 +09:00
commit 122b73d254
282 changed files with 72135 additions and 0 deletions
+172
View File
@@ -0,0 +1,172 @@
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가 주어지고 존재하면: 그 목록 순서대로 <name>.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 / "repository" / "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 / "temp" / "staging" / "XE9680_GUID.xlsx"))
else: # gpu
default_input_dir = Path(os.getenv("GPU_TXT_DIR", DATA_ROOT / "repository" / "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 / "temp" / "staging" / "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()
+166
View File
@@ -0,0 +1,166 @@
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 / "repository" / "guid_file"))
OUTPUT_XLSX = Path(
os.getenv("GUID_OUTPUT_XLSX", DATA_ROOT / "temp" / "staging" / "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}")
+109
View File
@@ -0,0 +1,109 @@
from __future__ import annotations
import os
from pathlib import Path
import pandas as pd
# ---------------------------------------------
# Cross-platform paths (Windows & Linux/Mac)
# ---------------------------------------------
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()
SERVER_LIST_DIR = DATA_ROOT / "server_list"
SERVER_LIST_FILE = SERVER_LIST_DIR / "server_list.txt"
MAC_TXT_DIR = DATA_ROOT / "repository" / "mac"
OUTPUT_XLSX = DATA_ROOT / "temp" / "staging" / "mac_info.xlsx"
# Ensure output directory exists
OUTPUT_XLSX.parent.mkdir(parents=True, exist_ok=True)
# ---------------------------------------------
# Helpers
# ---------------------------------------------
def read_lines_any_encoding(path: Path) -> list[str]:
"""Read a text file trying common encodings (handles Windows & UTF-8)."""
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()
# ---------------------------------------------
# Load server list (file names without .txt)
# ---------------------------------------------
if not SERVER_LIST_FILE.is_file():
raise FileNotFoundError(f"server_list.txt not found: {SERVER_LIST_FILE}")
file_names = read_lines_any_encoding(SERVER_LIST_FILE)
data_list: list[str] = []
index_list: list[int | str] = []
sequence_number = 1
for name in file_names:
# normalize and skip blanks
base = (name or "").strip()
if not base:
continue
txt_path = MAC_TXT_DIR / f"{base}.txt"
if not txt_path.is_file():
# if a file is missing, keep row aligned with an empty line
data_list.append("")
index_list.append("")
continue
lines = read_lines_any_encoding(txt_path)
for i, line in enumerate(lines):
cleaned = (line or "").strip().upper()
if cleaned:
data_list.append(cleaned)
if i == 0: # first line is always the service tag
index_list.append(sequence_number)
sequence_number += 1
else:
index_list.append("") # keep column blank if not service tag
else:
data_list.append("")
index_list.append("")
print(f"Length of index_list: {len(index_list)}")
print(f"Length of data_list: {len(data_list)}")
# ---------------------------------------------
# Save to Excel
# ---------------------------------------------
df = pd.DataFrame({
"Index": index_list, # will be column A
"Content": data_list, # will be column B
})
# header=False to start at column A without headers, index=False to omit row numbers
df.to_excel(OUTPUT_XLSX, index=False, header=False)
print(f"Saved: {OUTPUT_XLSX}")
+80
View File
@@ -0,0 +1,80 @@
7Y3J2H4
G8KS3H4
F9KS3H4
2BKS3H4
JW3J2H4
49KS3H4
BX3J2H4
5X3J2H4
HX3J2H4
1NRS3H4
7PRS3H4
4X3J2H4
29KS3H4
CY3J2H4
3RPJ5H4
GQPJ5H4
C4DY4H4
G3DY4H4
79KS3H4
8X3J2H4
9QPJ5H4
B4DY4H4
45DY4H4
HPPJ5H4
88KS3H4
JX3J2H4
7Z3J2H4
DVRS3H4
18KS3H4
5Y3J2H4
H3DY4H4
94DY4H4
F3DY4H4
24DY4H4
69PG4H4
19PG4H4
J1DY4H4
4QPJ5H4
D4DY4H4
8TPJ5H4
43DY4H4
53DY4H4
2QPJ5H4
CPPJ5H4
44DY4H4
FQPJ5H4
G2DY4H4
H7KS3H4
D9KS3H4
49PG4H4
5RPJ5H4
9RPJ5H4
2JKS3H4
23DY4H4
BNRS3H4
79PG4H4
JKPG4H4
GNRS3H4
FY3J2H4
9NRS3H4
B8PG4H4
39PG4H4
99PG4H4
48PG4H4
D8PG4H4
3PRS3H4
D8KS3H4
1X3J2H4
9X3J2H4
2X3J2H4
J2DY4H4
BQPJ5H4
2Z3J2H4
GX3J2H4
C9KS3H4
3WRG4H4
1JKS3H4
29PG4H4
DQPJ5H4
68DY4H4
+80
View File
@@ -0,0 +1,80 @@
7Y3J2H4
G8KS3H4
F9KS3H4
2BKS3H4
JW3J2H4
49KS3H4
BX3J2H4
5X3J2H4
HX3J2H4
1NRS3H4
7PRS3H4
4X3J2H4
29KS3H4
CY3J2H4
3RPJ5H4
GQPJ5H4
C4DY4H4
G3DY4H4
79KS3H4
8X3J2H4
9QPJ5H4
B4DY4H4
45DY4H4
HPPJ5H4
88KS3H4
JX3J2H4
7Z3J2H4
DVRS3H4
18KS3H4
5Y3J2H4
H3DY4H4
94DY4H4
F3DY4H4
24DY4H4
69PG4H4
19PG4H4
J1DY4H4
4QPJ5H4
D4DY4H4
8TPJ5H4
43DY4H4
53DY4H4
2QPJ5H4
CPPJ5H4
44DY4H4
FQPJ5H4
G2DY4H4
H7KS3H4
D9KS3H4
49PG4H4
5RPJ5H4
9RPJ5H4
2JKS3H4
23DY4H4
BNRS3H4
79PG4H4
JKPG4H4
GNRS3H4
FY3J2H4
9NRS3H4
B8PG4H4
39PG4H4
99PG4H4
48PG4H4
D8PG4H4
3PRS3H4
D8KS3H4
1X3J2H4
9X3J2H4
2X3J2H4
J2DY4H4
BQPJ5H4
2Z3J2H4
GX3J2H4
C9KS3H4
3WRG4H4
1JKS3H4
29PG4H4
DQPJ5H4
68DY4H4
+60
View File
@@ -0,0 +1,60 @@
DKK3674
GFF3674
HGK3674
JFF3674
2HF3674
4MK3674
BJF3674
6KK3674
2HK3674
FKK3674
CGF3674
6KF3674
4GF3674
FJK3674
1LK3674
8GF3674
FJF3674
7HF3674
5GF3674
6JF3674
8LK3674
FDF3674
8HK3674
FHK3674
5LK3674
HHK3674
7FF3674
CKK3674
3JF3674
2GF3674
3HF3674
GGK3674
6HK3674
CJK3674
3JK3674
8JK3674
FGF3674
5HF3674
4JF3674
5CF3674
282S574
HHF3674
DCF3674
4FF3674
2KF3674
HCF3674
8KK3674
DHK3674
HDF3674
GCF3674
5MK3674
5FF3674
DMK3674
4KF3674
BKK3674
CLK3674
6LK3674
2MK3674
4HK3674
BLK3674
Binary file not shown.
Binary file not shown.
Binary file not shown.
+72
View File
@@ -0,0 +1,72 @@
import os
import zipfile
# list.txt에서 파일명을 읽어오는 함수
def read_file_list():
list_file = os.path.join(os.getcwd(), 'list.txt')
if not os.path.isfile(list_file):
raise ValueError(f"'{list_file}'은(는) 파일이 아니거나 존재하지 않습니다.")
try:
with open(list_file, 'r', encoding='utf-8') as f:
return [line.strip() for line in f.readlines() if line.strip()]
except FileNotFoundError:
print(f"'{list_file}' 파일이 존재하지 않습니다.")
return []
# 특정 폴더에서 파일을 검색하고 압축하는 함수
def zip_selected_files(folder_path, file_list, output_zip):
with zipfile.ZipFile(output_zip, 'w') as zipf:
for file_name in file_list:
# 확장자를 .txt로 고정
file_name_with_ext = f"{file_name}.txt"
file_path = os.path.join(folder_path, file_name_with_ext)
if os.path.exists(file_path):
print(f"압축 중: {file_name_with_ext}")
zipf.write(file_path, arcname=file_name_with_ext)
else:
print(f"파일을 찾을 수 없거나 지원되지 않는 파일 형식입니다: {file_name_with_ext}")
print(f"완료: '{output_zip}' 파일이 생성되었습니다.")
# /app/idrac_info/backup/ 폴더 내 폴더를 나열하고 사용자 선택 받는 함수
def select_folder():
# Assume script is in data/server_list, backup is in data/system/backup
base_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), "..", "system", "backup")
if not os.path.isdir(base_path):
raise ValueError(f"기본 경로 '{base_path}'이(가) 존재하지 않습니다.")
folders = [f for f in os.listdir(base_path) if os.path.isdir(os.path.join(base_path, f))]
if not folders:
raise ValueError(f"'{base_path}'에 폴더가 존재하지 않습니다.")
print("사용 가능한 폴더:")
for idx, folder in enumerate(folders, start=1):
print(f"{idx}. {folder}")
choice = int(input("원하는 폴더의 번호를 선택하세요: ").strip())
if choice < 1 or choice > len(folders):
raise ValueError("올바른 번호를 선택하세요.")
return os.path.join(base_path, folders[choice - 1])
# 주요 실행 코드
if __name__ == "__main__":
try:
# /app/idrac_info/backup/ 폴더 내에서 폴더 선택
folder_path = select_folder()
output_zip_name = input("생성할 zip 파일명을 입력하세요 (확장자 제외, 예: output): ").strip()
# zip 파일 경로를 현재 디렉토리로 설정
output_zip = os.path.join(os.getcwd(), f"{output_zip_name}.zip")
# 파일명 리스트 가져오기
file_list = read_file_list()
if not file_list:
print("list.txt에 파일명이 없습니다.")
else:
zip_selected_files(folder_path, file_list, output_zip)
except ValueError as e:
print(e)
+20
View File
@@ -0,0 +1,20 @@
BDNXRH4
J9MXRH4
6DNXRH4
59MXRH4
G9MXRH4
79MXRH4
B9MXRH4
99MXRH4
9GMXRH4
49MXRH4
89MXRH4
G8MXRH4
H8MXRH4
1BMXRH4
4BMXRH4
GV5MQH4
6W5MQH4
JT5MQH4
DT5MQH4
3V5MQH4
+307
View File
@@ -0,0 +1,307 @@
from __future__ import annotations
import os
import argparse
import sys
import logging
from pathlib import Path
from collections import OrderedDict
import pandas as pd
# -----------------------------------------------------------------------------
# Logging Configuration
# -----------------------------------------------------------------------------
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s [INFO] %(message)s',
datefmt='%Y-%m-%d %H:%M:%S'
)
logger = logging.getLogger(__name__)
# -----------------------------------------------------------------------------
# Path Resolution
# -----------------------------------------------------------------------------
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()
# Fallback to current working directory assumption
cwd_data = Path.cwd() / "data"
if cwd_data.is_dir():
return cwd_data.resolve()
# Final fallback: Assume we are in data/server_list/ -> go up two levels
return here.parent.parent
DATA_ROOT = resolve_data_root()
# Default Paths (can be overridden by args)
DEFAULT_GUID_INPUT = DATA_ROOT / "repository" / "guid_file"
DEFAULT_GPU_INPUT = DATA_ROOT / "repository" / "gpu_serial"
DEFAULT_MAC_INPUT = DATA_ROOT / "repository" / "mac"
DEFAULT_GUID_LIST = DATA_ROOT / "server_list" / "guid_list.txt"
DEFAULT_GPU_LIST = DATA_ROOT / "server_list" / "gpu_serial_list.txt"
DEFAULT_MAC_LIST = DATA_ROOT / "server_list" / "server_list.txt"
DEFAULT_GUID_OUTPUT = DATA_ROOT / "temp" / "staging" / "XE9680_GUID.xlsx"
DEFAULT_GPU_OUTPUT = DATA_ROOT / "temp" / "staging" / "GPU_SERIALS.xlsx"
DEFAULT_MAC_OUTPUT = DATA_ROOT / "temp" / "staging" / "mac_info.xlsx"
# -----------------------------------------------------------------------------
# Utility Functions
# -----------------------------------------------------------------------------
def read_lines_any_encoding(path: Path) -> list[str]:
"""Read text file trying common encodings."""
if not path.is_file():
return []
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 path.open("r", encoding="utf-8", errors="replace") as f:
return f.read().splitlines()
def parse_txt_key_value(file_path: Path) -> dict:
"""
Parse standarized Key: Value files (GPU, GUID).
First line is assumed to be S/T (Service Tag).
"""
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)
# -----------------------------------------------------------------------------
# Mode: MAC
# -----------------------------------------------------------------------------
def process_mac(input_dir: Path, list_file: Path, output_xlsx: Path):
"""
Logic from excel.py
"""
if not list_file.is_file():
raise FileNotFoundError(f"Server list file not found: {list_file}")
file_names = read_lines_any_encoding(list_file)
data_list: list[str] = []
index_list: list[int | str] = []
sequence_number = 1
for name in file_names:
base = (name or "").strip()
if not base:
continue
txt_path = input_dir / f"{base}.txt"
if not txt_path.is_file():
# Missing file handling
data_list.append("")
index_list.append("")
continue
lines = read_lines_any_encoding(txt_path)
for i, line in enumerate(lines):
cleaned = (line or "").strip().upper()
if cleaned:
data_list.append(cleaned)
# First line is always the service tag
if i == 0:
index_list.append(sequence_number)
sequence_number += 1
else:
index_list.append("")
else:
data_list.append("")
index_list.append("")
# Create DataFrame
df = pd.DataFrame({
"Index": index_list,
"Content": data_list,
})
# Save without header, without index
output_xlsx.parent.mkdir(parents=True, exist_ok=True)
df.to_excel(output_xlsx, index=False, header=False)
logger.info(f"[MAC] Saved to {output_xlsx}")
# -----------------------------------------------------------------------------
# Mode: GPU
# -----------------------------------------------------------------------------
def process_gpu(input_dir: Path, list_file: Path, output_xlsx: Path):
"""
Logic from GPUTOExecl.py
"""
if not input_dir.is_dir():
raise FileNotFoundError(f"Input directory not found: {input_dir}")
# File collection
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:
logger.warning(f"[GPU] File not found: {p.name}")
else:
# Fallback to glob
files = sorted(input_dir.glob("*.txt"))
if not files:
logger.warning("[GPU] No files to process.")
return
# Parse
rows = []
for p in files:
rows.append(parse_txt_key_value(p))
df = pd.DataFrame(rows)
# Insert 'No' column
df.insert(0, "No", range(1, len(df) + 1))
output_xlsx.parent.mkdir(parents=True, exist_ok=True)
df.to_excel(output_xlsx, index=False)
logger.info(f"[GPU] Saved to {output_xlsx}")
# -----------------------------------------------------------------------------
# Mode: GUID
# -----------------------------------------------------------------------------
def process_guid(input_dir: Path, list_file: Path, output_xlsx: Path):
"""
Logic from GUIDtxtT0Execl.py
"""
if not input_dir.is_dir():
raise FileNotFoundError(f"Input directory not found: {input_dir}")
# Determine Slot Priority
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()]
logger.info(f"[GUID] Custom slot priority: {slot_priority}")
else:
slot_priority = ['38', '39', '37', '36', '32', '33', '34', '35', '31', '40']
logger.info(f"[GUID] Default slot priority: {slot_priority}")
# File collection
files: list[Path] = []
names = []
if list_file and list_file.is_file():
names = [x.strip() for x in read_lines_any_encoding(list_file) if x.strip()]
# Process
rows = []
for name in names:
txt_path = input_dir / f"{name}.txt"
if not txt_path.is_file():
logger.warning(f"[GUID] File not found: {txt_path.name}")
continue
raw_data = parse_txt_key_value(txt_path)
# Reorder and reconstruct GUID
ordered_data = OrderedDict()
ordered_data["S/T"] = raw_data.get("S/T", "")
new_guid_list = []
for slot_num in slot_priority:
slot_key = f"Slot.{slot_num}"
val = raw_data.get(slot_key)
if val:
ordered_data[slot_key] = val
if val != "Not Found" and ":" in val:
clean_hex = val.replace(":", "").upper()
new_guid_list.append(f"0x{clean_hex}")
# GUID Selection logic
if new_guid_list:
ordered_data["GUID"] = ";".join(new_guid_list)
elif "GUID" in raw_data:
ordered_data["GUID"] = raw_data["GUID"]
# Add remaining fields
for k, v in raw_data.items():
if k not in ordered_data:
ordered_data[k] = v
rows.append(dict(ordered_data))
if not rows:
logger.warning("[GUID] No data processed.")
# Create empty excel if needed or just return
return
df = pd.DataFrame(rows)
df.insert(0, "No", range(1, len(df) + 1))
output_xlsx.parent.mkdir(parents=True, exist_ok=True)
df.to_excel(output_xlsx, index=False)
logger.info(f"[GUID] Saved to {output_xlsx}")
# -----------------------------------------------------------------------------
# Main Entry Point
# -----------------------------------------------------------------------------
def main():
parser = argparse.ArgumentParser(description="Unified Excel Generator")
parser.add_argument("--mode", required=True, choices=["mac", "gpu", "guid"], help="Generation mode")
parser.add_argument("--input-dir", type=Path, help="Input directory (optional override)")
parser.add_argument("--list-file", type=Path, help="List file (optional override)")
parser.add_argument("--output-xlsx", type=Path, help="Output XLSX path (optional override)")
args = parser.parse_args()
mode = args.mode
# Determine effective paths based on mode and overrides
if mode == "mac":
input_dir = args.input_dir or DEFAULT_MAC_INPUT
list_file = args.list_file or DEFAULT_MAC_LIST
output_xlsx = args.output_xlsx or DEFAULT_MAC_OUTPUT
process_mac(input_dir, list_file, output_xlsx)
elif mode == "gpu":
input_dir = args.input_dir or DEFAULT_GPU_INPUT
list_file = args.list_file or DEFAULT_GPU_LIST
output_xlsx = args.output_xlsx or DEFAULT_GPU_OUTPUT
process_gpu(input_dir, list_file, output_xlsx)
elif mode == "guid":
input_dir = args.input_dir or DEFAULT_GUID_INPUT
list_file = args.list_file or DEFAULT_GUID_LIST
output_xlsx = args.output_xlsx or DEFAULT_GUID_OUTPUT
process_guid(input_dir, list_file, output_xlsx)
if __name__ == "__main__":
main()