"""
Generate README.OpenSource files for open source components.
This script provides interactive and command-line modes to create
standardized README.OpenSource files with SPDX license validation.
"""
import argparse
import json
import logging
import os
import sys
from pathlib import Path
from typing import Dict, List, Optional, Tuple
logging.basicConfig(level=logging.INFO, format='%(levelname)s: %(message)s')
logger = logging.getLogger(__name__)
try:
from spdx_converter import SPDXLicenseConverter
SPDX_AVAILABLE = True
except ImportError:
SPDX_AVAILABLE = False
logger.warning("SPDX converter not available, licenses will not be standardized")
REQUIRED_FIELDS = [
"Name",
"License",
"License File",
"Version Number",
"Owner",
"Upstream URL",
"Description",
]
def ask_question(prompt: str, default_value: Optional[str] = None) -> str:
"""Prompt user for input with optional default value."""
if default_value:
value = input(f"{prompt} [{default_value}]: ").strip()
return value or default_value
return input(f"{prompt}: ").strip()
def ask_for_list(prompt: str) -> List[str]:
"""Prompt user for a list of items separated by commas."""
value = input(f"{prompt} (多个项请用逗号分隔): ").strip()
return [item.strip() for item in value.split(",")] if value else []
def process_license_info(
converter: Optional["SPDXLicenseConverter"] = None
) -> Tuple[List[str], List[str]]:
"""
Process license information and corresponding file paths.
Args:
converter: Optional SPDX license converter for standardization.
Returns:
Tuple of (license_list, license_file_list).
"""
licenses = ask_question("请输入许可证名称(如有多个,用分号分隔)")
license_files = ask_question("请输入许可证文件路径(如果有多个,请使用分号分隔)")
license_list = (
[license.strip() for license in licenses.split(";")] if licenses else []
)
license_file_list = (
[file.strip() for file in license_files.split(";")] if license_files else []
)
if not license_list or not license_file_list:
raise ValueError("许可证和许可证文件路径不能为空。")
if len(license_list) != len(license_file_list):
if not (
(len(license_list) == 1 and len(license_file_list) > 1)
or (len(license_list) > 1 and len(license_file_list) == 1)
):
raise ValueError(
"许可证和许可证文件的数量不匹配,必须是一对一、一对多或多对一的关系。"
)
if converter and SPDX_AVAILABLE:
original_list = license_list.copy()
license_list = [converter.convert(lic) for lic in license_list]
for orig, new in zip(original_list, license_list):
if orig != new:
logger.info(f"许可证转换: '{orig}' -> '{new}'")
return license_list, license_file_list
def generate_readme_opensource(
output_dir: str,
converter: Optional["SPDXLicenseConverter"] = None,
interactive: bool = True
) -> None:
"""
Generate README.OpenSource file with support for multiple components.
Args:
output_dir: Output directory path.
converter: Optional SPDX license converter for standardization.
interactive: Whether to use interactive mode (vs batch mode).
"""
components = []
print("请输入开源组件的信息(输入完成后,可选择继续添加另一个组件):")
while True:
component = {}
component["Name"] = ask_question("Name: ")
component["Version Number"] = ask_question("Version Number: ")
component["Owner"] = ask_question("Owner: ")
component["Upstream URL"] = ask_question("Upstream URL: ")
component["Description"] = ask_question("Description: ")
license_list, license_file_list = process_license_info(converter)
component["License"] = "; ".join(license_list)
component["License File"] = "; ".join(license_file_list)
dependencies = ask_for_list("请输入该软件的依赖项(如果有多个,请用逗号分隔)")
if dependencies:
component["Dependencies"] = dependencies
components.append(component)
if interactive:
add_more = ask_question("是否添加另一个组件?(y/n): ").lower()
if add_more != "y":
break
else:
break
os.makedirs(output_dir, exist_ok=True)
readme_path = os.path.join(output_dir, "README.OpenSource")
with open(readme_path, "w", encoding="utf-8") as f:
json.dump(components, f, indent=2, ensure_ascii=False)
logger.info(f"已生成 {readme_path}")
if converter and SPDX_AVAILABLE:
validate_spdx_licenses(components)
def validate_spdx_licenses(components: List[Dict]) -> None:
"""Validate all license identifiers in components."""
from spdx_converter import SPDXLicenseConverter
converter = SPDXLicenseConverter()
all_valid = True
for component in components:
license_str = component.get("License", "")
licenses = [lic.strip() for lic in license_str.split(";")]
for lic in licenses:
if not converter.validate_spdx_id(lic):
logger.warning(
f"组件 '{component.get('Name')}' 的许可证可能不是有效的 SPDX 标识符: {lic}"
)
all_valid = False
if all_valid:
logger.info("所有许可证标识符均符合 SPDX 标准")
def main():
"""Main entry point with command-line argument support."""
parser = argparse.ArgumentParser(
description="生成 README.OpenSource 开源部件配置信息文件"
)
parser.add_argument(
"-o", "--output",
default=".",
help="输出目录路径(默认:当前目录)"
)
parser.add_argument(
"--no-spdx",
action="store_true",
help="禁用 SPDX 许可证标准化"
)
parser.add_argument(
"-v", "--verbose",
action="store_true",
help="启用详细日志"
)
args = parser.parse_args()
if args.verbose:
logging.getLogger().setLevel(logging.DEBUG)
converter = None
if not args.no_spdx and SPDX_AVAILABLE:
converter = SPDXLicenseConverter()
logger.info("SPDX 许可证标准化已启用")
generate_readme_opensource(args.output, converter)
if __name__ == "__main__":
main()