Skip to main content

fpr_cli_util/
lib.rs

1use core::fmt;
2pub use fpr_cli::*;
3use std::{
4    env::{args, var},
5    ffi::OsStr,
6    fs::{self, File},
7    io::{BufReader, Read},
8    panic::Location,
9    path::Path,
10};
11
12#[macro_export]
13macro_rules! version {
14    ($c:ty) => {
15        mod build {
16            include!(concat!(env!("OUT_DIR"), "/built.rs"));
17        }
18        #[derive(Args)]
19        #[args(f = version, desc = "Print version.")]
20        pub struct Version {}
21        fn version(_: &$c, _: Version) -> Result<(), String> {
22            use build::*;
23            println!(
24                "{} v{PKG_VERSION}: {TARGET}, {PROFILE}",
25                env!("CARGO_PKG_NAME"),
26            );
27            if !PKG_DESCRIPTION.is_empty() {
28                println!("{PKG_DESCRIPTION}");
29            }
30            if !PKG_HOMEPAGE.is_empty() {
31                println!("Homeage: {PKG_HOMEPAGE}");
32            }
33            if !PKG_LICENSE.is_empty() {
34                println!("License: {PKG_LICENSE}");
35            }
36            if !PKG_AUTHORS.is_empty() {
37                println!("Authors: {PKG_AUTHORS}");
38            }
39            if !PKG_REPOSITORY.is_empty() {
40                println!(
41                    "Repository: {PKG_REPOSITORY}, commit = {}, dirty = {}",
42                    GIT_COMMIT_HASH_SHORT.unwrap_or("none"),
43                    GIT_DIRTY
44                        .map(|e| e.to_string())
45                        .unwrap_or("none".to_string()),
46                );
47            }
48            Ok(())
49        }
50    };
51}
52
53pub trait Ctx
54where
55    Self: Sized,
56{
57    fn new(s: &str) -> Result<Self, String>;
58}
59
60fn irun<C: Ctx, M: Acts<C>, V: Args<C>, K: AsRef<OsStr>, P: AsRef<str>>(
61    config_path_override_env: K,
62    config_path_default_home_rel: P,
63) -> Result<(), String> {
64    let c = C::new(&var(config_path_override_env).unwrap_or_else(|_| {
65        let home = var("HOME").expect("Failed to get variable '$HOME'");
66        format!("{home}/{}", config_path_default_home_rel.as_ref())
67    }))?;
68    if args().into_iter().skip(1).any(|e| e == *"--version") {
69        V::next_impl(&c, &[]).map_err(|e| format!("{e}"))?;
70        return Ok(());
71    }
72    M::run(&c).map_err(|e| format!("{e}"))?;
73    Ok(())
74}
75
76pub fn run<C: Ctx, M: Acts<C>, V: Args<C>, K: AsRef<OsStr>, P: AsRef<str>>(
77    config_path_override_env: K,
78    config_path_default_home_rel: P,
79) {
80    if let Err(e) = irun::<C, M, V, K, P>(config_path_override_env, config_path_default_home_rel) {
81        eprintln!("{e}");
82    }
83}
84
85fn irund<C: Ctx, M: Args<C>, V: Args<C>, K: AsRef<OsStr>, P: AsRef<str>>(
86    config_path_override_env: K,
87    config_path_default_home_rel: P,
88) -> Result<(), String> {
89    let c = C::new(&var(config_path_override_env).unwrap_or_else(|_| {
90        let home = var("HOME").expect("Failed to get variable '$HOME'");
91        format!("{home}/{}", config_path_default_home_rel.as_ref())
92    }))?;
93    let args: Vec<_> = args().collect();
94    let args: Vec<_> = args.iter().map(|e| e.as_str()).collect();
95    if args.iter().any(|e| *e == "--version") {
96        V::next_impl(&c, &[]).map_err(|e| format!("{e}"))?;
97    } else {
98        if let Err(e) = M::next_impl(&c, &args[1..]) {
99            match e {
100                ErrArgs::Help => {
101                    println!("{}", M::full_usage(&c, args[0], &args[1..]));
102                }
103                e => Err(format!("{e}"))?,
104            }
105        }
106    }
107    Ok(())
108}
109pub fn rund<M: Args<C>, V: Args<C>, C: Ctx, K: AsRef<OsStr>, P: AsRef<str>>(
110    config_path_override_env: K,
111    config_path_default_home_rel: P,
112) {
113    if let Err(e) = irund::<C, M, V, K, P>(config_path_override_env, config_path_default_home_rel) {
114        eprintln!("{e}");
115    }
116}
117
118pub fn reader<P: AsRef<Path>>(p: P) -> Result<BufReader<Box<dyn Read>>, String> {
119    let f: Box<dyn Read> = Box::new(File::open(p.as_ref()).map_err(|e| {
120        format!(
121            "Failed to open file '{}' because '{e}'.",
122            p.as_ref().to_string_lossy()
123        )
124    })?);
125    Ok(BufReader::new(f))
126}
127pub fn from_toml<P: AsRef<Path>, O: serde::de::DeserializeOwned>(p: P) -> Result<O, String> {
128    let mut buf = String::new();
129    reader(p.as_ref())?.read_to_string(&mut buf).map_err(|e| {
130        format!(
131            "Error while reading '{}' because '{e}'.",
132            p.as_ref().to_string_lossy()
133        )
134    })?;
135    Ok(toml::from_str(&buf).map_err(|e| {
136        format!(
137            "Failed to parse '{}' because '{e}'.",
138            p.as_ref().to_string_lossy()
139        )
140    })?)
141}
142#[track_caller]
143pub fn write_toml<P: AsRef<Path>, O: serde::Serialize + fmt::Debug>(
144    o: &O,
145    p: P,
146) -> Result<(), String> {
147    let l = Location::caller();
148    let s = toml::to_string_pretty(o)
149        .map_err(|e| format!("Failed to serialize '{o:#?}' because '{e}' at {l}"))?;
150    fs::write(&p, s).map_err(|e| {
151        format!(
152            "Failed to write to '{}' because '{e}'",
153            p.as_ref().to_string_lossy()
154        )
155    })?;
156    Ok(())
157}
158#[track_caller]
159pub fn write_ctoml<P: AsRef<Path>, O: serde::Serialize + fmt::Debug>(
160    o: &O,
161    p: P,
162) -> Result<(), String> {
163    let l = Location::caller();
164    let s = toml::to_string(o)
165        .map_err(|e| format!("Failed to serialize '{o:#?}' because '{e}' at {l}"))?;
166    fs::write(&p, s).map_err(|e| {
167        format!(
168            "Failed to write to '{}' because '{e}'",
169            p.as_ref().to_string_lossy()
170        )
171    })?;
172    Ok(())
173}