0

我有一个带有多个子命令的 CLI,其中一些子命令有一个可选标志-f,可以使用该标志指定输入文件,例如

@CommandLine.Command(name = "get", description = ["Get something"])
class GetUserCommand: Runnable {

    @Option(names = ["-f", "--file"], description = ["Input file"])
    var filename: String? = null
    
    override fun run() {
       var content = read_file(filename)
    }
}


@CommandLine.Command(name = "query", description = ["Query something"])
class QueryUserCommand: Runnable {

    @Option(names = ["-f", "--file"], description = ["Input file"])
    var filename: String? = null
    
    override fun run() {
       var content = read_file(filename)
    }
}


输入文件格式可能因命令而异。理想情况下,如果文件被指定为参数,我想自动解析文件。此外,每个命令的文件内容可能不同(但将是特定格式,CSV 或 JSON)。

例如我想要这样的东西

data class First(val col1, val col2)

data class Second(val col1, val col2, val col3)

class CustomOption(// regular @Option parameters, targetClass=...) {
  // do generic file parsing
}


@CommandLine.Command(name = "get", description = ["Get something"])
class GetUserCommand: Runnable {

    @CustomOption(names = ["-f", "--file"], description = ["Input file"], targetClass=First))
    var content: List<First> = emptyList()
    
    override fun run() {
       // content now contains the parse file
    }
}


@CommandLine.Command(name = "query", description = ["Query something"])
class QueryUserCommand: Runnable {

    @CustomOption(names = ["-f", "--file"], description = ["Input file"], targetClass=Second))
    var content: List<Second> = emptyList()
    
    override fun run() {
       // content now contains the parse file
    }
}

如果这是可能的或如何做到这一点,有人会知道吗?

4

1 回答 1

1

换个说法:如何在解析过程中而不是在命令执行过程中对输入参数进行额外的处理?

(请注意,OP 没有说明为什么这是可取的。我假设目标是利用 picocli 的错误报告,或者将解析逻辑封装在某处以便于测试和重用。如果下面的解决方案并不令人满意。)

一种想法是使用 picocli 的自定义参数处理

可以为IParameterConsumer将处理该选项的参数的选项指定一个。

因此,例如当用户指定 时get -f somefile,自定义参数使用者将负责处理somefile参数。一个实现可能看起来像这样:

// java implementation, sorry I am not that fluent in Kotlin...
class FirstConsumer implements IParameterConsumer {
    public void consumeParameters(Stack<String> args,
                                  ArgSpec argSpec,
                                  CommandSpec commandSpec) {
        if (args.isEmpty()) {
            throw new ParameterException(commandSpec.commandLine(),
                    "Missing required filename for option " +
                    ((OptionSpec) argSpec).longestName());
        }
        String arg = args.pop();
        First first = parseFile(new File(arg), commandSpec);
        List<String> list = argSpec.getValue();
        list.add(first);
    }

    private First parseFile(File file,
                            ArgSpec argSpec,
                            CommandSpec commandSpec) {
        if (!file.isReadable()) {
            throw new ParameterException(commandSpec.commandLine(),
                    "Cannot find or read file " + file + " for option " +
                    ((OptionSpec) argSpec).longestName());
        }
        // other validation...
        // parse file contents...
        // finally, return the result...
        return new First(...);
    }
}

一旦定义了参数消费者类,您可以按如下方式使用它们:

@Command(name = "get", description = ["Get something"])
class GetUserCommand: Runnable {

    @Option(names = ["-f", "--file"], description = ["Input file"],
                  parameterConsumer = FirstConsumer::class))
    var content: List<First> = emptyList()
    
    override fun run() {
       // content now contains the parsed file
    }
}
于 2021-06-21T02:31:02.823 回答