스킬 · 포트폴리오

디자인 포트폴리오 구성·첨삭

리서치·스케치·렌더·시제품 자료로 근거 있는 가로형 포트폴리오를 구성하고 첨삭하는 원작 스킬(산업디자인). 원작자 truman-t3, MIT.

0번 설치됨
스킬스크립트 포함승인됨원작 truman-t3 · MIT · 올림 feeeldstudy · 판 1Claude · ChatGPT · Codex · Cursor
industrial-design-portfolio/scripts/validate_layout_library.py
#!/usr/bin/env python3"""Validate the bundled ID01-ID18 HTML layout fragment library."""from __future__ import annotationsimport reimport sysfrom pathlib import PathREGISTERED = {f"ID{i:02d}" for i in range(1, 19)}COMPOSITION_IDS = {"ID01", "ID03", "ID08", "ID10", "ID13", "ID14", "ID16"}COMPOSITION_PRESETS = {    "workshop-orange",    "instrument-blue",    "material-green",    "gallery-red",}def main() -> int:    root = Path(__file__).resolve().parent.parent    layout_dir = root / "assets" / "layouts"    findings: list[str] = []    files = {path.stem.upper(): path for path in layout_dir.glob("ID*.html")}    missing = sorted(REGISTERED - set(files))    extra = sorted(set(files) - REGISTERED)    if missing:        findings.append("Missing layout fragments: " + ", ".join(missing))    if extra:        findings.append("Unregistered layout fragments: " + ", ".join(extra))    for layout_id in sorted(REGISTERED & set(files)):        text = files[layout_id].read_text(encoding="utf-8")        sections = re.findall(r"<section\b[^>]*>", text, re.I)        if len(sections) != 1:            findings.append(f"{layout_id}: expected exactly one section element")            continue        section = sections[0]        if not re.search(r'class=["\'][^"\']*\bslide\b', section, re.I):            findings.append(f"{layout_id}: section is missing slide class")        match = re.search(r'data-layout=["\']([^"\']+)', section, re.I)        if not match or match.group(1).upper() != layout_id:            findings.append(f"{layout_id}: data-layout does not match file name")        if not re.search(r'data-project=["\'][^"\']+', section, re.I):            findings.append(f"{layout_id}: data-project is missing")        for image_index, tag in enumerate(re.findall(r"<img\b[^>]*>", text, re.I), start=1):            if not re.search(r'\balt=["\'][^"\']+["\']', tag, re.I):                findings.append(f"{layout_id}: image {image_index} has missing or empty alt text")    composition_root = root / "assets" / "compositions"    composition_count = 0    found_presets = {path.name for path in composition_root.iterdir() if path.is_dir()}    missing_presets = sorted(COMPOSITION_PRESETS - found_presets)    extra_presets = sorted(found_presets - COMPOSITION_PRESETS)    if missing_presets:        findings.append("Missing composition presets: " + ", ".join(missing_presets))    if extra_presets:        findings.append("Unregistered composition presets: " + ", ".join(extra_presets))    for preset in sorted(COMPOSITION_PRESETS & found_presets):        preset_dir = composition_root / preset        variants = {path.stem.upper(): path for path in preset_dir.glob("ID*.html")}        missing_variants = sorted(COMPOSITION_IDS - set(variants))        extra_variants = sorted(set(variants) - COMPOSITION_IDS)        if missing_variants:            findings.append(f"{preset}: missing composition recipes: " + ", ".join(missing_variants))        if extra_variants:            findings.append(f"{preset}: unregistered composition recipes: " + ", ".join(extra_variants))        for layout_id in sorted(COMPOSITION_IDS & set(variants)):            composition_count += 1            text = variants[layout_id].read_text(encoding="utf-8")            sections = re.findall(r"<section\b[^>]*>", text, re.I)            label = f"{preset}/{layout_id}"            if len(sections) != 1:                findings.append(f"{label}: expected exactly one section element")                continue            section = sections[0]            if not re.search(r'class=["\'][^"\']*\bslide\b', section, re.I):                findings.append(f"{label}: section is missing slide class")            match = re.search(r'data-layout=["\']([^"\']+)', section, re.I)            if not match or match.group(1).upper() != layout_id:                findings.append(f"{label}: data-layout does not match file name")            if not re.search(r'data-project=["\'][^"\']+', section, re.I):                findings.append(f"{label}: data-project is missing")            if f"composition-{preset.split('-', 1)[0]}" not in section:                findings.append(f"{label}: system composition class is missing")            for image_index, tag in enumerate(re.findall(r"<img\b[^>]*>", text, re.I), start=1):                if not re.search(r'\balt=["\'][^"\']+["\']', tag, re.I):                    findings.append(f"{label}: image {image_index} has missing or empty alt text")                if not re.search(r'\bdata-src=["\'][^"\']+["\']', tag, re.I):                    findings.append(f"{label}: image {image_index} must use data-src")    for message in findings:        print(f"P1: {message}")    print(        f"Checked {len(files)} shared layout fragment(s) and "        f"{composition_count} composition recipe(s): {len(findings)} finding(s)."    )    return 1 if findings else 0if __name__ == "__main__":    sys.exit(main())