cli.rs 7.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225
  1. use std::{
  2. env, fs,
  3. io::Write,
  4. marker::PhantomData,
  5. path::{Path, PathBuf},
  6. str,
  7. time::Duration,
  8. };
  9. use indicatif::{ProgressBar, ProgressStyle};
  10. use serde::{de::DeserializeOwned, Serialize};
  11. use simplelog::ConfigBuilder;
  12. use termion::color;
  13. use crate::{Error, Result};
  14. #[derive(Clone, Default)]
  15. pub struct Config<T> {
  16. config: PhantomData<T>,
  17. }
  18. impl<T: Serialize + DeserializeOwned> Config<T> {
  19. pub fn load(path: PathBuf) -> Result<T> {
  20. if Path::new(&path).exists() {
  21. let toml = fs::read(&path)?;
  22. let str_buff = str::from_utf8(&toml)?;
  23. let config: T = toml::from_str(str_buff)?;
  24. Ok(config)
  25. } else {
  26. let path = path.to_str();
  27. if path.is_some() {
  28. println!("Could not find/parse configuration file in: {}", path.unwrap());
  29. } else {
  30. println!("Could not find/parse configuration file");
  31. }
  32. println!("Please follow the instructions in the README");
  33. Err(Error::ConfigNotFound)
  34. }
  35. }
  36. }
  37. pub fn spawn_config(path: &Path, contents: &[u8]) -> Result<()> {
  38. if !path.exists() {
  39. if let Some(parent) = path.parent() {
  40. fs::create_dir_all(parent)?;
  41. }
  42. let mut file = fs::File::create(path)?;
  43. file.write_all(contents)?;
  44. println!("Config file created in '{:?}'. Please review it and try again.", path);
  45. std::process::exit(2);
  46. }
  47. Ok(())
  48. }
  49. pub fn get_log_level(verbosity_level: u64) -> simplelog::LevelFilter {
  50. match verbosity_level {
  51. 0 => simplelog::LevelFilter::Info,
  52. 1 => simplelog::LevelFilter::Debug,
  53. _ => simplelog::LevelFilter::Trace,
  54. }
  55. }
  56. pub fn get_log_config() -> simplelog::Config {
  57. match env::var("LOG_TARGETS") {
  58. Ok(x) => {
  59. let targets: Vec<String> = x.split(',').map(|x| x.to_string()).collect();
  60. let mut cfgbuilder = ConfigBuilder::new();
  61. for i in targets {
  62. if i.starts_with('!') {
  63. cfgbuilder.add_filter_ignore(i.trim_start_matches('!').to_string());
  64. } else {
  65. cfgbuilder.add_filter_allow(i);
  66. }
  67. }
  68. cfgbuilder.build()
  69. }
  70. Err(_) => simplelog::Config::default(),
  71. }
  72. }
  73. pub const ANSI_LOGO: &str = include_str!("../../contrib/darkfi.ansi");
  74. #[macro_export]
  75. macro_rules! cli_desc {
  76. () => {{
  77. let mut desc = env!("CARGO_PKG_DESCRIPTION").to_string();
  78. desc.push_str("\n");
  79. desc.push_str(darkfi::util::cli::ANSI_LOGO);
  80. Box::leak(desc.into_boxed_str()) as &'static str
  81. }};
  82. }
  83. /// This macro is used for a standard way of daemonizing darkfi binaries
  84. /// with TOML config file configuration, and argument parsing. It also
  85. /// spawns a multithreaded async executor and passes it into the given
  86. /// function.
  87. ///
  88. /// The Cargo.toml dependencies needed for this are:
  89. /// ```text
  90. /// async-channel = "1.6.1"
  91. /// async-executor = "1.4.1"
  92. /// async-std = "1.11.0"
  93. /// darkfi = { path = "../../", features = ["util"] }
  94. /// easy-parallel = "3.2.0"
  95. /// futures-lite = "1.12.0"
  96. /// simplelog = "0.12.0-alpha1"
  97. ///
  98. /// # Argument parsing
  99. /// serde = "1.0.136"
  100. /// serde_derive = "1.0.136"
  101. /// structopt = "0.3.26"
  102. /// structopt-toml = "0.5.0"
  103. /// ```
  104. ///
  105. /// Example usage:
  106. /// ```text
  107. /// use async_std::sync::Arc;
  108. /// use futures_lite::future;
  109. /// use structopt_toml::{serde::Deserialize, structopt::StructOpt, StructOptToml};
  110. ///
  111. /// use darkfi::{
  112. /// async_daemonize, cli_desc,
  113. /// util::{
  114. /// cli::{get_log_config, get_log_level, spawn_config},
  115. /// path::get_config_path, expand_path
  116. /// },
  117. /// Result,
  118. /// };
  119. ///
  120. /// const CONFIG_FILE: &str = "daemond_config.toml";
  121. /// const CONFIG_FILE_CONTENTS: &str = include_str!("../daemond_config.toml");
  122. ///
  123. /// #[derive(Clone, Debug, Deserialize, StructOpt, StructOptToml)]
  124. /// #[serde(default)]
  125. /// #[structopt(name = "daemond", about = cli_desc!())]
  126. /// struct Args {
  127. /// #[structopt(short, long)]
  128. /// /// Configuration file to use
  129. /// config: Option<String>,
  130. ///
  131. /// #[structopt(short, parse(from_occurrences))]
  132. /// /// Increase verbosity (-vvv supported)
  133. /// verbose: u8,
  134. /// }
  135. ///
  136. /// async_daemonize!(realmain);
  137. /// async fn realmain(args: Args, ex: Arc<Executor<'_>>) -> Result<()> {
  138. /// println!("Hello, world!");
  139. /// Ok(())
  140. /// }
  141. /// ```
  142. #[macro_export]
  143. macro_rules! async_daemonize {
  144. ($realmain:ident) => {
  145. fn main() -> Result<()> {
  146. let args = Args::from_args_with_toml("").unwrap();
  147. let cfg_path = get_config_path(args.config, CONFIG_FILE)?;
  148. spawn_config(&cfg_path, CONFIG_FILE_CONTENTS.as_bytes())?;
  149. let args = Args::from_args_with_toml(&std::fs::read_to_string(cfg_path)?).unwrap();
  150. let log_level = get_log_level(args.verbose.into());
  151. let log_config = get_log_config();
  152. let log_file_path = match std::env::var("DARKFI_LOG") {
  153. Ok(p) => p,
  154. Err(_) => {
  155. std::fs::create_dir_all(expand_path("~/.local")?)?;
  156. "~/.local/darkfi.log".into()
  157. }
  158. };
  159. let log_file_path = expand_path(&log_file_path)?;
  160. let log_file = std::fs::File::create(log_file_path)?;
  161. simplelog::CombinedLogger::init(vec![
  162. simplelog::TermLogger::new(
  163. log_level,
  164. log_config.clone(),
  165. simplelog::TerminalMode::Mixed,
  166. simplelog::ColorChoice::Auto,
  167. ),
  168. simplelog::WriteLogger::new(log_level, log_config, log_file),
  169. ])?;
  170. // https://docs.rs/smol/latest/smol/struct.Executor.html#examples
  171. let ex = Arc::new(async_executor::Executor::new());
  172. let (signal, shutdown) = async_channel::unbounded::<()>();
  173. let (_, result) = easy_parallel::Parallel::new()
  174. // Run four executor threads
  175. .each(0..4, |_| future::block_on(ex.run(shutdown.recv())))
  176. // Run the main future on the current thread.
  177. .finish(|| {
  178. future::block_on(async {
  179. $realmain(args, ex.clone()).await?;
  180. drop(signal);
  181. Ok::<(), darkfi::Error>(())
  182. })
  183. });
  184. result
  185. }
  186. };
  187. }
  188. pub fn progress_bar(message: &str) -> ProgressBar {
  189. let progress_bar = ProgressBar::new(42);
  190. progress_bar.set_style(
  191. ProgressStyle::default_spinner().template("{spinner:.green} {wide_msg}").unwrap(),
  192. );
  193. progress_bar.enable_steady_tick(Duration::from_millis(100));
  194. progress_bar.set_message(message.to_string());
  195. progress_bar
  196. }
  197. pub fn fg_red(message: &str) -> String {
  198. format!("{}{}{}", color::Fg(color::Red), message, color::Fg(color::Reset))
  199. }
  200. pub fn fg_green(message: &str) -> String {
  201. format!("{}{}{}", color::Fg(color::Green), message, color::Fg(color::Reset))
  202. }