Skip to content

Instantly share code, notes, and snippets.

@Hoverbear
Last active January 19, 2025 03:10
Show Gist options
  • Select an option

  • Save Hoverbear/a329ca3ee21b08b78725bc84aed3f100 to your computer and use it in GitHub Desktop.

Select an option

Save Hoverbear/a329ca3ee21b08b78725bc84aed3f100 to your computer and use it in GitHub Desktop.
Snippet for Rust that lets you take a URL or path in a clap arg.
#[derive(clap::Parser, Debug)]
pub(crate) struct CommandLineArguments {
/// A git repo or path to use as the source
#[clap(value_parser = clap::value_parser!(UrlOrPath))]
source: Option<UrlOrPath>,
}
use std::{fmt::Display, str::FromStr};
use camino::Utf8PathBuf;
use clap::error::{ContextKind, ContextValue};
use serde::{Deserialize, Serialize};
use url::Url;
#[derive(Debug)]
pub enum UrlOrPathError {
Parsing(String)
}
impl error_stack::Context for UrlOrPathError {}
impl std::fmt::Display for UrlOrPathError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
UrlOrPathError::Parsing(value) => f.write_fmt(format_args!("Error parsing `{value}` as URL or path")),
}
}
}
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize, Clone)]
pub enum UrlOrPath {
Url(Url),
Path(Utf8PathBuf),
}
impl Display for UrlOrPath {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
UrlOrPath::Url(url) => f.write_fmt(format_args!("{url}")),
UrlOrPath::Path(path) => f.write_fmt(format_args!("{path}")),
}
}
}
impl FromStr for UrlOrPath {
type Err = url::ParseError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match Url::parse(s) {
Ok(url) => Ok(UrlOrPath::Url(url)),
Err(url::ParseError::RelativeUrlWithoutBase) => {
// This is most likely a relative path (`./boop` or `boop`)
// or an absolute path (`/boop`)
Ok(UrlOrPath::Path(Utf8PathBuf::from(s)))
},
Err(e) => Err(e),
}
}
}
impl clap::builder::TypedValueParser for UrlOrPath {
type Value = UrlOrPath;
fn parse_ref(
&self,
cmd: &clap::Command,
_arg: Option<&clap::Arg>,
value: &std::ffi::OsStr,
) -> Result<Self::Value, clap::Error> {
let value_str = value.to_str().ok_or_else(|| {
let mut err = clap::Error::new(clap::error::ErrorKind::InvalidValue);
err.insert(
ContextKind::InvalidValue,
ContextValue::String(format!("`{value:?}` not a UTF-8 string")),
);
err
})?;
match UrlOrPath::from_str(value_str) {
Ok(v) => Ok(v),
Err(from_str_error) => {
let mut err = clap::Error::new(clap::error::ErrorKind::InvalidValue).with_cmd(cmd);
err.insert(
clap::error::ContextKind::Custom,
clap::error::ContextValue::String(from_str_error.to_string()),
);
Err(err)
},
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment