0

我想支持一个子命令 CLI 模型,就像使用的那样git我遇到问题的特定位是“更改目录”选项。就像git,我想要一个-C DIR选项,在执行子命令之前将程序更改为指定目录。不是真正的问题,使用子解析器,我也想在解析期间应用参数使用该argparse.ArgumentParser(fromfile_prefix_chars='@')机制。-C DIR

这是问题所在:fromfile参数扩展是argparse 在所有其他参数处理之前执行的。因此,任何此类fromfile参数都必须使用绝对路径,或者在调用解析器时相对于 CWD 的路径。我想要绝对路径;我“需要”使用与选项相关的fromfile路径。我写了自己的来做显而易见的事情。它工作得很好,但由于参数已经扩展,它没有给我我想要的东西。(在发现这种不是我想要的行为后,我查看并发现同样的挫败感嵌入在冰冷、坚硬、无情的代码中。)-C DIRclass ChdirAction(argparse.Action)fromfilepython3.5/argparse.py

这是一个目录图,可能有助于解释我想要什么:

/ foo / aaa / iii / arg.txt
      |     |
      |     + jjj / arg.txt
      |     |
      |     + arg.txt
      |
      + bbb / iii / arg.txt
            |
            + jjj / arg.txt

考虑 CWD 何时aaa或何时bbb解析命令行参数。如果我使用类似的东西运行,prog -C ./iii @arg.txt 我希望解析器@arg.txt使用来自/foo/aaa/iii/arg.txt. 实际发生的是fromfile/foo/aaa/arg.txt. 当 CWD 为/foo/aaa“错误”文件时;当/foo/bbb它引发“错误:[Errno 2]没有这样的文件或目录:'arg.txt'”

更一般地,prog -C ./DIR @arg.txt应该从/foo/aaa/DIR/arg.txt应该工作的扩展,即使fromfile有“上目录”部分,例如prog -C ./iii @../arg.txt应该从/foo/aaa/arg.txt.

如果可以使这种行为发生,那么我可以从常见的命令行构造-C DIR中获取任何{aaa,bbb}/{iii,jjj}并获得一致的行为。

如上所述,我的问题不是什么大问题。如果可以提供-C DIR, 以通过os.chdir(DIR)后参数解析来实现,那么我也可以构造适当的fromfile参数。它们在解析时(在任何-C DIR生效之前)可以是绝对的或相对于 CWD 。这可能看起来像:

cd /foo/aaa; prog -C ./DIR @arg.txt @./DIR/arg.txt

我不喜欢它,但没关系。真正的问题是我使用的实际更改目录参数更像-C PATTERN. 在我真正的问题案例中,PATTERN 可能是一个简单的路径(绝对或相对)。或者,它可能是一个 glob 模式,或者是具有“非平凡”解析逻辑的部分名称,用于查找os.chdir(DIR). 在这种情况下(我正在努力解决),我不能让程序的调用者解析fromfile路径的实际位置。

实际上,我可以,但这会给调用者带来不适当的负担。而且,当那个调用程序是一个 Eclipse 启动程序时,我真的没有必要的控制流能力来做到这一点。所以,它又回到了让程序照顾自己的需求;一个更好的抽象,但我该如何实现呢?

4

2 回答 2

0

我认为该resolve位可以替换为

chdirParser = argparse.ArgumentParser(add_help=False)

并省略-h定义并保存。让第二个解析器处理sys.argv不变(因为您包含(但忽略)-C参数)。

如果您希望用户使用多个命令,那append和测试应该可以工作。使用默认操作的替代方法,最终保存这些重复中的最后一个。为什么用户可能会重复,您为什么要关心?通常我们只是忽略重复。len(partial.chdir_pattern) > 1-C dir1 ... -C dir2...store-C

你可能会更换

print("Failed to resolve -C {!r}".format(pattern), file=sys.stderr)
sys.exit(1)

