Expand description
“Batteries included” prelude with crates, types and functions useful for writing command-line interface tools and quick scripts.
This prelude aims to be useful in generic context of CLI tools and will try to minimise dependencies.
Basic command-line interface program template
Example starting point for your program that features command line argument parsing with help message, logger setup and human-friendly error and panic messages.
use cotton::prelude::*;
/// Example script description
#[derive(Parser)]
struct Cli {
#[command(flatten)]
logging: ArgsLogger,
#[command(flatten)]
dry_run: ArgsDryRun,
}
fn main() -> FinalResult {
let Cli {
logging,
dry_run,
} = Cli::parse();
setup_logger(logging, vec![module_path!()]);
if !dry_run.enabled {
warn!("Hello world!");
}
Ok(())
}
Features
A small list of crates is always included in cotton. These are adding some common data types, language usability aids and common standard library imports:
- itertools - extends standard iterators
- linked-hash-map and linked-hash_set - ordered maps and sets
- maybe-string - handle probably UTF-8 encoded binary data
- boolinator - convert Option to bool
- tap - avoid need for
let
bindings
Cotton will also always import large number of commonly used standard library items.
All other dependencies are optional and can be opted-out by disabling default features and opting-in to only selected crates.
For convenience there are features defined that group several crates together:
regex
- regular expressions- regex - An implementation of regular expressions for Rust
args
- parsing of command line arguments- clap - A simple to use, efficient, and full-featured Command Line Argument Parser
logging
- logging macros and loggertime
- time and date- chrono - Date and time library for Rust
term
- working with terminal emulatorshashing
- digest calculations and hex encodingfiles
- file metadata and temporary files- tempfile - A library for managing temporary files and directories
- filetime - Platform-agnostic accessors of timestamps in File metadata
- file-mode - Decode Unix file mode bits, change them and apply them to files
- file-owner - Set and get Unix file owner and group
signals
- UNIX signal handling- signal-hook - Unix signal handling
- uninterruptible - Guard type that keeps selected Unix signals suppressed
errors
- flexible error handling and error context- problem - Error handling for command line applications or prototypes
- error-context - Methods and types that help with adding additional context information to error types
- scopeguard - A RAII scope guard that will run a given closure when it goes out of scope
- assert_matches - Asserts that a value matches a pattern
app
- application environment- directories - A tiny mid-level library that provides platform-specific standard locations of directories
process
- running programs and handling input/output- shellwords - Manipulate strings according to the word parsing rules of the UNIX Bourne shell
- exec - Use the POSIX exec function to replace the running program with another
- mkargs - Build command arguments
- cradle - Execute child processes with ease
Non-default features:
backtrace
- enable backtraces for problem::Problem errors (also run your program withRUST_BACKTRACE=1
)
For example you my include cotton
like this in Cargo.toml
:
cotton = { version = "0.1.0", default-features = false, features = ["errors", "args", "logging", "app", "hashing", "process"] }
Error context
Generally libraries should not add context to the errors as it may be considered sensitive for some uses. In this library context (like file paths) will be provided by default.
Static error types
When you need proper error handling (e.g. on the internal modules or when you need to act on the errors specifically) use standard way of doing this.
Use enums with Debug
, Display
and Error
trait implementations.
Add additional From
implementations to make ?
operator to work.
If you need to add context to an error you can use error_context crate that is included in the prelude.
Example custom static error type implementation
use cotton::prelude::*;
#[derive(Debug)]
enum FileResourceError {
FileDigestError(PathBuf, FileDigestError),
NotAFileError(PathBuf),
}
impl Display for FileResourceError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
// Do not include chained error message in the message; let the client handle this (e.g. with Problem type)
FileResourceError::FileDigestError(path, _) => write!(f, "digest of a file {:?} could not be calculated", path),
FileResourceError::NotAFileError(path) => write!(f, "path {:?} is not a file", path),
}
}
}
impl Error for FileResourceError {
fn source(&self) -> Option<&(dyn Error + 'static)> {
match self {
// Chain the internal error
FileResourceError::FileDigestError(_, err) => Some(err),
FileResourceError::NotAFileError(_) => None,
}
}
}
// This allows for calls like `foo().wrap_error_while_with(|| self.path.clone())?` to add extra `PathBuf` context to the error
impl From<ErrorContext<FileDigestError, PathBuf>> for FileResourceError {
fn from(err: ErrorContext<FileDigestError, PathBuf>) -> FileResourceError {
FileResourceError::FileDigestError(err.context, err.error)
}
}
Re-exports
pub use itertools;
pub use linked_hash_map;
pub use linked_hash_set;
pub use boolinator;
pub use tap;
pub use regex;
pub use tempfile;
pub use filetime;
pub use file_owner;
pub use file_mode;
pub use problem;
pub use error_context;
pub use scopeguard;
pub use assert_matches;
pub use chrono;
pub use ansi_term;
pub use atty;
pub use zzz;
pub use term_size;
pub use clap;
pub use log;
pub use stderrlog;
pub use sha2;
pub use digest;
pub use shellwords;
pub use exec;
pub use mkargs;
pub use cradle;
pub use hex;
pub use maybe_string;
pub use signal_hook;
pub use uninterruptible;
pub use directories;
Modules
Macros
- Initializes application name and author with CARGO_PKG_NAME and CARGO_PKG_AUTHORS.