#![allow(clippy::print_stderr, reason = "The CLI is allowed to use stderr")]
#![allow(clippy::print_stdout, reason = "The CLI is allowed to use stdout")]
#![doc = include_str!("../README.md")]
#[cfg(test)]
mod test;
mod guess_version;
mod lint;
mod opts;
mod price;
mod print;
use std::{
env, fmt,
io::{self, IsTerminal as _},
path::PathBuf,
};
use clap::Parser;
use ocpi_tariffs::{warning, ParseError};
#[doc(hidden)]
#[derive(Parser)]
#[command(version)]
pub struct Opts {
#[clap(subcommand)]
command: opts::Command,
}
impl Opts {
pub fn run(self) -> Result<(), Error> {
self.command.run()
}
}
#[derive(Copy, Clone, Debug, clap::ValueEnum)]
pub enum ObjectKind {
Cdr,
Tariff,
}
#[doc(hidden)]
#[derive(Debug)]
pub enum Error {
Handled,
CdrRequired,
Deserialize(ParseError),
FileIO {
path: PathBuf,
error: io::Error,
},
Internal(Box<dyn std::error::Error + Send + Sync + 'static>),
InvalidTimezone(chrono_tz::ParseError),
PathNotFile {
path: PathBuf,
},
PathNoParentDir {
path: PathBuf,
},
Price(warning::Error<ocpi_tariffs::price::Warning>),
StdIn(io::Error),
TariffRequired,
}
impl std::error::Error for Error {}
impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Handled => Ok(()),
Self::CdrRequired => f.write_str("`--cdr` is required when the process is a TTY"),
Self::Deserialize(err) => write!(f, "{err}"),
Self::FileIO { path, error } => {
write!(f, "File error `{}`: {}", path.display(), error)
}
Self::Internal(err) => {
write!(f, "{err}")
}
Self::InvalidTimezone(err) => write!(f, "the timezone given is invalid; {err}"),
Self::PathNotFile { path } => {
write!(f, "The path given is not a file: `{}`", path.display())
}
Self::PathNoParentDir { path } => {
write!(
f,
"The path given doesn't have a parent dir: `{}`",
path.display()
)
}
Self::Price(err) => write!(f, "{err}"),
Self::StdIn(err) => {
write!(f, "Stdin error {err}")
}
Self::TariffRequired => f.write_str("`--tariff` is required when the process is a TTY"),
}
}
}
impl From<warning::Error<ocpi_tariffs::price::Warning>> for Error {
fn from(value: warning::Error<ocpi_tariffs::price::Warning>) -> Self {
Self::Price(value)
}
}
impl From<ParseError> for Error {
fn from(err: ParseError) -> Self {
Self::Deserialize(err)
}
}
impl Error {
pub fn then_file_io(path: PathBuf) -> impl FnOnce(io::Error) -> Error {
|error| Self::FileIO { path, error }
}
pub fn stdin(err: io::Error) -> Self {
Self::StdIn(err)
}
}
#[doc(hidden)]
pub fn setup_logging() -> Result<(), &'static str> {
let stderr = io::stderr();
let builder = tracing_subscriber::fmt()
.without_time()
.with_writer(io::stderr)
.with_ansi(stderr.is_terminal());
let level = match env::var("RUST_LOG") {
Ok(s) => s.parse().unwrap_or(tracing::Level::INFO),
Err(err) => match err {
env::VarError::NotPresent => tracing::Level::INFO,
env::VarError::NotUnicode(_) => {
return Err("RUST_LOG is not unicode");
}
},
};
builder.with_max_level(level).init();
Ok(())
}