Есть ли способ использовать docopt для передачи вектора u8 из командной строки?

Есть ли способ заставить пользователя запрашивать байты внутри скобок, разделенные запятыми или что-то подобное?

./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);
}

1 ответ

Решение

Это возможно, но вы должны реализовать Deserialize рукой.

Vec<u8> уже реализует Deserialize и эта реализация не знает о строках, содержащих разделенные запятыми списки, и не знает docopt::Deserializer, поскольку обычный способ передачи списка в командной строке - поэлементно. Таким образом, вы должны создать новый тип, который будет десериализован из нужного вам формата.

Естественно, вы также можете реализовать Deref<Target = Vec<u8>> а также DerefMut за Bytes, если вы хотите рассматривать это как Vec<u8>, Некоторые люди могут счесть это небольшим злоупотреблением 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)
    }
}

Вот оно на детской площадке. Вот изменения, которые я сделал, чтобы заставить это работать:

  1. замещать [<bin>...] с <bin> поэтому Докопт будет знать, чтобы искать одну вещь, а не последовательность... вещей. (Если вы этого не сделаете, docopt просто выдаст вам пустую строку.)
  2. Ввести новый тип Bytes обертка вокруг Vec<u8>,
  3. Воплощать в жизнь serde::de::Deserialize за Bytes, Это влечет за собой создание структуры, которая реализует serde::de::Visitor черта, помещая код, который разделяет строку внутри visit_str метод, и передача посетителя deserialize_str, который рассказывает Deserializer ожидать строку и передать ее посетителю visit_str,

Я не осознавал этого, пока почти не сделал, но вы могли бы реализовать visit_seq вместо этого, и сделайте это разобрать bytes [1, 2, 3] (без цитирования списка). Но я бы не стал, потому что это противоречит правилам командной строки; если вы используете оболочку для разделения аргументов в любом случае, вы должны пройти весь путь и просто принять bytes 1 2 3,

Другие вопросы по тегам