有没有办法使用docopt从命令行传递u8的向量?

问题描述 投票:0回答:1

有没有办法让用户提示括号内的字节并用逗号或类似的东西分隔?

./main bytes [0, 1, 2, 3, 4, 5]

我设法让它看起来像这样:

./main bytes 0 1 2 3 4 5

这是我的代码:

extern crate docopt;
#[macro_use]
extern crate serde_derive;

use docopt::Docopt;

const USAGE: &'static str = "
    Puzzle Solver.

    Usage:
      puzzle_solver string <text>
      puzzle_solver bytes [<bin>...] 
      puzzle_solver (-h | --help)
      puzzle_solver --version

    Options:
      -h --help     Show this screen.
      --version     Show version.
    ";

#[derive(Debug, Deserialize)]
struct Args {
    cmd_string: bool,
    arg_text: Option<String>,
    cmd_bytes: bool,
    arg_bin: Option<Vec<u8>>,
}

fn main() {
    let args: Args = Docopt::new(USAGE)
        .and_then(|d| d.deserialize())
        .unwrap_or_else(|e| e.exit());

    println!("ARGS: {:?}", args);
}
rust docopt
1个回答
2
投票

这是可能的,但你必须亲自实施Deserialize

Vec<u8>已经实现了Deserialize,并且该实现不知道包含逗号分隔的括号列表的字符串,docopt::Deserializer也不知道,因为在命令行上传递列表的常规方法是逐个元素。因此,您必须创建一个将从您想要的格式反序列化的新类型。

当然,如果你想把它当作Deref<Target = Vec<u8>>,你也可以为DerefMut实现BytesVec<u8>Some people might consider this a slight misuse of Deref,但在这样的情况下它可能很好。

extern crate docopt;
extern crate serde;
#[macro_use]
extern crate serde_derive;

use docopt::Docopt;
use serde::de;
use std::fmt;

const USAGE: &'static str = "
    Puzzle Solver.

    Usage:
      puzzle_solver string <text>
      puzzle_solver bytes <bin>
      puzzle_solver (-h | --help)
      puzzle_solver --version

    Options:
      -h --help     Show this screen.
      --version     Show version.
    ";

#[derive(Debug, Deserialize)]
struct Args {
    cmd_string: bool,
    arg_text: Option<String>,
    cmd_bytes: bool,
    arg_bin: Option<Bytes>,
}

#[derive(Debug)]
struct Bytes(Vec<u8>);

impl<'de> de::Deserialize<'de> for Bytes {
    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
    where
        D: de::Deserializer<'de>,
    {
        struct BytesVisitor;

        impl<'de> de::Visitor<'de> for BytesVisitor {
            type Value = Bytes;

            fn expecting(&self, formatter: &mut fmt::Formatter) -> Result<(), fmt::Error> {
                write!(formatter, "a bracketed, comma-delimited string")
            }

            fn visit_str<E: de::Error>(self, v: &str) -> Result<Self::Value, E> {
                let v = if v.starts_with('[') && v.ends_with(']') {
                    &v[1..v.len() - 1]
                } else {
                    return Err(E::custom(format!("expected a bracketed list, got {:?}", v)));
                };
                let values: Result<Vec<u8>, _> = v.split(",").map(|s| s.trim().parse()).collect();
                Ok(Bytes(values.map_err(E::custom)?))
            }
        }

        deserializer.deserialize_str(BytesVisitor)
    }
}

Here it is in the playground.以下是我为实现这一目标所做的更改:

  1. [<bin>...]替换为<bin>,因此docopt将知道寻找单个事物而不是一系列事物。 (如果你不这样做,docopt实际上只会抛出一个空字符串。)
  2. Bytes周围引入newtype Vec<u8>包装器。
  3. Implement serde::de::DeserializeBytes。这需要创建一个实现serde::de::Visitor特性的结构,将代码选择在其visit_str方法中的字符串,并将访问者传递给deserialize_str,它告诉Deserializer期望一个字符串并将其传递给访问者的visit_str

我几乎没有意识到它,但你可以实现visit_seq,并使其解析bytes [1, 2, 3](不引用列表)。但我不会,因为这违反了命令行惯例;如果你正在使用shell来分割参数,你应该全力以赴并接受bytes 1 2 3

© www.soinside.com 2019 - 2024. All rights reserved.