mod args;
mod commands;
mod exit;
mod render;
use std::io::IsTerminal;
use std::path::{Path, PathBuf};
use alba_core::{CoreError, SourceMap};
use alba_engine::Selection;
use clap::Parser;
use args::{Cli, Command, RunFlags};
use exit::EXIT_ALBA_ERROR;
use render::LineSink;
fn main() {
let cli = Cli::parse();
std::process::exit(run(cli));
}
fn run(cli: Cli) -> i32 {
if let Some(Command::Plugin { command }) = &cli.command {
return commands::plugin::run(command);
}
if let Some(Command::Update) = &cli.command {
return commands::update::run();
}
let beamfile = match resolve_beamfile(cli.file.as_deref()) {
Ok(path) => path,
Err(message) => {
if cli.file.is_none() && matches!(cli.command, Some(Command::Hook { .. })) {
return 0;
}
LineSink::stderr().line(&message);
return EXIT_ALBA_ERROR;
}
};
if let Some(Command::Cache { command }) = &cli.command {
return commands::cache::run(&beamfile, command);
}
let (project, sources) = match alba_core::load_project(&beamfile) {
Ok(loaded) => loaded,
Err(err) => {
LineSink::stderr().line(render_load_error(&err).trim_end());
return EXIT_ALBA_ERROR;
}
};
match cli.command {
Some(Command::Cache { .. }) => unreachable!("cache is dispatched before loading"),
Some(Command::Plugin { .. }) => unreachable!("plugin is dispatched before loading"),
Some(Command::Update) => unreachable!("update is dispatched before loading"),
Some(Command::Check) => commands::check::run(&project, &beamfile),
Some(Command::Hook { name, args }) => {
commands::hook::run(&project, &sources, &beamfile, &name, args)
}
Some(Command::Hooks { command }) => commands::hooks::run(&beamfile, &command),
Some(Command::Affected {
reference,
log_format,
}) => commands::affected::run(&project, &sources, &beamfile, &reference, log_format),
Some(Command::Run {
beam,
params,
flags,
}) => {
let selection = match flags.affected.clone() {
Some(reference) => Selection::Affected {
reference,
within: beam.map(alba_core::BeamId),
},
None => match beam.map(alba_core::BeamId).or_else(|| {
project
.default
.as_ref()
.map(|default| default.value.clone())
}) {
Some(target) => Selection::Beam(target),
None => {
LineSink::stderr().line(
"no beam named and this Beamfile declares no `default`; \
run `alba` to list the available beams",
);
return EXIT_ALBA_ERROR;
}
},
};
commands::run::run(&project, &sources, &beamfile, &selection, params, &flags)
}
None => match &project.default {
Some(target) => commands::run::run(
&project,
&sources,
&beamfile,
&Selection::Beam(target.value.clone()),
Vec::new(),
&RunFlags::default(),
),
None => commands::list::run(&project),
},
}
}
fn resolve_beamfile(file: Option<&Path>) -> Result<PathBuf, String> {
let (path, dir, name): (PathBuf, PathBuf, String) = match file {
Some(file) => {
let dir = file
.parent()
.filter(|dir| !dir.as_os_str().is_empty())
.map(Path::to_path_buf)
.unwrap_or_else(|| PathBuf::from("."));
let name = file
.file_name()
.map(|name| name.to_string_lossy().into_owned())
.unwrap_or_else(|| file.display().to_string());
(file.to_path_buf(), dir, name)
}
None => (
PathBuf::from("Beamfile"),
PathBuf::from("."),
"Beamfile".to_string(),
),
};
if path.is_file() {
return Ok(path);
}
let dir = std::path::absolute(&dir).unwrap_or(dir);
Err(format!(
"no Beamfile found in {} (searched {name})",
dir.display()
))
}
pub(crate) fn render_load_error(err: &alba_core::LoadError) -> String {
render_core_error(&err.error, &err.sources)
}
pub(crate) fn render_core_error(error: &CoreError, sources: &SourceMap) -> String {
let diagnostic = error.clone().into_diagnostic();
match sources.get(error.source_id) {
Some((path, source)) => {
alba_syntax::render_diagnostic(source, &path.display().to_string(), &diagnostic)
}
None => format!("{diagnostic}\n"),
}
}
pub(crate) fn color_enabled() -> bool {
std::io::stdout().is_terminal() && std::env::var_os("NO_COLOR").is_none()
}