CoolFace
Apppublic

malepati/custom_template_working

sourceHugging Faceupdated 8mo agoView on Hugging Face
0likes
rebuild_notice_all.py334 linesDownload Raw Back to scripts
1#!/usr/bin/env python32import json3import os4import re5import sys6from pathlib import Path7from typing import Dict, List, Optional, Tuple8 9REPO_ROOT = Path(__file__).resolve().parent.parent10FASTAPI_DIR = REPO_ROOT / "servers" / "fastapi"11NEXT_DIR = REPO_ROOT / "servers" / "nextjs"12NOTICE_PATH = REPO_ROOT / "NOTICE"13 14PY_LICENSE_CANDIDATES = [15    "LICENSE",16    "LICENSE.txt",17    "LICENSE.md",18    "LICENCE",19    "COPYING",20    "COPYING.txt",21    "NOTICE",22    "NOTICE.txt",23]24 25NODE_LICENSE_CANDIDATES = [26    "LICENSE",27    "LICENSE.txt",28    "LICENSE.md",29    "LICENCE",30    "LICENCE.txt",31    "COPYING",32    "COPYING.txt",33    "NOTICE",34    "NOTICE.txt",35]36 37 38def read_text_safe(path: Path) -> str:39    try:40        return path.read_text(encoding="utf-8", errors="replace").strip()41    except Exception:42        return ""43 44 45def parse_rfc822_metadata(text: str) -> Dict[str, str]:46    data: Dict[str, str] = {}47    key: Optional[str] = None48    for raw_line in text.splitlines():49        if not raw_line:50            key = None51            continue52        if raw_line[0] in " \t" and key:53            data[key] += "\n" + raw_line.strip()54            continue55        if ":" in raw_line:56            k, v = raw_line.split(":", 1)57            key = k.strip()58            data[key] = v.strip()59    return data60 61 62def find_python_site_packages(venv_dir: Path) -> Optional[Path]:63    # Linux/mac64    lib_dir = venv_dir / "lib"65    if lib_dir.exists():66        for child in lib_dir.iterdir():67            if child.is_dir() and child.name.startswith("python"):68                sp = child / "site-packages"69                if sp.exists():70                    return sp71    # Windows72    sp = venv_dir / "Lib" / "site-packages"73    if sp.exists():74        return sp75    return None76 77 78def detect_python_venv() -> Optional[Path]:79    env_path = os.environ.get("NOTICE_PYTHON_VENV")80    if env_path:81        v = Path(env_path)82        if v.exists():83            return v84    default = FASTAPI_DIR / ".venv"85    if default.exists():86        return default87    active = os.environ.get("VIRTUAL_ENV")88    if active and FASTAPI_DIR.as_posix() in Path(active).as_posix():89        return Path(active)90    return None91 92 93def scan_python_packages(site_packages_dir: Path) -> List[Dict[str, str]]:94    entries: List[Dict[str, str]] = []95    dist_infos = sorted(site_packages_dir.glob("*.dist-info"))96    for dist in dist_infos:97        metadata_path = dist / "METADATA"98        if not metadata_path.exists():99            continue100        meta = parse_rfc822_metadata(read_text_safe(metadata_path))101        name = meta.get("Name", "").strip()102        version = meta.get("Version", "").strip()103        license_name = meta.get("License", "").strip()104        if not name:105            # Fallback to folder name pattern106            # e.g., requests-2.32.3.dist-info107            base = dist.name[:-10]108            if "-" in base:109                parts = base.rsplit("-", 1)110                if len(parts) == 2:111                    name = parts[0]112                    version = version or parts[1]113        author = meta.get("Author", meta.get("Maintainer", meta.get("Author-email", ""))).strip()114 115        # License text candidates inside dist-info116        license_text = ""117        for cand in PY_LICENSE_CANDIDATES:118            p = dist / cand119            if p.exists():120                license_text = read_text_safe(p)121                if license_text:122                    break123 124        # Search via RECORD for license files elsewhere125        if not license_text:126            record = dist / "RECORD"127            if record.exists():128                for line in read_text_safe(record).splitlines():129                    path_part = line.split(",", 1)[0]130                    lower = path_part.lower()131                    if any(token in lower for token in ["license", "licence", "copying", "notice"]):132                        target = site_packages_dir / path_part133                        if target.exists():134                            license_text = read_text_safe(target)135                            if license_text:136                                break137 138        # As last resort, embed the License: field content139        if not license_text and license_name:140            license_text = f"License field from METADATA:\n{license_name}"141 142        entries.append({143            "name": name or dist.name,144            "version": version,145            "license": license_name,146            "author": author,147            "license_text": license_text,148        })149 150    # Sort by name for stability151    entries.sort(key=lambda e: (e["name"].lower(), e["version"]))152    return entries153 154 155def find_license_file_in_dir(base_dir: Path, depth_limit: int = 2) -> Optional[Path]:156    # First, try immediate candidates157    for cand in NODE_LICENSE_CANDIDATES:158        p = base_dir / cand159        if p.exists():160            return p161        # case-insensitive check162        for child in base_dir.iterdir():163            if child.is_file() and child.name.lower() == cand.lower():164                return child165 166    # Recursive limited-depth scan excluding nested node_modules167    def walk(dir_path: Path, depth: int) -> Optional[Path]:168        if depth > depth_limit:169            return None170        try:171            it = list(dir_path.iterdir())172        except Exception:173            return None174        for child in it:175            name_lower = child.name.lower()176            if child.is_dir():177                if child.name == "node_modules" or child.name.startswith('.'):178                    continue179                found = walk(child, depth + 1)180                if found:181                    return found182            else:183                if any(tok in name_lower for tok in ["license", "licence", "copying", "notice"]):184                    return child185        return None186 187    return walk(base_dir, 0)188 189 190def scan_node_modules(node_modules_dir: Path) -> List[Dict[str, str]]:191    entries: List[Dict[str, str]] = []192    seen: set[str] = set()193 194    def visit_pkg(pkg_dir: Path):195        pkg_json = pkg_dir / "package.json"196        if not pkg_json.exists():197            return198        try:199            data = json.loads(read_text_safe(pkg_json) or "{}")200        except Exception:201            return202        name = data.get("name") or pkg_dir.name203        version = str(data.get("version") or "")204        key = f"{name}@{version}"205        if key in seen:206            return207        seen.add(key)208 209        license_name = ""210        lic_field = data.get("license")211        if isinstance(lic_field, str):212            license_name = lic_field213        elif isinstance(lic_field, dict):214            license_name = lic_field.get("type", "")215        elif isinstance(data.get("licenses"), list):216            license_name = ", ".join([str(x.get("type", "")) for x in data["licenses"] if isinstance(x, dict)])217 218        author = ""219        a = data.get("author")220        if isinstance(a, str):221            author = a222        elif isinstance(a, dict):223            author = a.get("name", "")224 225        license_text = ""226        lic_file = find_license_file_in_dir(pkg_dir, depth_limit=2)227        if lic_file:228            license_text = read_text_safe(lic_file)229 230        entries.append({231            "name": name,232            "version": version,233            "license": license_name,234            "author": author,235            "license_text": license_text,236        })237 238    def walk_node_modules(base: Path):239        if not base.exists():240            return241        for entry in base.iterdir():242            if not entry.is_dir():243                continue244            if entry.name == ".bin":245                continue246            if entry.name.startswith("@"):  # scoped packages247                for scoped in entry.iterdir():248                    if scoped.is_dir():249                        visit_pkg(scoped)250                        # nested node_modules inside the package251                        nested = scoped / "node_modules"252                        walk_node_modules(nested)253                continue254            visit_pkg(entry)255            nested = entry / "node_modules"256            walk_node_modules(nested)257 258    walk_node_modules(node_modules_dir)259    # Sort by package name260    entries.sort(key=lambda e: (e["name"].lower(), e["version"]))261    return entries262 263 264def format_section(title: str, entries: List[Dict[str, str]]) -> str:265    header = [266        "-------------------------------------",267        title,268        "-------------------------------------",269        "",270    ]271    lines: List[str] = ["\n".join(header)]272    for e in entries:273        block = [274            e.get("name", "").strip(),275            e.get("version", "").strip(),276            e.get("license", "").strip(),277            e.get("author", "").strip(),278            "",279            (e.get("license_text", "") or "LICENSE TEXT NOT FOUND").strip(),280            "",281            "",282        ]283        lines.append("\n".join(block))284    return "".join(lines).rstrip() + "\n"285 286 287def main():288    # Optional CLI overrides289    import argparse290    parser = argparse.ArgumentParser(description="Rebuild NOTICE from installed packages")291    parser.add_argument("--python-venv", dest="python_venv", default=None, help="Path to Python venv to scan")292    parser.add_argument("--node-modules", dest="node_modules", default=None, help="Path to node_modules to scan")293    args = parser.parse_args()294    python_entries: List[Dict[str, str]] = []295    node_entries: List[Dict[str, str]] = []296 297    # Python scan298    venv = Path(args.python_venv) if args.python_venv else detect_python_venv()299    if venv:300        sp = find_python_site_packages(venv)301        if sp and sp.exists():302            python_entries = scan_python_packages(sp)303        else:304            print(f"Warning: site-packages not found under {venv}", file=sys.stderr)305    else:306        print("Warning: Python venv not found. Set NOTICE_PYTHON_VENV or create servers/fastapi/.venv", file=sys.stderr)307 308    # Node scan309    node_modules_dir = Path(args.node_modules or os.environ.get("NOTICE_NODE_MODULES") or (NEXT_DIR / "node_modules"))310    if node_modules_dir.exists():311        node_entries = scan_node_modules(node_modules_dir)312    else:313        print(f"Warning: node_modules not found at {node_modules_dir}", file=sys.stderr)314 315    # Build NOTICE content316    parts: List[str] = []317    if python_entries:318        parts.append(format_section("PYTHON PACKAGES", python_entries))319    if node_entries:320        parts.append(format_section("NODE PACKAGES", node_entries))321    if not parts:322        print("Error: No sections generated. Ensure .venv and node_modules exist.", file=sys.stderr)323        sys.exit(1)324 325    content = "\n".join(parts)326    NOTICE_PATH.write_text(content, encoding="utf-8")327    print("NOTICE rebuilt from installed packages")328 329 330if __name__ == "__main__":331    main()332 333 334