use std::sync::{Arc, OnceLock, PoisonError, RwLock};
use cranpose_core::{CompositionLocal, CompositionLocalProvider, compositionLocalOfWithPolicy};
use cranpose_macros::composable;
#[derive(Clone, Debug, PartialEq)]
pub enum LaunchArgValue {
Bool(bool),
Int(i32),
Long(i64),
Float(f32),
Text(String),
}
#[derive(Clone, Debug, Default, PartialEq)]
pub struct LaunchArgs {
entries: Vec<(Box<str>, LaunchArgValue)>,
debuggable: bool,
}
pub type LaunchArgsRef = Arc<LaunchArgs>;
impl LaunchArgs {
pub fn new(
entries: impl IntoIterator<Item = (String, LaunchArgValue)>,
debuggable: bool,
) -> Self {
let mut collected: Vec<(Box<str>, LaunchArgValue)> = Vec::new();
for (name, value) in entries {
if name.is_empty() || collected.iter().any(|(known, _)| **known == *name) {
continue;
}
collected.push((name.into_boxed_str(), value));
}
Self {
entries: collected,
debuggable,
}
}
pub fn is_debuggable(&self) -> bool {
self.debuggable
}
pub fn contains(&self, name: &str) -> bool {
self.value(name).is_some()
}
pub fn names(&self) -> impl Iterator<Item = &str> {
self.entries.iter().map(|(name, _)| &**name)
}
pub fn len(&self) -> usize {
self.entries.len()
}
pub fn is_empty(&self) -> bool {
self.entries.is_empty()
}
pub fn value(&self, name: &str) -> Option<&LaunchArgValue> {
self.entries
.iter()
.find(|(known, _)| &**known == name)
.map(|(_, value)| value)
}
pub fn boolean(&self, name: &str) -> Option<bool> {
match self.value(name)? {
LaunchArgValue::Bool(value) => Some(*value),
LaunchArgValue::Text(text) => parse_boolean(text),
_ => None,
}
}
pub fn int(&self, name: &str) -> Option<i32> {
match self.value(name)? {
LaunchArgValue::Int(value) => Some(*value),
LaunchArgValue::Long(value) => i32::try_from(*value).ok(),
LaunchArgValue::Text(text) => text.trim().parse().ok(),
_ => None,
}
}
pub fn long(&self, name: &str) -> Option<i64> {
match self.value(name)? {
LaunchArgValue::Long(value) => Some(*value),
LaunchArgValue::Int(value) => Some(i64::from(*value)),
LaunchArgValue::Text(text) => text.trim().parse().ok(),
_ => None,
}
}
pub fn float(&self, name: &str) -> Option<f32> {
match self.value(name)? {
LaunchArgValue::Float(value) => Some(*value),
LaunchArgValue::Int(value) => Some(*value as f32),
LaunchArgValue::Long(value) => Some(*value as f32),
LaunchArgValue::Text(text) => text.trim().parse().ok(),
_ => None,
}
}
pub fn string(&self, name: &str) -> Option<&str> {
match self.value(name)? {
LaunchArgValue::Text(text) => Some(text),
_ => None,
}
}
}
fn parse_boolean(text: &str) -> Option<bool> {
match text.trim().to_ascii_lowercase().as_str() {
"true" | "1" | "yes" | "on" => Some(true),
"false" | "0" | "no" | "off" => Some(false),
_ => None,
}
}
static PLATFORM_LAUNCH_ARGS: RwLock<Option<LaunchArgsRef>> = RwLock::new(None);
static DEFAULT_LAUNCH_ARGS: OnceLock<LaunchArgsRef> = OnceLock::new();
pub fn set_platform_launch_args(args: LaunchArgsRef) {
*PLATFORM_LAUNCH_ARGS
.write()
.unwrap_or_else(PoisonError::into_inner) = Some(args);
}
pub fn clear_platform_launch_args() {
*PLATFORM_LAUNCH_ARGS
.write()
.unwrap_or_else(PoisonError::into_inner) = None;
}
pub fn launch_args() -> LaunchArgsRef {
if let Some(args) = PLATFORM_LAUNCH_ARGS
.read()
.unwrap_or_else(PoisonError::into_inner)
.as_ref()
{
return Arc::clone(args);
}
Arc::clone(DEFAULT_LAUNCH_ARGS.get_or_init(|| Arc::new(default_launch_args())))
}
pub fn is_debuggable() -> bool {
launch_args().is_debuggable()
}
fn default_launch_args() -> LaunchArgs {
#[cfg(not(target_arch = "wasm32"))]
{
launch_args_from_command_line(std::env::args().skip(1), cfg!(debug_assertions))
}
#[cfg(target_arch = "wasm32")]
{
LaunchArgs::new(std::iter::empty(), cfg!(debug_assertions))
}
}
pub fn launch_args_from_command_line(
tokens: impl IntoIterator<Item = String>,
debuggable: bool,
) -> LaunchArgs {
let mut entries = Vec::new();
for token in tokens {
if token == "--" {
break;
}
let Some(option) = token.strip_prefix("--") else {
continue;
};
match option.split_once('=') {
Some((name, value)) => {
entries.push((name.to_string(), LaunchArgValue::Text(value.to_string())));
}
None => entries.push((option.to_string(), LaunchArgValue::Bool(true))),
}
}
LaunchArgs::new(entries, debuggable)
}
pub fn local_launch_args() -> CompositionLocal<LaunchArgsRef> {
crate::composition_locals::cached_local(
|locals| &locals.launch_args,
|| compositionLocalOfWithPolicy(launch_args, Arc::ptr_eq),
)
}
#[composable]
pub fn ProvideLaunchArgs(args: LaunchArgsRef, content: impl FnOnce()) {
let local = local_launch_args();
CompositionLocalProvider([local.provides(args)], move || {
content();
});
}
#[composable]
pub fn isDebuggable() -> bool {
local_launch_args().current().is_debuggable()
}
#[cfg(test)]
#[path = "tests/launch_args_tests.rs"]
mod tests;