project_base_directory/
lib.rs1#![allow(clippy::result_large_err)]
2use crate::error::{Error, Result};
3use serde::{Deserialize, Serialize};
4use std::path::PathBuf;
5use tokio::io::AsyncReadExt;
6use tracing::debug;
7
8pub mod constants;
9pub mod error;
10
11#[derive(Default, Debug, Deserialize, Serialize)]
12pub struct Project {
13 pub root_directory: Option<PathBuf>,
16 pub project_id: Option<String>,
18 pub config_home: Option<PathBuf>,
20 pub cache_home: Option<PathBuf>,
22 pub data_home: Option<PathBuf>,
24}
25
26impl Project {
27 pub fn discover() -> Result<Self> {
29 let project_root = get_project_root()?;
30 let project_data = std::env::var(constants::PROJECT_DATA_HOME)
31 .map(PathBuf::from)
32 .ok();
33 let project_config = std::env::var(constants::PROJECT_CONFIG_HOME)
34 .map(PathBuf::from)
35 .ok();
36 let project_cache = std::env::var(constants::PROJECT_CACHE)
37 .map(PathBuf::from)
38 .ok();
39 let project_id = std::env::var(constants::PROJECT_ID).ok();
40
41 Ok(Self {
42 root_directory: project_root,
43 project_id,
44 data_home: project_data,
45 config_home: project_config,
46 cache_home: project_cache,
47 })
48 }
49
50 pub async fn discover_and_assume() -> Result<Self> {
53 let mut value = Self::discover()?;
54 match value.root_directory {
56 Some(_) => {}
57 None => return Err(Error::ProjectRootNotFound(std::env::current_dir().unwrap())),
58 }
59
60 match value.config_home {
61 Some(_) => {}
62 None => {
63 let mut directory = value.root_directory.clone().unwrap();
64 directory.push(constants::DEFAULT_CONFIG_HOME);
65 value.config_home = Some(directory);
66 }
67 }
68
69 match value.data_home {
70 Some(_) => {}
71 None => {
72 let mut directory = value.root_directory.clone().unwrap();
73 directory.push(constants::DEFAULT_DATA_HOME);
74 value.data_home = Some(directory);
75 }
76 }
77
78 match value.cache_home {
79 Some(_) => {}
80 None => {
81 let mut directory = value.root_directory.clone().unwrap();
82 directory.push(constants::DEFAULT_CACHE_HOME);
83 value.cache_home = Some(directory);
84 }
85 }
86
87 match value.project_id {
88 Some(_) => {}
89 None => {
90 let mut file = value.config_home.clone().unwrap();
91 file.push(constants::PROJECT_ID_FILE);
92 if file.exists() {
93 let mut file = tokio::fs::File::open(file).await.unwrap();
94 let mut contents = String::new();
95 file.read_to_string(&mut contents).await.unwrap();
96 value.project_id = Some(contents.trim().to_string());
97 }
98 }
99 }
100
101 Ok(value)
102 }
103
104 pub fn project_hashmap(&self) -> std::collections::HashMap<String, Option<String>> {
107 let mut hashmap = std::collections::HashMap::new();
108
109 hashmap.insert(
110 constants::PROJECT_ROOT.to_string(),
111 self.root_directory
112 .as_ref()
113 .map(|p| p.to_str().unwrap().to_string()),
114 );
115 hashmap.insert(
116 constants::PROJECT_DATA_HOME.to_string(),
117 self.data_home
118 .as_ref()
119 .map(|p| p.to_str().unwrap().to_string()),
120 );
121 hashmap.insert(
122 constants::PROJECT_CONFIG_HOME.to_string(),
123 self.config_home
124 .as_ref()
125 .map(|p| p.to_str().unwrap().to_string()),
126 );
127 hashmap.insert(
128 constants::PROJECT_CACHE.to_string(),
129 self.cache_home
130 .as_ref()
131 .map(|p| p.to_str().unwrap().to_string()),
132 );
133 hashmap.insert(
134 constants::PROJECT_ID.to_string(),
135 self.project_id.as_ref().map(|p| p.to_string()),
136 );
137
138 hashmap
139 }
140}
141
142pub fn get_project_root() -> Result<Option<PathBuf>> {
147 let project_root = std::env::var(constants::PROJECT_ROOT).ok();
148 if let Some(project_root) = project_root {
149 debug!(
150 "using {} environment variable as project root",
151 constants::PROJECT_ROOT
152 );
153 let path = PathBuf::from(project_root);
154 return Ok(Some(path));
155 }
156
157 #[cfg(feature = "git")]
158 {
159 let current_dir = std::env::current_dir().unwrap();
160 let git_repository = gix::discover(current_dir)?;
161 if let Some(directory) = git_repository.work_dir() {
162 debug!(?directory, "using git repository as project root");
163 return Ok(Some(directory.to_owned()));
164 }
165 }
166
167 Ok(None)
168}