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
5#[cfg(test)]
6mod test;
7
8mod guess_version;
9mod lint;
10mod opts;
11mod price;
12mod print;
13
14use std::{
15 env, fmt,
16 io::{self, IsTerminal as _},
17 path::PathBuf,
18};
19
20use clap::Parser;
21use ocpi_tariffs::{warning, ParseError};
22
23#[doc(hidden)]
24#[derive(Parser)]
25#[command(version)]
26pub struct Opts {
27 #[clap(subcommand)]
28 command: opts::Command,
29}
30
31impl Opts {
32 pub fn run(self) -> Result<(), Error> {
33 self.command.run()
34 }
35}
36
37#[derive(Copy, Clone, Debug, clap::ValueEnum)]
38pub enum ObjectKind {
39 Cdr,
40 Tariff,
41}
42
43#[doc(hidden)]
44#[derive(Debug)]
45pub enum Error {
46 Handled,
47
48 CdrRequired,
50
51 Deserialize(ParseError),
53
54 FileIO {
56 path: PathBuf,
57 error: io::Error,
58 },
59
60 Internal(Box<dyn std::error::Error + Send + Sync + 'static>),
62
63 InvalidTimezone(chrono_tz::ParseError),
65
66 PathNotFile {
68 path: PathBuf,
69 },
70
71 PathNoParentDir {
73 path: PathBuf,
74 },
75
76 Price(warning::Error<ocpi_tariffs::price::Warning>),
78
79 StdIn(io::Error),
81
82 TariffRequired,
84}
85
86impl std::error::Error for Error {}
87
88impl fmt::Display for Error {
89 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
90 match self {
91 Self::Handled => Ok(()),
92 Self::CdrRequired => f.write_str("`--cdr` is required when the process is a TTY"),
93 Self::Deserialize(err) => write!(f, "{err}"),
94 Self::FileIO { path, error } => {
95 write!(f, "File error `{}`: {}", path.display(), error)
96 }
97 Self::Internal(err) => {
98 write!(f, "{err}")
99 }
100 Self::InvalidTimezone(err) => write!(f, "the timezone given is invalid; {err}"),
101 Self::PathNotFile { path } => {
102 write!(f, "The path given is not a file: `{}`", path.display())
103 }
104 Self::PathNoParentDir { path } => {
105 write!(
106 f,
107 "The path given doesn't have a parent dir: `{}`",
108 path.display()
109 )
110 }
111 Self::Price(err) => write!(f, "{err}"),
112 Self::StdIn(err) => {
113 write!(f, "Stdin error {err}")
114 }
115 Self::TariffRequired => f.write_str("`--tariff` is required when the process is a TTY"),
116 }
117 }
118}
119
120impl From<warning::Error<ocpi_tariffs::price::Warning>> for Error {
121 fn from(value: warning::Error<ocpi_tariffs::price::Warning>) -> Self {
122 Self::Price(value)
123 }
124}
125
126impl From<ParseError> for Error {
127 fn from(err: ParseError) -> Self {
128 Self::Deserialize(err)
129 }
130}
131
132impl Error {
133 pub fn then_file_io(path: PathBuf) -> impl FnOnce(io::Error) -> Error {
135 |error| Self::FileIO { path, error }
136 }
137
138 pub fn stdin(err: io::Error) -> Self {
139 Self::StdIn(err)
140 }
141}
142
143#[doc(hidden)]
144pub fn setup_logging() -> Result<(), &'static str> {
145 let stderr = io::stderr();
146 let builder = tracing_subscriber::fmt()
147 .without_time()
148 .with_writer(io::stderr)
149 .with_ansi(stderr.is_terminal());
150
151 let level = match env::var("RUST_LOG") {
152 Ok(s) => s.parse().unwrap_or(tracing::Level::INFO),
153 Err(err) => match err {
154 env::VarError::NotPresent => tracing::Level::INFO,
155 env::VarError::NotUnicode(_) => {
156 return Err("RUST_LOG is not unicode");
157 }
158 },
159 };
160
161 builder.with_max_level(level).init();
162
163 Ok(())
164}