diff --git a/build1.py b/build1.py new file mode 100644 index 0000000..1529cce --- /dev/null +++ b/build1.py @@ -0,0 +1,653 @@ +#!/usr/bin/env python3 +import os +import sys +import subprocess +import platform +from pathlib import Path + +# 定义颜色类 +class Colors: + RESET = '\033[0m' + RED = '\033[31m' + GREEN = '\033[32m' + YELLOW = '\033[33m' + BLUE = '\033[34m' + MAGENTA = '\033[35m' + CYAN = '\033[36m' + WHITE = '\033[37m' + BOLD = '\033[1m' + BOLD_GREEN = '\033[1;32m' + BOLD_YELLOW = '\033[1;33m' + BOLD_CYAN = '\033[1;36m' + BOLD_RED = '\033[1;31m' + +# 确保Windows终端支持ANSI颜色 +def enable_windows_ansi_support(): + if platform.system() == 'Windows': + try: + import ctypes + kernel32 = ctypes.windll.kernel32 + kernel32.SetConsoleMode(kernel32.GetStdHandle(-11), 7) + except: + pass + +# 打印带颜色的文本 +def print_color(text, color=Colors.RESET): + print(f"{color}{text}{Colors.RESET}") + +# 运行命令并返回结果 +def run_command(cmd, cwd=None, shell=True, check=False): + try: + result = subprocess.run(cmd, cwd=cwd, shell=shell, check=check, + capture_output=True, text=True) + return result + except subprocess.CalledProcessError as e: + return e + +# 构建Windows EXE +def build_exe(): + print_color("\n========================================", Colors.BOLD_CYAN) + print_color("Building Windows EXE", Colors.BOLD_YELLOW) + print_color("========================================", Colors.BOLD_CYAN) + print() + + print_color("Checking PyInstaller...", Colors.CYAN) + result = run_command("pip show pyinstaller", check=False) + if result.returncode != 0: + print_color("PyInstaller not found, installing...", Colors.YELLOW) + result = run_command("pip install pyinstaller", check=True) + if result.returncode != 0: + print_color("Failed to install PyInstaller", Colors.BOLD_RED) + input("Press Enter to continue...") + return False + + print_color("Building scl.py...", Colors.CYAN) + result = run_command( + "pyinstaller --onefile --name scl --distpath dist\\exe --workpath build\\exe --specpath build\\exe scl.py", + check=True + ) + if result.returncode != 0: + print_color("Failed to build scl.py", Colors.BOLD_RED) + input("Press Enter to continue...") + return False + + print() + print_color("EXE build completed!", Colors.BOLD_GREEN) + print_color("Output directory: dist\\exe", Colors.GREEN) + print() + + print_color("Copying plugins directory...", Colors.CYAN) + os.makedirs("dist\\exe\\plugins", exist_ok=True) + result = run_command("xcopy /E /I /Y plugins dist\\exe\\plugins", check=False) + print() + input("Press Enter to continue...") + return True + +# 构建Windows EXE - 愚人节版本 +def build_exe_april_fools(): + print_color("\n========================================", Colors.BOLD_CYAN) + print_color("Building Windows EXE - April Fools Edition", Colors.BOLD_YELLOW) + print_color("========================================", Colors.BOLD_CYAN) + print() + + print_color("Checking PyInstaller...", Colors.CYAN) + result = run_command("pip show pyinstaller", check=False) + if result.returncode != 0: + print_color("PyInstaller not found, installing...", Colors.YELLOW) + result = run_command("pip install pyinstaller", check=True) + if result.returncode != 0: + print_color("Failed to install PyInstaller", Colors.BOLD_RED) + input("Press Enter to continue...") + return False + + if not os.path.exists("sdp_yrj.py"): + print_color("Error: sdp_yrj.py not found", Colors.BOLD_RED) + input("Press Enter to continue...") + return False + + print_color("Building sdp_yrj.py...", Colors.CYAN) + result = run_command( + "pyinstaller --onefile --name sdp_yrj --distpath dist\\exe --workpath build\\exe --specpath build\\exe sdp_yrj.py", + check=False + ) + if result.returncode != 0: + print_color("Failed to build sdp_yrj.py", Colors.BOLD_RED) + else: + print_color("Successfully built sdp_yrj.exe", Colors.BOLD_GREEN) + + print() + print_color("April Fools EXE build completed!", Colors.BOLD_GREEN) + print_color("Output directory: dist\\exe", Colors.GREEN) + print() + + print_color("Copying plugins directory...", Colors.CYAN) + os.makedirs("dist\\exe\\plugins", exist_ok=True) + result = run_command("xcopy /E /I /Y plugins dist\\exe\\plugins", check=False) + print() + input("Press Enter to continue...") + return True + +# 构建Linux DEB - SCL Full Package (via WSL) +def build_deb(): + print_color("\n========================================", Colors.BOLD_CYAN) + print_color("Building Linux DEB via WSL", Colors.BOLD_YELLOW) + print_color("========================================", Colors.BOLD_CYAN) + print() + + print_color("Checking WSL...", Colors.CYAN) + result = run_command("wsl echo 'WSL OK'", check=False) + if result.returncode != 0: + print_color("WSL is not installed or not available", Colors.BOLD_RED) + print_color("Please install WSL first:", Colors.YELLOW) + print_color(" wsl --install", Colors.GREEN) + input("Press Enter to continue...") + return False + + print_color("WSL found. Preparing build environment...", Colors.GREEN) + + # 获取当前目录的WSL路径 + result = run_command("wsl wslpath '%cd%'", check=True) + wsl_path = result.stdout.strip() + print_color(f"WSL path: {wsl_path}", Colors.BLUE) + + print_color("Creating dist directory...", Colors.CYAN) + os.makedirs("dist", exist_ok=True) + + print_color("Copying build script to WSL...", Colors.CYAN) + result = run_command(f"wsl cp '{wsl_path}/build_deb_wsl.sh' /tmp/build_deb_wsl.sh", check=True) + result = run_command("wsl chmod +x /tmp/build_deb_wsl.sh", check=True) + + print_color("Building DEB package in WSL...", Colors.CYAN) + result = run_command(f"wsl /tmp/build_deb_wsl.sh '{wsl_path}'", check=True) + if result.returncode != 0: + print() + print_color("Failed to build DEB package", Colors.BOLD_RED) + print_color("Make sure dpkg-deb is available in WSL", Colors.YELLOW) + print_color("Run: wsl sudo apt update ^&^& wsl sudo apt install dpkg-dev", Colors.GREEN) + input("Press Enter to continue...") + return False + + print() + print_color("DEB build completed!", Colors.BOLD_GREEN) + print_color("Output file: dist\\scl-1.0.0.deb", Colors.GREEN) + print() + input("Press Enter to continue...") + return True + +# 构建Linux DEB - SDP Only (via WSL) +def build_sdp_deb(): + print_color("\n========================================", Colors.BOLD_CYAN) + print_color("Building Linux DEB - SDP Only (via WSL)", Colors.BOLD_YELLOW) + print_color("========================================", Colors.BOLD_CYAN) + print() + + print_color("Checking WSL...", Colors.CYAN) + result = run_command("wsl echo 'WSL OK'", check=False) + if result.returncode != 0: + print_color("WSL is not installed or not available", Colors.BOLD_RED) + print_color("Please install WSL first:", Colors.YELLOW) + print_color(" wsl --install", Colors.GREEN) + input("Press Enter to continue...") + return False + + print_color("WSL found. Preparing build environment...", Colors.GREEN) + + # 获取当前目录的WSL路径 + result = run_command("wsl wslpath '%cd%'", check=True) + wsl_path = result.stdout.strip() + print_color(f"WSL path: {wsl_path}", Colors.BLUE) + + print_color("Creating dist directory...", Colors.CYAN) + os.makedirs("dist", exist_ok=True) + + print_color("Copying build script to WSL...", Colors.CYAN) + result = run_command(f"wsl cp '{wsl_path}/build_sdp_deb.sh' /tmp/build_sdp_deb.sh", check=True) + + print_color("Building SDP DEB package in WSL...", Colors.CYAN) + result = run_command(f"wsl /tmp/build_sdp_deb.sh '{wsl_path}'", check=True) + if result.returncode != 0: + print() + print_color("Failed to build SDP DEB package", Colors.BOLD_RED) + print_color("Make sure dpkg-deb is available in WSL", Colors.YELLOW) + print_color("Run: wsl sudo apt update ^&^& wsl sudo apt install dpkg-dev", Colors.GREEN) + input("Press Enter to continue...") + return False + + print() + print_color("SDP DEB build completed!", Colors.BOLD_GREEN) + print_color("Output file: dist\\scl-sdp-1.0.0.deb", Colors.GREEN) + print() + input("Press Enter to continue...") + return True + +# 构建Linux DEB - SDP April Fools Edition (via WSL) +def build_sdp_deb_april_fools(): + print_color("\n========================================", Colors.BOLD_CYAN) + print_color("Building Linux DEB - SDP April Fools Edition (via WSL)", Colors.BOLD_YELLOW) + print_color("========================================", Colors.BOLD_CYAN) + print() + + if not os.path.exists("sdp_yrj.py"): + print_color("Error: sdp_yrj.py not found", Colors.BOLD_RED) + input("Press Enter to continue...") + return False + + print_color("Checking WSL...", Colors.CYAN) + result = run_command("wsl echo 'WSL OK'", check=False) + if result.returncode != 0: + print_color("WSL is not installed or not available", Colors.BOLD_RED) + print_color("Please install WSL first:", Colors.YELLOW) + print_color(" wsl --install", Colors.GREEN) + input("Press Enter to continue...") + return False + + print_color("WSL found. Preparing build environment...", Colors.GREEN) + + # 获取当前目录的WSL路径 + result = run_command("wsl wslpath '%cd%'", check=True) + wsl_path = result.stdout.strip() + print_color(f"WSL path: {wsl_path}", Colors.BLUE) + + print_color("Creating dist directory...", Colors.CYAN) + os.makedirs("dist", exist_ok=True) + + # 创建愚人节版本的构建脚本 + print_color("Creating April Fools build script...", Colors.CYAN) + sdp_yrj_deb_script = '''#!/bin/bash +set -e + +WSL_PATH="$1" +VERSION="1.0.0" + +cd "$WSL_PATH" + +# 创建临时目录 +mkdir -p deb_temp/usr/local/bin +mkdir -p deb_temp/DEBIAN + +# 复制sdp_yrj.py +echo "Copying sdp_yrj.py..." +cp sdp_yrj.py deb_temp/usr/local/bin/sdp_yrj +chmod +x deb_temp/usr/local/bin/sdp_yrj + +# 复制插件目录 +echo "Copying plugins directory..." +mkdir -p deb_temp/usr/local/lib/scl/plugins +cp -r plugins/* deb_temp/usr/local/lib/scl/plugins/ + +# 创建DEBIAN控制文件 +echo "Creating DEBIAN control file..." +cat > deb_temp/DEBIAN/control << 'EOF' +Package: scl-sdp-yrj +Version: 1.0.0 +Section: utils +Priority: optional +Architecture: all +Depends: python3 +Maintainer: SCL Team +Description: SCL Package Manager - April Fools Edition +EOF + +# 构建DEB包 +echo "Building DEB package..." +dpkg-deb --build deb_temp "$WSL_PATH/dist/scl-sdp-yrj-1.0.0.deb" + +# 清理 +echo "Cleaning up..." +rm -rf deb_temp + +echo "SDP April Fools DEB build completed!" +echo "Output: $WSL_PATH/dist/scl-sdp-yrj-1.0.0.deb" +''' + + # 写入构建脚本 + with open("build_sdp_yrj_deb.sh", "w") as f: + f.write(sdp_yrj_deb_script) + + # 为脚本添加执行权限 + os.chmod("build_sdp_yrj_deb.sh", 0o755) + + print_color("Copying build script to WSL...", Colors.CYAN) + result = run_command(f"wsl cp '{wsl_path}/build_sdp_yrj_deb.sh' /tmp/build_sdp_yrj_deb.sh", check=True) + result = run_command("wsl chmod +x /tmp/build_sdp_yrj_deb.sh", check=True) + + print_color("Building SDP April Fools DEB package in WSL...", Colors.CYAN) + result = run_command(f"wsl /tmp/build_sdp_yrj_deb.sh '{wsl_path}'", check=True) + if result.returncode != 0: + print() + print_color("Failed to build SDP April Fools DEB package", Colors.BOLD_RED) + print_color("Make sure dpkg-deb is available in WSL", Colors.YELLOW) + print_color("Run: wsl sudo apt update ^&^& wsl sudo apt install dpkg-dev", Colors.GREEN) + input("Press Enter to continue...") + return False + + print() + print_color("SDP April Fools DEB build completed!", Colors.BOLD_GREEN) + print_color("Output file: dist\\scl-sdp-yrj-1.0.0.deb", Colors.GREEN) + print() + input("Press Enter to continue...") + return True + +# 构建Linux DEB - Editor Only (via WSL) +def build_editor_deb(): + print_color("\n========================================", Colors.BOLD_CYAN) + print_color("Building Linux DEB - Editor Only (via WSL)", Colors.BOLD_YELLOW) + print_color("========================================", Colors.BOLD_CYAN) + print() + + print_color("Checking WSL...", Colors.CYAN) + result = run_command("wsl echo 'WSL OK'", check=False) + if result.returncode != 0: + print_color("WSL is not installed or not available", Colors.BOLD_RED) + print_color("Please install WSL first:", Colors.YELLOW) + print_color(" wsl --install", Colors.GREEN) + input("Press Enter to continue...") + return False + + print_color("WSL found. Preparing build environment...", Colors.GREEN) + + # 获取当前目录的WSL路径 + result = run_command("wsl wslpath '%cd%'", check=True) + wsl_path = result.stdout.strip() + print_color(f"WSL path: {wsl_path}", Colors.BLUE) + + print_color("Creating dist directory...", Colors.CYAN) + os.makedirs("dist", exist_ok=True) + + print_color("Copying build script to WSL...", Colors.CYAN) + result = run_command(f"wsl cp '{wsl_path}/build_editor_deb.sh' /tmp/build_editor_deb.sh", check=True) + + print_color("Building Editor DEB package in WSL...", Colors.CYAN) + result = run_command(f"wsl /tmp/build_editor_deb.sh '{wsl_path}'", check=True) + if result.returncode != 0: + print() + print_color("Failed to build Editor DEB package", Colors.BOLD_RED) + print_color("Make sure dpkg-deb is available in WSL", Colors.YELLOW) + print_color("Run: wsl sudo apt update ^&^& wsl sudo apt install dpkg-dev", Colors.GREEN) + input("Press Enter to continue...") + return False + + print() + print_color("Editor DEB build completed!", Colors.BOLD_GREEN) + print_color("Output file: dist\\scl-editor-1.0.0.deb", Colors.GREEN) + print() + input("Press Enter to continue...") + return True + +# 生成macOS Build Script (Mach-O) +def generate_macos_script(): + print_color("\n========================================", Colors.BOLD_CYAN) + print_color("Generating macOS Build Script", Colors.BOLD_YELLOW) + print_color("========================================", Colors.BOLD_CYAN) + print() + + print_color("Creating macOS build script...", Colors.CYAN) + script_content = '''#!/bin/bash +set -e + +echo "Building SCL for macOS (Mach-O)..." + +VERSION="1.0.0" +BUILD_DIR="build/macos" +DIST_DIR="dist/macos" + +echo "Checking PyInstaller..." +if ! command -v pyinstaller &> /dev/null; then + echo "PyInstaller found" +else + echo "Installing PyInstaller..." + pip3 install pyinstaller +fi + +echo "Creating build directories..." +mkdir -p "$BUILD_DIR" +mkdir -p "$DIST_DIR" + +echo "Building scl..." +pyinstaller --onefile --name scl --distpath "$DIST_DIR" --workpath "$BUILD_DIR" --specpath "$BUILD_DIR" scl.py + +echo "Building sdp..." +pyinstaller --onefile --name sdp --distpath "$DIST_DIR" --workpath "$BUILD_DIR" --specpath "$BUILD_DIR" sdp.py + +echo "Building scl_editor_linux..." +pyinstaller --onefile --name scl_editor --distpath "$DIST_DIR" --workpath "$BUILD_DIR" --specpath "$BUILD_DIR" scl_editor_linux.py + +echo "Creating DMG package..." +mkdir -p "$DIST_DIR/SCL.app/Contents/MacOS" +mkdir -p "$DIST_DIR/SCL.app/Contents/Resources" + +echo "Copying executables..." +cp "$DIST_DIR/scl" "$DIST_DIR/SCL.app/Contents/MacOS/" +cp "$DIST_DIR/sdp" "$DIST_DIR/SCL.app/Contents/MacOS/" +cp "$DIST_DIR/scl_editor" "$DIST_DIR/SCL.app/Contents/MacOS/" + +echo "Creating Info.plist..." +cat < "$DIST_DIR/SCL.app/Contents/Info.plist" << EOF + + + + + CFBundleExecutable + scl + CFBundleIdentifier + com.scl.lang + CFBundleName + SCL + CFBundlePackageType + APPL + CFBundleShortVersionString + $VERSION + CFBundleVersion + $VERSION + + +EOF + +echo "Building DMG..." +if command -v hdiutil &> /dev/null; then + hdiutil create -volname "SCL" -srcfolder "$DIST_DIR/SCL.app" -ov -format UDZO "$DIST_DIR/SCL-$VERSION.dmg" + echo "DMG created: $DIST_DIR/SCL-$VERSION.dmg" +else + echo "hdiutil not found, skipping DMG creation" + echo "App bundle created: $DIST_DIR/SCL.app" +fi + +echo "Build completed!" +echo "Output: $DIST_DIR/" +''' + + with open("build_macos.sh", "w") as f: + f.write(script_content) + + # 为脚本添加执行权限 + os.chmod("build_macos.sh", 0o755) + + print() + print_color("macOS build script created: build_macos.sh", Colors.BOLD_GREEN) + print() + print_color("Instructions:", Colors.BOLD_YELLOW) + print_color("1. Copy build_macos.sh to a macOS system", Colors.GREEN) + print_color("2. Make it executable: chmod +x build_macos.sh", Colors.GREEN) + print_color("3. Run it: ./build_macos.sh", Colors.GREEN) + print() + input("Press Enter to continue...") + return True + +# 生成SDP macOS Build Script +def generate_sdp_macos_script(): + print_color("\n========================================", Colors.BOLD_CYAN) + print_color("Generating SDP macOS Build Script", Colors.BOLD_YELLOW) + print_color("========================================", Colors.BOLD_CYAN) + print() + + print_color("Creating SDP macOS build script...", Colors.CYAN) + script_content = '''#!/bin/bash +set -e + +echo "Building SDP for macOS (Mach-O)..." + +VERSION="1.0.0" +BUILD_DIR="build/sdp-macos" +DIST_DIR="dist/sdp-macos" + +echo "Checking PyInstaller..." +if ! command -v pyinstaller &> /dev/null; then + echo "Installing PyInstaller..." + pip3 install pyinstaller +else + echo "PyInstaller found" +fi + +echo "Creating build directories..." +mkdir -p "$BUILD_DIR" +mkdir -p "$DIST_DIR" + +echo "Building SDP..." +pyinstaller --onefile --name sdp --distpath "$DIST_DIR" --workpath "$BUILD_DIR" --specpath "$BUILD_DIR" sdp.py + +echo "Creating SDP App Bundle..." +APP_DIR="$DIST_DIR/SDP.app" +mkdir -p "$APP_DIR/Contents/MacOS" +mkdir -p "$APP_DIR/Contents/Resources" + +echo "Copying executable..." +cp "$DIST_DIR/sdp" "$APP_DIR/Contents/MacOS/" + +echo "Creating Info.plist..." +cat > "$APP_DIR/Contents/Info.plist" << 'EOF' + + + + + CFBundleExecutable + sdp + CFBundleIdentifier + com.scl.sdp + CFBundleName + SDP + CFBundlePackageType + APPL + CFBundleShortVersionString + $VERSION + CFBundleVersion + $VERSION + + +EOF + +echo "Building SDP DMG..." +if command -v hdiutil &> /dev/null; then + hdiutil create -volname "SDP" -srcfolder "$APP_DIR" -ov -format UDZO "$DIST_DIR/SDP-$VERSION.dmg" + echo "DMG created: $DIST_DIR/SDP-$VERSION.dmg" +else + echo "hdiutil not found, skipping DMG creation" + echo "App bundle created: $APP_DIR" +fi + +echo "SDP build completed!" +echo "Output: $DIST_DIR/" +''' + + with open("build_sdp_macos.sh", "w") as f: + f.write(script_content) + + # 为脚本添加执行权限 + os.chmod("build_sdp_macos.sh", 0o755) + + print() + print_color("SDP macOS build script created: build_sdp_macos.sh", Colors.BOLD_GREEN) + print() + print_color("Instructions:", Colors.BOLD_YELLOW) + print_color("1. Copy build_sdp_macos.sh to a macOS system", Colors.GREEN) + print_color("2. Make it executable: chmod +x build_sdp_macos.sh", Colors.GREEN) + print_color("3. Run it: ./build_sdp_macos.sh", Colors.GREEN) + print() + input("Press Enter to continue...") + return True + +# 构建所有 (EXE + DEB) +def build_all(): + print_color("\n========================================", Colors.BOLD_CYAN) + print_color("Building All Packages (EXE + DEB)", Colors.BOLD_YELLOW) + print_color("========================================", Colors.BOLD_CYAN) + print() + + print_color("Starting Windows EXE build...", Colors.CYAN) + print() + if not build_exe(): + return False + + print_color("Starting Linux DEB build...", Colors.CYAN) + print() + if not build_deb(): + return False + + print() + print_color("All builds completed!", Colors.BOLD_GREEN) + print() + input("Press Enter to continue...") + return True + +# 显示菜单 +def show_menu(): + while True: + print_color("========================================", Colors.BOLD_CYAN) + print_color("SCL Build Tool", Colors.BOLD_YELLOW) + print_color("========================================", Colors.BOLD_CYAN) + print() + + print_color("Please select build type:", Colors.BOLD_YELLOW) + print_color("[1] Build Windows EXE", Colors.GREEN) + print_color("[2] Build Linux DEB - SCL Full Package (via WSL)", Colors.GREEN) + print_color("[3] Build Linux DEB - SDP Only (via WSL)", Colors.GREEN) + print_color("[4] Build Linux DEB - Editor Only (via WSL)", Colors.GREEN) + print_color("[5] Generate macOS Build Script (Mach-O)", Colors.GREEN) + print_color("[6] Generate SDP macOS Build Script", Colors.GREEN) + print_color("[7] Build All (EXE + DEB)", Colors.GREEN) + print_color("[8] Build Windows EXE - April Fools Edition", Colors.GREEN) + print_color("[9] Build Linux DEB - SDP April Fools Edition (via WSL)", Colors.GREEN) + print_color("[10] Exit", Colors.GREEN) + print() + + try: + choice = input(f"{Colors.BOLD_CYAN}Enter option (1-10):{Colors.RESET} ") + print() + + if choice == "1": + build_exe() + elif choice == "2": + build_deb() + elif choice == "3": + build_sdp_deb() + elif choice == "4": + build_editor_deb() + elif choice == "5": + generate_macos_script() + elif choice == "6": + generate_sdp_macos_script() + elif choice == "7": + build_all() + elif choice == "8": + build_exe_april_fools() + elif choice == "9": + build_sdp_deb_april_fools() + elif choice == "10": + break + else: + print_color("Invalid option, please try again", Colors.BOLD_RED) + print() + except KeyboardInterrupt: + print() + break + except Exception as e: + print_color(f"An error occurred: {e}", Colors.BOLD_RED) + print() + + print_color("\nThank you for using SCL Build Tool!", Colors.BOLD_YELLOW) + print() + input("Press Enter to exit...") + +# 主函数 +if __name__ == "__main__": + enable_windows_ansi_support() + show_menu() \ No newline at end of file diff --git a/pack.py b/pack.py new file mode 100644 index 0000000..7bc0289 --- /dev/null +++ b/pack.py @@ -0,0 +1,424 @@ +#!/usr/bin/env python3 +""" +SCL to Python Compiler +Converts SCL code to Python code with interpreter and plugins embedded +""" + +import sys +import os +import re +import ast + + +class PluginAnalyzer: + """Analyze plugin code to understand its syntax and implementation""" + + def __init__(self): + self.plugins = {} # Cache for analyzed plugins + self.plugin_file_cache = {} # Cache for plugin file contents + + def analyze_plugin(self, plugin_name): + """Analyze a plugin's code""" + if plugin_name in self.plugins: + return self.plugins[plugin_name] + + # Get the plugins directory path relative to the script + script_dir = os.path.dirname(os.path.abspath(__file__)) + plugin_file = os.path.join(script_dir, 'plugins', f'{plugin_name}.py') + + if not os.path.exists(plugin_file): + print(f"Error: Plugin file '{plugin_file}' not found") + return None + + # Check if file content is cached + mtime = os.path.getmtime(plugin_file) + cache_key = (plugin_file, mtime) + if cache_key in self.plugin_file_cache: + code = self.plugin_file_cache[cache_key] + else: + try: + with open(plugin_file, 'r', encoding='utf-8') as f: + code = f.read() + # Cache the file content + self.plugin_file_cache[cache_key] = code + except Exception as e: + print(f"Error reading plugin file {plugin_file}: {e}") + return None + + # Parse the code + try: + tree = ast.parse(code) + except SyntaxError as e: + print(f"Error parsing plugin {plugin_name}: Syntax error at line {e.lineno}, column {e.offset}: {e.msg}") + return None + + # Extract information + try: + plugin_info = self._extract_plugin_info(tree, plugin_name) + plugin_info['code'] = code + self.plugins[plugin_name] = plugin_info + return plugin_info + except Exception as e: + print(f"Error extracting plugin information from {plugin_name}: {e}") + import traceback + traceback.print_exc() + return None + + def _extract_plugin_info(self, tree, plugin_name): + """Extract plugin information from AST""" + info = { + 'name': plugin_name, + 'imports': [], + 'dependencies': [], + 'commands': {}, + 'class_name': f'{plugin_name.capitalize()}Plugin' + } + + # Get the plugins directory path relative to the script + script_dir = os.path.dirname(os.path.abspath(__file__)) + plugins_dir = os.path.join(script_dir, 'plugins') + + # Extract imports and dependencies + for node in ast.walk(tree): + if isinstance(node, ast.Import): + for alias in node.names: + info['imports'].append(alias.name) + # Check if this is a plugin dependency + if os.path.exists(plugins_dir) and alias.name in os.listdir(plugins_dir) and os.path.isfile(os.path.join(plugins_dir, f'{alias.name}.py')): + info['dependencies'].append(alias.name) + elif isinstance(node, ast.ImportFrom): + if node.module: + info['imports'].append(node.module) + # Check if this is a plugin dependency + if os.path.exists(plugins_dir) and node.module in os.listdir(plugins_dir) and os.path.isfile(os.path.join(plugins_dir, f'{node.module}.py')): + info['dependencies'].append(node.module) + + # Extract command patterns from parse_statement + elif isinstance(node, ast.FunctionDef) and node.name == 'parse_statement': + self._extract_commands_from_parse(node, info) + + return info + + def _extract_commands_from_parse(self, node, info): + """Extract command patterns from parse_statement function""" + for child in ast.walk(node): + if isinstance(child, ast.Compare) and isinstance(child.left, ast.Subscript): + # Check for tokens[pos][1] == 'command' + if self._is_tokens_access(child.left): + if isinstance(child.comparators[0], ast.Constant): + command = child.comparators[0].value + if isinstance(command, str): + info['commands'][command] = {'type': 'direct'} + + elif isinstance(child, ast.Compare) and isinstance(child.left, ast.Name): + # Check for command_suffix == 'c' + if child.left.id == 'command_suffix': + if isinstance(child.comparators[0], ast.Constant): + suffix = child.comparators[0].value + if isinstance(suffix, str): + info['commands'][f'sui-{suffix}'] = {'type': 'sui'} + + def _is_tokens_access(self, node): + """Check if node is tokens[pos][1]""" + if (isinstance(node, ast.Subscript) and + isinstance(node.value, ast.Name) and + node.value.id == 'tokens'): + if isinstance(node.slice, ast.Constant) or isinstance(node.slice, ast.Name): + return True + return False + + +class SCLCompiler: + def __init__(self): + self.variables = {} + self.imports = set() + self.indent_level = 0 + self.plugin_analyzer = PluginAnalyzer() + self.loaded_plugins = set() + + def _detect_plugin_conflicts(self, resolved_plugins): + """Detect command conflicts between plugins""" + command_map = {} + conflicts = [] + + for plugin_name in resolved_plugins: + plugin_info = self.plugin_analyzer.analyze_plugin(plugin_name) + if not plugin_info: + continue + + for command in plugin_info.get('commands', {}): + if command in command_map: + conflicts.append((command, command_map[command], plugin_name)) + else: + command_map[command] = plugin_name + + return conflicts + + def compile(self, scl_code, filename="input.scl", minify=False): + """Compile SCL code to Python code with embedded interpreter and plugins""" + lines = scl_code.split('\n') + + # First pass: collect plugins + plugins_to_load = [] + for line in lines: + if line.startswith('simp{') and line.endswith('}'): + import_content = line[5:-1].strip() + if not import_content.startswith('scl :'): + plugins_to_load.append(import_content) + + # Resolve plugin dependencies + resolved_plugins = self._resolve_plugin_dependencies(plugins_to_load) + + # Detect plugin conflicts + conflicts = self._detect_plugin_conflicts(resolved_plugins) + if conflicts: + print("Warning: Command conflicts detected between plugins:") + for command, plugin1, plugin2 in conflicts: + print(f" Command '{command}' is defined in both {plugin1} and {plugin2}") + print(" The last loaded plugin will take precedence.") + + # Load SCL interpreter code + scl_interpreter_code = self._load_scl_interpreter() + + # Load plugin codes + plugin_codes = {} + for plugin_name in resolved_plugins: + plugin_info = self.plugin_analyzer.analyze_plugin(plugin_name) + if plugin_info: + plugin_codes[plugin_name] = plugin_info['code'] + + # Generate embedded Python code + python_code = self._generate_embedded_code(scl_code, scl_interpreter_code, plugin_codes, filename, minify) + return python_code + + def _resolve_plugin_dependencies(self, plugins_to_load): + """Resolve plugin dependencies and return an ordered list""" + resolved = set() + unresolved = set(plugins_to_load) + order = [] + + print(f"Resolving dependencies for plugins: {plugins_to_load}") + + while unresolved: + # Find plugins with all dependencies resolved + progress = False + for plugin_name in list(unresolved): + plugin_info = self.plugin_analyzer.analyze_plugin(plugin_name) + if not plugin_info: + print(f"Warning: Skipping plugin {plugin_name} due to analysis errors") + unresolved.remove(plugin_name) + continue + + # Check if all dependencies are resolved + dependencies = plugin_info.get('dependencies', []) + print(f"Plugin {plugin_name} has dependencies: {dependencies}") + + if all(dep in resolved for dep in dependencies): + # Add to resolved and order + resolved.add(plugin_name) + order.append(plugin_name) + unresolved.remove(plugin_name) + progress = True + print(f"Resolved plugin: {plugin_name}") + + # Add dependencies to unresolved if not already processed + for dep in dependencies: + if dep not in resolved and dep not in unresolved: + print(f"Adding dependency {dep} for plugin {plugin_name}") + unresolved.add(dep) + + if not progress: + # No progress made, break to avoid infinite loop + print(f"Warning: Unable to resolve all dependencies. Remaining: {unresolved}") + break + + print(f"Resolved plugin order: {order}") + return order + + def _minify_code(self, code): + """Minify Python code by removing comments and whitespace""" + lines = code.split('\n') + minified_lines = [] + in_multiline_comment = False + + for line in lines: + # Skip empty lines + if not line.strip(): + continue + + # Handle multiline comments + if '"""' in line or "''" in line: + in_multiline_comment = not in_multiline_comment + continue + + if in_multiline_comment: + continue + + # Remove single-line comments + if '#' in line: + line = line.split('#')[0].rstrip() + if not line: + continue + + # Remove leading and trailing whitespace + line = line.strip() + if line: + minified_lines.append(line) + + return '\n'.join(minified_lines) + + def _load_scl_interpreter(self): + """Load SCL interpreter code""" + try: + # Get the script directory + script_dir = os.path.dirname(os.path.abspath(__file__)) + scl_path = os.path.join(script_dir, 'scl.py') + + with open(scl_path, 'r', encoding='utf-8') as f: + code = f.read() + + # Remove the main function execution at the end + lines = code.split('\n') + filtered_lines = [] + in_main_block = False + + for line in lines: + if line.strip() == 'if __name__ == "__main__":': + in_main_block = True + continue + if in_main_block: + if line.strip() and not line.startswith(' '): + in_main_block = False + else: + continue + filtered_lines.append(line) + + return '\n'.join(filtered_lines) + except Exception as e: + print(f"Error loading SCL interpreter: {e}") + return "" + + def _generate_header(self, filename): + """Generate the header for the embedded code""" + return [ + '#!/usr/bin/env python3', + f'# Generated from {filename} by SCL Compiler', + '# Embedded SCL interpreter and plugins', + '' + ] + + def _generate_interpreter_code(self, scl_interpreter_code, minify): + """Generate the SCL interpreter code""" + code = ['\n# ==== SCL INTERPRETER ====\n'] + if minify: + code.append(self._minify_code(scl_interpreter_code)) + else: + code.append(scl_interpreter_code) + code.append('') + return code + + def _generate_plugin_codes(self, plugin_codes, minify): + """Generate the plugin codes""" + code = [] + for plugin_name, plugin_code in plugin_codes.items(): + code.append(f'\n# ==== PLUGIN: {plugin_name} ====\n') + if minify: + code.append(self._minify_code(plugin_code)) + else: + code.append(plugin_code) + code.append('') + return code + + def _generate_main_function(self, plugin_codes, scl_code): + """Generate the main function code""" + code = ['\n# ==== MAIN EXECUTION ====\n\ndef main():\n """Run the embedded SCL code"""\n print("Running embedded SCL code...")\n print("=" * 50)\n \n # Create SCL interpreter\n interpreter = SCLInterpreter()\n \n # Load plugins\n'] + + # Add plugin loading code + for plugin_name in plugin_codes: + code.append(f" interpreter.load_plugin('{plugin_name}')\n") + + # Add SCL code execution + code.append('\n # SCL code to execute\n scl_code = """') + # 安全地嵌入 SCL 代码,处理三引号 + for line in scl_code.split('\n'): + code.append(line.replace('"""', '\\"""')) + code.append('\n') + code.append('"""\n') + + # Add execution code + code.append('\n # Execute SCL code\n try:\n success = interpreter.execute(scl_code, "embedded")\n if not success:\n print("Error: Failed to execute SCL code")\n except Exception as e:\n print(f"Error: {e}")\n \n print("=" * 50)\n print("Execution completed.")\n\n\nif __name__ == "__main__":\n main()\n') + + return code + + def _generate_embedded_code(self, scl_code, scl_interpreter_code, plugin_codes, filename, minify=False): + """Generate embedded Python code""" + # Use a single string buffer for more efficient concatenation + code_buffer = [] + + # Add header + code_buffer.extend(self._generate_header(filename)) + + # Add SCL interpreter code + code_buffer.extend(self._generate_interpreter_code(scl_interpreter_code, minify)) + + # Add plugin codes + code_buffer.extend(self._generate_plugin_codes(plugin_codes, minify)) + + # Add main function + code_buffer.extend(self._generate_main_function(plugin_codes, scl_code)) + + return ''.join(code_buffer) + + +def main(): + """Main function""" + import argparse + + # Parse command line arguments + parser = argparse.ArgumentParser(description='SCL to Python Compiler') + parser.add_argument('input_file', help='Input SCL file') + parser.add_argument('output_file', nargs='?', help='Output Python file (optional, default: stdout)') + parser.add_argument('-m', '--minify', action='store_true', help='Minify the generated code') + + args = parser.parse_args() + + input_file = args.input_file + output_file = args.output_file + minify = args.minify + + # Check if input file exists + if not os.path.exists(input_file): + print(f"Error: Input file '{input_file}' not found") + sys.exit(1) + + # Read SCL code + try: + with open(input_file, 'r', encoding='utf-8') as f: + scl_code = f.read() + except Exception as e: + print(f"Error reading file: {e}") + sys.exit(1) + + # Compile to Python + compiler = SCLCompiler() + python_code = compiler.compile(scl_code, input_file, minify) + + # Output + if output_file: + try: + with open(output_file, 'w', encoding='utf-8') as f: + f.write(python_code) + print(f"Successfully compiled {input_file} to {output_file}") + if minify: + print("The file has been minified to reduce size.") + else: + print("The file includes embedded SCL interpreter and plugins.") + except Exception as e: + print(f"Error writing file: {e}") + sys.exit(1) + else: + print(python_code) + + +if __name__ == '__main__': + main() diff --git a/scl.py b/scl.py new file mode 100644 index 0000000..e2d951c --- /dev/null +++ b/scl.py @@ -0,0 +1,1366 @@ +#!/usr/bin/env python3 +""" +SunsetCodeLang (SCL) Interpreter +Main interpreter for the SCL language +""" + +import sys +import os +import re +import importlib.util +import tkinter as tk + +class SCLInterpreter: + def __init__(self): + self.variables = {} + self.functions = {} + self.plugins = {} + self.loaded_plugins = set() + self.debug_mode = False # 默认为false,不开启debug模式 + + def _find_plugin_class(self, plugin_module): + """Find the plugin class in a module""" + for name in dir(plugin_module): + if name.endswith('Plugin') and name[0].isupper(): + return name + return None + + def _register_plugin_instance(self, plugin_name, plugin_instance, is_web=False, url=None): + """Register a plugin instance""" + self.plugins[plugin_name] = plugin_instance + if hasattr(plugin_instance, 'register_syntax'): + plugin_instance.register_syntax() + if self.debug_mode: + if is_web: + print(f"Debug: Loaded web plugin: {plugin_name} from {url}, Total plugins: {list(self.plugins.keys())}") + else: + print(f"Debug: Loaded plugin: {plugin_name}, Total plugins: {list(self.plugins.keys())}") + + def load_plugin(self, plugin_path): + """Load a plugin from the given path or URL""" + try: + if plugin_path.startswith('web : '): + return self._load_web_plugin(plugin_path) + else: + return self._load_local_plugin(plugin_path) + except Exception as e: + print(f"Error loading plugin {plugin_path}: {e}") + import traceback + traceback.print_exc() + return False + + def _load_web_plugin(self, plugin_path): + """Load a plugin from a web URL""" + import urllib.request + import tempfile + import hashlib + + url = plugin_path[6:].strip() + plugin_name = 'web_' + hashlib.md5(url.encode()).hexdigest()[:8] + + if plugin_name in self.plugins: + return True + + print(f"Downloading plugin from: {url}") + try: + with urllib.request.urlopen(url) as response: + if hasattr(response, 'status') and response.status is not None and response.status != 200: + print(f"Error: Failed to download plugin from {url}, status code: {response.status}") + return False + plugin_code = response.read().decode('utf-8') + except Exception as e: + print(f"Error: Failed to download plugin from {url}: {e}") + return False + + temp_plugin_file = None + try: + with tempfile.NamedTemporaryFile(suffix='.py', delete=False) as temp_file: + temp_file.write(plugin_code.encode('utf-8')) + temp_plugin_file = temp_file.name + + spec = importlib.util.spec_from_file_location(plugin_name, temp_plugin_file) + plugin_module = importlib.util.module_from_spec(spec) + spec.loader.exec_module(plugin_module) + + plugin_class_name = self._find_plugin_class(plugin_module) + if not plugin_class_name: + print(f"Error: Plugin from {url} does not have a proper plugin class") + return False + + plugin_class = getattr(plugin_module, plugin_class_name) + plugin_instance = plugin_class(self) + self._register_plugin_instance(plugin_name, plugin_instance, True, url) + return True + finally: + if temp_plugin_file and os.path.exists(temp_plugin_file): + os.unlink(temp_plugin_file) + + def _load_local_plugin(self, plugin_path): + """Load a plugin from the local filesystem""" + module_name = plugin_path.replace('>', '.') + full_module_name = f'plugins.{module_name}' + + if full_module_name in self.loaded_plugins: + return True + + plugin_file = os.path.join('plugins', plugin_path.replace('>', os.sep) + '.py') + + if not os.path.exists(plugin_file): + print(f"Error: Plugin {plugin_path} not found at {plugin_file}") + return False + + spec = importlib.util.spec_from_file_location(full_module_name, plugin_file) + plugin_module = importlib.util.module_from_spec(spec) + spec.loader.exec_module(plugin_module) + + plugin_class = getattr(plugin_module, f'{plugin_path.split(">")[-1].capitalize()}Plugin', None) + if not plugin_class: + print(f"Error: Plugin {plugin_path} does not have a proper plugin class") + return False + + plugin_instance = plugin_class(self) + self.plugins[plugin_path] = plugin_instance + self.loaded_plugins.add(full_module_name) + self._register_plugin_instance(plugin_path, plugin_instance) + return True + + def import_scl_file(self, scl_path, line_num): + """Import and execute another SCL file + + Args: + scl_path: Path to SCL file (local or URL) + line_num: Current line number for error reporting + """ + try: + # Check if path is a URL + if scl_path.startswith('http://') or scl_path.startswith('https://'): + # Download from URL + import urllib.request + import tempfile + + print(f"Downloading SCL file from: {scl_path}") + + try: + with urllib.request.urlopen(scl_path) as response: + code = response.read().decode('utf-8') + except Exception as e: + print(f"Error at line {line_num}: Failed to download SCL file from {scl_path}") + print(f"Error: {e}") + return False + else: + # Load from local file + if not os.path.exists(scl_path): + print(f"Error at line {line_num}: SCL file not found: {scl_path}") + return False + + with open(scl_path, 'r', encoding='utf-8') as f: + code = f.read() + + # Execute the imported SCL code + print(f"Executing imported SCL file: {scl_path}") + success = self.execute(code, scl_path) + + if not success: + print(f"Error at line {line_num}: Failed to execute imported SCL file: {scl_path}") + return False + + return True + except Exception as e: + print(f"Error at line {line_num}: Failed to import SCL file {scl_path}") + print(f"Error: {e}") + import traceback + traceback.print_exc() + return False + + def tokenize(self, code): + """Tokenize the SCL code (optimized)""" + tokens = [] + code = code.strip() + i = 0 + n = len(code) + + # 预定义常量 + whitespace_chars = {' ', '\t', '\n', '\r'} + operator_chars = {'+', '-', '*', '/', '=', '<', '>', '!', '&', '|'} + paren_chars = {'(', ')', '[', ']', '{', '}'} + + while i < n: + # 快速跳过空白字符 + while i < n and code[i] in whitespace_chars: + i += 1 + + if i >= n: + break + + char = code[i] + + if char == '"': + # 字符串处理 + j = i + 1 + string_content = [] + while j < n and code[j] != '"': + if code[j] == '\\' and j + 1 < n: + j += 1 + if code[j] == 'n': + string_content.append('\n') + elif code[j] == '\\': + string_content.append('\\') + else: + string_content.append(code[j]) + else: + string_content.append(code[j]) + j += 1 + tokens.append(('STRING', ''.join(string_content))) + i = j + 1 if j < n else n + + elif char.isdigit(): + # 数字处理 + j = i + while j < n and (code[j].isdigit() or code[j] == '.'): + j += 1 + tokens.append(('NUMBER', code[i:j])) + i = j + + elif char.isalpha() or char == '_': + # 标识符处理 + j = i + while j < n and (code[j].isalnum() or code[j] == '_'): + j += 1 + tokens.append(('IDENTIFIER', code[i:j])) + i = j + + elif char == '|': + tokens.append(('SEPARATOR', char)) + i += 1 + + elif char == ':': + tokens.append(('ASSIGN', char)) + i += 1 + + elif char in operator_chars: + # 运算符处理 + j = i + while j < n and code[j] in operator_chars: + j += 1 + tokens.append(('OPERATOR', code[i:j])) + i = j + + elif char in paren_chars: + tokens.append(('PAREN', char)) + i += 1 + + elif char == '#': + # 注释处理 + j = i + while j < n and code[j] != '\n': + j += 1 + tokens.append(('COMMENT', code[i:j])) + i = j + + elif char == '/' and i + 1 < n and code[i + 1] == '/': + # 注释处理 + j = i + while j < n and code[j] != '\n': + j += 1 + tokens.append(('COMMENT', code[i:j])) + i = j + + else: + tokens.append(('UNKNOWN', char)) + i += 1 + + return tokens + + def parse_expression(self, tokens, pos): + """Parse an expression from the tokens""" + # 简单表达式解析器,支持二元运算 + if pos >= len(tokens): + return None, pos + + # 解析左操作数 + left, pos = self.parse_primary(tokens, pos) + if left is None: + return None, pos + + # 解析运算符和右操作数 + while pos < len(tokens) and tokens[pos][0] == 'OPERATOR' and tokens[pos][1] in '+-*/': + op = tokens[pos] + pos += 1 + right, pos = self.parse_primary(tokens, pos) + if right is None: + return None, pos + # 构建二元表达式 + left = [left, op, right] + + return left, pos + + def parse_primary(self, tokens, pos): + """Parse a primary expression""" + if pos >= len(tokens): + return None, pos + + token = tokens[pos] + if token[0] == 'STRING' or token[0] == 'NUMBER': + return token, pos + 1 + elif token[0] == 'IDENTIFIER': + # Check if this is a function call: identifier(arguments) + if pos + 1 < len(tokens) and tokens[pos + 1][0] == 'PAREN' and tokens[pos + 1][1] == '(': + # Parse function name + func_name = token[1] + pos += 2 # Skip identifier and '(' + + # Parse arguments + args = [] + while pos < len(tokens) and not (tokens[pos][0] == 'PAREN' and tokens[pos][1] == ')'): + arg, pos = self.parse_expression(tokens, pos) + if arg: + args.append(arg) + # Skip commas + if pos < len(tokens) and (tokens[pos][0] == 'SEPARATOR' and tokens[pos][1] == ',' or tokens[pos][0] == 'UNKNOWN' and tokens[pos][1] == ','): + pos += 1 + + if pos < len(tokens) and tokens[pos][0] == 'PAREN' and tokens[pos][1] == ')': + pos += 1 + # Return a function call expression + return ('FUNCTION_CALL', func_name, args), pos + # Otherwise, return the identifier + return token, pos + 1 + elif token[0] == 'PAREN' and token[1] == '(': + expr, pos = self.parse_expression(tokens, pos + 1) + if pos < len(tokens) and tokens[pos][0] == 'PAREN' and tokens[pos][1] == ')': + return expr, pos + 1 + return None, pos + return None, pos + + def _skip_whitespace(self, tokens, pos): + """Skip whitespace tokens""" + while pos < len(tokens) and (tokens[pos][0] == 'UNKNOWN' and tokens[pos][1].isspace()): + pos += 1 + return pos + + def parse_statement(self, tokens, pos): + """Parse a statement from the tokens""" + # Check for variable assignment with plugin pipeline: set var | var : plugin : command + if pos < len(tokens) and tokens[pos][0] == 'IDENTIFIER' and tokens[pos][1] == 'set': + var_pos = pos + 1 + if var_pos < len(tokens) and tokens[var_pos][0] == 'IDENTIFIER': + var_name = tokens[var_pos][1] + pipe_pos = var_pos + 1 + if pipe_pos < len(tokens) and tokens[pipe_pos][0] == 'SEPARATOR' and tokens[pipe_pos][1] == '|': + after_pipe_pos = pipe_pos + 1 + if after_pipe_pos < len(tokens) and tokens[after_pipe_pos][0] == 'IDENTIFIER' and tokens[after_pipe_pos][1] == var_name: + assign_pos = after_pipe_pos + 1 + if assign_pos < len(tokens) and tokens[assign_pos][0] == 'ASSIGN' and tokens[assign_pos][1] == ':': + plugin_pos = assign_pos + 1 + if plugin_pos < len(tokens) and tokens[plugin_pos][0] == 'IDENTIFIER': + plugin_name = tokens[plugin_pos][1] + plugin_assign_pos = plugin_pos + 1 + if plugin_assign_pos < len(tokens) and tokens[plugin_assign_pos][0] == 'ASSIGN' and tokens[plugin_assign_pos][1] == ':': + command_pos = plugin_assign_pos + 1 + if command_pos < len(tokens) and tokens[command_pos][0] == 'IDENTIFIER': + command_name = tokens[command_pos][1] + args_pos = command_pos + 1 + + # Parse command arguments in angle brackets + args_tokens = [] + if args_pos < len(tokens) and tokens[args_pos][0] == 'OPERATOR' and tokens[args_pos][1] == '<': + current_arg_pos = args_pos + 1 + while current_arg_pos < len(tokens) and not (tokens[current_arg_pos][0] == 'OPERATOR' and tokens[current_arg_pos][1] == '>'): + args_tokens.append(tokens[current_arg_pos]) + current_arg_pos += 1 + if current_arg_pos < len(tokens) and tokens[current_arg_pos][0] == 'OPERATOR' and tokens[current_arg_pos][1] == '>': + current_arg_pos += 1 + return ('PLUGIN_ASSIGN', var_name, plugin_name, command_name, args_tokens), current_arg_pos + + if pos < len(tokens): + token = tokens[pos] + if token[0] == 'IDENTIFIER' and token[1] == 'sdebug': + if pos + 1 < len(tokens) and tokens[pos + 1][0] == 'ASSIGN' and tokens[pos + 1][1] == ':': + if pos + 2 < len(tokens) and tokens[pos + 2][0] == 'IDENTIFIER' and tokens[pos + 2][1] in ['true', 'false']: + debug_value = tokens[pos + 2][1] == 'true' + return ('DEBUG', debug_value), pos + 3 + + + + # 处理end语句 + if pos < len(tokens) and tokens[pos][0] == 'IDENTIFIER' and tokens[pos][1] == 'end': + return ('END',), pos + 1 + + # 直接处理sif、seif、sel和swhi语句 + if pos < len(tokens) and tokens[pos][0] == 'IDENTIFIER' and tokens[pos][1] == 'sif': + current_pos = pos + 1 # Skip 'sif' + + # Find separator '|' for condition + cond_end = current_pos + while cond_end < len(tokens) and not (tokens[cond_end][0] == 'SEPARATOR' and tokens[cond_end][1] == '|'): + cond_end += 1 + + if cond_end >= len(tokens): + return None, pos + + # Extract condition + condition = tokens[current_pos:cond_end] + current_pos = cond_end + 1 # Skip '|' + + # Parse if body + if_body = [] + body_end = current_pos + while body_end < len(tokens): + token = tokens[body_end] + if token[0] == 'IDENTIFIER' and token[1] in ['seif', 'sel', 'end']: + break + stmt, new_pos = self.parse_statement(tokens, body_end) + if stmt: + if_body.append(stmt) + body_end = new_pos + else: + body_end += 1 + + current_pos = body_end + + # Parse elif clauses + elif_clauses = [] + while current_pos < len(tokens) and tokens[current_pos][0] == 'IDENTIFIER' and tokens[current_pos][1] == 'seif': + current_pos += 1 # Skip 'seif' + + # Find separator '|' for elif condition + elif_cond_end = current_pos + while elif_cond_end < len(tokens) and not (tokens[elif_cond_end][0] == 'SEPARATOR' and tokens[elif_cond_end][1] == '|'): + elif_cond_end += 1 + + if elif_cond_end >= len(tokens): + return None, pos + + # Extract elif condition + elif_condition = tokens[current_pos:elif_cond_end] + current_pos = elif_cond_end + 1 # Skip '|' + + # Parse elif body + elif_body = [] + elif_body_end = current_pos + while elif_body_end < len(tokens): + token = tokens[elif_body_end] + if token[0] == 'IDENTIFIER' and token[1] in ['seif', 'sel', 'end']: + break + stmt, new_pos = self.parse_statement(tokens, elif_body_end) + if stmt: + elif_body.append(stmt) + elif_body_end = new_pos + else: + elif_body_end += 1 + + elif_clauses.append((elif_condition, elif_body)) + current_pos = elif_body_end + + # Parse else clause + else_body = [] + if current_pos < len(tokens) and tokens[current_pos][0] == 'IDENTIFIER' and tokens[current_pos][1] == 'sel': + current_pos += 1 # Skip 'sel' + + # Check for separator '|' + if current_pos >= len(tokens) or not (tokens[current_pos][0] == 'SEPARATOR' and tokens[current_pos][1] == '|'): + return None, pos + + current_pos += 1 # Skip '|' + + # Parse else body + else_body_end = current_pos + while else_body_end < len(tokens): + token = tokens[else_body_end] + if token[0] == 'IDENTIFIER' and token[1] == 'end': + break + stmt, new_pos = self.parse_statement(tokens, else_body_end) + if stmt: + else_body.append(stmt) + else_body_end = new_pos + else: + else_body_end += 1 + + current_pos = else_body_end + + # Check for end statement + if current_pos >= len(tokens) or not (tokens[current_pos][0] == 'IDENTIFIER' and tokens[current_pos][1] == 'end'): + return None, pos + + current_pos += 1 # Skip 'end' + + return ('IF_ELSE', condition, if_body, elif_clauses, else_body), current_pos + + # Handle while statement (swhi) + elif pos < len(tokens) and tokens[pos][0] == 'IDENTIFIER' and tokens[pos][1] == 'swhi': + current_pos = pos + 1 # Skip 'swhi' + + # Find separator '|' for condition + cond_end = current_pos + while cond_end < len(tokens) and not (tokens[cond_end][0] == 'SEPARATOR' and tokens[cond_end][1] == '|'): + cond_end += 1 + + if cond_end >= len(tokens): + return None, pos + + # Extract condition + condition = tokens[current_pos:cond_end] + current_pos = cond_end + 1 # Skip '|' + + # Parse while body + body = [] + body_end = current_pos + while body_end < len(tokens): + token = tokens[body_end] + if token[0] == 'IDENTIFIER' and token[1] == 'end': + break + stmt, new_pos = self.parse_statement(tokens, body_end) + if stmt: + body.append(stmt) + body_end = new_pos + else: + body_end += 1 + + current_pos = body_end + + # Check for end statement + if current_pos >= len(tokens) or not (tokens[current_pos][0] == 'IDENTIFIER' and tokens[current_pos][1] == 'end'): + return None, pos + + current_pos += 1 # Skip 'end' + + return ('WHILE', condition, body), current_pos + + if pos < len(tokens): + token = tokens[pos] + if token[0] == 'IDENTIFIER': + local_pos = pos + 1 + local_pos = self._skip_whitespace(tokens, local_pos) + if local_pos < len(tokens) and tokens[local_pos][0] == 'OPERATOR' and tokens[local_pos][1] == '=': + local_pos += 1 + local_pos = self._skip_whitespace(tokens, local_pos) + expr, new_pos = self.parse_expression(tokens, local_pos) + if expr: + return ('ASSIGNMENT', token[1], expr), new_pos + + # 然后调用siew插件,确保它能处理sif和swhile语句 + if 'siew' in self.plugins: + stmt, new_pos = self.plugins['siew'].parse_statement(tokens, pos) + if stmt: + return stmt, new_pos + + # 最后调用其他插件 + if self.debug_mode: + print(f"Debug: Available plugins: {list(self.plugins.keys())}") + for plugin_name, plugin in self.plugins.items(): + if plugin_name != 'siew': + if self.debug_mode: + print(f"Debug: Checking plugin {plugin_name}") + if hasattr(plugin, 'parse_statement'): + if self.debug_mode: + print(f"Debug: Calling {plugin_name}.parse_statement at pos {pos}") + stmt, new_pos = plugin.parse_statement(tokens, pos) + if stmt: + if self.debug_mode: + print(f"Debug: {plugin_name} returned statement: {stmt}") + return stmt, new_pos + else: + if self.debug_mode: + print(f"Debug: {plugin_name} returned None") + else: + if self.debug_mode: + print(f"Debug: {plugin_name} has no parse_statement method") + return None, pos + def evaluate_condition(self, condition_tokens): + """Evaluate a condition from tokens""" + if len(condition_tokens) >= 3: + left = condition_tokens[0] + op = condition_tokens[1] + right = condition_tokens[2] + + left_value = self.evaluate_expression(left) + right_value = self.evaluate_expression(right) + + if op[1] == '>': + return left_value > right_value + elif op[1] == '<': + return left_value < right_value + elif op[1] == '==': + return left_value == right_value + elif op[1] == '!=': + return left_value != right_value + elif op[1] == '>=': + return left_value >= right_value + elif op[1] == '<=': + return left_value <= right_value + elif op[1] == '&': + return left_value and right_value + elif op[1] == '|': + return left_value or right_value + return False + + def evaluate_expression(self, expr): + """Evaluate an expression (optimized)""" + if isinstance(expr, list): + if len(expr) == 3 and expr[1][0] == 'OPERATOR': + left = self.evaluate_expression(expr[0]) + op = expr[1][1] + right = self.evaluate_expression(expr[2]) + + # 使用字典映射代替多个 if-elif 分支 + op_map = { + '+': lambda l, r: str(l) + str(r) if isinstance(l, str) or isinstance(r, str) else l + r, + '-': lambda l, r: l - r, + '*': lambda l, r: l * r, + '/': lambda l, r: l / r if r != 0 else 0 + } + return op_map.get(op, lambda l, r: 0)(left, right) + + if expr[0] == 'STRING': + return expr[1] + elif expr[0] == 'NUMBER': + # 缓存数字转换结果 + try: + num_str = expr[1] + if '.' in num_str: + return float(num_str) + else: + return int(num_str) + except (ValueError, TypeError): + return 0 + elif expr[0] == 'IDENTIFIER': + # 快速变量查找 + return self.variables.get(expr[1], 0) + elif expr[0] == 'FUNCTION_CALL': + # 处理函数调用 + func_name = expr[1] + args = expr[2] + + # 评估参数 + evaluated_args = [self.evaluate_expression(arg) for arg in args] + + # 检查是否有插件处理函数调用 + for plugin in self.plugins.values(): + result = plugin.execute_statement(('FUNCTION_CALL', func_name, evaluated_args)) + # 检查结果是否不是布尔值 - 这意味着函数返回了一个值 + if not isinstance(result, bool): + return result + + # 如果没有插件处理,返回 0 + return 0 + return 0 + + def execute_statement(self, stmt): + """Execute a statement""" + # 检查是否是debug语句 + if stmt[0] == 'DEBUG': + # 设置debug模式 + self.debug_mode = stmt[1] + # 打印debug信息 + print(f"Debug mode: {self.debug_mode}") + return True + + # 直接执行PRINT语句,不依赖basic插件 + if stmt[0] == 'PRINT': + value = self.evaluate_expression(stmt[1]) + print(value) + return True + + + + # 处理END语句 + if stmt[0] == 'END': + return True + + # 处理IF_ELSE语句 + if stmt[0] == 'IF_ELSE': + if_condition = stmt[1] + if_body = stmt[2] + elif_clauses = stmt[3] + else_body = stmt[4] + + if self.evaluate_condition(if_condition): + for body_stmt in if_body: + self.execute_statement(body_stmt) + return True + + for elif_condition, elif_body in elif_clauses: + if self.evaluate_condition(elif_condition): + for body_stmt in elif_body: + self.execute_statement(body_stmt) + return True + + if else_body: + for body_stmt in else_body: + self.execute_statement(body_stmt) + + return True + + # 处理WHILE语句 + elif stmt[0] == 'WHILE': + condition_tokens = stmt[1] + body = stmt[2] + + while self.evaluate_condition(condition_tokens): + for body_stmt in body: + self.execute_statement(body_stmt) + + return True + + # 执行赋值语句 + if stmt[0] == 'ASSIGNMENT': + var_name = stmt[1] + value = self.evaluate_expression(stmt[2]) + self.variables[var_name] = value + if self.debug_mode: + print(f"Debug: Assigned {value} to variable {var_name}") + return True + + # 处理插件赋值语句: set var | var : plugin : command + if stmt[0] == 'PLUGIN_ASSIGN': + var_name = stmt[1] + plugin_name = stmt[2] + command_name = stmt[3] + args_tokens = stmt[4] + + # Check if plugin exists + if plugin_name not in self.plugins: + print(f"Error: Plugin '{plugin_name}' not found") + return False + + plugin = self.plugins[plugin_name] + + # Create a statement that the plugin can understand + plugin_stmt = None + + # For math plugin + if plugin_name == 'math': + plugin_stmt = ('MATH', command_name, args_tokens) + # For string plugin + elif plugin_name == 'string': + plugin_stmt = ('STRING_OP', command_name, args_tokens) + # For crypto plugin + elif plugin_name == 'crypto': + plugin_stmt = ('CRYPTO_OP', command_name, args_tokens) + # For unit plugin + elif plugin_name == 'unit': + plugin_stmt = ('UNIT_CONVERT', command_name, args_tokens) + # For request plugin + elif plugin_name == 'request': + plugin_stmt = ('REQUEST', command_name.upper(), args_tokens) + # For json plugin + elif plugin_name == 'json': + plugin_stmt = ('JSON_OP', command_name, args_tokens) + # For color plugin + elif plugin_name == 'color': + # Color plugin doesn't return values, it just prints + return plugin.execute_statement(('COLOR_OUTPUT', args_tokens[0], args_tokens[2])) if len(args_tokens) >= 3 else False + + if plugin_stmt: + # Create a temporary variable to capture the result + temp_result = None + + # Override print for this execution to capture the result + import builtins + original_print = builtins.print + + def capture_print(*args, **kwargs): + nonlocal temp_result + temp_result = ' '.join(map(str, args)) + original_print(*args, **kwargs) + + builtins.print = capture_print + + # Execute the plugin statement + try: + success = plugin.execute_statement(plugin_stmt) + finally: + # Restore original print + builtins.print = original_print + + if success and temp_result is not None: + # Try to convert to appropriate type + try: + if '.' in temp_result: + result_value = float(temp_result) + else: + result_value = int(temp_result) + except ValueError: + # Keep as string + result_value = temp_result + + # Assign the result to the variable + self.variables[var_name] = result_value + if self.debug_mode: + print(f"Debug: Assigned plugin result '{result_value}' to variable '{var_name}'") + return True + + print(f"Error: Failed to execute plugin assignment for '{var_name}'") + return False + + # 优先调用siew插件,确保它能处理IF、IF_ELSE和WHILE语句 + if 'siew' in self.plugins: + if self.plugins['siew'].execute_statement(stmt): + return True + + # 然后调用其他插件 + for plugin_name, plugin in self.plugins.items(): + if plugin_name != 'siew' and hasattr(plugin, 'execute_statement'): + if plugin.execute_statement(stmt): + return True + return False + + def execute(self, code, file_path=None): + """Execute the given SCL code (optimized)""" + def smart_split(code): + """Optimized line splitting that handles strings correctly""" + lines = [] + current_line = [] + in_string = False + + # 预分配空间,减少列表扩展开销 + code_len = len(code) + current_line = [''] * code_len + line_pos = 0 + + for char in code: + if char == '"': + in_string = not in_string + current_line[line_pos] = char + line_pos += 1 + elif char == '\n' and not in_string: + lines.append(''.join(current_line[:line_pos])) + current_line = [''] * code_len + line_pos = 0 + else: + current_line[line_pos] = char + line_pos += 1 + + if line_pos > 0: + lines.append(''.join(current_line[:line_pos])) + + return lines + + lines = smart_split(code) + + # 用于处理多行语句 + multi_line_buffer = [] + in_multi_line = False + nested_level = 0 # 用于跟踪嵌套级别 + + # 预定义常量 + multi_line_starts = { + 'sif ', 'swhile :', 'swhile |', 'swhi ', 'srg', 'sdef <', 'sclass <' + } + + for line_num, line in enumerate(lines, 1): + # 快速跳过空行和注释 + stripped_line = line.strip() + if not stripped_line or stripped_line.startswith('#') or stripped_line.startswith('//'): + continue + + # 处理插件导入和SCL文件导入 + if line.startswith('simp{') and line.endswith('}'): + import_content = line[5:-1].strip() + + # 检查是否是SCL文件导入: simp{scl : 路径} + if import_content.startswith('scl :'): + scl_path = import_content[5:].strip() + if not self.import_scl_file(scl_path, line_num): + return False + else: + # 插件导入 + if not self.load_plugin(import_content): + print(f"Error at line {line_num}: Failed to load plugin {import_content}") + print(f"Code: {line}") + return False + continue + + try: + # 检查是否是多行语句的开始 + is_multi_line_start = False + for prefix in multi_line_starts: + if line.startswith(prefix): + is_multi_line_start = True + break + + # 特殊处理 sde 语句 + if not is_multi_line_start and line.startswith('sde ') and line.endswith(' :'): + is_multi_line_start = True + + if is_multi_line_start: + # 打印调试信息 + if self.debug_mode: + print(f"Debug: Found multi-line statement start: {line}") + multi_line_buffer.append(line) + if not in_multi_line: + in_multi_line = True + nested_level = 1 # 开始一个新的多行语句,嵌套级别为1 + # 打印调试信息 + if self.debug_mode: + print(f"Debug: Started new multi-line statement, nested_level = {nested_level}") + else: + nested_level += 1 # 遇到新的嵌套语句,嵌套级别加1 + # 打印调试信息 + if self.debug_mode: + print(f"Debug: Found nested statement, nested_level = {nested_level}") + # 检查是否是多行语句的结束 + elif line == 'end' and in_multi_line: + # 打印调试信息 + if self.debug_mode: + print(f"Debug: Found multi-line statement end: {line}") + multi_line_buffer.append(line) + nested_level -= 1 # 遇到end,嵌套级别减1 + # 打印调试信息 + if self.debug_mode: + print(f"Debug: Decreased nested_level to {nested_level}") + if nested_level == 0: + # 嵌套级别为0,说明是外部语句的结束 + in_multi_line = False + # 处理完整的多行语句 + multi_line_code = '\n'.join(multi_line_buffer) + multi_line_buffer = [] + # 打印调试信息 + if self.debug_mode: + print(f"Debug: Processing complete multi-line statement:\n{multi_line_code}") + # 这里我们需要特殊处理多行语句 + # 简单起见,我们将多行语句作为一个整体进行tokenize和parse + # 注意:这种方法可能不是最优的,但是可以解决当前的问题 + tokens = self.tokenize(multi_line_code) + # 打印调试信息 + if self.debug_mode: + print(f"Debug: Tokens: {tokens}") + if tokens: + stmt, _ = self.parse_statement(tokens, 0) + # 打印调试信息 + if self.debug_mode: + print(f"Debug: Parsed statement: {stmt}") + if stmt: + if not self.execute_statement(stmt): + print(f"Error at line {line_num}: Failed to execute statement") + print(f"Code: {multi_line_code}") + return False + else: + print(f"Error at line {line_num}: Invalid syntax") + print(f"Code: {multi_line_code}") + return False + # 处理多行语句的中间部分 + elif in_multi_line: + multi_line_buffer.append(line) + # 检查是否是新的嵌套语句的开始 + if line.startswith('sif :') or line.startswith('swhile :'): + nested_level += 1 # 遇到新的嵌套语句,嵌套级别加1 + # 处理单行语句 + else: + # 使用原始的line,不移除空白字符 + tokens = self.tokenize(line) + if tokens: + stmt, _ = self.parse_statement(tokens, 0) + if stmt: + if not self.execute_statement(stmt): + print(f"Error at line {line_num}: Failed to execute statement") + print(f"Code: {line}") + return False + else: + print(f"Error at line {line_num}: Invalid syntax") + print(f"Code: {line}") + return False + except Exception as e: + print(f"Error at line {line_num}: {e}") + print(f"Code: {line}") + import traceback + traceback.print_exc() + return False + + # 检查是否有未完成的多行语句 + if in_multi_line: + print(f"Error at line {line_num}: Unclosed multi-line statement") + print(f"Code: {''.join(multi_line_buffer)}") + return False + + return True + +def nano_editor(file_path=None): + """仿nano的终端文本编辑器""" + import shutil + + # 获取终端大小 + def get_terminal_size(): + return shutil.get_terminal_size() + + # 清屏 + def clear_screen(): + os.system('cls' if os.name == 'nt' else 'clear') + + # 读取文件内容 + lines = [] + if file_path and os.path.exists(file_path): + try: + with open(file_path, 'r', encoding='utf-8') as f: + lines = f.read().split('\n') + except: + lines = [''] + else: + lines = [''] + + # 确保至少有一行 + if not lines: + lines = [''] + + # 编辑器状态 + cursor_x = 0 + cursor_y = 0 + scroll_y = 0 + modified = False + filename = file_path if file_path else "New File" + + # 隐藏光标(Windows) + if os.name == 'nt': + import ctypes + kernel32 = ctypes.windll.kernel32 + handle = kernel32.GetStdHandle(-11) + # 禁用行输入和回显 + mode = ctypes.c_uint32() + kernel32.GetConsoleMode(handle, ctypes.byref(mode)) + kernel32.SetConsoleMode(handle, mode.value & ~0x0004 & ~0x0002) + + def draw_editor(): + """绘制编辑器界面""" + clear_screen() + term_width, term_height = get_terminal_size() + + # 标题栏 + title = f" SCL Nano Editor - {filename}" + if modified: + title += " [Modified]" + title = title[:term_width].ljust(term_width) + print(f"\033[7m{title}\033[0m") + + # 编辑区域 + edit_height = term_height - 3 + for i in range(edit_height): + line_idx = scroll_y + i + if line_idx < len(lines): + line = lines[line_idx] + # 显示行号 + line_num = str(line_idx + 1).rjust(4) + # 截断行内容以适应屏幕 + content = line[:term_width - 6] + content = content.ljust(term_width - 6) + + # 高亮当前行 + if line_idx == cursor_y: + print(f"\033[7m{line_num}\033[0m {content}") + else: + print(f"{line_num} {content}") + else: + print("~".ljust(term_width)) + + # 状态栏 + status = f" Line {cursor_y + 1}/{len(lines)}, Col {cursor_x + 1} | Ctrl+S=Save, Ctrl+X=Exit, Ctrl+O=Open, Ctrl+R=Run" + status = status[:term_width].ljust(term_width) + print(f"\033[7m{status}\033[0m") + + # 移动光标到正确位置 + cursor_screen_y = cursor_y - scroll_y + 1 + # 光标位置 = 行号宽度(5) + cursor_x + 1 + # 行号占4位 + 1个空格 = 5,内容从第6列开始 + # cursor_x=0 时,光标应在第6列(内容的第1个字符) + cursor_screen_x = 5 + cursor_x + 1 + # 限制在屏幕范围内 + cursor_screen_x = min(cursor_screen_x, term_width) + print(f"\033[{cursor_screen_y + 1};{cursor_screen_x}H", end='', flush=True) + + def save_file(): + """保存文件""" + nonlocal modified, filename + if not filename or filename == "New File": + # 提示输入文件名 + term_width, term_height = get_terminal_size() + prompt = "Filename to write: " + print(f"\033[{term_height};1H\033[0K{prompt}", end='', flush=True) + + # 简单输入文件名 + new_filename = input() + if new_filename: + filename = new_filename + else: + return False + + try: + with open(filename, 'w', encoding='utf-8') as f: + f.write('\n'.join(lines)) + modified = False + return True + except Exception as e: + term_width, term_height = get_terminal_size() + print(f"\033[{term_height};1H\033[0KError saving: {e}", end='', flush=True) + input(" Press Enter to continue...") + return False + + def open_file(): + """打开文件""" + nonlocal lines, cursor_x, cursor_y, scroll_y, modified, filename + term_width, term_height = get_terminal_size() + prompt = "Filename to open: " + print(f"\033[{term_height};1H\033[0K{prompt}", end='', flush=True) + + new_filename = input() + if new_filename and os.path.exists(new_filename): + try: + with open(new_filename, 'r', encoding='utf-8') as f: + lines = f.read().split('\n') + if not lines: + lines = [''] + filename = new_filename + cursor_x = 0 + cursor_y = 0 + scroll_y = 0 + modified = False + return True + except Exception as e: + print(f"\033[{term_height};1H\033[0KError opening: {e}", end='', flush=True) + input(" Press Enter to continue...") + return False + + # 主循环 + try: + while True: + draw_editor() + + # 读取按键 + if os.name == 'nt': + import msvcrt + key = msvcrt.getch() + + # 处理特殊键 + if key == b'\x00' or key == b'\xe0': + key = msvcrt.getch() + if key == b'H': # 上箭头 + if cursor_y > 0: + cursor_y -= 1 + cursor_x = min(cursor_x, len(lines[cursor_y]) if lines[cursor_y] else 0) + elif key == b'P': # 下箭头 + if cursor_y < len(lines) - 1: + cursor_y += 1 + cursor_x = min(cursor_x, len(lines[cursor_y]) if lines[cursor_y] else 0) + elif key == b'K': # 左箭头 + if cursor_x > 0: + cursor_x -= 1 + elif key == b'M': # 右箭头 + if cursor_x < len(lines[cursor_y]): + cursor_x += 1 + elif key == b'\x13': # Ctrl+S + save_file() + elif key == b'\x18': # Ctrl+X + if modified: + term_width, term_height = get_terminal_size() + print(f"\033[{term_height};1H\033[0KSave modified buffer? (Y/N/C)", end='', flush=True) + response = input().upper() + if response == 'Y': + if save_file(): + break + elif response == 'N': + break + else: + break + elif key == b'\x0f': # Ctrl+O + open_file() + elif key == b'\x12': # Ctrl+R - 运行文件 + if filename and filename != "New File": + # 先保存文件 + if modified: + save_file() + # 退出编辑器并运行文件 + clear_screen() + print(f"Running {filename}...") + print("=" * 50) + # 创建解释器并执行 + interpreter = SCLInterpreter() + try: + with open(filename, 'r', encoding='utf-8') as f: + code = f.read() + interpreter.execute(code, filename) + except Exception as e: + print(f"Error: {e}") + print("=" * 50) + print("Press any key to return to editor...") + msvcrt.getch() + else: + # 提示先保存文件 + term_width, term_height = get_terminal_size() + print(f"\033[{term_height};1H\033[0KPlease save file first (Ctrl+S)", end='', flush=True) + msvcrt.getch() + elif key == b'\r': # Enter + # 在当前位置分割行 + current_line = lines[cursor_y] + lines.insert(cursor_y + 1, current_line[cursor_x:]) + lines[cursor_y] = current_line[:cursor_x] + cursor_y += 1 + cursor_x = 0 + modified = True + elif key == b'\x08': # Backspace + if cursor_x > 0: + lines[cursor_y] = lines[cursor_y][:cursor_x-1] + lines[cursor_y][cursor_x:] + cursor_x -= 1 + modified = True + elif cursor_y > 0: + # 合并到上一行 + cursor_x = len(lines[cursor_y - 1]) + lines[cursor_y - 1] += lines[cursor_y] + del lines[cursor_y] + cursor_y -= 1 + modified = True + elif key == b'\x7f': # Delete + if cursor_x < len(lines[cursor_y]): + lines[cursor_y] = lines[cursor_y][:cursor_x] + lines[cursor_y][cursor_x+1:] + modified = True + elif cursor_y < len(lines) - 1: + # 合并下一行 + lines[cursor_y] += lines[cursor_y + 1] + del lines[cursor_y + 1] + modified = True + elif key == b'"': # 双引号 + lines[cursor_y] = lines[cursor_y][:cursor_x] + '"' + lines[cursor_y][cursor_x:] + cursor_x += 1 + modified = True + elif key == b"'": # 单引号 + lines[cursor_y] = lines[cursor_y][:cursor_x] + "'" + lines[cursor_y][cursor_x:] + cursor_x += 1 + modified = True + elif key >= b' ' and key <= b'~': # 其他可打印字符 + try: + char = key.decode('utf-8') + except: + char = key.decode('ascii', errors='replace') + lines[cursor_y] = lines[cursor_y][:cursor_x] + char + lines[cursor_y][cursor_x:] + cursor_x += 1 + modified = True + else: + # Linux/Mac 使用简单输入 + import tty + import termios + import select + + fd = sys.stdin.fileno() + old_settings = termios.tcgetattr(fd) + try: + tty.setraw(fd) + key = sys.stdin.read(1) + + if key == '\x13': # Ctrl+S + save_file() + elif key == '\x18': # Ctrl+X + if modified: + print("\nSave modified buffer? (Y/N/C)") + response = input().upper() + if response == 'Y': + if save_file(): + break + elif response == 'N': + break + else: + break + elif key == '\x0f': # Ctrl+O + open_file() + elif key == '\x12': # Ctrl+R - 运行文件 + if filename and filename != "New File": + # 先保存文件 + if modified: + save_file() + # 退出编辑器并运行文件 + clear_screen() + print(f"Running {filename}...") + print("=" * 50) + # 创建解释器并执行 + interpreter = SCLInterpreter() + try: + with open(filename, 'r', encoding='utf-8') as f: + code = f.read() + interpreter.execute(code, filename) + except Exception as e: + print(f"Error: {e}") + print("=" * 50) + print("Press any key to return to editor...") + sys.stdin.read(1) + else: + # 提示先保存文件 + term_width, term_height = get_terminal_size() + print(f"\033[{term_height};1H\033[0KPlease save file first (Ctrl+S)") + sys.stdin.read(1) + elif key == '\r': # Enter + current_line = lines[cursor_y] + lines.insert(cursor_y + 1, current_line[cursor_x:]) + lines[cursor_y] = current_line[:cursor_x] + cursor_y += 1 + cursor_x = 0 + modified = True + elif key == '\x7f': # Backspace + if cursor_x > 0: + lines[cursor_y] = lines[cursor_y][:cursor_x-1] + lines[cursor_y][cursor_x:] + cursor_x -= 1 + modified = True + elif key == '"' or key == "'": # 引号 + lines[cursor_y] = lines[cursor_y][:cursor_x] + key + lines[cursor_y][cursor_x:] + cursor_x += 1 + modified = True + elif ord(key) >= 32 and ord(key) <= 126: # 其他可打印字符 + lines[cursor_y] = lines[cursor_y][:cursor_x] + key + lines[cursor_y][cursor_x:] + cursor_x += 1 + modified = True + finally: + termios.tcsetattr(fd, termios.TCSADRAIN, old_settings) + + # 调整滚动位置 + term_width, term_height = get_terminal_size() + edit_height = term_height - 3 + if cursor_y < scroll_y: + scroll_y = cursor_y + elif cursor_y >= scroll_y + edit_height: + scroll_y = cursor_y - edit_height + 1 + + finally: + # 恢复光标(Windows) + if os.name == 'nt': + import ctypes + kernel32 = ctypes.windll.kernel32 + handle = kernel32.GetStdHandle(-11) + mode = ctypes.c_uint32() + kernel32.GetConsoleMode(handle, ctypes.byref(mode)) + kernel32.SetConsoleMode(handle, mode.value | 0x0004 | 0x0002) + + clear_screen() + print(f"Exited nano editor. File: {filename}") + +def main(): + """Main function""" + if len(sys.argv) < 2: + print("Usage: python scl.py ") + print(" python scl.py -nano [file.scl] # Open nano editor") + sys.exit(1) + + # 检查是否是nano编辑器模式 + if sys.argv[1] == '-nano': + file_path = sys.argv[2] if len(sys.argv) > 2 else None + nano_editor(file_path) + sys.exit(0) + + file_path = sys.argv[1] + + if not os.path.exists(file_path): + print(f"Error: File {file_path} not found") + sys.exit(1) + + interpreter = SCLInterpreter() + + try: + with open(file_path, 'r', encoding='utf-8') as f: + code = f.read() + + success = interpreter.execute(code, file_path) + if not success: + sys.exit(1) + except Exception as e: + print(f"Error executing file: {e}") + import traceback + traceback.print_exc() + sys.exit(1) + +if __name__ == "__main__": + main() + diff --git a/sdp.py b/sdp.py new file mode 100644 index 0000000..215202a --- /dev/null +++ b/sdp.py @@ -0,0 +1,1059 @@ +#!/usr/bin/env python3 +""" +SCL Package Manager (SDP) +Package manager for SCL plugins - similar to apt/yum/composer +""" + +import os +import sys +import json +import urllib.request +import urllib.parse + +class SCLEPackageManager: + # Language translations + TRANSLATIONS = { + "english": { + "installing": "Installing", + "updating": "Updating", + "reinstalling": "Reinstalling", + "removing": "Removing", + "downloading": "Downloading", + "downloaded": "Downloaded", + "installed": "installed successfully", + "updated": "updated successfully", + "reinstalled": "reinstalled successfully", + "removed": "removed successfully", + "already_installed": "is already installed", + "not_installed": "is not installed", + "use_install": "Use 'sdp install' to install", + "use_update": "Use 'sdp update' to update", + "no_plugins": "No plugins installed", + "local_plugins": "Local plugins:", + "community_plugins": "Community plugins:", + "upgradable_plugins": "Plugins available for upgrade:", + "testing_network": "Testing network connectivity...", + "network_ok": "Network connection OK", + "network_fail": "Network connection failed", + "config_saved": "Configuration saved", + "config_created": "Created", + "language_set": "Language set to", + "available_languages": "Available languages:", + "unknown_command": "Unknown command", + "usage": "Usage", + "options": "Options", + "commands": "Commands" + }, + "chinese_simplified": { + "installing": "正在安装", + "updating": "正在更新", + "reinstalling": "正在重新安装", + "removing": "正在删除", + "downloading": "正在下载", + "downloaded": "已下载", + "installed": "安装成功", + "updated": "更新成功", + "reinstalled": "重新安装成功", + "removed": "删除成功", + "already_installed": "已安装", + "not_installed": "未安装", + "use_install": "使用 'sdp install' 安装", + "use_update": "使用 'sdp update' 更新", + "no_plugins": "没有已安装的插件", + "local_plugins": "本地插件:", + "community_plugins": "社区插件:", + "upgradable_plugins": "可升级的插件:", + "testing_network": "测试网络连接...", + "network_ok": "网络连接正常", + "network_fail": "网络连接失败", + "config_saved": "配置已保存", + "config_created": "已创建", + "language_set": "语言已设置为", + "available_languages": "可用语言:", + "unknown_command": "未知命令", + "usage": "用法", + "options": "选项", + "commands": "命令" + }, + "chinese_hongkong": { + "installing": "正在安裝", + "updating": "正在更新", + "reinstalling": "正在重新安裝", + "removing": "正在移除", + "downloading": "正在下載", + "downloaded": "已下載", + "installed": "安裝成功", + "updated": "更新成功", + "reinstalled": "重新安裝成功", + "removed": "移除成功", + "already_installed": "已安裝", + "not_installed": "未安裝", + "use_install": "使用 'sdp install' 安裝", + "use_update": "使用 'sdp update' 更新", + "no_plugins": "沒有已安裝的插件", + "local_plugins": "本地插件:", + "community_plugins": "社區插件:", + "upgradable_plugins": "可升級的插件:", + "testing_network": "測試網絡連接...", + "network_ok": "網絡連接正常", + "network_fail": "網絡連接失敗", + "config_saved": "配置已保存", + "config_created": "已創建", + "language_set": "語言已設置為", + "available_languages": "可用語言:", + "unknown_command": "未知指令", + "usage": "用法", + "options": "選項", + "commands": "指令" + }, + "classical_chinese": { + "installing": "正在安裝", + "updating": "正在更新", + "reinstalling": "正在重裝", + "removing": "正在移除", + "downloading": "正在下載", + "downloaded": "已下載", + "installed": "安裝畢", + "updated": "更新畢", + "reinstalled": "重裝畢", + "removed": "移除畢", + "already_installed": "已安裝矣", + "not_installed": "未安裝", + "use_install": "以 'sdp install' 安裝之", + "use_update": "以 'sdp update' 更新之", + "no_plugins": "無已安裝之插件", + "local_plugins": "本地插件:", + "community_plugins": "社群插件:", + "upgradable_plugins": "可升級之插件:", + "testing_network": "測試網絡...", + "network_ok": "網絡通暢", + "network_fail": "網絡不通", + "config_saved": "配置已存", + "config_created": "已創建", + "language_set": "語言已設為", + "available_languages": "可用語言:", + "unknown_command": "未知之令", + "usage": "用法", + "options": "選項", + "commands": "指令" + }, + "latin": { + "installing": "Installans", + "updating": "Actualizans", + "reinstalling": "Reinstallans", + "removing": "Removens", + "downloading": "Downloadens", + "downloaded": "Downloadatum", + "installed": "installatum est", + "updated": "actualizatum est", + "reinstalled": "reinstallatum est", + "removed": "removitum est", + "already_installed": "iam installatum est", + "not_installed": "non installatum est", + "use_install": "Utere 'sdp install' ad installandum", + "use_update": "Utere 'sdp update' ad actualizandum", + "no_plugins": "Nulla plugins installata", + "local_plugins": "Plugins locales:", + "community_plugins": "Plugins communitatis:", + "upgradable_plugins": "Plugins actualizabiles:", + "testing_network": "Testans conexionem rete...", + "network_ok": "Conexio retis bona est", + "network_fail": "Conexio retis fallita est", + "config_saved": "Configuratio servata est", + "config_created": "Creatum est", + "language_set": "Lingua posita est ad", + "available_languages": "Linguae disponibiles:", + "unknown_command": "Commandum ignotum", + "usage": "Usus", + "options": "Optiones", + "commands": "Commanda" + }, + "portuguese": { + "installing": "Instalando", + "updating": "Atualizando", + "reinstalling": "Reinstalando", + "removing": "Removendo", + "downloading": "Baixando", + "downloaded": "Baixado", + "installed": "instalado com sucesso", + "updated": "atualizado com sucesso", + "reinstalled": "reinstalado com sucesso", + "removed": "removido com sucesso", + "already_installed": "já está instalado", + "not_installed": "não está instalado", + "use_install": "Use 'sdp install' para instalar", + "use_update": "Use 'sdp update' para atualizar", + "no_plugins": "Nenhum plugin instalado", + "local_plugins": "Plugins locais:", + "community_plugins": "Plugins da comunidade:", + "upgradable_plugins": "Plugins disponíveis para atualização:", + "testing_network": "Testando conectividade de rede...", + "network_ok": "Conexão de rede OK", + "network_fail": "Falha na conexão de rede", + "config_saved": "Configuração salva", + "config_created": "Criado", + "language_set": "Idioma definido para", + "available_languages": "Idiomas disponíveis:", + "unknown_command": "Comando desconhecido", + "usage": "Uso", + "options": "Opções", + "commands": "Comandos" + }, + "korean": { + "installing": "설치 중", + "updating": "업데이트 중", + "reinstalling": "재설치 중", + "removing": "제거 중", + "downloading": "다운로드 중", + "downloaded": "다운로드 완료", + "installed": "설치 성공", + "updated": "업데이트 성공", + "reinstalled": "재설치 성공", + "removed": "제거 성공", + "already_installed": "이미 설치됨", + "not_installed": "설치되지 않음", + "use_install": "'sdp install'로 설치하세요", + "use_update": "'sdp update'로 업데이트하세요", + "no_plugins": "설치된 플러그인 없음", + "local_plugins": "로컬 플러그인:", + "community_plugins": "커뮤니티 플러그인:", + "upgradable_plugins": "업그레이드 가능한 플러그인:", + "testing_network": "네트워크 연결 테스트 중...", + "network_ok": "네트워크 연결 정상", + "network_fail": "네트워크 연결 실패", + "config_saved": "설정 저장됨", + "config_created": "생성됨", + "language_set": "언어가 설정됨", + "available_languages": "사용 가능한 언어:", + "unknown_command": "알 수 없는 명령", + "usage": "사용법", + "options": "옵션", + "commands": "명령" + } + } + + LANGUAGE_NAMES = { + "english": "English", + "chinese_simplified": "简体中文", + "chinese_hongkong": "中文(香港)", + "classical_chinese": "文言文", + "latin": "Latin", + "portuguese": "Português", + "korean": "한국어" + } + + # Acknowledgments list + ACKNOWLEDGMENTS = [ + "JGZ_YES - Create SunsetCodeLang(scl) & SunsetCodeLang-DownloadPlugins(sdp)", + "XiaoXiaoBai - Termux Adapt" + ] + + def __init__(self): + self.base_url = "https://scl.ecuil.com/forum/api/download.php" + self.api_url = "https://scl.ecuil.com/forum/api/packages.php" + self.run_plugins_url = "https://scl.ecuil.com/run/plugins/" + self.plugins_dir = "./plugins" + self.config_file = "composer.json" + self.settings_file = "sdp_settings.json" + self.version = "1.0" + self.language = self.load_language() + self.repositories = self.load_repositories() + + def t(self, key): + """Get translation for a key""" + return self.TRANSLATIONS.get(self.language, self.TRANSLATIONS["english"]).get(key, key) + + def load_language(self): + """Load language from settings file""" + if os.path.exists(self.settings_file): + try: + with open(self.settings_file, 'r', encoding='utf-8') as f: + settings = json.load(f) + lang = settings.get('language', 'english') + if lang in self.TRANSLATIONS: + return lang + except Exception: + pass + return "english" + + def save_language(self, language): + """Save language to settings file""" + if language not in self.TRANSLATIONS: + return False + + settings = {} + if os.path.exists(self.settings_file): + try: + with open(self.settings_file, 'r', encoding='utf-8') as f: + settings = json.load(f) + except Exception: + pass + + settings['language'] = language + + try: + with open(self.settings_file, 'w', encoding='utf-8') as f: + json.dump(settings, f, indent=2) + return True + except Exception as e: + self.error(f"Failed to save language setting: {e}") + return False + + def set_language(self, language): + """Set the interface language""" + lang_map = { + "english": "english", + "chinese": "chinese_simplified", + "chinese_simplified": "chinese_simplified", + "简体中文": "chinese_simplified", + "chinese_hongkong": "chinese_hongkong", + "中文(香港)": "chinese_hongkong", + "classical_chinese": "classical_chinese", + "文言文": "classical_chinese", + "latin": "latin", + "portuguese": "portuguese", + "korean": "korean" + } + + normalized_lang = lang_map.get(language.lower(), language.lower()) + + if normalized_lang not in self.TRANSLATIONS: + self.error(f"Unknown language: {language}") + self.show_available_languages() + return False + + if self.save_language(normalized_lang): + self.language = normalized_lang + self.success(f"{self.t('language_set')} {self.LANGUAGE_NAMES[normalized_lang]}") + return True + return False + + def show_available_languages(self): + """Show available languages""" + self.log(self.t("available_languages")) + for key, name in self.LANGUAGE_NAMES.items(): + marker = " *" if key == self.language else "" + self.log(f" {key}: {name}{marker}") + + def load_repositories(self): + """Load repositories from settings file""" + default_repos = [ + { + "name": "official", + "base_url": "https://scl.ecuil.com/forum/api/download.php", + "api_url": "https://scl.ecuil.com/forum/api/packages.php", + "run_plugins_url": "https://scl.ecuil.com/run/plugins/" + }, + { + "name": "official_hk", + "base_url": "https://hk-api.ecuil.com/scl/repo/api/download.php", + "api_url": "https://hk-api.ecuil.com/scl/repo/api/packages.php", + "run_plugins_url": "https://hk-api.ecuil.com/scl/repo/" + } + ] + + if os.path.exists(self.settings_file): + try: + with open(self.settings_file, 'r', encoding='utf-8') as f: + settings = json.load(f) + repos = settings.get('repositories', []) + if repos: + return repos + except Exception: + pass + return default_repos + + def save_repositories(self): + """Save repositories to settings file""" + settings = {} + if os.path.exists(self.settings_file): + try: + with open(self.settings_file, 'r', encoding='utf-8') as f: + settings = json.load(f) + except Exception: + pass + + settings['repositories'] = self.repositories + + try: + with open(self.settings_file, 'w', encoding='utf-8') as f: + json.dump(settings, f, indent=2) + return True + except Exception as e: + self.error(f"Failed to save repositories: {e}") + return False + + def add_repository(self, repo_url): + """Add a new repository""" + # Normalize URL + repo_url = repo_url.rstrip('/') + + # Check if already exists + for repo in self.repositories: + if repo.get('run_plugins_url', '').rstrip('/') == repo_url or \ + repo.get('base_url', '').rstrip('/') == repo_url: + self.warning(f"Repository already exists: {repo_url}") + return False + + # Try to detect repository structure + # Support formats: + # 1. https://example.com/run/plugins/ -> direct plugin URL + # 2. https://example.com/ -> try to detect API endpoints + + new_repo = { + "name": f"repo_{len(self.repositories)}", + "base_url": f"{repo_url}/api/download.php" if not repo_url.endswith('.php') else repo_url, + "api_url": f"{repo_url}/api/packages.php" if not repo_url.endswith('.php') else repo_url.replace('download.php', 'packages.php'), + "run_plugins_url": repo_url if '/run/plugins' in repo_url else f"{repo_url}/run/plugins/" + } + + # Test repository + try: + test_url = f"{new_repo['run_plugins_url']}test.py" + req = urllib.request.Request(test_url, method='HEAD') + req.add_header('User-Agent', 'SCL-PackageManager/1.0') + with urllib.request.urlopen(req, timeout=5) as response: + pass + except: + pass # Ignore test failure, repository might still work + + self.repositories.append(new_repo) + + if self.save_repositories(): + self.success(f"Repository added: {repo_url}") + return True + return False + + def remove_repository(self, repo_name_or_url): + """Remove a repository""" + for i, repo in enumerate(self.repositories): + if repo.get('name') == repo_name_or_url or \ + repo.get('run_plugins_url', '').rstrip('/') == repo_name_or_url.rstrip('/') or \ + repo.get('base_url', '').rstrip('/') == repo_name_or_url.rstrip('/'): + if i == 0 and len(self.repositories) == 1: + self.error("Cannot remove the last repository") + return False + removed = self.repositories.pop(i) + if self.save_repositories(): + self.success(f"Repository removed: {removed.get('name', repo_name_or_url)}") + return True + return False + + self.error(f"Repository not found: {repo_name_or_url}") + return False + + def list_repositories(self): + """List all repositories""" + self.log("Configured repositories:") + self.log("-" * 60) + for i, repo in enumerate(self.repositories, 1): + name = repo.get('name', f'repo_{i}') + url = repo.get('run_plugins_url', repo.get('base_url', 'Unknown')) + marker = " (default)" if i == 1 else "" + self.log(f"{i}. {name}{marker}") + self.log(f" URL: {url}") + + def log(self, message): + print(message) + + def error(self, message): + print(f"Error: {message}", file=sys.stderr) + + def success(self, message): + print(f"✓ {message}") + + def warning(self, message): + print(f"⚠ {message}") + + def ensure_plugins_dir(self): + os.makedirs(self.plugins_dir, exist_ok=True) + + def get_installed_plugins(self): + if not os.path.exists(self.plugins_dir): + return [] + + plugins = [] + for file in os.listdir(self.plugins_dir): + if file.endswith('.py') and file != '__init__.py': + plugin_name = file[:-3] + plugins.append(plugin_name.lower()) + return plugins + + def get_plugin_info(self, plugin_name): + try: + req = urllib.request.Request(f"{self.api_url}?action=info&name={plugin_name.lower()}") + req.add_header('User-Agent', 'SCL-PackageManager/1.0') + + with urllib.request.urlopen(req, timeout=10) as response: + if response.status == 200: + data = json.loads(response.read().decode('utf-8')) + if data.get('success'): + return data.get('package', None) + except Exception as e: + self.error(f"Failed to get plugin info: {e}") + return None + + def download_plugin(self, plugin_name): + self.log(f"{self.t('downloading')} {plugin_name}...") + + # Try each repository in order + for i, repo in enumerate(self.repositories): + repo_name = repo.get('name', f'repo_{i}') + run_plugins_url = repo.get('run_plugins_url', '') + base_url = repo.get('base_url', '') + + try: + # Try run_plugins_url first + if run_plugins_url: + run_url = f"{run_plugins_url}{plugin_name.lower()}.py" + req = urllib.request.Request(run_url) + req.add_header('User-Agent', 'SCL-PackageManager/1.0') + + try: + with urllib.request.urlopen(req, timeout=15) as response: + if response.status == 200: + content = response.read() + self.success(f"{self.t('downloaded')} {len(content)} bytes from {repo_name}") + return content + except urllib.error.HTTPError as e: + if e.code != 404: + raise + + # Try base_url as fallback + if base_url: + url = f"{base_url}?name={plugin_name.lower()}" + req = urllib.request.Request(url) + req.add_header('User-Agent', 'SCL-PackageManager/1.0') + + with urllib.request.urlopen(req, timeout=30) as response: + content = response.read() + content_type = response.headers.get('Content-Type', '') + + if 'application/json' in content_type: + data = json.loads(content.decode('utf-8')) + if data.get('error'): + continue # Try next repository + return None + + self.success(f"{self.t('downloaded')} {len(content)} bytes from {repo_name}") + return content + + except urllib.error.HTTPError as e: + if e.code == 404: + continue # Try next repository + self.warning(f"{repo_name}: HTTP {e.code}") + except Exception as e: + self.warning(f"{repo_name}: {e}") + continue + + self.error(f"Plugin '{plugin_name}' not found in any repository") + return None + + def install_plugin(self, plugin_name): + self.ensure_plugins_dir() + + installed = self.get_installed_plugins() + if plugin_name.lower() in installed: + self.warning(f"Plugin '{plugin_name}' {self.t('already_installed')}") + self.log(self.t('use_update')) + return False + + content = self.download_plugin(plugin_name) + if not content: + return False + + dest_path = os.path.join(self.plugins_dir, f"{plugin_name.lower()}.py") + with open(dest_path, 'wb') as f: + f.write(content) + + self.success(f"Plugin '{plugin_name}' {self.t('installed')}") + self.log(f"Usage: simp{{{plugin_name}}}") + return True + + def install_plugins(self, plugin_names): + """Install multiple plugins at once""" + self.ensure_plugins_dir() + + installed_count = 0 + failed_count = 0 + + for plugin_name in plugin_names: + self.log(f"\n=== Installing {plugin_name} ===") + if self.install_plugin(plugin_name): + installed_count += 1 + else: + failed_count += 1 + + self.log(f"\n=== Summary ===") + self.success(f"Successfully installed: {installed_count}") + if failed_count > 0: + self.error(f"Failed to install: {failed_count}") + + return installed_count > 0 + + def update_plugin(self, plugin_name): + self.ensure_plugins_dir() + + installed = self.get_installed_plugins() + if plugin_name.lower() not in installed: + self.error(f"Plugin '{plugin_name}' {self.t('not_installed')}") + self.log(self.t('use_install')) + return False + + content = self.download_plugin(plugin_name) + if not content: + return False + + dest_path = os.path.join(self.plugins_dir, f"{plugin_name.lower()}.py") + with open(dest_path, 'wb') as f: + f.write(content) + + self.success(f"Plugin '{plugin_name}' {self.t('updated')}") + return True + + def update_plugins(self, plugin_names=None): + """Update multiple plugins at once. If no plugins specified, update all installed plugins""" + self.ensure_plugins_dir() + + if not plugin_names: + plugin_names = self.get_installed_plugins() + if not plugin_names: + self.log(self.t('no_plugins')) + return False + + updated_count = 0 + failed_count = 0 + + for plugin_name in plugin_names: + self.log(f"\n=== Updating {plugin_name} ===") + if self.update_plugin(plugin_name): + updated_count += 1 + else: + failed_count += 1 + + self.log(f"\n=== Summary ===") + self.success(f"Successfully updated: {updated_count}") + if failed_count > 0: + self.error(f"Failed to update: {failed_count}") + + return updated_count > 0 + + def reinstall_plugin(self, plugin_name): + self.ensure_plugins_dir() + + installed = self.get_installed_plugins() + if plugin_name.lower() not in installed: + self.error(f"Plugin '{plugin_name}' {self.t('not_installed')}") + self.log(self.t('use_install')) + return False + + self.log(f"{self.t('reinstalling')} {plugin_name}...") + + content = self.download_plugin(plugin_name) + if not content: + return False + + dest_path = os.path.join(self.plugins_dir, f"{plugin_name.lower()}.py") + with open(dest_path, 'wb') as f: + f.write(content) + + self.success(f"Plugin '{plugin_name}' {self.t('reinstalled')}") + return True + + def remove_plugin(self, plugin_name): + installed = self.get_installed_plugins() + if plugin_name.lower() not in installed: + self.error(f"Plugin '{plugin_name}' {self.t('not_installed')}") + return False + + dest_path = os.path.join(self.plugins_dir, f"{plugin_name.lower()}.py") + os.remove(dest_path) + + self.success(f"Plugin '{plugin_name}' {self.t('removed')}") + return True + + def autoremove_plugins(self): + installed = self.get_installed_plugins() + if not installed: + self.log(self.t('no_plugins')) + return True + + self.log("Checking for unused plugins...") + self.warning("This feature requires dependency tracking") + self.log("Currently removing all installed plugins") + + for plugin_name in installed: + self.remove_plugin(plugin_name) + + self.success("All plugins removed") + return True + + def test_network(self): + self.log(self.t('testing_network')) + + test_urls = [ + "https://scl.ecuil.com", + "https://ssl.ecuil.com", + "https://tg.ecuil.com" + ] + + for url in test_urls: + try: + req = urllib.request.Request(url, method='HEAD') + req.add_header('User-Agent', 'SCL-PackageManager/1.0') + + with urllib.request.urlopen(req, timeout=5) as response: + if response.status == 200: + self.success(f"{url} - OK") + else: + self.warning(f"{url} - HTTP {response.status}") + except Exception as e: + self.error(f"{url} - Failed: {e}") + + return True + + def list_plugins(self, mode='community'): + if mode == 'local': + self.list_local_plugins() + elif mode == 'upgraded': + self.list_upgraded_plugins() + else: + self.list_community_plugins() + + def list_local_plugins(self): + self.log(self.t('local_plugins')) + self.log("-" * 60) + + installed = self.get_installed_plugins() + if not installed: + self.log(self.t('no_plugins')) + return + + for i, plugin in enumerate(installed, 1): + plugin_path = os.path.join(self.plugins_dir, f"{plugin}.py") + size = os.path.getsize(plugin_path) if os.path.exists(plugin_path) else 0 + self.log(f"{i}. {plugin} ({size} bytes)") + + def search_plugins(self, query): + """Search for plugins by name or description""" + self.log(f"Searching for plugins matching '{query}'...") + self.log("-" * 60) + + try: + req = urllib.request.Request(f"{self.api_url}?action=search&query={urllib.parse.quote(query)}") + req.add_header('User-Agent', 'SCL-PackageManager/1.0') + + with urllib.request.urlopen(req, timeout=30) as response: + if response.status != 200: + self.error(f"Failed to search: HTTP {response.status}") + return + + data = json.loads(response.read().decode('utf-8')) + + if data.get('success'): + packages = data.get('packages', []) + installed = self.get_installed_plugins() + + if not packages: + self.log("No plugins found matching your query") + return + + for i, pkg in enumerate(packages, 1): + rating = pkg['rating_count'] > 0 and round(pkg['rating'] / pkg['rating_count'], 1) or 'N/A' + status = "✓ Installed" if pkg['name'].lower() in installed else "" + + self.log(f"{i}. {pkg['name']}") + self.log(f" Description: {pkg['description']}") + self.log(f" Category: {pkg['category']} | Version: {pkg['version']} | Author: {pkg['author_name']}") + self.log(f" Downloads: {pkg['downloads']} | Rating: {rating} {status}") + self.log("") + else: + self.error(f"Failed to search: {data.get('error', 'Unknown error')}") + + except Exception as e: + self.error(f"Failed to search: {e}") + + def list_community_plugins(self): + self.log(self.t('community_plugins')) + self.log("-" * 60) + + try: + req = urllib.request.Request(f"{self.api_url}?action=list") + req.add_header('User-Agent', 'SCL-PackageManager/1.0') + + with urllib.request.urlopen(req, timeout=30) as response: + if response.status != 200: + self.error(f"Failed to fetch list: HTTP {response.status}") + return + + data = json.loads(response.read().decode('utf-8')) + + if data.get('success'): + packages = data.get('packages', []) + installed = self.get_installed_plugins() + + for i, pkg in enumerate(packages, 1): + rating = pkg['rating_count'] > 0 and round(pkg['rating'] / pkg['rating_count'], 1) or 'N/A' + status = "✓ Installed" if pkg['name'].lower() in installed else "" + + self.log(f"{i}. {pkg['name']}") + self.log(f" Description: {pkg['description']}") + self.log(f" Category: {pkg['category']} | Version: {pkg['version']} | Author: {pkg['author_name']}") + self.log(f" Downloads: {pkg['downloads']} | Rating: {rating} {status}") + self.log("") + else: + self.error(f"Failed to fetch list: {data.get('error', 'Unknown error')}") + + except Exception as e: + self.error(f"Failed to fetch list: {e}") + + def list_upgraded_plugins(self): + self.log(self.t('upgradable_plugins')) + self.log("-" * 60) + + installed = self.get_installed_plugins() + if not installed: + self.log(self.t('no_plugins')) + return + + for plugin_name in installed: + info = self.get_plugin_info(plugin_name) + if info: + self.log(f"{plugin_name} - Latest: {info.get('version', 'Unknown')}") + else: + self.log(f"{plugin_name} - Version info unavailable") + + def load_composer_config(self): + if not os.path.exists(self.config_file): + return None + + try: + with open(self.config_file, 'r', encoding='utf-8') as f: + return json.load(f) + except Exception as e: + self.error(f"Failed to load {self.config_file}: {e}") + return None + + def save_composer_config(self, config): + try: + with open(self.config_file, 'w', encoding='utf-8') as f: + json.dump(config, f, indent=2) + self.success(f"Configuration saved to {self.config_file}") + except Exception as e: + self.error(f"Failed to save {self.config_file}: {e}") + + def init_composer(self): + if os.path.exists(self.config_file): + self.warning(f"{self.config_file} already exists") + return + + config = { + "name": "scl-project", + "description": "SCL Project", + "require": {}, + "version": "1.0.0" + } + + self.save_composer_config(config) + self.log(f"Created {self.config_file}") + + def show_acknowledgments(self): + """Show acknowledgments list""" + self.log("=" * 60) + self.log("SCL Package Manager - Acknowledgments") + self.log("=" * 60) + self.log("Special thanks to everyone who has contributed to SCL:") + print() + for i, person in enumerate(self.ACKNOWLEDGMENTS, 1): + self.log(f"{i}. {person}") + print() + self.log("Without your contributions, SCL wouldn't be what it is today!") + self.log("=" * 60) + + def show_help(self): + self.log("=" * 60) + self.log("SCL Package Manager (SDP) v" + self.version) + self.log("=" * 60) + print() + self.log("Usage: sdp [command] [options]") + print() + self.log("Commands:") + self.log(" install Install a plugin") + self.log(" install-multi ... Install multiple plugins") + self.log(" update Update a plugin") + self.log(" update-all Update all installed plugins") + self.log(" update-multi ... Update multiple plugins") + self.log(" reinstall Reinstall a plugin") + self.log(" remove Remove a plugin") + self.log(" autoremove Auto-remove unused plugins") + print() + self.log(" search Search for plugins") + self.log(" test Test network connectivity") + self.log(" list [option] List plugins") + self.log(" -lw List community plugins (default)") + self.log(" -local List local plugins") + self.log(" -upgraded List upgradable plugins") + self.log(" -u, --upgrade List plugins available for upgrade") + self.log(" -a, --all List all plugins in current repository") + print() + self.log(" init Initialize composer.json") + self.log(" add Add a plugin repository") + self.log(" remove-repo Remove a repository") + self.log(" repos List all repositories") + self.log(" setting -lang Set language (english/chinese_simplified/chinese_hongkong/classical_chinese/latin/portuguese/korean)") + self.log(" ae Show acknowledgments") + self.log(" help Show this help") + print() + self.log("Examples:") + self.log(" sdp install basic") + self.log(" sdp install-multi basic color fileio") + self.log(" sdp update basic") + self.log(" sdp update-all") + self.log(" sdp search text") + self.log(" sdp list -local") + self.log(" sdp test") + self.log(" sdp add https://scl.ecuil.com/run/plugins/") + self.log(" sdp repos") + self.log(" sdp ae") + print() + self.log("Configuration:") + self.log(" composer.json - Local package configuration") + self.log(" sdp_settings.json - SDP settings") + self.log(" Use 'sdp init' to create composer.json") + self.log("=" * 60) + +def main(): + if len(sys.argv) < 2: + manager = SCLEPackageManager() + manager.show_help() + return + + # Check for short options first + if sys.argv[1] in ['-u', '--upgrade']: + manager = SCLEPackageManager() + manager.list_plugins('upgraded') + return + elif sys.argv[1] in ['-a', '--all']: + manager = SCLEPackageManager() + manager.list_plugins('community') + return + + command = sys.argv[1].lower() + manager = SCLEPackageManager() + + if command == 'help' or command == '-h' or command == '--help': + manager.show_help() + + elif command == 'ae': + manager.show_acknowledgments() + + elif command == 'install': + if len(sys.argv) < 3: + manager.error("Usage: sdp install ") + return + manager.install_plugin(sys.argv[2]) + + elif command == 'install-multi': + if len(sys.argv) < 3: + manager.error("Usage: sdp install-multi ...") + return + manager.install_plugins(sys.argv[2:]) + + elif command == 'update': + if len(sys.argv) < 3: + manager.error("Usage: sdp update ") + return + manager.update_plugin(sys.argv[2]) + + elif command == 'update-all': + manager.update_plugins() + + elif command == 'update-multi': + if len(sys.argv) < 3: + manager.error("Usage: sdp update-multi ...") + return + manager.update_plugins(sys.argv[2:]) + + elif command == 'reinstall': + if len(sys.argv) < 3: + manager.error("Usage: sdp reinstall ") + return + manager.reinstall_plugin(sys.argv[2]) + + elif command == 'remove': + if len(sys.argv) < 3: + manager.error("Usage: sdp remove ") + return + manager.remove_plugin(sys.argv[2]) + + elif command == 'autoremove': + manager.autoremove_plugins() + + elif command == 'search': + if len(sys.argv) < 3: + manager.error("Usage: sdp search ") + return + manager.search_plugins(sys.argv[2]) + + elif command == 'test': + manager.test_network() + + elif command == 'list': + if len(sys.argv) >= 3: + option = sys.argv[2].lower() + if option == '-lw': + manager.list_plugins('community') + elif option == '-local': + manager.list_plugins('local') + elif option == '-upgraded': + manager.list_plugins('upgraded') + else: + manager.error(f"Unknown option: {option}") + manager.log("Use -lw, -local, or -upgraded") + else: + manager.list_plugins('community') + + elif command == 'init': + manager.init_composer() + + elif command == 'add': + if len(sys.argv) < 3: + manager.error("Usage: sdp add ") + manager.log("Example: sdp add https://scl.ecuil.com/run/plugins/") + return + manager.add_repository(sys.argv[2]) + + elif command == 'remove-repo': + if len(sys.argv) < 3: + manager.error("Usage: sdp remove-repo ") + manager.list_repositories() + return + manager.remove_repository(sys.argv[2]) + + elif command == 'repos': + manager.list_repositories() + + elif command == 'setting': + if len(sys.argv) < 3: + manager.error("Usage: sdp setting -lang ") + manager.log("Available languages:") + manager.show_available_languages() + return + + subcommand = sys.argv[2].lower() + if subcommand == '-lang' or subcommand == '--lang' or subcommand == 'lang': + if len(sys.argv) < 4: + manager.error("Usage: sdp setting -lang ") + manager.show_available_languages() + return + manager.set_language(sys.argv[3]) + else: + manager.error(f"Unknown setting: {subcommand}") + manager.log("Use 'sdp setting -lang '") + + else: + manager.error(f"Unknown command: {command}") + manager.log("Use 'sdp help' for usage information") + +if __name__ == "__main__": + main()