#cli #prelude

cotton

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

21 releases

0.0.20 Sep 15, 2021
0.0.17 Jun 30, 2021
0.0.15 Mar 29, 2021
0.0.13 Oct 9, 2020
0.0.1 Apr 30, 2019

#259 in Command-line interface

Download history 23/week @ 2021-10-04 48/week @ 2021-10-11 9/week @ 2021-10-18 25/week @ 2021-10-25 22/week @ 2021-11-01 36/week @ 2021-11-08 41/week @ 2021-11-15 36/week @ 2021-11-22 54/week @ 2021-11-29 52/week @ 2021-12-06 38/week @ 2021-12-13 20/week @ 2021-12-20 43/week @ 2021-12-27 46/week @ 2022-01-03 48/week @ 2022-01-10 38/week @ 2022-01-17

179 downloads per month
Used in less than 6 crates

MIT license

71KB
1K SLoC

Latest Version Documentation License

"Batteries included" prelude with crates, types and functions useful for writing command-line interface tools.

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,
  • I/O including reading from stdin,
  • common file operations and directory structure,
  • logging,
  • executing commands,
  • extensions to stdlib and language functionality,
  • digests and checksums,
  • time and duration.

Things that will not be included:

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

lib.rs:

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

~7.5MB
~143K SLoC