From fb63c8de43c538bcaf9afd57122fbf1c542966d0 Mon Sep 17 00:00:00 2001 From: boasting-squirrel Date: Thu, 14 Feb 2019 22:24:07 +0100 Subject: Switched to structopt --- src/args.rs | 201 +++++++++++++++++++-------------------------------------- src/listing.rs | 38 +++-------- src/main.rs | 8 ++- 3 files changed, 82 insertions(+), 165 deletions(-) (limited to 'src') diff --git a/src/args.rs b/src/args.rs index ae86108..5241b04 100644 --- a/src/args.rs +++ b/src/args.rs @@ -1,173 +1,106 @@ -use crate::auth; -use crate::listing; -use clap::{crate_authors, crate_description, crate_name, crate_version}; use std::net::{IpAddr, Ipv4Addr, Ipv6Addr}; use std::path::PathBuf; +use structopt::StructOpt; + +use crate::auth; +use crate::listing; /// Possible characters for random routes const ROUTE_ALPHABET: [char; 16] = [ '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', 'a', 'b', 'c', 'd', 'e', 'f', ]; -/// Checks wether a path is valid, i.e. it exists on the system and points to a file/directory -fn is_valid_path(path: String) -> Result<(), String> { - let path_to_check = PathBuf::from(path); - if path_to_check.is_file() || path_to_check.is_dir() { - return Ok(()); - } - Err(String::from( - "Path either doesn't exist or is not a regular file or a directory", - )) -} - -/// Checks wether a port is valid -fn is_valid_port(port: String) -> Result<(), String> { - port.parse::() - .and(Ok(())) - .or_else(|e| Err(e.to_string())) +#[derive(StructOpt, Debug)] +#[structopt(name = "miniserve")] +struct CLIArgs { + /// Be verbose, includes emitting access logs + #[structopt(short = "v", long = "verbose")] + verbose: bool, + /// Which path to serve + #[structopt(name = "PATH", parse(from_os_str))] + path: Option, + /// Port to use + #[structopt(short = "p", long = "port", default_value = "8080")] + port: u16, + /// Interface to listen on + #[structopt(short = "i", long = "if", parse(try_from_str = "parse_interface"))] + interfaces: Vec, + /// Set authentication (username:password) + #[structopt(short = "a", long = "auth", parse(try_from_str = "parse_auth"))] + auth: Option<(String, String)>, + /// Generate a random 6-hexdigit route + #[structopt(long = "random-route")] + random_route: bool, + /// Sort files + #[structopt( + short = "s", + long = "sort", + raw( + possible_values = "&listing::SortingMethods::variants()", + case_insensitive = "true" + ) + )] + sort_method: Option, + /// Reverse sorting + #[structopt(long = "reverse")] + reverse_sort: bool, + /// Do not follow symbolic links + #[structopt(short = "P", long = "no-symlinks")] + no_symlinks: bool, } - /// Checks wether an interface is valid, i.e. it can be parsed into an IP address -fn is_valid_interface(interface: String) -> Result<(), String> { - interface - .parse::() - .and(Ok(())) - .or_else(|e| Err(e.to_string())) +fn parse_interface(src: &str) -> Result { + src.parse::() } /// Checks wether the auth string is valid, i.e. it follows the syntax username:password -fn is_valid_auth(auth: String) -> Result<(), String> { - auth.find(':') - .ok_or_else(|| "Correct format is username:password".to_owned()) - .map(|_| ()) +fn parse_auth(src: &str) -> Result<(String, String), String> { + match src.find(':') { + Some(_) => { + let split = src.split(':').collect::>(); + Ok((split[0].to_owned(), split[1].to_owned())) + } + None => Err("Correct format is username:password".to_owned()), + } } /// Parses the command line arguments pub fn parse_args() -> crate::MiniserveConfig { - use clap::{App, AppSettings, Arg}; + let args = CLIArgs::from_args(); - let matches = App::new(crate_name!()) - .version(crate_version!()) - .author(crate_authors!()) - .about(crate_description!()) - .global_setting(AppSettings::ColoredHelp) - .arg( - Arg::with_name("verbose") - .short("v") - .long("verbose") - .help("Be verbose, includes emitting access logs"), - ) - .arg( - Arg::with_name("PATH") - .required(false) - .validator(is_valid_path) - .help("Which path to serve"), - ) - .arg( - Arg::with_name("port") - .short("p") - .long("port") - .help("Port to use") - .validator(is_valid_port) - .required(false) - .default_value("8080") - .takes_value(true), - ) - .arg( - Arg::with_name("interfaces") - .short("i") - .long("if") - .help("Interface to listen on") - .validator(is_valid_interface) - .required(false) - .takes_value(true) - .multiple(true), - ) - .arg( - Arg::with_name("auth") - .short("a") - .long("auth") - .validator(is_valid_auth) - .help("Set authentication (username:password)") - .takes_value(true), - ) - .arg( - Arg::with_name("random-route") - .long("random-route") - .help("Generate a random 6-hexdigit route"), - ) - .arg( - Arg::with_name("sort") - .short("s") - .long("sort") - .possible_values(&["natural", "alpha", "dirsfirst"]) - .default_value("natural") - .help("Sort files"), - ) - .arg( - Arg::with_name("reverse") - .long("reverse") - .help("Reverse sorting order"), - ) - .arg( - Arg::with_name("no-symlinks") - .short("P") - .long("no-symlinks") - .help("Do not follow symbolic links"), - ) - .get_matches(); - - let verbose = matches.is_present("verbose"); - let no_symlinks = matches.is_present("no-symlinks"); - let path = matches.value_of("PATH"); - let port = matches.value_of("port").unwrap().parse().unwrap(); - let interfaces = if let Some(interfaces) = matches.values_of("interfaces") { - interfaces.map(|x| x.parse().unwrap()).collect() + let interfaces = if !args.interfaces.is_empty() { + args.interfaces } else { vec![ IpAddr::V6(Ipv6Addr::new(0, 0, 0, 0, 0, 0, 0, 0)), IpAddr::V4(Ipv4Addr::new(0, 0, 0, 0)), ] }; - let auth = if let Some(auth_split) = matches.value_of("auth").map(|x| x.splitn(2, ':')) { - let auth_vec = auth_split.collect::>(); - if auth_vec.len() == 2 { - Some(auth::BasicAuthParams { - username: auth_vec[0].to_owned(), - password: auth_vec[1].to_owned(), - }) - } else { - None - } - } else { - None + + let auth = match args.auth { + Some((username, password)) => Some(auth::BasicAuthParams { username, password }), + None => None, }; - let random_route = if matches.is_present("random-route") { + let random_route = if args.random_route { Some(nanoid::custom(6, &ROUTE_ALPHABET)) } else { None }; - let sort_method = matches - .value_of("sort") - .unwrap() - .parse::() - .unwrap(); - - let reverse_sort = matches.is_present("reverse"); + let path_explicitly_chosen = args.path.is_some(); crate::MiniserveConfig { - verbose, - path: PathBuf::from(path.unwrap_or(".")), - port, + verbose: args.verbose, + path: args.path.unwrap_or_else(|| PathBuf::from(".")), + port: args.port, interfaces, auth, - path_explicitly_chosen: path.is_some(), - no_symlinks, + path_explicitly_chosen, + no_symlinks: args.no_symlinks, random_route, - sort_method, - reverse_sort, + sort_method: args.sort_method.unwrap_or(listing::SortingMethods::Natural), + reverse_sort: args.reverse_sort, } } diff --git a/src/listing.rs b/src/listing.rs index f0662ef..6d956ac 100644 --- a/src/listing.rs +++ b/src/listing.rs @@ -6,26 +6,19 @@ use std::cmp::Ordering; use std::fmt::Write as FmtWrite; use std::io; use std::path::Path; -use std::str::FromStr; -#[derive(Clone, Copy, Debug)] -/// Available sorting methods -pub enum SortingMethods { - /// Natural sorting method - /// 1 -> 2 -> 3 -> 11 - Natural, - - /// Pure alphabetical sorting method - /// 1 -> 11 -> 2 -> 3 - Alpha, - - /// Directories are listed first, alphabetical sorting is also applied - /// 1/ -> 2/ -> 3/ -> 11 -> 12 - DirsFirst, +arg_enum! { + #[derive(Clone, Copy, Debug)] + /// Available sorting methods + pub enum SortingMethods { + Natural, + Alpha, + DirsFirst, + } } #[derive(PartialEq)] -/// Possible entry types +/// Possible entry types enum EntryType { /// Entry is a directory Directory, @@ -75,19 +68,6 @@ impl Entry { } } -impl FromStr for SortingMethods { - type Err = (); - - fn from_str(s: &str) -> Result { - match s { - "natural" => Ok(SortingMethods::Natural), - "alpha" => Ok(SortingMethods::Alpha), - "dirsfirst" => Ok(SortingMethods::DirsFirst), - _ => Err(()), - } - } -} - pub fn file_handler(req: &HttpRequest) -> Result { let path = &req.state().path; Ok(fs::NamedFile::open(path)?) diff --git a/src/main.rs b/src/main.rs index 7c31976..7bdb986 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,3 +1,7 @@ +extern crate structopt; +#[macro_use] +extern crate clap; + use actix_web::{fs, middleware, server, App}; use clap::crate_version; use simplelog::{Config, LevelFilter, TermLogger}; @@ -16,11 +20,11 @@ mod listing; pub struct MiniserveConfig { /// Enable verbose mode pub verbose: bool, - + /// Path to be served by miniserve pub path: std::path::PathBuf, - /// Port on which miniserve will be listening + /// Port on which miniserve will be listening pub port: u16, /// IP address(es) on which miniserve will be available -- cgit v1.2.3 From d04b98c14b94fbbb2324b326a2bd45dcf6df2c3e Mon Sep 17 00:00:00 2001 From: boasting-squirrel Date: Fri, 15 Feb 2019 07:18:51 +0100 Subject: Added some line breaks so CLIArgs struct is easier to read --- src/args.rs | 8 ++++++++ 1 file changed, 8 insertions(+) (limited to 'src') diff --git a/src/args.rs b/src/args.rs index 5241b04..c836215 100644 --- a/src/args.rs +++ b/src/args.rs @@ -16,21 +16,27 @@ struct CLIArgs { /// Be verbose, includes emitting access logs #[structopt(short = "v", long = "verbose")] verbose: bool, + /// Which path to serve #[structopt(name = "PATH", parse(from_os_str))] path: Option, + /// Port to use #[structopt(short = "p", long = "port", default_value = "8080")] port: u16, + /// Interface to listen on #[structopt(short = "i", long = "if", parse(try_from_str = "parse_interface"))] interfaces: Vec, + /// Set authentication (username:password) #[structopt(short = "a", long = "auth", parse(try_from_str = "parse_auth"))] auth: Option<(String, String)>, + /// Generate a random 6-hexdigit route #[structopt(long = "random-route")] random_route: bool, + /// Sort files #[structopt( short = "s", @@ -41,9 +47,11 @@ struct CLIArgs { ) )] sort_method: Option, + /// Reverse sorting #[structopt(long = "reverse")] reverse_sort: bool, + /// Do not follow symbolic links #[structopt(short = "P", long = "no-symlinks")] no_symlinks: bool, -- cgit v1.2.3 From 604abc121a760615f22039495be3c4355e0163ec Mon Sep 17 00:00:00 2001 From: boasting-squirrel Date: Fri, 15 Feb 2019 07:28:11 +0100 Subject: Added global_settings to CLIArgs struct --- src/args.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) (limited to 'src') diff --git a/src/args.rs b/src/args.rs index c836215..781910c 100644 --- a/src/args.rs +++ b/src/args.rs @@ -11,7 +11,10 @@ const ROUTE_ALPHABET: [char; 16] = [ ]; #[derive(StructOpt, Debug)] -#[structopt(name = "miniserve")] +#[structopt( + name = "miniserve", + raw(global_settings = "&[structopt::clap::AppSettings::ColoredHelp]") +)] struct CLIArgs { /// Be verbose, includes emitting access logs #[structopt(short = "v", long = "verbose")] -- cgit v1.2.3 From 8207a48a612fb6971e0b8fc8924c133e12b2ea36 Mon Sep 17 00:00:00 2001 From: boasting-squirrel Date: Fri, 15 Feb 2019 07:28:47 +0100 Subject: Added docstrings to SortingMethods enum --- src/listing.rs | 9 +++++++++ 1 file changed, 9 insertions(+) (limited to 'src') diff --git a/src/listing.rs b/src/listing.rs index 6d956ac..f82764e 100644 --- a/src/listing.rs +++ b/src/listing.rs @@ -10,6 +10,15 @@ use std::path::Path; arg_enum! { #[derive(Clone, Copy, Debug)] /// Available sorting methods + /// + /// Natural: natural sorting method + /// 1 -> 2 -> 3 -> 11 + /// + /// Alpha: pure alphabetical sorting method + /// 1 -> 11 -> 2 -> 3 + /// + /// DirsFirst: directories are listed first, alphabetical sorting is also applied + /// 1/ -> 2/ -> 3/ -> 11 -> 12 pub enum SortingMethods { Natural, Alpha, -- cgit v1.2.3 From 5c58453b698ec2925e1be7c885301a65e3a7af03 Mon Sep 17 00:00:00 2001 From: boasting-squirrel Date: Fri, 15 Feb 2019 19:02:57 +0100 Subject: Removed extern crate invokations --- src/listing.rs | 1 + src/main.rs | 4 ---- 2 files changed, 1 insertion(+), 4 deletions(-) (limited to 'src') diff --git a/src/listing.rs b/src/listing.rs index f82764e..056c847 100644 --- a/src/listing.rs +++ b/src/listing.rs @@ -1,5 +1,6 @@ use actix_web::{fs, HttpRequest, HttpResponse, Result}; use bytesize::ByteSize; +use clap::{_clap_count_exprs, arg_enum}; use htmlescape::encode_minimal as escape_html_entity; use percent_encoding::{utf8_percent_encode, DEFAULT_ENCODE_SET}; use std::cmp::Ordering; diff --git a/src/main.rs b/src/main.rs index 7bdb986..378a4d0 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,7 +1,3 @@ -extern crate structopt; -#[macro_use] -extern crate clap; - use actix_web::{fs, middleware, server, App}; use clap::crate_version; use simplelog::{Config, LevelFilter, TermLogger}; -- cgit v1.2.3 From ba12a1e3a8a138cb90974b09de797e967e6e68ea Mon Sep 17 00:00:00 2001 From: boasting-squirrel Date: Sat, 16 Feb 2019 10:54:07 +0100 Subject: Set number_of_values argument to interfaces --- src/args.rs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) (limited to 'src') diff --git a/src/args.rs b/src/args.rs index 781910c..637c224 100644 --- a/src/args.rs +++ b/src/args.rs @@ -29,7 +29,12 @@ struct CLIArgs { port: u16, /// Interface to listen on - #[structopt(short = "i", long = "if", parse(try_from_str = "parse_interface"))] + #[structopt( + short = "i", + long = "if", + parse(try_from_str = "parse_interface"), + raw(number_of_values = "1") + )] interfaces: Vec, /// Set authentication (username:password) -- cgit v1.2.3