dango/system/
application.rs1use std::collections::HashSet;
8use std::path::{Path, PathBuf};
9
10use clap::{Arg, Command};
11use semver::Version;
12use thiserror::Error;
13use uninode::{loaders::UniNodeLoadError, UniNode};
14
15#[derive(Debug, Error)]
16pub enum AppError {
17 #[error(transparent)]
18 SemVer(#[from] semver::Error),
19 #[error(transparent)]
20 Load(#[from] UniNodeLoadError),
21}
22
23pub trait Application {
24 fn new(name: &str, version: &str, summary: &str) -> Result<Self, AppError>
25 where
26 Self: Sized;
27
28 fn name(&self) -> &str;
29
30 fn version(&self) -> &Version;
31
32 fn summary(&self) -> &str;
33
34 fn add_default_config<P: AsRef<Path>>(&mut self, path: P);
35
36 fn config(&self) -> &UniNode;
37}
38
39type ApplicationBootstrap = Box<dyn FnOnce(&UniNode) -> anyhow::Result<()> + 'static>;
40
41pub struct DangoApplication {
42 name: String,
43 summary: String,
44 version: Version,
45 default_configs: Vec<PathBuf>,
46 config: UniNode,
47 bootstraps: Vec<ApplicationBootstrap>,
48}
49
50impl Application for DangoApplication {
51 fn new(name: &str, version: &str, summary: &str) -> Result<Self, AppError> {
52 Ok(Self {
53 name: name.to_string(),
54 summary: summary.to_string(),
55 version: Version::parse(version)?,
56 default_configs: Vec::new(),
57 config: UniNode::Null,
58 bootstraps: Vec::new(),
59 })
60 }
61
62 fn name(&self) -> &str {
63 &self.name
64 }
65
66 fn version(&self) -> &Version {
67 &self.version
68 }
69
70 fn summary(&self) -> &str {
71 &self.summary
72 }
73
74 fn config(&self) -> &UniNode {
75 &self.config
76 }
77
78 fn add_default_config<P: AsRef<Path>>(&mut self, path: P) {
79 self.default_configs.push(path.as_ref().to_path_buf());
80 }
81}
82
83impl DangoApplication {
84 pub fn register_bootstrap<B>(&mut self, bootstrap: B)
85 where
86 B: FnOnce(&UniNode) -> anyhow::Result<()> + 'static,
87 {
88 self.bootstraps.push(Box::new(bootstrap));
89 }
90
91 pub fn start(&mut self) -> anyhow::Result<()> {
92 let version = self.version().to_string();
93 let app = Command::new(self.name())
94 .version(version.as_ref())
95 .about(self.summary())
96 .arg(
97 Arg::new("config")
98 .short('c')
99 .long("config")
100 .value_name("FILE")
101 .help("Sets a custom config file")
102 .multiple_values(true)
103 .forbid_empty_values(true)
104 .takes_value(true),
105 );
106 let matches = app.get_matches();
107
108 fn load_configs(config_files: &[PathBuf]) -> Result<UniNode, UniNodeLoadError> {
109 let config_files: HashSet<&PathBuf> = config_files.iter().collect();
110 let mut config = UniNode::empty_object();
111 for c_file in config_files {
112 config.merge(UniNode::load(c_file)?);
113 }
114 Ok(config)
115 }
116
117 self.config = if matches.occurrences_of("config") == 0 {
118 load_configs(&self.default_configs)?
119 } else {
120 let opt_files = matches
121 .values_of("config")
122 .unwrap()
123 .map(PathBuf::from)
124 .collect::<Vec<_>>();
125 load_configs(&opt_files)?
126 };
127
128 if let Some(logger_cfg) = self.config.find("logger") {
129 super::logging::logger_init(logger_cfg).unwrap();
130 }
131
132 for bootstrap in self.bootstraps.drain(..) {
133 (bootstrap)(&self.config)?;
134 }
135
136 Ok(())
137 }
138}