substrate_manager/util/
config.rs1use anyhow::Context as _;
2use core::fmt;
3use inquire::Confirm;
4use std::{
5 env,
6 fmt::Debug,
7 path::{Path, PathBuf},
8 str::FromStr,
9};
10use toml_edit::{value, Document, Item, Table, Value};
11
12use crate::core::manifest::Manifest;
13
14use super::SubstrateResult;
15
16fn find_field_recursive<'a>(table: &'a Table, field_path: &str) -> Option<&'a Item> {
17 let mut current = table;
18
19 for field in field_path.split('.') {
20 if let Some(value) = current.get(field) {
21 if let Some(next_table) = value.as_table() {
22 current = next_table;
23 } else {
24 return Some(value);
25 }
26 } else {
27 return None;
28 }
29 }
30
31 None
32}
33
34fn from_manifest_or_default<T>(manifest: Option<&Document>, field_path: &str, default: &str) -> T
35where
36 T: FromStr,
37 T::Err: Debug,
38{
39 let option = match manifest.map(|doc| doc.as_table()) {
40 Some(table) => find_field_recursive(table, field_path)
41 .and_then(|v| v.as_str())
42 .unwrap_or(default),
43 None => default,
44 };
45
46 option
47 .parse::<T>()
48 .unwrap_or_else(|_| panic!("Could not parse configuration option: {}", field_path))
49}
50
51#[derive(Debug, Clone)]
52pub struct Config {
53 pub cwd: PathBuf,
55 pub project_type: Option<ProjectType>,
56}
57
58#[derive(Debug, Clone)]
60pub struct ChainInfo {
61 pub node_path: PathBuf,
62 pub node_name: Option<String>,
63 pub runtime_path: PathBuf,
64 pub runtime_name: Option<String>,
65 pub frontend_path: PathBuf,
66}
67
68#[derive(Debug, Clone)]
70pub struct ContractInfo {
71 pub name: String,
72}
73
74#[derive(Debug, Clone)]
75pub enum ProjectType {
76 Chain(ChainInfo),
77 Contract(ContractInfo),
78}
79
80impl fmt::Display for ProjectType {
81 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
82 match self {
83 Self::Chain(info) => {
84 let node_name = info.node_name.as_deref().unwrap_or("unknown");
85 let runtime_name = info.runtime_name.as_deref().unwrap_or("unknown");
86 write!(f, "Chain: node - {}, runtime - {}", node_name, runtime_name)
87 }
88 Self::Contract(info) => write!(f, "Contract: {}", info.name),
89 }
90 }
91}
92
93pub fn get_package_name(path: &Path) -> SubstrateResult<Option<String>> {
94 let name = if path.join("Cargo.toml").exists() {
96 let manifest = Manifest::new(path.join("Cargo.toml")).read_document()?;
97 Some(
98 manifest["package"]
99 .get("name")
100 .with_context(|| "no name found in manifest")?
101 .as_str()
102 .with_context(|| "unknown name type in manifest")?
103 .to_string(),
104 )
105 } else {
106 None
107 };
108
109 Ok(name)
110}
111
112fn deduce_project_type_for_wd(
114 cwd: &Path,
115 node_path: &Path,
116 runtime_path: &Path,
117 frontend_path: &Path,
118) -> SubstrateResult<Option<ProjectType>> {
119 if cwd.join(node_path).exists() || cwd.join(runtime_path).exists() {
120 if Confirm::new("Found a potential chain project in the current directory. Do you want to continue? (y/n)").prompt()? {
121 let mut manifest = Manifest::new(cwd.join("Substrate.toml"));
122 let mut document = Document::new();
123 document.insert("type", value("chain"));
124 manifest.write_document(document)?;
125 } else {
126 return Ok(None);
127 }
128
129 let node_name = get_package_name(&cwd.join(node_path))?;
130 let runtime_name = get_package_name(&cwd.join(runtime_path))?;
131
132 return Ok(Some(ProjectType::Chain(ChainInfo {
133 node_path: PathBuf::from(node_path),
134 node_name,
135 runtime_path: PathBuf::from(runtime_path),
136 runtime_name,
137 frontend_path: PathBuf::from(frontend_path),
138 })));
139 } else if cwd.join("lib.rs").exists() {
140 if Confirm::new("Found a potential smart contract project in the current directory. Do you want to continue? (y/n)").prompt()? {
141 let mut manifest = Manifest::new(cwd.join("Substrate.toml"));
142 let mut document = Document::new();
143 document.insert("type", value("contract"));
144 manifest.write_document(document)?;
145 } else {
146 return Ok(None);
147 }
148
149 let contract_manifest = Manifest::new(cwd.join("Cargo.toml")).read_document()?;
151 let contract_name = contract_manifest["package"]
152 .get("name")
153 .with_context(|| "no name found in contract manifest")?
154 .as_str()
155 .with_context(|| "unknown name type in contract manifest")?
156 .to_string();
157
158 return Ok(Some(ProjectType::Contract(ContractInfo {
159 name: contract_name,
160 })));
161 }
162
163 Ok(None)
164}
165
166impl Config {
167 pub fn new(cwd: PathBuf, project_type: Option<ProjectType>) -> Self {
170 Self {
171 cwd,
173 project_type,
174 }
175 }
176
177 #[allow(clippy::should_implement_trait)]
178 pub fn default() -> SubstrateResult<Self> {
179 let cwd = env::current_dir()
181 .with_context(|| "couldn't get the current directory of the process")?;
182
183 let manifest = Manifest::new(cwd.join("Substrate.toml"))
184 .read_document()
185 .ok();
186
187 let node_path =
188 from_manifest_or_default::<PathBuf>(manifest.as_ref(), "paths.node", "node");
189 let runtime_path =
190 from_manifest_or_default::<PathBuf>(manifest.as_ref(), "paths.runtime", "runtime");
191 let frontend_path =
192 from_manifest_or_default::<PathBuf>(manifest.as_ref(), "paths.frontend", "frontend");
193
194 let contract_path =
195 from_manifest_or_default::<PathBuf>(manifest.as_ref(), "paths.contract", "");
196
197 let project_type = match manifest.as_ref() {
198 Some(doc) => match doc["type"].as_str() {
199 Some("chain") => {
200 let node_name = get_package_name(&cwd.join(&node_path))?;
201 let runtime_name = get_package_name(&cwd.join(&runtime_path))?;
202
203 Some(ProjectType::Chain(ChainInfo {
204 node_path,
205 node_name,
206 runtime_path,
207 runtime_name,
208 frontend_path,
209 }))
210 }
211 Some("contract") => {
212 let name = get_package_name(&cwd.join(contract_path))?;
213 Some(ProjectType::Contract(ContractInfo {
214 name: name.unwrap(),
215 }))
216 }
217 _ => anyhow::bail!("Incorrect project type in Substrate.toml.\nSupported types: \"chain\" and \"contract\"."),
218 },
219 None => deduce_project_type_for_wd(&cwd, &node_path, &runtime_path, &frontend_path)?,
220 };
221
222 Ok(Self::new(cwd, project_type))
223 }
224
225 pub fn cwd(&self) -> &Path {
232 &self.cwd
233 }
234
235 pub fn project_type(&self) -> &Option<ProjectType> {
237 &self.project_type
238 }
239}