使用 Cliché Shell 瞬间创建极简 UI






4.33/5 (3投票s)
命令行 UI 自动生成,轻松使代码可运行。
引言:用例
我们经常需要试验代码:也许是为了学习新算法,或者玩弄一个库来发现它的特点。或者,我们只是为自己开发一个工具,不想花时间设计复杂的软件 UI,只想让它尽快工作。
这里描述的工具就是为此目的而服务的:通过提供极其简单但功能强大 UI 自动生成,让 Java 代码在零时间内可运行。
当我在开发一个 Web 应用的数据库管理后台控制台时,想到了创建这个工具的创意。我已经有了包含所有我需要的方法的数据库类,但方法很多,所以创建一个‘正常’的 UI 会很困难。我决定创建一个交互式的命令行 UI,一种 shell。为了避免编写大量相似的代码来处理命令行,我采用了基于反射的方法映射到命令。就这样,Cliché Shell 诞生了。
“你好,世界!”
让我们编写一个方法来加两个数字。因为是 Java,你需要一个类和其他东西。
public class HelloWorld {
public int add(int a, int b) {
return a + b;
}
}
现在我们想让代码可运行。
最简单的传统方法是添加一个 main()
方法,然后将 params[]
转换为 int
。这并不难,但这种方法无法扩展:如果有很多方法具有不同的参数,你将不得不编写一个巨大的 switch 语句。我希望你能理解其中的弊端。
Cliché 的方法
import asg.cliche.ShellFactory;
import asg.cliche.Command;
import java.io.IOException;
public class HelloWorld {
@Command
public int add(int a, int b) {
return a + b;
}
public static void main(String[] params) throws IOException {
ShellFactory.createConsoleShell(“helloâ€, null, new HelloWorld())
.commandLoop();
}
}
运行代码,然后在提示符“hello>”下输入add 4 6。输入exit退出 shell。
它很简单,而且可扩展:对于每个新方法,你只需要添加一个 @Command
注释。
这种简单性的主要限制在于类型转换:如果你的方法需要 String
或基本类型(及其 Object 对应项,如 Integer
)以外的类型,那么你需要一个包装方法或一个 Converter。Converters 是 Cliché Shell 的扩展,稍后会讨论。
Shell 语言
这是语言基础。
命名约定与 Java 不同。即,commandName
通过自动名称翻译变为 command-name
。
命令名称的缩写由命令名称中单词的首字母组成,例如,command-name
的默认缩写是 cn
。
如果命令方法名以 cmd
或 cli
开头,则前缀不包含在命令名称中。例如,cmdSomeMethod
变为 some-method
。
引用也不同:单引号和双引号等效,除了引号外没有转义字符。
"a string"
与'a string'
相同,并转换为a string
。'a "string" 2'
与"a ""string"" 2'
相同,并转换为"string" 2
。- 你可以写
a" "string
,这与a string
相同。
以下是最重要的内置命令。
exit
是退出 shell 的命令。?list
(或?l
) 列出所有用户定义的命令。?list-all
(或?la
) 列出所有可用命令,包括内置命令。?help command-name
输出有关命令的详细信息(如果存在)。
请参见 ?la
获取完整列表。
Shell 功能
记录命令
虽然默认命令名称基于方法名称且相当具有描述性,但 Java 不保留方法参数名称的信息(如果我错了,请告诉我)。Cliché Shell 通过 @Param
注释支持参数命名。您还可以使用 @Command
注释重命名命令或添加详细信息。
@Command(description="Varargs example")
public Integer add(
@Param(name="numbers", description="some numbers to add")
Integer... numbers) {
int result = 0;
for (int i : numbers) {
result += i;
}
return result;
}
在这里,您还看到 Cliché 支持 varargs
方法。(注意 Integer
类的使用:Cliché 支持基本类型,如 int
,除非您要覆盖类型转换)。
输入转换
您可以为新类定义转换器,或覆盖内置转换,如下面的代码所示。
public static final InputConverter[] CLI_INPUT_CONVERTERS = {
// You can use Input Converters to support named constants
new InputConverter() {
public Integer convertInput(String original, Class toClass) throws Exception {
if (toClass.equals(Integer.class)) {
if (original.equals("one")) return 1;
if (original.equals("two")) return 2;
if (original.equals("three")) return 3;
}
return null;
}
}
};
现在,您可以编写 add one two
并得到 3
。
CLI_INPUT_CONVERTERS
是一个特殊字段,Shell 会检查它来查找输入转换器。
三点考虑
CLI_INPUT_CONVERTERS
中的转换器优先于内置转换规则。- 如果您不知道如何处理给定的类型,请返回
null
:Shell 将尝试查找更合适的转换器。 - 由于它处理的是对象,所以您不能转换为基本类型,因此也不能覆盖基本类型的转换。
输出转换
您还可以覆盖 Shell 将命令方法结果转换为字符串的方式(默认是调用 toString()
)。
public static final OutputConverter[] CLI_OUTPUT_CONVERTERS = {
new OutputConverter() {
public Object convertOutput(Object o) {
if (o.getClass().equals(Integer.class)) {
int num = (Integer) o;
if (num == 1) return "one";
if (num == 2) return "two";
if (num == 3) return "three";
}
return null;
}
}
};
现在,add one two
返回 three
。当然,还有更好的用途。我曾经为 double[]
编写了一个转换器,该转换器显示了一个带有图形的 Swing 框架。
而且,由于不需要使 CLI_OUTPUT_CONVERTERS
字段 static final
,您可以通过其他命令来控制转换。
private boolean displayResults = false;
@Command(description="Turns on table function display")
public void enableResults() {
displayResults = true;
}
public OutputConverter[] CLI_OUTPUT_CONVERTERS = {
new OutputConverter() {
public Object convertOutput(Object toBeFormatted) {
if (toBeFormatted instanceof U[]) {
return displayResults ? toBeFormatted : String.format(
"[%d rows skipped; see '?h er']", ((U[])toBeFormatted).length);
} else {
return null;
}
}
}
};
还有一些我在这里没有描述的其他功能。请查看示例代码,Shell 非常简单且有用!