chore: import upstream snapshot with attribution
This commit is contained in:
@@ -0,0 +1,258 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Shared helpers for the software copyright materials skill."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import os
|
||||
import re
|
||||
from pathlib import Path
|
||||
from typing import Any, Iterable
|
||||
|
||||
|
||||
EXCLUDE_DIRS = {
|
||||
".git",
|
||||
".hg",
|
||||
".svn",
|
||||
".idea",
|
||||
".vscode",
|
||||
"__pycache__",
|
||||
"node_modules",
|
||||
"dist",
|
||||
"build",
|
||||
".next",
|
||||
".nuxt",
|
||||
".output",
|
||||
"coverage",
|
||||
"target",
|
||||
"vendor",
|
||||
"软件著作权申请资料",
|
||||
"software-copyright-materials",
|
||||
}
|
||||
|
||||
CODE_EXTS = {
|
||||
".vue",
|
||||
".ts",
|
||||
".tsx",
|
||||
".js",
|
||||
".jsx",
|
||||
".mjs",
|
||||
".cjs",
|
||||
".css",
|
||||
".scss",
|
||||
".sass",
|
||||
".less",
|
||||
".html",
|
||||
".svelte",
|
||||
".astro",
|
||||
".json",
|
||||
".md",
|
||||
}
|
||||
|
||||
KNOWN_CONFIG_FILES = {
|
||||
".babelrc",
|
||||
".eslintrc",
|
||||
".eslintrc.json",
|
||||
".eslintrc.yaml",
|
||||
".eslintrc.yml",
|
||||
".prettierrc",
|
||||
".prettierrc.json",
|
||||
".prettierrc.yaml",
|
||||
".prettierrc.yml",
|
||||
".swcrc",
|
||||
"angular.json",
|
||||
"app.json",
|
||||
"astro.config.mjs",
|
||||
"astro.config.ts",
|
||||
"babel.config.js",
|
||||
"babel.config.json",
|
||||
"Cargo.lock",
|
||||
"Cargo.toml",
|
||||
"composer.json",
|
||||
"docker-compose.yaml",
|
||||
"docker-compose.yml",
|
||||
"eslint.config.cjs",
|
||||
"eslint.config.js",
|
||||
"eslint.config.mjs",
|
||||
"go.mod",
|
||||
"go.sum",
|
||||
"jsconfig.json",
|
||||
"lerna.json",
|
||||
"manifest.json",
|
||||
"next.config.js",
|
||||
"next.config.mjs",
|
||||
"next.config.ts",
|
||||
"nuxt.config.js",
|
||||
"nuxt.config.ts",
|
||||
"nx.json",
|
||||
"package-lock.json",
|
||||
"package.json",
|
||||
"playwright.config.js",
|
||||
"playwright.config.ts",
|
||||
"postcss.config.cjs",
|
||||
"postcss.config.js",
|
||||
"prettier.config.cjs",
|
||||
"prettier.config.js",
|
||||
"prettier.config.mjs",
|
||||
"project.json",
|
||||
"pyproject.toml",
|
||||
"rollup.config.js",
|
||||
"rollup.config.mjs",
|
||||
"rollup.config.ts",
|
||||
"svelte.config.js",
|
||||
"stylelintrc.json",
|
||||
"tailwind.config.js",
|
||||
"tailwind.config.ts",
|
||||
"tsconfig.app.json",
|
||||
"tsconfig.json",
|
||||
"tsconfig.node.json",
|
||||
"tslint.json",
|
||||
"turbo.json",
|
||||
"vite.config.js",
|
||||
"vite.config.mjs",
|
||||
"vite.config.ts",
|
||||
"vitest.config.js",
|
||||
"vitest.config.ts",
|
||||
"webpack.config.js",
|
||||
"webpack.config.ts",
|
||||
"workspace.json",
|
||||
}
|
||||
|
||||
FRONTEND_EXTS = {
|
||||
".vue",
|
||||
".ts",
|
||||
".tsx",
|
||||
".js",
|
||||
".jsx",
|
||||
".mjs",
|
||||
".css",
|
||||
".scss",
|
||||
".sass",
|
||||
".less",
|
||||
".html",
|
||||
".svelte",
|
||||
".astro",
|
||||
}
|
||||
|
||||
SUPPLEMENT_CODE_EXTS = {
|
||||
".py",
|
||||
".java",
|
||||
".go",
|
||||
".rs",
|
||||
".cs",
|
||||
".php",
|
||||
".rb",
|
||||
".kt",
|
||||
".swift",
|
||||
".sql",
|
||||
".sh",
|
||||
".json",
|
||||
}
|
||||
|
||||
COPYRIGHT_CODE_EXTS = FRONTEND_EXTS | SUPPLEMENT_CODE_EXTS
|
||||
|
||||
LOCK_FILES = {
|
||||
"package-lock.json",
|
||||
"pnpm-lock.yaml",
|
||||
"yarn.lock",
|
||||
"bun.lockb",
|
||||
"bun.lock",
|
||||
}
|
||||
|
||||
|
||||
def repo_root_from_script() -> Path:
|
||||
return Path(__file__).resolve().parents[3]
|
||||
|
||||
|
||||
def is_excluded(path: Path) -> bool:
|
||||
parts = set(path.parts)
|
||||
if parts & EXCLUDE_DIRS:
|
||||
return True
|
||||
name = path.name
|
||||
if name.startswith(".") and name not in {".env.example"}:
|
||||
return True
|
||||
if name in LOCK_FILES:
|
||||
return True
|
||||
if name.endswith(".map") or name.endswith(".min.js") or name.endswith(".min.css"):
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def iter_project_files(project: Path, exts: set[str] | None = None) -> Iterable[Path]:
|
||||
project = project.resolve()
|
||||
for root, dirs, files in os.walk(project):
|
||||
root_path = Path(root)
|
||||
dirs[:] = [d for d in dirs if not is_excluded(root_path / d)]
|
||||
for filename in files:
|
||||
path = root_path / filename
|
||||
if is_excluded(path):
|
||||
continue
|
||||
if exts is not None and path.suffix.lower() not in exts:
|
||||
continue
|
||||
yield path
|
||||
|
||||
|
||||
def rel(path: Path, root: Path) -> str:
|
||||
return path.resolve().relative_to(root.resolve()).as_posix()
|
||||
|
||||
|
||||
def read_text(path: Path, limit: int | None = None) -> str:
|
||||
data = path.read_bytes()
|
||||
if limit is not None:
|
||||
data = data[:limit]
|
||||
for encoding in ("utf-8", "utf-8-sig", "gb18030", "latin-1"):
|
||||
try:
|
||||
return data.decode(encoding)
|
||||
except UnicodeDecodeError:
|
||||
continue
|
||||
return data.decode("utf-8", errors="replace")
|
||||
|
||||
|
||||
def read_json(path: Path) -> dict[str, Any]:
|
||||
return json.loads(read_text(path))
|
||||
|
||||
|
||||
def write_json(path: Path, data: Any) -> None:
|
||||
path.parent.mkdir(parents=True, exist_ok=True)
|
||||
path.write_text(json.dumps(data, ensure_ascii=False, indent=2), encoding="utf-8")
|
||||
|
||||
|
||||
def count_text_lines(path: Path, skip_blank: bool = True) -> int:
|
||||
try:
|
||||
text = read_text(path)
|
||||
except Exception:
|
||||
return 0
|
||||
if not text:
|
||||
return 0
|
||||
if skip_blank:
|
||||
return sum(1 for line in text.splitlines() if line.strip())
|
||||
return len(text.splitlines())
|
||||
|
||||
|
||||
def is_known_config_file(path: Path) -> bool:
|
||||
"""Return True for well-known config files that shouldn't count as source code."""
|
||||
return path.name in KNOWN_CONFIG_FILES
|
||||
|
||||
|
||||
def looks_binary(path: Path) -> bool:
|
||||
try:
|
||||
chunk = path.read_bytes()[:4096]
|
||||
except Exception:
|
||||
return True
|
||||
return b"\x00" in chunk
|
||||
|
||||
|
||||
def normalize_title(value: str) -> str:
|
||||
value = re.sub(r"[-_]+", " ", value).strip()
|
||||
value = re.sub(r"\s+", " ", value)
|
||||
return value or "待命名软件"
|
||||
|
||||
|
||||
def safe_filename(value: str) -> str:
|
||||
value = re.sub(r'[\\/:*?"<>|]+', "_", value).strip()
|
||||
return value or "软件"
|
||||
|
||||
|
||||
def ensure_dir(path: Path) -> Path:
|
||||
path.mkdir(parents=True, exist_ok=True)
|
||||
return path
|
||||
Reference in New Issue
Block a user