use std::{cell::RefCell, rc::Rc};
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 = Rc<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,
}
}
thread_local! {
static PLATFORM_LAUNCH_ARGS: RefCell<Option<LaunchArgsRef>> = const { RefCell::new(None) };
static DEFAULT_LAUNCH_ARGS: RefCell<Option<LaunchArgsRef>> = const { RefCell::new(None) };
}
pub fn set_platform_launch_args(args: LaunchArgsRef) {
PLATFORM_LAUNCH_ARGS.with(|cell| *cell.borrow_mut() = Some(args));
}
pub fn clear_platform_launch_args() {
PLATFORM_LAUNCH_ARGS.with(|cell| *cell.borrow_mut() = None);
}
pub fn launch_args() -> LaunchArgsRef {
if let Some(args) = PLATFORM_LAUNCH_ARGS.with(|cell| cell.borrow().clone()) {
return args;
}
DEFAULT_LAUNCH_ARGS.with(|cell| {
let mut cached = cell.borrow_mut();
cached
.get_or_insert_with(|| Rc::new(default_launch_args()))
.clone()
})
}
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> {
thread_local! {
static LOCAL_LAUNCH_ARGS: RefCell<Option<CompositionLocal<LaunchArgsRef>>> = const { RefCell::new(None) };
}
LOCAL_LAUNCH_ARGS.with(|cell| {
let mut local = cell.borrow_mut();
local
.get_or_insert_with(|| compositionLocalOfWithPolicy(launch_args, Rc::ptr_eq))
.clone()
})
}
#[composable]
pub fn ProvideLaunchArgs(args: LaunchArgsRef, content: impl FnOnce()) {
let local = local_launch_args();
CompositionLocalProvider(vec![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;