1#![forbid(unsafe_code)]
2#![deny(missing_docs)]
3use std::{ffi::OsString, fmt};
30
31mod args;
32mod boot;
33mod bootloader;
34mod codec_boot;
35mod crates_io;
36mod envelope;
37mod exit;
38#[cfg(feature = "registry")]
39mod git_registry;
40mod handoff;
41mod introspect;
42mod load;
43mod receipt;
44mod source;
45
46#[cfg(test)]
47mod codec_boot_tests;
48#[cfg(test)]
49mod handoff_tests;
50#[cfg(test)]
51mod introspect_tests;
52#[cfg(test)]
53mod load_tests;
54#[cfg(test)]
55mod publish_tests;
56#[cfg(test)]
57mod scenario_tests;
58
59pub use args::{CliCommand, parse_args};
60pub use boot::{CliBoot, CliEnvelope, Payload};
61pub use bootloader::Bootloader;
62pub use codec_boot::{DEFAULT_CODEC_NAME, boot_codec_name, codec_lib_symbol};
63pub use crates_io::{CratesIoResolver, CratesIoSpec, ResolvedCratesIoSource, VersionReq};
64#[cfg(feature = "registry")]
65pub use git_registry::{GIT_REGISTRY_ENDPOINT_ENV, GitRegistryResolver};
66pub use handoff::{CLI_MAIN_ENTRYPOINT, CliEntrypoint, cli_main_entrypoint_symbol};
67pub use load::LoadSession;
68pub use receipt::{LoadReceipt, LoadReceiptRole};
69pub use source::LibSourceSpec;
70
71const HELP: &str = "\
72Usage: sim [OPTIONS] [PAYLOAD...]
73
74Options:
75 --help Print this help text.
76 --version Print the binary version.
77 --codec NAME Select the boot codec name.
78 --load SRC Add a library source to load.
79 --native-audio-provider SRC
80 Try a native audio provider source and degrade if absent.
81 --list Request a loaded-lib list.
82 --inspect SYMBOL Request inspection of a loaded lib or export.
83 --eval TEXT Carry eval text for loaded-lib handoff.
84 --script PATH Carry a script path for loaded-lib handoff.
85 --stdin TEXT Carry stdin text for loaded-lib handoff.
86
87Note: the bootloader bakes in no codec. By default it fetches nothing over
88the network and boots only libraries provided via --load (an artifact source) or
89already present in the local cache. A build with the registry feature can fetch from
90an explicit git registry endpoint installed by the host. With no source it reports
91`no codec '<name>' available`.
92";
93
94#[derive(Debug, Clone, PartialEq, Eq)]
96pub struct CliError {
97 message: String,
98}
99
100impl CliError {
101 pub fn new(message: impl Into<String>) -> Self {
103 Self {
104 message: message.into(),
105 }
106 }
107
108 pub(crate) fn unsupported(arg: &str) -> Self {
109 Self::new(format!("unsupported argument: {arg}"))
110 }
111
112 pub(crate) fn missing_value(flag: &str) -> Self {
113 Self::new(format!("{flag} requires a value"))
114 }
115
116 pub(crate) fn duplicate(flag: &str) -> Self {
117 Self::new(format!("{flag} was provided more than once"))
118 }
119}
120
121impl fmt::Display for CliError {
122 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
123 f.write_str(&self.message)
124 }
125}
126
127impl std::error::Error for CliError {}
128
129pub fn version_line() -> String {
131 format!("sim {}\n", env!("CARGO_PKG_VERSION"))
132}
133
134pub fn run<I, S>(args: I) -> Result<i32, CliError>
145where
146 I: IntoIterator<Item = S>,
147 S: Into<OsString>,
148{
149 Bootloader::standard().run(args)
150}
151
152pub fn run_with_session<I, S>(args: I, session: &mut LoadSession) -> Result<i32, CliError>
154where
155 I: IntoIterator<Item = S>,
156 S: Into<OsString>,
157{
158 run_command_with_session(parse_args(args)?, session)
159}
160
161pub fn run_command_with_session(
163 command: CliCommand,
164 session: &mut LoadSession,
165) -> Result<i32, CliError> {
166 match command {
167 CliCommand::Help => {
168 print!("{HELP}");
169 Ok(0)
170 }
171 CliCommand::Version => {
172 print!("{}", version_line());
173 Ok(0)
174 }
175 CliCommand::Boot(boot) => session.run_loaded_boot(&boot),
176 }
177}
178
179#[cfg(test)]
180mod tests {
181 use super::*;
182
183 #[test]
184 fn version_line_uses_package_version() {
185 assert_eq!(
186 version_line(),
187 format!("sim {}\n", env!("CARGO_PKG_VERSION"))
188 );
189 }
190
191 #[test]
192 fn direct_payload_enters_loaded_boot() {
193 let err = run(["sim", "run"]).unwrap_err();
194 assert!(err.to_string().starts_with("no codec 'lisp' available"));
195 }
196}