#![forbid(unsafe_code)]
#![doc = include_str!("../README.md")]
use core::fmt;
use std::path::Path;
pub mod prelude {
pub use crate::{
CommandName, CommandNameError, ExecutableName, executable_name_from_path,
is_valid_command_name,
};
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum CommandNameError {
Empty,
ContainsSeparator,
InvalidCharacter,
NonUnicode,
}
impl fmt::Display for CommandNameError {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Empty => formatter.write_str("command name cannot be empty"),
Self::ContainsSeparator => {
formatter.write_str("command name cannot contain path separators")
},
Self::InvalidCharacter => {
formatter.write_str("command name cannot contain control characters")
},
Self::NonUnicode => formatter.write_str("executable name is not valid Unicode"),
}
}
}
impl std::error::Error for CommandNameError {}
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub struct CommandName {
name: String,
}
impl CommandName {
pub fn new(name: impl Into<String>) -> Result<Self, CommandNameError> {
let name = name.into();
validate_command_name(&name)?;
Ok(Self { name })
}
#[must_use]
pub fn as_str(&self) -> &str {
&self.name
}
#[must_use]
pub fn into_string(self) -> String {
self.name
}
}
impl AsRef<str> for CommandName {
fn as_ref(&self) -> &str {
self.as_str()
}
}
impl fmt::Display for CommandName {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter.write_str(&self.name)
}
}
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub struct ExecutableName {
command_name: CommandName,
}
impl ExecutableName {
pub fn new(name: impl Into<String>) -> Result<Self, CommandNameError> {
Ok(Self {
command_name: CommandName::new(name)?,
})
}
pub fn from_path(path: impl AsRef<Path>) -> Result<Self, CommandNameError> {
executable_name_from_path(path)
}
#[must_use]
pub const fn command_name(&self) -> &CommandName {
&self.command_name
}
#[must_use]
pub fn display_name(&self) -> &str {
self.command_name.as_str()
}
#[must_use]
pub fn into_command_name(self) -> CommandName {
self.command_name
}
}
impl fmt::Display for ExecutableName {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter.write_str(self.display_name())
}
}
#[must_use]
pub fn is_valid_command_name(name: &str) -> bool {
validate_command_name(name).is_ok()
}
pub fn executable_name_from_path(
path: impl AsRef<Path>,
) -> Result<ExecutableName, CommandNameError> {
let name = path
.as_ref()
.file_name()
.ok_or(CommandNameError::Empty)?
.to_str()
.ok_or(CommandNameError::NonUnicode)?;
ExecutableName::new(name)
}
fn validate_command_name(name: &str) -> Result<(), CommandNameError> {
if name.is_empty() {
return Err(CommandNameError::Empty);
}
if name.contains('/') || name.contains('\\') {
return Err(CommandNameError::ContainsSeparator);
}
if name.chars().any(char::is_control) {
return Err(CommandNameError::InvalidCharacter);
}
Ok(())
}
#[cfg(test)]
mod tests {
use super::{
CommandName, CommandNameError, ExecutableName, executable_name_from_path,
is_valid_command_name,
};
#[test]
fn validates_command_names() {
assert!(is_valid_command_name("rustuse"));
assert!(is_valid_command_name("rustuse.exe"));
assert!(!is_valid_command_name(""));
assert!(!is_valid_command_name("bin/rustuse"));
assert_eq!(
CommandName::new("bin/rustuse"),
Err(CommandNameError::ContainsSeparator)
);
}
#[test]
fn stores_command_and_executable_names() -> Result<(), CommandNameError> {
let command = CommandName::new("rustuse")?;
let executable = ExecutableName::new("rustuse.exe")?;
assert_eq!(command.as_str(), "rustuse");
assert_eq!(executable.display_name(), "rustuse.exe");
Ok(())
}
#[test]
fn extracts_executable_name_from_path() -> Result<(), CommandNameError> {
let executable = executable_name_from_path("target/debug/rustuse")?;
assert_eq!(executable.display_name(), "rustuse");
Ok(())
}
}