Is there a way to make the user prompt for the bytes inside brackets and separated by commas or something similar?
./main bytes [0, 1, 2, 3, 4, 5]
I managed to make it look like this:
./main bytes 0 1 2 3 4 5
This is my code:
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);
}
It's possible, but you have to implement Deserialize
by hand.
Vec<u8>
already implements Deserialize
, and that implementation doesn't know about strings containing comma-delimited bracketed lists, nor does docopt::Deserializer
, since the normal way to pass a list on the command line is element-by-element. So you have to make a new type that will deserialize from the format you want.
Naturally, you can also implement Deref<Target = Vec<u8>>
and DerefMut
for Bytes
, if you want to treat it as a Vec<u8>
. Some people might consider this a slight misuse of Deref
, but it's probably fine in a situation like this.
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. These are the changes I made to get this to work:
[<bin>...]
with <bin>
so docopt will know to look for a single thing and not a sequence of... things. (If you don't do this, docopt actually just throws you an empty string.)Bytes
wrapper around Vec<u8>
.serde::de::Deserialize
for Bytes
. This entails creating a struct that implements the serde::de::Visitor
trait, putting the code that picks apart the string inside its visit_str
method, and passing the visitor to deserialize_str
, which tells the Deserializer
to expect a string and pass it to the visitor's visit_str
.I didn't realize it until almost done, but you could implement visit_seq
instead, and make it parse bytes [1, 2, 3]
(without quoting the list). But I wouldn't, because that defies command line convention; if you're using the shell to split the arguments anyway, you should go the whole way and just accept bytes 1 2 3
.