parser.error("Failed to resolve -C {!r}".format(pattern)')

它打印使用情况(只有-C) and does ansys.exit(2)`。不太一样,但可能足够接近。

对于第二个解析器,-C可能会简化(使用默认值):

argParser.add_argument("-C", "--chdir-spec", help="Before anything else, chdir to SPEC", metavar="SPEC")

并使用完整的sys.argv.

return argParser.parse_args()

否则,使用 2 个解析器是有意义的,因为fromfile存在于更改的目录中(并且您希望忽略初始目录中的任何此类文件)。

我想也许一个:arg.txt字符串命令行会在第一个解析器中出现问题。但有了parse_known_args它只会把它当作一个未知的位置。但证据在测试中。

于 2016-05-19T05:27:26.047 回答
0

即使我正在充实这个问题,我也想出了一个主意。所以我试了一下,它有点,有点,好吧(ish)。我可以得到我真正想要的受限版本,但这对我来说已经足够了(目前),所以我想我不妨分享一下。它也可能对你来说已经足够好了。更好的是,它可能会从某个地方引出一个真正的解决方案,也许是 S.Bethard?

我的技巧是分两个阶段进行解析:第一个阶段,在不启用机制的情况下足以获取-C PATTERN论点。如果第一次(最小)解析的结果产生一个目录更改参数,那么我处理它。如果指定了多个,或者无法明确解析 PATTERN,程序将中止。ArgumentParser.parse_known_args(...) fromfile-C PATTERN

然后,我使用一个完全独立的ArgumentParser对象,配置了我真正想要的全套参数规范,并fromfile启用机制的情况下解析它。

有一些猴子业务可以使--help参数起作用(设置适当的冲突解决策略,然后仅接受第一个解析器中的 arg 只是传递给第二个,它实际上具有所有“真实”参数规范)。此外,第一个解析器应该支持与第二个解析器相同的详细/安静选项,尊重它们的设置并从第一个解析器传递到第二个解析器。

这是我的应用程序级 arg 解析器方法的简化版本。它在第一个解析器阶段不支持详细/安静选项。我省略了如何将 a-C PATTERN解析为实际目录的复杂性。此外,我删除了第二个解析器的大部分参数规范,只留下了第二个解析器的-C PATTERN参数(--help输出所需)。

注意:两个解析器都有一个-C PATTERN参数。在chdirParser它是有意义的;因为argParser它只存在所以它会显示在帮助输出中。应该为详细/安静选项做类似的事情 - 可能不是那么棘手,但这对我来说(还)不重要,所以我不介意总是报告目录更改,即使在安静模式下也是如此。

def cli_args_from_argv():

    import argparse
    import glob
    import os
    import sys

    chdirParser = argparse.ArgumentParser(conflict_handler='resolve')
    chdirParser.add_argument("-C",           dest="chdir_pattern", action="append"    , default=None)
    chdirParser.add_argument("--help", "-h", dest="help",          action="store_true", default=False)

    (partial, remainder) = chdirParser.parse_known_args()

    if partial.help:
        remainder = ['--help']

    elif partial.chdir_pattern:
        if len(partial.chdir_pattern) > 1:
            print(r'Too many -C options - at most one may be given, but received: {!r}'.format(partial.chdir_pattern), file=sys.stderr)
            sys.exit(1)

        pattern  = partial.chdir_pattern[0]
        resolved_dir = pattern
        if os.path.exists(resolved_dir):
            resolved_dir = pattern

        else:

            ### ELIDED: resolution of pattern into an unambiguous and existing directory

            if not resolved_dir:
                print("Failed to resolve -C {!r}".format(pattern), file=sys.stderr)
                sys.exit(1)

        print("Changing to directory: {!r}".format(resolved_dir))
        print("");

        os.chdir(target_dir)


    argParser = argparse.ArgumentParser(usage="usage: PROG [common-args] SUBCMD [subcmd-args]", fromfile_prefix_chars=':')

    ### ELIDED: a bunches of add_argument(...)

    argParser.add_argument("-C", dest="chdir_spec", action="store", default=None, help="Before anything else, chdir to SPEC", metavar="SPEC")

    return argParser.parse_args(args=remainder)

我有一种感觉,可能有更好的方法......你知道吗?

于 2016-05-19T03:43:43.950 回答