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