#cli #prelude #command-line #script #type #logging

cotton

A prelude with crates, types and functions useful for writing CLI tools

23 releases

0.1.1 Mar 30, 2023
0.1.0 Dec 22, 2022
0.0.20 Sep 15, 2021
0.0.17 Jun 30, 2021
0.0.1 Apr 30, 2019

#378 in Command-line interface

Download history 3/week @ 2024-01-08 5/week @ 2024-02-19 132/week @ 2024-02-26 6/week @ 2024-03-04

143 downloads per month
Used in 7 crates

MIT license

44KB
662 lines

Latest Version Documentation License

"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.

Things that fit this prelude:

  • argument parsing,
  • logging,
  • I/O including reading from stdin,
  • common file operations and directory structure,
  • executing commands,
  • extensions to stdlib and language functionality,
  • digests and checksums,
  • time and duration
  • commonly used stdlib imports.

Things that will not be included:

  • JSON parser or other formats,
  • HTTP client or specific API clients
  • TLS or other encryption libraries.

lib.rs:

"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:

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 logger
  • log - A lightweight logging facade for Rust
  • stderrlog - Logger that logs to stderr based on verbosity specified
  • time - time and date
  • chrono - Date and time library for Rust
  • term - working with terminal emulators
  • ansi_term - Library for ANSI terminal colours and styles (bold, underline)
  • atty - A simple interface for querying atty
  • zzz - Fast progress bar with sane defaults
  • term_size - functions for determining terminal sizes and dimensions
  • hashing - digest calculations and hex encoding
  • hex - Encoding and decoding data into/from hexadecimal representation
  • sha2 - Pure Rust implementation of the SHA-2 hash function family
  • digest - Traits for cryptographic hash functions and message authentication codes
  • files - 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 with RUST_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)
}
}

Dependencies

~3–16MB
~182K SLoC