use std::path::{Path, PathBuf};
use std::{fmt, fs};
use cairo_lang_filesystem::db::CORELIB_CRATE_NAME;
use cairo_lang_project::PROJECT_FILE_NAME;
use lsp_types::notification::ShowMessage;
use lsp_types::{MessageType, ShowMessageParams};
use serde::Deserialize;
use tracing::error;
use crate::server::client::Notifier;
use crate::toolchain::scarb::SCARB_TOML;
#[cfg(test)]
#[path = "project_manifest_path_test.rs"]
mod project_manifest_path_test;
const MAX_CRATE_DETECTION_DEPTH: usize = 20;
#[derive(Clone, Debug, PartialEq, Eq, Ord, PartialOrd, Hash)]
pub enum ProjectManifestPath {
CairoProject(PathBuf),
Scarb(PathBuf),
}
impl ProjectManifestPath {
pub fn discover(path: &Path, notifier: &Notifier) -> Option<ProjectManifestPath> {
let project_config_path = find_in_parent_dirs(path.to_path_buf(), PROJECT_FILE_NAME);
let scarb_manifest_path = find_in_parent_dirs(path.to_path_buf(), SCARB_TOML);
if project_config_path.is_some()
&& let Some(scarb_manifest_path) = &scarb_manifest_path
{
let is_core = match fs::read_to_string(scarb_manifest_path) {
Ok(content) => {
toml::from_str::<ScarbManifest>(&content).unwrap_or_default().package.name
== CORELIB_CRATE_NAME
}
Err(err) => {
error!("{err:?}");
false
}
};
if !is_core {
notifier.notify::<ShowMessage>(ShowMessageParams {
typ: MessageType::WARNING,
message: format!(
"Found conflicting manifest files: {} and {}. `cairo_project.toml` \
manifest will be used.",
project_config_path.as_ref().unwrap().to_string_lossy(),
scarb_manifest_path.to_string_lossy(),
),
});
}
}
return project_config_path
.map(ProjectManifestPath::CairoProject)
.or(scarb_manifest_path.map(ProjectManifestPath::Scarb));
#[derive(Default, Deserialize)]
struct ScarbManifest {
package: Package,
}
#[derive(Default, Deserialize)]
struct Package {
name: String,
}
fn find_in_parent_dirs(mut path: PathBuf, target_file_name: &str) -> Option<PathBuf> {
for _ in 0..MAX_CRATE_DETECTION_DEPTH {
if !path.pop() {
return None;
}
let manifest_path = path.join(target_file_name);
if fs::metadata(&manifest_path).is_ok() {
return Some(manifest_path);
};
}
None
}
}
}
impl fmt::Display for ProjectManifestPath {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
ProjectManifestPath::CairoProject(path) | ProjectManifestPath::Scarb(path) => {
fmt::Display::fmt(&path.display(), f)
}
}
}
}