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().skip(1).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 M::next_impl(&c, &args).map_err(|e| format!("{e}"))?;
99 }
100 Ok(())
101}
102pub fn rund<M: Args<C>, V: Args<C>, C: Ctx, K: AsRef<OsStr>, P: AsRef<str>>(
103 config_path_override_env: K,
104 config_path_default_home_rel: P,
105) {
106 if let Err(e) = irund::<C, M, V, K, P>(config_path_override_env, config_path_default_home_rel) {
107 eprintln!("{e}");
108 }
109}
110
111pub fn reader<P: AsRef<Path>>(p: P) -> Result<BufReader<Box<dyn Read>>, String> {
112 let f: Box<dyn Read> = Box::new(File::open(p.as_ref()).map_err(|e| {
113 format!(
114 "Failed to open file '{}' because '{e}'.",
115 p.as_ref().to_string_lossy()
116 )
117 })?);
118 Ok(BufReader::new(f))
119}
120pub fn from_toml<P: AsRef<Path>, O: serde::de::DeserializeOwned>(p: P) -> Result<O, String> {
121 let mut buf = String::new();
122 reader(p.as_ref())?.read_to_string(&mut buf).map_err(|e| {
123 format!(
124 "Error while reading '{}' because '{e}'.",
125 p.as_ref().to_string_lossy()
126 )
127 })?;
128 Ok(toml::from_str(&buf).map_err(|e| {
129 format!(
130 "Failed to parse '{}' because '{e}'.",
131 p.as_ref().to_string_lossy()
132 )
133 })?)
134}
135#[track_caller]
136pub fn write_toml<P: AsRef<Path>, O: serde::Serialize + fmt::Debug>(
137 o: &O,
138 p: P,
139) -> Result<(), String> {
140 let l = Location::caller();
141 let s = toml::to_string_pretty(o)
142 .map_err(|e| format!("Failed to serialize '{o:#?}' because '{e}' at {l}"))?;
143 fs::write(&p, s).map_err(|e| {
144 format!(
145 "Failed to write to '{}' because '{e}'",
146 p.as_ref().to_string_lossy()
147 )
148 })?;
149 Ok(())
150}
151#[track_caller]
152pub fn write_ctoml<P: AsRef<Path>, O: serde::Serialize + fmt::Debug>(
153 o: &O,
154 p: P,
155) -> Result<(), String> {
156 let l = Location::caller();
157 let s = toml::to_string(o)
158 .map_err(|e| format!("Failed to serialize '{o:#?}' because '{e}' at {l}"))?;
159 fs::write(&p, s).map_err(|e| {
160 format!(
161 "Failed to write to '{}' because '{e}'",
162 p.as_ref().to_string_lossy()
163 )
164 })?;
165 Ok(())
166}