1#![allow(clippy::print_stderr, reason = "The CLI is allowed to use stderr")]
2#![allow(clippy::print_stdout, reason = "The CLI is allowed to use stdout")]
3#![doc = include_str!("../README.md")]
4
5mod analyzse;
6mod opts;
7mod print;
8mod validate;
9
10use std::{
11 env, fmt,
12 io::{self, IsTerminal as _},
13 path::PathBuf,
14};
15
16use clap::Parser;
17
18#[doc(hidden)]
19#[derive(Parser)]
20pub struct Opts {
21 #[clap(subcommand)]
22 command: opts::Command,
23}
24
25impl Opts {
26 pub fn run(self) -> Result<(), Error> {
27 self.command.run()
28 }
29}
30
31#[doc(hidden)]
32#[derive(Debug)]
33pub enum Error {
34 PathNotFile {
36 path: PathBuf,
37 },
38 File {
39 path: PathBuf,
40 error: io::Error,
41 },
42 StdIn {
43 error: io::Error,
44 },
45 Internal(ocpi_tariffs::Error),
46 TotalsDoNotMatch,
48
49 CdrRequired,
51}
52impl std::error::Error for Error {}
53
54impl fmt::Display for Error {
55 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
56 match self {
57 Self::PathNotFile { path } => {
58 write!(f, "The path given is not a file: `{}`", path.display())
59 }
60 Self::File { path, error } => {
61 write!(f, "File error `{}`: {}", path.display(), error)
62 }
63 Self::StdIn { error } => {
64 write!(f, "Stdin error {error}")
65 }
66 Self::Internal(e) => write!(f, "{e}"),
67 Self::TotalsDoNotMatch => {
68 f.write_str("Calculation does not match all totals in the CDR")
69 }
70 Self::CdrRequired => f.write_str("`--cdr` is required when the process is a TTY"),
71 }
72 }
73}
74
75impl From<ocpi_tariffs::Error> for Error {
76 fn from(err: ocpi_tariffs::Error) -> Self {
77 Self::Internal(err)
78 }
79}
80
81impl Error {
82 pub fn file(path: PathBuf, error: io::Error) -> Self {
83 Self::File { path, error }
84 }
85
86 pub fn stdin(error: io::Error) -> Self {
87 Self::StdIn { error }
88 }
89}
90
91#[doc(hidden)]
92pub fn setup_logging() -> Result<(), &'static str> {
93 let stderr = io::stderr();
94 let builder = tracing_subscriber::fmt()
95 .without_time()
96 .with_writer(io::stderr)
97 .with_ansi(stderr.is_terminal());
98
99 let level = match env::var("RUST_LOG") {
100 Ok(s) => s.parse().unwrap_or(tracing::Level::INFO),
101 Err(err) => match err {
102 env::VarError::NotPresent => tracing::Level::INFO,
103 env::VarError::NotUnicode(_) => {
104 return Err("RUST_LOG is not unicode");
105 }
106 },
107 };
108
109 builder.with_max_level(level).init();
110
111 Ok(())
112}