rust_script_ext/lib.rs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285
//! Opinionated set of extensions for use with
//! [`rust-script`](https://github.com/fornwall/rust-script).
//! Using `rust-script` to run Rust like a shell script is great!
//! This crate provides an opinionated set of extensions tailored towards common patterns in scripts.
//! These patterns include file reading, argument parsing, error handling.
//!
//! # Argument Parsing
//!
//! A rudimentary argument parser is provided, simply call [`args`](args::args).
//!
//! The parsing is meant to be simple, tailored to script usage. For fully featured CLI apps,
//! consider importing [`clap`](https://docs.rs/clap/latest/clap/index.html).
//!
//! # Error Handling
//!
//! Error handling uses the [`miette`] crate.
//! A `Result` type alias is exposed, and [`IntoDiagnostic`](prelude::IntoDiagnostic) can be used
//! to convert errors.
//!
//! ```rust
//! # use rust_script_ext::prelude::*;
//! fn foo() -> Result<String> {
//! std::fs::read_to_string("foo.txt")
//! .into_diagnostic()
//! .wrap_err("failed to open 'foo.txt'")
//! }
//! ```
//!
//! # Invoking Commands
//!
//! Running commands is done through `std::process::Command`.
//! There are a few helper traits and macros to assist in:
//!
//! 1. Building a `Command`, and
//! 2. Executing a command.
//!
//! Building commands can leverage the [`cmd!`](crate::prelude::cmd) macro.
//! This can be used to succintly build a command with arguments.
//!
//! ```rust
//! # use rust_script_ext::prelude::*;
//! let x = 1.0;
//! let cmd = cmd!(./my-script.sh: foo/bar, --verbose, {x + 2.14});
//! assert_eq!(&cmd.cmd_str(), "./my-script.sh foo/bar --verbose 3.14");
//! ```
//!
//! The [`CommandExecute`](crate::prelude::CommandExecute) trait provides some methods which
//! can execute a command and automatically collect the output, along with providing verbose
//! error messages if something fails.
//!
//! ```rust,no_run
//! # use rust_script_ext::prelude::*;
//! // Verbose means also print stdout/stderr to terminal as execution occurs
//! cmd!(ls: src).execute_str(Verbose).unwrap();
//! ```
//!
//! # Serialisation
//!
//! [`Serialize`](::serde::Serialize), [`Deserialize`](::serde::Deserialize),
//! and [`DeserializeOwned`](::serde::de::DeserializeOwned) are all exposed.
//! Because of some path quirks with re-exported proc-macros, all derived values need to be tagged
//! with the path to the serde crate, as shown below.
//!
//! ```rust
//! # use rust_script_ext::prelude::*;
//! #[derive(Deserialize)]
//! #[serde(crate = "deps::serde")]
//! struct PizzaOrder {
//! ham: bool,
//! cheese: bool,
//! pineapple: bool,
//! }
//! ```
//!
//! # Structured IO
//!
//! A common pattern is to read/write with a particular serialisation format.
//! Examples include reading a CSV file from disk, or writing JSON to stdout.
//! An abstraction is provided (structured IO) which produces
//! [`read_as`](crate::prelude::ReadAs) and [`write_as`](crate::prelude::WriteAs) functions on
//! typical targets so working with structured data is ergonomic.
//!
//! ```rust
//! # use rust_script_ext::prelude::*;
//! #[derive(Serialize, Deserialize, Debug, PartialEq)]
//! struct City {
//! city: String,
//! pop: u32,
//! }
//!
//! let csv = "city,pop\nBrisbane,100000\nSydney,200000\n";
//!
//! // read_as on anything that is Read
//! let x = csv.as_bytes().read_as::<CSV, City>().unwrap();
//!
//! assert_eq!(
//! x,
//! vec![
//! City {
//! city: "Brisbane".to_string(),
//! pop: 100_000,
//! },
//! City {
//! city: "Sydney".to_string(),
//! pop: 200_000,
//! }
//! ]
//! );
//!
//! let mut buf = Vec::new();
//!
//! // write &[T] (T: Serialize) as a CSV into a Writer
//! x.write_as(CSV, &mut buf).unwrap();
//! assert_eq!(buf, csv.as_bytes());
//! ```
//!
//! # Date and Time
//!
//! Date and time is handled by exposing the [`time`](::time) crate.
//! For _duration_, [`humantime`](::humantime) is used, exposing its `Duration` directly. This is
//! done for duration parsing similar to what is experienced in unix tools.
//!
//! # Number formatting
//!
//! [`numfmt::Formatter`] is exposed (as [`NumFmt`](prelude::NumFmt)) which can be used
//! to format numbers in a nice way. The `numfmt` module documentation describes ways to
//! build a formatter, along with the syntax for parsing a format string.
//!
//! # Progress reporting
//!
//! [`how-u-doin`](::howudoin) can be used to show progress of a longer running script.
//!
//! # Tabular printing
//!
//! Tables can be printed neatly with [`TablePrinter`](prelude::TablePrinter), which is just
//! exposing [`comfy-table`](::comfy_table).
#![warn(missing_docs)]
mod args;
mod cmd;
mod fs;
mod io;
/// Exposed dependency crates.
pub mod deps {
pub use ::comfy_table;
pub use ::csv;
pub use ::fastrand;
pub use ::flume;
pub use ::globset;
pub use ::howudoin;
pub use ::humantime;
pub use ::itertools;
pub use ::miette;
pub use ::numfmt;
pub use ::rayon;
pub use ::regex;
pub use ::serde;
pub use ::serde_json;
pub use ::time;
pub use ::toml;
}
/// Typical imports.
pub mod prelude {
pub use super::deps;
pub use super::args::{args, Args};
pub use super::cmd::{
CommandBuilder, CommandExecute, CommandString,
Output::{self, *},
};
pub use ::comfy_table::{self, Table as TablePrinter};
/// CSV [`Reader`](::csv::Reader) backed by a [`File`](super::fs::File).
pub type CsvReader = ::csv::Reader<super::fs::File>;
/// CSV [`Writer`](::csv::Writer) backed by a [`File`](super::fs::File).
pub type CsvWriter = ::csv::Writer<super::fs::File>;
pub use super::fs::{ls, File};
pub use super::io::{Format, ReadAs, WriteAs, CSV, JSON, TOML};
pub use ::fastrand;
pub use ::flume::{bounded, unbounded, Receiver, Sender};
pub use ::howudoin;
pub use ::humantime::{parse_duration, Duration, Timestamp};
pub use ::itertools::Itertools;
pub use ::miette::{bail, ensure, miette, Error, IntoDiagnostic, Result, WrapErr};
pub use ::numfmt::Formatter as NumFmt;
pub use ::rayon;
pub use ::regex::Regex;
pub use ::serde::{de::DeserializeOwned, Deserialize, Serialize};
pub use ::serde_json::Value as JsonValue;
pub use ::time::{Date, Month, OffsetDateTime, PrimitiveDateTime, Time, UtcOffset, Weekday};
pub use std::io::{Read, Write};
// publically document cargs! and cmd! here
/// Construct a `[String]` array from a list of arguments.
///
/// This macro is primarily for use with [`cmd!`](cmd), but can also be independently
/// used, a great location is [`Command::args`](std::process::Command::args).
///
/// Arguments are delimited by commas, any text between delimiters is stringified and
/// passed through.
/// Arguments wrapped in braces (`{ ... }`) are treated as expressions to be evaluated.
/// This effectively writes `{ ... }.to_string()`.
///
/// ```plaintext
/// arg1, arg2/foo, {expr}
/// ```
///
/// # Example
/// ```rust
/// # use rust_script_ext::prelude::*;
///
/// let x = "hello";
/// let c = cargs!(foo, bar/zog, {x}, {1 + 2});
/// assert_eq!(c, [
/// "foo".to_string(),
/// "bar/zog".to_string(),
/// "hello".to_string(),
/// "3".to_string()
/// ]);
/// ```
pub use ::macros::cargs;
/// Helper to construct a [`Command`] with arguments.
///
/// The macro uses the syntax:
/// ```plaintext
/// cmd: arg1, arg2
/// ```
///
/// That is, the command path, optionally followed by a colon (`:`) followed by one or
/// more _comma delimited_ arguments.
///
/// Note that `cmd!` defers to [`cargs!`](cargs) to parse the arguments.
///
/// The macro is powerful enough to support raw path identifiers:
/// ```rust
/// # use rust_script_ext::prelude::*;
/// let c = cmd!(ls); // no args
/// assert_eq!(&c.cmd_str(), "ls");
///
/// let c = cmd!(ls: foo/bar, zog);
/// assert_eq!(&c.cmd_str(), "ls foo/bar zog");
///
/// let c = cmd!(./local-script.sh: foo/bar, zog);
/// assert_eq!(&c.cmd_str(), "./local-script.sh foo/bar zog");
/// ```
///
/// Literals are supported:
/// ```rust
/// # use rust_script_ext::prelude::*;
/// let c = cmd!(ls: "foo bar", 1.23);
/// assert_eq!(&c.cmd_str(), r#"ls "foo bar" 1.23"#);
/// ```
///
/// Arguments wrapped in braces (`{ ... }`) are treated as expressions to be evaluated.
/// This effectively writes `{ ... }.to_string()`.
///
/// ```rust
/// # use rust_script_ext::prelude::*;
/// let h = "hello";
/// let c = cmd!(ls: {h}, {format!("world")});
/// assert_eq!(&c.cmd_str(), "ls hello world");
/// ```
///
/// [`Command`]: std::process::Command
pub use ::macros::cmd;
}
#[cfg(test)]
fn pretty_print_err(err: miette::Error) -> String {
use miette::*;
let mut buf = String::new();
GraphicalReportHandler::new_themed(GraphicalTheme::unicode_nocolor())
.render_report(&mut buf, err.as_ref())
.unwrap();
buf
}