feat: 打印动态生成的通用帮助信息

This commit is contained in:
Luke 2025-06-18 00:05:42 +08:00
parent ee09f14bfa
commit 8ca75787b7
2 changed files with 111 additions and 27 deletions

View File

@ -2,67 +2,98 @@ package org.jcnc.snow.compiler.cli;
import org.jcnc.snow.compiler.cli.commands.CompileCommand; import org.jcnc.snow.compiler.cli.commands.CompileCommand;
import org.jcnc.snow.compiler.cli.commands.RunCommand; import org.jcnc.snow.compiler.cli.commands.RunCommand;
import org.jcnc.snow.compiler.cli.commands.VersionCommand;
import java.util.Arrays; import java.util.Arrays;
import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Set;
import java.util.function.Supplier; import java.util.function.Supplier;
/** /**
* SnowCLI 是项目的命令行入口类负责解析用户输入分发子命令并统一处理帮助和错误 * SnowCLI 是项目的命令行入口类负责解析用户输入
* 分发子命令并统一处理帮助版本和错误输出
*/ */
public class SnowCLI { public class SnowCLI {
/** Snow 编程语言的版本号。 */
public static final String VERSION = "1.0.0";
/** 全局帮助标志,当输入匹配时显示帮助信息。 */
private static final Set<String> GLOBAL_HELP_FLAGS = Set.of("help", "-h", "--help");
/** 全局版本标志,当输入匹配时显示版本信息。 */
private static final Set<String> GLOBAL_VERSION_FLAGS = Set.of("-v", "--version");
/** /**
* 保存所有可用子命令的映射命令名称 -> 命令实例的提供者 * 全局选项定义列表
* 每个 Option 包含可用标志列表及对应的描述信息
*/
private static final List<Option> GLOBAL_OPTIONS = List.of(
new Option(List.of("-h", "--help"), "Show this help message and exit"),
new Option(List.of("-v", "--version"), "Print snow programming language version and exit")
);
/**
* 所有可用子命令的映射
* 键为命令名称值为对应命令实例的提供者
* 通过 Map.of 初始化添加新命令时只需在此注册 * 通过 Map.of 初始化添加新命令时只需在此注册
*/ */
private static final Map<String, Supplier<CLICommand>> COMMANDS = Map.of( private static final Map<String, Supplier<CLICommand>> COMMANDS = Map.of(
"compile", CompileCommand::new, "compile", CompileCommand::new,
"run", RunCommand::new "run", RunCommand::new,
"version", VersionCommand::new
); );
/** /**
* 程序主入口解析和分发命令 * 程序主入口
* <p>
* 负责处理以下逻辑
* <ul>
* <li>全局帮助标志无参数或 help 标志时显示通用帮助并退出</li>
* <li>全局版本标志-v/--version 时打印版本并退出</li>
* <li>子命令调度根据第一个参数匹配子命令并分发执行</li>
* <li>子命令帮助子命令后带 --help 时显示该命令帮助</li>
* <li>错误处理捕获执行异常并打印错误信息</li>
* </ul>
* *
* @param args 用户在命令行中输入的参数数组 * @param args 用户在命令行中输入的参数数组
*/ */
public static void main(String[] args) { public static void main(String[] args) {
// 如果未给出任何参数则打印通用帮助并退出 // 全局帮助 //
if (args.length == 0) { if (args.length == 0 || GLOBAL_HELP_FLAGS.contains(args[0])) {
printGeneralUsage(); printGeneralUsage();
System.exit(1); System.exit(0);
} }
// 第一个参数为子命令名称 // 全局版本 //
if (GLOBAL_VERSION_FLAGS.contains(args[0])) {
new VersionCommand().execute(new String[0]);
System.exit(0);
}
// 子命令调度 //
String cmdName = args[0]; String cmdName = args[0];
Supplier<CLICommand> cmdSupplier = COMMANDS.get(cmdName); Supplier<CLICommand> cmdSupplier = COMMANDS.get(cmdName);
// 如果命令不存在则打印错误和帮助
if (cmdSupplier == null) { if (cmdSupplier == null) {
System.err.println("Unknown command: " + cmdName); System.err.println("Unknown command: " + cmdName);
printGeneralUsage(); printGeneralUsage();
System.exit(1); System.exit(1);
} }
// 创建子命令实例
CLICommand cmd = cmdSupplier.get(); CLICommand cmd = cmdSupplier.get();
String[] subArgs = Arrays.copyOfRange(args, 1, args.length);
// 提取子命令的剩余参数 // 子命令帮助 //
String[] sub = Arrays.copyOfRange(args, 1, args.length); if (subArgs.length > 0 && GLOBAL_HELP_FLAGS.contains(subArgs[0])) {
// 支持统一的帮助标志help, -h, --help
if (sub.length > 0 && Set.of("help", "-h", "--help").contains(sub[0])) {
// 调用子命令自己的 printUsage
cmd.printUsage(); cmd.printUsage();
System.exit(0); System.exit(0);
} }
// 执行子命令并捕获异常 // 执行子命令 //
try { try {
int exitCode = cmd.execute(sub); int exitCode = cmd.execute(subArgs);
System.exit(exitCode); System.exit(exitCode);
} catch (Exception e) { } catch (Exception e) {
// 打印错误信息和堆栈
System.err.println("Error: " + e.getMessage()); System.err.println("Error: " + e.getMessage());
System.exit(1); System.exit(1);
} }
@ -70,20 +101,36 @@ public class SnowCLI {
/** /**
* 打印动态生成的通用帮助信息 * 打印动态生成的通用帮助信息
* 会遍历 COMMANDS输出所有子命令的名称及描述信息 * <p>
* 列出全局选项和所有注册子命令的名称与描述
*/ */
private static void printGeneralUsage() { private static void printGeneralUsage() {
// 使用方式说明 System.out.println("Usage:");
System.out.println("Usage: snow <command> [options]"); System.out.println(" snow [OPTIONS] <command>");
System.out.println();
System.out.println("Options:");
// 动态遍历全局选项
for (Option opt : GLOBAL_OPTIONS) {
String flags = String.join(", ", opt.flags());
System.out.printf(" %-15s %s%n", flags, opt.description());
}
System.out.println();
System.out.println("Commands:"); System.out.println("Commands:");
// 遍历注册的子命令动态输出
// 对每个注册的子命令获取 description 并格式化输出
COMMANDS.forEach((name, supplier) -> { COMMANDS.forEach((name, supplier) -> {
CLICommand c = supplier.get(); CLICommand c = supplier.get();
// %-10s 保证命令名称列宽度为 10
System.out.printf(" %-10s %s%n", name, c.description()); System.out.printf(" %-10s %s%n", name, c.description());
}); });
// 提示如何查看子命令帮助 System.out.println();
System.out.println("Use 'snow <command> --help' to see command-specific options."); System.out.println("Use \"snow <command> --help\" for command-specific options.");
}
/**
* 全局选项的数据结构包含标志列表和描述信息
*
* @param flags 选项的所有标志例如 -h, --help
* @param description 选项的功能说明
*/
private record Option(List<String> flags, String description) {
} }
} }

View File

@ -0,0 +1,37 @@
package org.jcnc.snow.compiler.cli.commands;
import org.jcnc.snow.compiler.cli.CLICommand;
import org.jcnc.snow.compiler.cli.SnowCLI;
/**
* <p>
* 子命令`snow version`
* <br>
* 用于打印当前 SnowCLI 的版本号
* </p>
*/
public final class VersionCommand implements CLICommand {
@Override
public String name() {
return "version";
}
@Override
public String description() {
return "Print snow version.";
}
@Override
public void printUsage() {
System.out.println("Usage:");
System.out.println(" snow version");
}
@Override
public int execute(String[] args) {
// 直接输出 SnowCLI.VERSION 常量
System.out.println("snow version " + SnowCLI.VERSION);
return 0;
}
}