#![forbid(unsafe_code)]
#![deny(missing_docs)]
use std::{ffi::OsString, fmt};
mod args;
mod boot;
mod bootloader;
mod codec_boot;
mod config;
mod crates_io;
mod envelope;
mod exit;
#[cfg(feature = "registry")]
mod git_registry;
mod handoff;
mod host;
mod introspect;
mod load;
mod receipt;
mod report;
mod source;
#[cfg(test)]
mod codec_boot_tests;
#[cfg(test)]
mod config_report_tests;
#[cfg(test)]
mod config_site_tests;
#[cfg(test)]
mod config_tests;
#[cfg(test)]
mod handoff_tests;
#[cfg(test)]
mod introspect_tests;
#[cfg(test)]
mod load_tests;
#[cfg(test)]
mod publish_tests;
#[cfg(test)]
mod scenario_tests;
pub use args::{CliCommand, parse_args};
pub use boot::{CliBoot, CliEnvelope, Payload};
pub use bootloader::Bootloader;
pub use codec_boot::{DEFAULT_CODEC_NAME, boot_codec_name, codec_lib_symbol};
pub use config::{
ConfigLoadOptions, RuntimeConfigState, load_config_sources, load_config_sources_with_probes,
run_config_probe,
};
pub use crates_io::{CratesIoResolver, CratesIoSpec, ResolvedCratesIoSource, VersionReq};
#[cfg(feature = "registry")]
pub use git_registry::{GIT_REGISTRY_ENDPOINT_ENV, GitRegistryResolver};
pub use handoff::{CLI_MAIN_ENTRYPOINT, CliEntrypoint, cli_main_entrypoint_symbol};
pub use load::LoadSession;
pub use receipt::{LoadReceipt, LoadReceiptRole};
pub use report::{
ConfigReportKind, ConfigReportRequest, ConfigSourceReport, LoadedLibReport, LoadedStateReport,
SourceStatus, format_config_sources, format_config_sources_json, format_config_status,
format_config_status_json, format_effective_config, format_effective_config_json,
render_config_report,
};
pub use source::LibSourceSpec;
const HELP: &str = "\
Usage: sim [OPTIONS] [PAYLOAD...]
Options:
--help Print this help text.
--version Print the binary version.
--codec NAME Select the boot codec name.
--load SRC Add a library source to load.
--native-audio-provider SRC
Try a native audio provider source and degrade if absent.
--config-home PATH Read home config from PATH.
--config-work PATH Read working config from PATH.
--config-file PATH Read one shared config Dir file after root files.
--config-site SYMBOL
Read a config Dir from a loaded site export.
--no-config-files Skip filesystem config discovery.
--list Request a loaded-lib list.
--inspect SYMBOL Request inspection of a loaded lib or export.
config status Report loaded libs, config sources, probes, and diagnostics.
config effective LIB
Report the effective config table for LIB.
config sources Report config source provenance and diagnostics.
--json Render a config report command as stable JSON.
--eval TEXT Carry eval text for loaded-lib handoff.
--script PATH Carry a script path for loaded-lib handoff.
--stdin TEXT Carry stdin text for loaded-lib handoff.
Note: the bootloader bakes in no codec. By default it fetches nothing over
the network and boots only libraries provided via --load (an artifact source) or
already present in the local cache. A build with the registry feature can fetch from
an explicit git registry endpoint installed by the host. With no source it reports
`no codec '<name>' available`.
";
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct CliError {
message: String,
}
impl CliError {
pub fn new(message: impl Into<String>) -> Self {
Self {
message: message.into(),
}
}
pub(crate) fn unsupported(arg: &str) -> Self {
Self::new(format!("unsupported argument: {arg}"))
}
pub(crate) fn missing_value(flag: &str) -> Self {
Self::new(format!("{flag} requires a value"))
}
pub(crate) fn duplicate(flag: &str) -> Self {
Self::new(format!("{flag} was provided more than once"))
}
}
impl fmt::Display for CliError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(&self.message)
}
}
impl std::error::Error for CliError {}
pub fn version_line() -> String {
format!("sim {}\n", env!("CARGO_PKG_VERSION"))
}
pub fn run<I, S>(args: I) -> Result<i32, CliError>
where
I: IntoIterator<Item = S>,
S: Into<OsString>,
{
Bootloader::standard().run(args)
}
pub fn run_with_session<I, S>(args: I, session: &mut LoadSession) -> Result<i32, CliError>
where
I: IntoIterator<Item = S>,
S: Into<OsString>,
{
run_command_with_session(parse_args(args)?, session)
}
pub fn run_command_with_session(
command: CliCommand,
session: &mut LoadSession,
) -> Result<i32, CliError> {
match command {
CliCommand::Help => {
print!("{HELP}");
Ok(0)
}
CliCommand::Version => {
print!("{}", version_line());
Ok(0)
}
CliCommand::Boot(boot) => session.run_loaded_boot(&boot),
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn version_line_uses_package_version() {
assert_eq!(
version_line(),
format!("sim {}\n", env!("CARGO_PKG_VERSION"))
);
}
#[test]
fn direct_payload_enters_loaded_boot() {
let err = run(["sim", "run"]).unwrap_err();
assert!(err.to_string().starts_with("no codec 'lisp' available"));
}
}