clio is a rust library for parsing CLI file names.
It implements the standard unix conventions of when the file name is "-"
then sending the
data to stdin/stdout as appropriate. With the clap-parse
feature
it also adds a bunch of useful filters to validate paths from command line parameters,
e.g. it exists or is/isn't a directory.
Input
s and Output
s can be created directly from args in args_os
.
They will error if the file cannot be opened for any reason
// a cat replacement
fn main() -> clio::Result<()> {
for arg in std::env::args_os().skip(1) {
let mut input = clio::Input::new(&arg)?;
std::io::copy(&mut input, &mut std::io::stdout())?;
}
Ok(())
}
If you want to defer opening the file you can use InputPath
s and OutputPath
s.
This avoid leaving empty Output files around if you error out very early.
These check that the path exists, is a file and could in theory be opened when created to get
nicer error messages from clap. Since that leaves room for
TOCTTOU bugs, they will
still return a Err
if something has changed when it comes time
to actually open the file.
With the clap-parse
feature they are also designed to be used with clap 3.2+.
See the older docs for examples of older clap/structopt
# #[cfg(feature="clap-parse")]{
use clap::Parser;
use clio::*;
use std::io::Write;
#[derive(Parser)]
#[clap(name = "cat")]
struct Opt {
/// Input file, use '-' for stdin
#[clap(value_parser, default_value="-")]
input: Input,
/// Output file '-' for stdout
#[clap(long, short, value_parser, default_value="-")]
output: Output,
/// Directory to store log files in
#[clap(long, short, value_parser = clap::value_parser!(ClioPath).exists().is_dir(), default_value = ".")]
log_dir: ClioPath,
}
fn main() {
let mut opt = Opt::parse();
let mut log = opt.log_dir.join("cat.log").create().unwrap_or(Output::std_err());
match std::io::copy(&mut opt.input, &mut opt.output) {
Ok(len) => writeln!(log, "Copied {} bytes", len),
Err(e) => writeln!(log, "Error {:?}", e),
};
}
# }
Nameless is an alternative to clap that provides full-service command-line parsing. This means you just write a main function with arguments with the types you want, add a conventional documentation comment, and it uses the magic of procedural macros to take care of the rest.
It's input and output streams have the many of the same features as clio (e.g. '-' for stdin) but also support transparently decompressing inputs, and more remote options such as scp://
If you are as horified as I am by the amount of code in this crate for what feels like it should have been a very simple task, then patharg
is a much lighter crate that works with clap for treating '-' as stdin/stdout.
It does not open the file, or otherwise validate the path until you ask it avoiding TOCTTOU issues but in the process looses the nice clap error messages.
It also avoids a whole pile of complexity for dealing with seeking and guessing up front if the input supports seeking.
Also watch out patharg has no custom clap ValueParser so older versions of clap will convert via a String so path will need to be valid utf-8 which is not guarnatied by linux nor windows.
If all you really need is support mapping '-'
to stdin()
try this lovely function distilled from patharg
.
It works because either has helpfully added impl
s for many common traits when both sides implement them.
use either::Either;
use std::io;
use std::ffi::OsStr;
use std::fs::File;
pub fn open(path: &OsStr) -> io::Result<impl io::BufRead> {
Ok(if path == "-" {
Either::Left(io::stdin().lock())
} else {
Either::Right(io::BufReader::new(File::open(path)?))
})
}
The corresponding create
function is left as an exercise for the reader.
clap-parse
Implements ValueParserFactory
for all the types and
adds a bad implementation of [Clone
] to all types as well to keep clap
happy.
If a url is passed to Input::new
then it will perform and HTTP GET
. This has the advantage vs just piping in the output of curl as you know the input size, and can infer related urls, e.g. get the Cargo.lock
to match the Cargo.toml
.
If a url is passed to Output::new
then it will perform and HTTP PUT
.
The main advantage over just piping to curl is you can use OutputPath::create_with_len
to set the size before the upload starts e.g.
needed if you are sending a file to S3.
http-ureq
bundles in ureq as a HTTP client.
http-curl
bundles in curl as a HTTP client.