import sys
import os
import argparse
from pathlib import Path
from .logger import info, ok, warn, error, debug, has_error, error_exit, register_cleanup_callback, set_software_info
from .prerequisite import check_scancode_available
from .extract import run_extractcode
from .scancode import run_scancode
from .parse_and_duplication import extract_and_duplicate_copyright
from .license_extraction import process_license_params, write_license_report
from .readme_opensource import write_readme_opensource
from .cleanup import cleanup_extract_dir
def print_guide():
"""打印详细使用指南"""
guide = """
OSS information Extraction Tool
版权与许可证信息提取工具使用指南
【工具简介 / Tool Introduction】
本工具用于从源代码压缩包或目录中提取版权信息和许可证信息,生成标准化的
Readme.opensource文件。
This tool extracts copyright and license information from source code archives
or directories and generates a standardized Readme.opensource file.
--------------------------------------------------------------------------------
【必填参数 / Required Parameters】
--------------------------------------------------------------------------------
-t, --target 目标文件或目录路径
Target file or directory path
示例: -t package.zip 或 -t /path/to/source
-n, --name 软件名称
Software name
示例: -n "json" 或 -n "Apache Commons"
-v, --version 软件版本号
Software version
示例: -v "3.11.3" 或 -v "2.0.0"
--------------------------------------------------------------------------------
【可选参数 / Optional Parameters】
--------------------------------------------------------------------------------
-l, --license 许可证名称(SPDX标识符)
License name (SPDX identifier)
示例: -l "MIT", -l "Apache-2.0", -l "GPL-3.0-only"
说明: 如果不提供,工具将自动检测许可证
-p, --license-path 许可证文件相对路径
License file relative path
示例: -p "LICENSE", -p "LICENSES/MIT.txt"
说明: 如果不提供,工具将自动查找许可证文件
-o, --output-dir 输出目录
Output directory
示例: -o "./output"
说明: 默认输出到目标文件所在目录
--------------------------------------------------------------------------------
【使用场景 / Usage Scenarios】
--------------------------------------------------------------------------------
场景1: 完全自动提取
cret -t package.zip -n "MyLib" -v "1.0.0"
说明: 工具自动扫描并提取所有版权和许可证信息
Note: Tool automatically scans and extracts all copyright and license info
场景2: 指定许可证名称
cret -t package.zip -n "MyLib" -v "1.0.0" -l "MIT"
说明: 当已知许可证名称时,可指定以获得更准确的结果
Note: Specify license name when known for more accurate results
场景3: 指定许可证文件路径
cret -t package.zip -n "MyLib" -v "1.0.0" -p "LICENSE"
说明: 当已知许可证文件位置时,可指定路径
Note: Specify license file path when known
场景4: 同时指定许可证名称和路径 (推荐 / Recommended)
cret -t package.zip -n "MyLib" -v "1.0.0" -l "MIT" -p "LICENSE"
说明: 同时提供名称和路径,跳过自动检测,结果最准确
Note: Provide both to skip automatic detection, most accurate results
--------------------------------------------------------------------------------
【输出文件 / Output Files】
--------------------------------------------------------------------------------
Readme.opensource - 最终输出的许可证声明文件
Final license declaration file
*_copyright - 版权信息提取报告
Copyright extraction report
*_license - 许可证信息提取报告
License extraction report
result.json - ScanCode扫描原始结果(调试用)
Raw ScanCode scan results (for debugging)
--------------------------------------------------------------------------------
【注意事项 / Important Notes】
--------------------------------------------------------------------------------
1. 许可证文件仅接受以下位置:
License files are only accepted from:
- 项目根目录 / Project root directory
- 根目录下的LICENSES目录 / LICENSES directory under root
2. 当未同时提供许可证名称和路径时,工具会提示人工核对结果
When license name and path are not both provided, manual verification
is recommended
3. 支持的压缩格式: .zip, .tar, .tar.gz, .tgz, .tar.bz2, .tbz2
Supported archive formats: .zip, .tar, .tar.gz, .tgz, .tar.bz2, .tbz2
--------------------------------------------------------------------------------
【常见许可证SPDX标识符 / Common SPDX Identifiers】
--------------------------------------------------------------------------------
MIT - MIT License
Apache-2.0 - Apache License 2.0
BSD-3-Clause - BSD 3-Clause License
BSD-2-Clause - BSD 2-Clause License
GPL-3.0-only - GNU General Public License v3.0 only
GPL-3.0-or-later - GNU General Public License v3.0 or later
LGPL-3.0 - GNU Lesser General Public License v3.0
MPL-2.0 - Mozilla Public License 2.0
CC0-1.0 - Creative Commons Zero v1.0 Universal
--------------------------------------------------------------------------------
【日志级别控制 / Log Level Control】
--------------------------------------------------------------------------------
工具支持通过环境变量 LOG_LEVEL 控制日志输出级别:
默认模式 (INFO):
显示 INFO, OK, WARN, ERROR 级别日志
示例: cret -t package.zip -n "MyLib" -v "1.0.0"
调试模式 (DEBUG):
显示全部日志,包括 DEBUG 调试信息
示例: LOG_LEVEL=DEBUG cret -t package.zip -n "MyLib" -v "1.0.0"
静默模式 (QUIET):
只显示 ERROR 错误信息
示例: LOG_LEVEL=QUIET cret -t package.zip -n "MyLib" -v "1.0.0"
Log level control via LOG_LEVEL environment variable:
(default) - INFO, OK, WARN, ERROR logs
DEBUG - All logs including DEBUG
QUIET - ERROR logs only
"""
print(guide)
def parse_args():
"""解析命令行参数"""
parser = argparse.ArgumentParser(
description="Extract copyright and license information from source code archives. "
"\n提取源代码压缩包中的版权和许可证信息。",
formatter_class=argparse.RawDescriptionHelpFormatter,
epilog="""
Examples / 示例:
cret -t package.zip -n "MySoftware" -v "1.0.0"
cret -t package.zip -n "MySoftware" -v "1.0.0" -l "MIT"
cret -t package.zip -n "MySoftware" -v "1.0.0" -p "LICENSES/MIT.txt"
cret -t package.zip -n "MySoftware" -v "1.0.0" -l "MIT" -p "LICENSES/MIT.txt"
Run 'cret --guide' for detailed usage instructions.
运行 'cret --guide' 查看详细使用指南。
"""
)
parser.add_argument(
"-t", "--target",
required=True,
help="Target file or directory to analyze / 目标文件或目录路径 (e.g., package.zip)"
)
parser.add_argument(
"-n", "--name",
required=True,
help="Software name / 软件名称 (required)"
)
parser.add_argument(
"-v", "--version",
required=True,
help="Software version / 软件版本号 (required)"
)
parser.add_argument(
"-l", "--license",
dest="license_name",
default=None,
help="License name (SPDX identifier) / 许可证名称,如 MIT, Apache-2.0"
)
parser.add_argument(
"-p", "--license-path",
dest="license_path",
default=None,
help="Relative path to license file / 许可证文件相对路径"
)
parser.add_argument(
"-o", "--output-dir",
dest="output_dir",
default=None,
help="Output directory for results / 输出目录 (default: same as target)"
)
parser.add_argument(
"--guide",
action="store_true",
help="Show detailed usage guide / 显示详细使用指南"
)
if "--guide" in sys.argv or "-g" in sys.argv:
print_guide()
sys.exit(0)
args = parser.parse_args()
return args
def main():
args = parse_args()
target = Path(args.target).resolve()
if not target.exists():
error_exit(f"Target does not exist: {target}")
if args.output_dir:
output_dir = Path(args.output_dir).resolve()
output_dir.mkdir(parents=True, exist_ok=True)
else:
output_dir = target.parent if target.is_file() else target
result_json = output_dir / "result.json"
output_copyright = output_dir / f"{target.stem}_copyright"
output_license = output_dir / f"{target.stem}_license"
output_readme = output_dir / "Readme.opensource"
extract_dir = None
scan_target = target
def cleanup():
"""清理过程文件和临时目录"""
if result_json.exists():
result_json.unlink()
if output_copyright.exists():
output_copyright.unlink()
if output_license.exists():
output_license.unlink()
if output_readme.exists():
output_readme.unlink()
nonlocal extract_dir
if extract_dir:
cleanup_extract_dir(extract_dir)
register_cleanup_callback(cleanup)
software_name = args.name
software_version = args.version
license_name = args.license_name
license_path = args.license_path
set_software_info(software_name, software_version)
info(f"Software Name: {software_name}")
info(f"Software Version: {software_version}")
info(f"License Name: {license_name or 'Not provided'}")
info(f"License Path: {license_path or 'Not provided'}")
try:
if target.is_file():
extract_dir = run_extractcode(target)
scan_target = extract_dir
try:
items = list(extract_dir.iterdir())
if len(items) == 1 and items[0].is_dir():
scan_target = items[0]
info(f"Detected nested directory, using as root: {scan_target}")
except Exception:
pass
if license_path:
normalized_license_path = license_path.lstrip("/").replace("\\", "/")
license_full_path = scan_target / normalized_license_path
if not license_full_path.exists():
error_exit(f"License file not found: {license_path}")
license_path = normalized_license_path
check_scancode_available()
run_scancode(scan_target, result_json, scan_license=True)
import json
with result_json.open("r", encoding="utf-8") as f:
scan_data = json.load(f)
copyright_records = extract_and_duplicate_copyright(scan_data, output_copyright)
license_records = process_license_params(
data=scan_data,
root_path=scan_target,
license_name=license_name,
license_path=license_path,
)
if has_error():
error_exit("Errors occurred during processing, aborting.")
write_license_report(
output_txt=output_license,
software_name=software_name,
software_version=software_version,
license_records=license_records,
)
write_readme_opensource(
output_path=output_readme,
software_name=software_name,
software_version=software_version,
copyright_records=copyright_records,
license_records=license_records,
root_path=scan_target,
)
except Exception as e:
error("Pipeline failed, extracted directory will be kept for debugging.")
print(e)
raise
else:
if extract_dir:
cleanup_extract_dir(extract_dir)
debug(f"Result JSON kept at: {result_json}")
if __name__ == "__main__":
main()