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 analyze;
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;
17use ocpi_tariffs::{price, timezone};
18
19#[doc(hidden)]
20#[derive(Parser)]
21pub struct Opts {
22 #[clap(subcommand)]
23 command: opts::Command,
24}
25
26impl Opts {
27 pub fn run(self) -> Result<(), Error> {
28 self.command.run()
29 }
30}
31
32#[doc(hidden)]
33#[derive(Debug)]
34pub enum Error {
35 PathNotFile {
37 path: PathBuf,
38 },
39 File {
40 path: PathBuf,
41 error: io::Error,
42 },
43 StdIn {
44 error: io::Error,
45 },
46
47 Price(price::Error),
49
50 Timezone(timezone::Error),
52
53 TotalsDoNotMatch,
55
56 CdrRequired,
58}
59
60impl std::error::Error for Error {}
61
62impl fmt::Display for Error {
63 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
64 match self {
65 Self::PathNotFile { path } => {
66 write!(f, "The path given is not a file: `{}`", path.display())
67 }
68 Self::File { path, error } => {
69 write!(f, "File error `{}`: {}", path.display(), error)
70 }
71 Self::StdIn { error } => {
72 write!(f, "Stdin error {error}")
73 }
74 Self::Price(err) => write!(f, "{err}"),
75 Self::Timezone(err) => write!(f, "{err}"),
76 Self::TotalsDoNotMatch => {
77 f.write_str("Calculation does not match all totals in the CDR")
78 }
79 Self::CdrRequired => f.write_str("`--cdr` is required when the process is a TTY"),
80 }
81 }
82}
83
84impl From<price::Error> for Error {
85 fn from(err: price::Error) -> Self {
86 Self::Price(err)
87 }
88}
89
90impl From<timezone::Error> for Error {
91 fn from(err: timezone::Error) -> Self {
92 Self::Timezone(err)
93 }
94}
95
96impl Error {
97 pub fn file(path: PathBuf, error: io::Error) -> Self {
98 Self::File { path, error }
99 }
100
101 pub fn stdin(error: io::Error) -> Self {
102 Self::StdIn { error }
103 }
104}
105
106#[doc(hidden)]
107pub fn setup_logging() -> Result<(), &'static str> {
108 let stderr = io::stderr();
109 let builder = tracing_subscriber::fmt()
110 .without_time()
111 .with_writer(io::stderr)
112 .with_ansi(stderr.is_terminal());
113
114 let level = match env::var("RUST_LOG") {
115 Ok(s) => s.parse().unwrap_or(tracing::Level::INFO),
116 Err(err) => match err {
117 env::VarError::NotPresent => tracing::Level::INFO,
118 env::VarError::NotUnicode(_) => {
119 return Err("RUST_LOG is not unicode");
120 }
121 },
122 };
123
124 builder.with_max_level(level).init();
125
126 Ok(())
127}