Initial project upload
This commit is contained in:
@@ -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()
|
||||
|
||||
@@ -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}")
|
||||
@@ -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}")
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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.
@@ -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)
|
||||
@@ -0,0 +1,20 @@
|
||||
BDNXRH4
|
||||
J9MXRH4
|
||||
6DNXRH4
|
||||
59MXRH4
|
||||
G9MXRH4
|
||||
79MXRH4
|
||||
B9MXRH4
|
||||
99MXRH4
|
||||
9GMXRH4
|
||||
49MXRH4
|
||||
89MXRH4
|
||||
G8MXRH4
|
||||
H8MXRH4
|
||||
1BMXRH4
|
||||
4BMXRH4
|
||||
GV5MQH4
|
||||
6W5MQH4
|
||||
JT5MQH4
|
||||
DT5MQH4
|
||||
3V5MQH4
|
||||
@@ -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()
|
||||
Reference in New Issue
Block a user