use std::path::{Path, PathBuf};
use anyhow::{anyhow, bail, Context, Result};
use nova_diagnostics::{render, FileDb, Severity};
use nova_driver::{Program, Roots};
use nova_index::{Index, SyncError, SyncRequest, Synced};
use nova_pm::Unlock;
pub enum Mode {
File(PathBuf),
Project {
root: PathBuf,
target_dir: PathBuf,
},
}
impl Mode {
pub fn program(&self, roots: Roots) -> Program {
match self {
Mode::File(file) => Program::for_file(file),
Mode::Project { root, .. } => Program::for_package(root, roots),
}
}
}
pub fn mode(file: Option<PathBuf>) -> Result<Mode> {
if let Some(file) = file {
return Ok(Mode::File(file));
}
let cwd = std::env::current_dir().context("reading the current directory")?;
let Some(root) = nova_pm::find_root(&cwd) else {
return Ok(Mode::File(PathBuf::from("src/main.nova")));
};
let root = if root == cwd { PathBuf::new() } else { root };
Ok(Mode::Project {
target_dir: root.join("target"),
root,
})
}
pub fn program_to_run(mode: &Mode) -> Result<Program> {
let program = mode.program(Roots::Program);
if let (Mode::Project { .. }, Some(graph)) = (mode, &program.graph) {
let root = graph.root();
if !program.has_errors() && root.has_lib && !root.has_main {
bail!(
"`{}` is a library: it has no src/main.nova; `nova check` and `nova test` \
work on it",
root.name
);
}
}
Ok(program)
}
pub fn package_name(program: &Program) -> String {
program
.graph
.as_ref()
.map_or_else(|| "out".to_string(), |graph| graph.root().name.clone())
}
pub fn refuse_cached(root: &Path) -> Result<()> {
let Some(registry) = nova_pm::registry_dir() else {
return Ok(());
};
let dir = nova_pm::real_path(if root.as_os_str().is_empty() {
Path::new(".")
} else {
root
});
if dir.starts_with(nova_pm::real_path(®istry)) {
bail!(
"`{}` is a downloaded package in nova's cache; it is read only",
dir.display()
);
}
Ok(())
}
pub fn package_root(mode: &Mode) -> Option<PathBuf> {
match mode {
Mode::Project { root, .. } => Some(root.clone()),
Mode::File(file) => nova_driver::package_of(file).map(|(root, _)| root),
}
}
pub fn refuse_library(mode: &Mode) -> Result<()> {
let Mode::Project { root, .. } = mode else {
return Ok(());
};
let src = root.join("src");
if !src.join("lib.nova").is_file() || src.join("main.nova").is_file() {
return Ok(());
}
let text = std::fs::read_to_string(root.join(nova_pm::MANIFEST)).unwrap_or_default();
let (Some(manifest), _) = nova_pm::parse(&text, nova_diagnostics::FileId::DUMMY) else {
return Ok(());
};
bail!(
"`{}` is a library: it has no src/main.nova; `nova check` and `nova test` work on it",
manifest.package.name
)
}
pub fn sync(mode: &Mode) -> Result<()> {
sync_with(mode, Unlock::Nothing).map(|_| ())
}
pub fn sync_with(mode: &Mode, unlock: Unlock) -> Result<Option<Synced>> {
let Some(root) = package_root(mode) else {
return Ok(None);
};
refuse_cached(&root)?;
let index = Index::from_env().map_err(|e| anyhow!(e))?;
let mut reader = nova_index::reader_for(&index);
let registry = nova_pm::registry_dir();
let mut db = FileDb::new();
let result = nova_index::sync(
SyncRequest {
root: &root,
manifest: None,
dev: true,
unlock,
write: true,
index: &index,
reader: reader.as_mut(),
registry: registry.as_deref(),
},
&mut db,
);
finish(result, &db).map(Some)
}
pub fn finish(result: Result<Synced, SyncError>, db: &FileDb) -> Result<Synced> {
match result {
Ok(synced) => {
for note in &synced.notes {
eprintln!("note: {note}");
}
for package in &synced.unpacked {
eprintln!("downloaded {package}");
}
Ok(synced)
}
Err(SyncError::Diagnostics(diagnostics)) => {
render::emit_all(db, &diagnostics);
let errors = diagnostics
.iter()
.filter(|d| d.severity == Severity::Error)
.count();
bail!(
"could not resolve the dependencies due to {errors} previous error{}",
if errors == 1 { "" } else { "s" }
)
}
Err(SyncError::Other(why)) => Err(anyhow!(why)),
}
}