use std::path::PathBuf;
use bevy_app::prelude::*;
use bevy_ecs::prelude::*;
use rig_harness_protocol::{
AgentDescription, DescribedCommand, DescribedFlag, DescribedSecret, env,
};
use crate::host::cli::{Front, command_refusal, declared, flags};
use crate::host::runner::finish_plugins;
use crate::load::{PluginSource, ProvidedBy};
use crate::secrets::Secret;
#[derive(Resource, Clone, Copy, Debug)]
pub(crate) struct Describing;
pub(crate) fn describing() -> Option<PathBuf> {
std::env::var_os(env::DESCRIBE)
.filter(|path| !path.is_empty())
.map(PathBuf::from)
}
pub fn run(mut app: App) -> AppExit {
let Some(path) = describing() else {
return app.run();
};
app.insert_resource(Describing);
finish_plugins(&mut app);
match report(app.world_mut()) {
Ok(described) => match std::fs::write(&path, described.write()) {
Ok(()) => AppExit::Success,
Err(failure) => {
eprintln!("rig: could not write {}: {failure}", path.display());
AppExit::from_code(1)
}
},
Err(why) => {
eprintln!("rig: {why}");
AppExit::from_code(2)
}
}
}
pub fn report(world: &mut World) -> Result<AgentDescription, String> {
let commands: Vec<(Entity, Front)> = declared::<Front>(world)
.into_iter()
.filter(|(_, front)| front.command.is_some())
.collect();
let mut described = AgentDescription::default();
for (at, (entity, front)) in commands.iter().enumerate() {
let Some(command) = &front.command else {
continue;
};
if let Some(why) = command_refusal(command) {
return Err(format!("{}: {why}", plugin_of(world, *entity)));
}
let earlier = commands.iter().take(at).find(|(_, other)| {
other.command.as_ref().map(|other| other.name) == Some(command.name)
});
if let Some((other, _)) = earlier {
return Err(format!(
"plugins {} and {} both add the command `{}`",
plugin_of(world, *other),
plugin_of(world, *entity),
command.name
));
}
described.commands.push(DescribedCommand {
name: command.name.to_owned(),
usage: command.usage.to_owned(),
terminal: front.terminal,
sessionless: front.sessionless,
help: command.help.to_owned(),
});
}
described.flags = flags(world)
.into_iter()
.map(|flag| DescribedFlag {
name: flag.name.to_owned(),
value: flag.value.map(str::to_owned),
help: flag.help.to_owned(),
})
.collect();
described.secrets = declared::<Secret>(world)
.into_iter()
.map(|(_, secret)| DescribedSecret {
name: secret.name.to_owned(),
help: secret.help.to_owned(),
})
.collect();
described.conflicts = crate::load::stage_conflicts(world);
Ok(described)
}
fn plugin_of(world: &World, entity: Entity) -> String {
world
.get::<ProvidedBy>(entity)
.and_then(|&ProvidedBy(plugin)| world.get::<PluginSource>(plugin))
.map_or_else(
|| "a plugin added in main.rs".to_owned(),
|source| format!("`{}`", source.package),
)
}
#[cfg(test)]
mod tests;