dyn calling works + vibed dioxus ui components + some mo provision prototyping
This commit is contained in:
5699
Cargo.lock
generated
5699
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
@@ -5,6 +5,12 @@ members = [
|
||||
"apps/os",
|
||||
"apps/obsidian-plugin",
|
||||
"parts/*",
|
||||
"parts/mize.ui/dioxus-components",
|
||||
]
|
||||
exclude = [
|
||||
"parts/mize.ui",
|
||||
"parts/mize.ui/upstream",
|
||||
"parts/mize.ui/fluxer",
|
||||
]
|
||||
default-members = [
|
||||
"apps/obsidian-plugin",
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
use std::any::Any;
|
||||
use std::fmt::Debug;
|
||||
|
||||
use crate::MizeResult;
|
||||
|
||||
@@ -7,7 +8,8 @@ use crate::id::MizeId;
|
||||
use crate::item::{IntoItemData, ItemData};
|
||||
use crate::Mize;
|
||||
|
||||
type ConfigFun = Box<dyn Fn() -> MizeResult<ItemData> + Send + Sync + 'static>;
|
||||
pub type ConfigFun = Box<dyn Fn() -> MizeResult<ItemData> + Send + Sync + 'static>;
|
||||
pub type MizeFun = Box<dyn Fn(ItemData) -> MizeResult<ItemData> + Send + Sync + 'static>;
|
||||
|
||||
pub struct ConfigOpt {
|
||||
pub name: String,
|
||||
@@ -16,6 +18,16 @@ pub struct ConfigOpt {
|
||||
pub deps: Vec<String>,
|
||||
}
|
||||
|
||||
pub trait FromItemData {
|
||||
fn from_itemdata(data: ItemData) -> MizeResult<Self> where Self: Sized;
|
||||
}
|
||||
|
||||
impl Debug for ConfigOpt {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "({}: {})", self.name, self.val.clone().unwrap_or(ItemData::new()))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
#[repr(C)]
|
||||
pub struct ConfigOptId {
|
||||
@@ -25,6 +37,11 @@ pub struct ConfigOptId {
|
||||
impl ConfigOptId {
|
||||
pub fn default_val(self, val: ItemData) -> Self {
|
||||
let mize = get_global_mize().unwrap();
|
||||
// in generate mode: record the default value as metadata, skip normal op
|
||||
if mize.is_generate_mode().unwrap_or(false) {
|
||||
mize.record_opt("_default_val", self.id.clone(), Some(val)).ok();
|
||||
return self.clone();
|
||||
}
|
||||
let mut config_opts = mize.config_opts.lock().unwrap();
|
||||
let opt = config_opts.get_mut(&self.id).unwrap();
|
||||
opt.val = Some(val);
|
||||
@@ -32,13 +49,35 @@ impl ConfigOptId {
|
||||
}
|
||||
pub fn fun(self, fun: ConfigFun) -> Self {
|
||||
let mize = get_global_mize().unwrap();
|
||||
// in generate mode: record as an opt with a fun, skip normal op
|
||||
if mize.is_generate_mode().unwrap_or(false) {
|
||||
mize.record_opt("_fun", self.id.clone(), None).ok();
|
||||
return self.clone();
|
||||
}
|
||||
let mut config_opts = mize.config_opts.lock().unwrap();
|
||||
println!("get_mut: {}", self.id);
|
||||
let opt = config_opts.get_mut(&self.id).unwrap();
|
||||
opt.fun = Some(fun);
|
||||
self.clone()
|
||||
}
|
||||
pub fn method<F: Fn(ItemData) -> MizeResult<ItemData>>(self, _name: &str, _fun: F) -> ConfigOptId {
|
||||
pub fn method<F: Fn(ItemData) -> MizeResult<ItemData> + Send + Sync + 'static>(self, name: &str, fun: F) -> ConfigOptId {
|
||||
let mize = get_global_mize().unwrap();
|
||||
let mut cur_part = self.id.clone();
|
||||
let id = cur_part.join(name).unwrap();
|
||||
// in generate mode: record method metadata, skip normal registration
|
||||
if mize.is_generate_mode().unwrap_or(false) {
|
||||
mize.record_method(name, id.clone(), "method").ok();
|
||||
return self.clone();
|
||||
}
|
||||
let mut opts = mize.config_opts.lock().unwrap();
|
||||
let opt = ConfigOpt {
|
||||
name: id.dot(),
|
||||
val: None,
|
||||
fun: None,
|
||||
deps: Vec::new(),
|
||||
};
|
||||
let mut fun_values = mize.fun_values.lock().unwrap();
|
||||
fun_values.insert(id.clone(), Box::new(fun));
|
||||
opts.insert(id.clone(), opt);
|
||||
|
||||
self.clone()
|
||||
}
|
||||
@@ -48,13 +87,27 @@ impl ConfigOptId {
|
||||
//fn into_item_data(self) -> ItemData;
|
||||
//}
|
||||
|
||||
pub trait MizeAny {
|
||||
fn as_raw_ptr(&self) -> *const ();
|
||||
}
|
||||
|
||||
impl<T> MizeAny for T {
|
||||
fn as_raw_ptr(&self) -> *const () {
|
||||
self as *const Self as *const ()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
pub fn opt(name: &str) -> crate::config::ConfigOptId {
|
||||
let mize = get_global_mize().unwrap();
|
||||
let mut cur_part = mize.cur_eval_part.lock().unwrap().clone().unwrap();
|
||||
let mut opts = mize.config_opts.lock().unwrap();
|
||||
let id = cur_part.join(name).unwrap();
|
||||
// in generate mode: record opt metadata, skip normal registration
|
||||
if mize.is_generate_mode().unwrap_or(false) {
|
||||
mize.record_opt(name, id.clone(), None).ok();
|
||||
return ConfigOptId { id };
|
||||
}
|
||||
let mut opts = mize.config_opts.lock().unwrap();
|
||||
let opt = ConfigOpt {
|
||||
name: id.dot(),
|
||||
val: None,
|
||||
@@ -65,47 +118,68 @@ pub fn opt(name: &str) -> crate::config::ConfigOptId {
|
||||
ConfigOptId { id }
|
||||
}
|
||||
|
||||
pub fn method<F: Fn(ItemData) -> MizeResult<ItemData>>(name: &str, fun: F) -> ConfigOptId {
|
||||
pub fn method<F: Fn(ItemData) -> MizeResult<ItemData> + Send + Sync + 'static>(name: &str, fun: F) -> ConfigOptId {
|
||||
let mize = get_global_mize().unwrap();
|
||||
let mut cur_part = mize.cur_eval_part.lock().unwrap().clone().unwrap();
|
||||
let mut opts = mize.config_opts.lock().unwrap();
|
||||
let id = cur_part.join(name).unwrap();
|
||||
// in generate mode: record method metadata, skip normal registration
|
||||
if mize.is_generate_mode().unwrap_or(false) {
|
||||
mize.record_method(name, id.clone(), "method").ok();
|
||||
return ConfigOptId { id };
|
||||
}
|
||||
let mut opts = mize.config_opts.lock().unwrap();
|
||||
let opt = ConfigOpt {
|
||||
name: id.dot(),
|
||||
val: Some(ItemData::from_native(fun)?),
|
||||
val: None,
|
||||
fun: None,
|
||||
deps: Vec::new(),
|
||||
};
|
||||
let mut fun_values = mize.fun_values.lock().unwrap();
|
||||
fun_values.insert(id.clone(), Box::new(fun));
|
||||
opts.insert(id.clone(), opt);
|
||||
ConfigOptId { id }
|
||||
}
|
||||
|
||||
pub fn effect<F: Fn(ItemData) -> MizeResult<ItemData>>(_name: &str, _fun: F) -> ConfigOptId {
|
||||
pub fn effect<F: Fn(ItemData) -> MizeResult<ItemData> + Send + Sync + 'static>(name: &str, fun: F) -> ConfigOptId {
|
||||
let mize = get_global_mize().unwrap();
|
||||
let mut cur_part = mize.cur_eval_part.lock().unwrap().clone().unwrap();
|
||||
let mut opts = mize.config_opts.lock().unwrap();
|
||||
let id = cur_part.join(name).unwrap();
|
||||
// in generate mode: record effect metadata, skip normal registration
|
||||
if mize.is_generate_mode().unwrap_or(false) {
|
||||
mize.record_method(name, id.clone(), "effect").ok();
|
||||
return ConfigOptId { id };
|
||||
}
|
||||
let mut opts = mize.config_opts.lock().unwrap();
|
||||
let opt = ConfigOpt {
|
||||
name: id.dot(),
|
||||
val: Some(ItemData::from_native(fun)?),
|
||||
val: None,
|
||||
fun: None,
|
||||
deps: Vec::new(),
|
||||
};
|
||||
let mut fun_values = mize.fun_values.lock().unwrap();
|
||||
fun_values.insert(id.clone(), Box::new(fun));
|
||||
opts.insert(id.clone(), opt);
|
||||
ConfigOptId { id }
|
||||
}
|
||||
|
||||
pub fn thread<F: Fn(ItemData) -> MizeResult<ItemData>>(_name: &str, _fun: F) -> ConfigOptId {
|
||||
pub fn thread<F: Fn(ItemData) -> MizeResult<ItemData> + Send + Sync + 'static>(name: &str, fun: F) -> ConfigOptId {
|
||||
let mize = get_global_mize().unwrap();
|
||||
let mut cur_part = mize.cur_eval_part.lock().unwrap().clone().unwrap();
|
||||
let mut opts = mize.config_opts.lock().unwrap();
|
||||
let id = cur_part.join(name).unwrap();
|
||||
// in generate mode: record thread metadata, skip normal registration
|
||||
if mize.is_generate_mode().unwrap_or(false) {
|
||||
mize.record_method(name, id.clone(), "thread").ok();
|
||||
return ConfigOptId { id };
|
||||
}
|
||||
let mut opts = mize.config_opts.lock().unwrap();
|
||||
let opt = ConfigOpt {
|
||||
name: id.dot(),
|
||||
val: Some(ItemData::from_native(fun)?),
|
||||
val: None,
|
||||
fun: None,
|
||||
deps: Vec::new(),
|
||||
};
|
||||
let mut fun_values = mize.fun_values.lock().unwrap();
|
||||
fun_values.insert(id.clone(), Box::new(fun));
|
||||
opts.insert(id.clone(), opt);
|
||||
ConfigOptId { id }
|
||||
}
|
||||
@@ -125,7 +199,7 @@ pub mod c_api {
|
||||
super::opt(&string)
|
||||
}
|
||||
}
|
||||
pub extern "C" fn method(name: *const c_char, c_fun: CFun) {
|
||||
pub extern "C" fn method(name: *const c_char, c_fun: CFun) -> ConfigOptId {
|
||||
unsafe {
|
||||
let c_str = CStr::from_ptr(name);
|
||||
let string = c_str.to_string_lossy();
|
||||
@@ -137,7 +211,7 @@ pub mod c_api {
|
||||
})
|
||||
}
|
||||
}
|
||||
pub extern "C" fn effect(name: *const c_char, c_fun: CFun) {
|
||||
pub extern "C" fn effect(name: *const c_char, c_fun: CFun) -> ConfigOptId {
|
||||
unsafe {
|
||||
let c_str = CStr::from_ptr(name);
|
||||
let string = c_str.to_string_lossy();
|
||||
@@ -149,7 +223,7 @@ pub mod c_api {
|
||||
})
|
||||
}
|
||||
}
|
||||
pub extern "C" fn thread(name: *const c_char, c_fun: CFun) {
|
||||
pub extern "C" fn thread(name: *const c_char, c_fun: CFun) -> ConfigOptId {
|
||||
unsafe {
|
||||
let c_str = CStr::from_ptr(name);
|
||||
let string = c_str.to_string_lossy();
|
||||
|
||||
@@ -7,12 +7,12 @@ use interner::shared::{StringPool, VecStringPool};
|
||||
use std::any::Any;
|
||||
use std::collections::HashMap;
|
||||
use std::ops::{Deref, DerefMut};
|
||||
use std::sync::{Arc, Mutex};
|
||||
use std::sync::{Arc, Mutex, RwLock};
|
||||
use std::{thread, vec};
|
||||
use tracing::{debug, info, trace};
|
||||
|
||||
use crate::set_global_mize;
|
||||
use crate::config::{ConfigOpt, ConfigOptId};
|
||||
use crate::config::{ConfigOpt, ConfigOptId, MizeFun};
|
||||
use crate::error::{MizeError, MizeResult};
|
||||
use crate::id::{IntoMizeId, MizeId, Namespace};
|
||||
use crate::instance::store::Store;
|
||||
@@ -86,11 +86,51 @@ pub struct Mize {
|
||||
|
||||
pub native_values: Arc<Mutex<HashMap<u32, Box<dyn Any + Send + Sync>>>>,
|
||||
pub next_native_value_id: Arc<Mutex<u32>>,
|
||||
pub fun_values: Arc<Mutex<HashMap<MizeId, MizeFun>>>,
|
||||
pub next_fun_values_id: Arc<Mutex<u32>>,
|
||||
|
||||
// generate mode: when true, part_smth functions record metadata instead of executing
|
||||
pub generate_mode: Arc<RwLock<bool>>,
|
||||
// recorded metadata per part (keyed by part id) while in generate mode
|
||||
pub part_metadata: Arc<Mutex<HashMap<MizeId, PartMetadata>>>,
|
||||
|
||||
#[cfg(feature = "async")]
|
||||
pub runtime: Arc<Mutex<Runtime>>,
|
||||
}
|
||||
|
||||
/// Metadata recorded about a part while in generate mode
|
||||
#[derive(Clone, Debug, Default)]
|
||||
pub struct PartMetadata {
|
||||
pub part_name: String,
|
||||
pub opts: Vec<OptMetadata>,
|
||||
pub methods: Vec<MethodMetadata>,
|
||||
pub effects: Vec<MethodMetadata>,
|
||||
pub threads: Vec<MethodMetadata>,
|
||||
pub natives: Vec<NativeMetadata>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct OptMetadata {
|
||||
pub name: String,
|
||||
pub id: MizeId,
|
||||
pub default_val: Option<ItemData>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct MethodMetadata {
|
||||
pub name: String,
|
||||
pub id: MizeId,
|
||||
pub kind: String, // "method", "effect", "thread"
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct NativeMetadata {
|
||||
pub name: String,
|
||||
pub type_name: String,
|
||||
pub associated: Vec<String>,
|
||||
pub methods: Vec<String>,
|
||||
}
|
||||
|
||||
pub struct InstanceRef {
|
||||
inner: Arc<Mutex<Mize>>,
|
||||
}
|
||||
@@ -160,6 +200,10 @@ impl Mize {
|
||||
cur_eval_part: Arc::new(Mutex::new(None)),
|
||||
native_values: Arc::new(Mutex::new(HashMap::new())),
|
||||
next_native_value_id: Arc::new(Mutex::new(0)),
|
||||
fun_values: Arc::new(Mutex::new(HashMap::new())),
|
||||
next_fun_values_id: Arc::new(Mutex::new(0)),
|
||||
generate_mode: Arc::new(RwLock::new(false)),
|
||||
part_metadata: Arc::new(Mutex::new(HashMap::new())),
|
||||
|
||||
#[cfg(feature = "async")]
|
||||
runtime: Arc::new(Mutex::new(
|
||||
@@ -242,7 +286,6 @@ impl Mize {
|
||||
name: &str,
|
||||
handler: Box<dyn Fn(MizeMessage, &mut Mize) -> MizeResult<()> + Send + Sync>,
|
||||
) -> MizeResult<()> {
|
||||
println!("adding listener: {}", name);
|
||||
let name = name.to_owned();
|
||||
let mut listeners = self.listeners.lock().unwrap();
|
||||
listeners.insert(name, Arc::new(handler));
|
||||
@@ -260,7 +303,7 @@ impl Mize {
|
||||
|
||||
// end of platform specific init code
|
||||
|
||||
// load the modules, as specified in the parts config
|
||||
// load the parts, as specified in the parts config
|
||||
match self
|
||||
.get_config("parts")
|
||||
{
|
||||
@@ -274,7 +317,6 @@ impl Mize {
|
||||
}
|
||||
}
|
||||
Err(_err) => {
|
||||
println!("hooooo");
|
||||
// no parts to load
|
||||
}
|
||||
}
|
||||
@@ -286,7 +328,6 @@ impl Mize {
|
||||
pub fn with_config(config: ItemData) -> MizeResult<Mize> {
|
||||
trace!("[ {} ] Instance::with_config()", "CALL".yellow());
|
||||
trace!("config: {}", config);
|
||||
println!("here ....");
|
||||
|
||||
let mut mize = Mize::empty()?;
|
||||
|
||||
@@ -354,7 +395,6 @@ impl Mize {
|
||||
drop(msg_wait_inner);
|
||||
|
||||
let id = rx.recv()?;
|
||||
println!("new_item namespace: {:?}", id.namespace());
|
||||
|
||||
return Ok(Item::new(id, self));
|
||||
}
|
||||
@@ -463,7 +503,7 @@ impl Mize {
|
||||
}
|
||||
|
||||
pub fn fetch_part(&mut self, name: &str) -> MizeResult<String> {
|
||||
// platform specific way to fetch a module
|
||||
// platform specific way to fetch a part
|
||||
crate::platform::fetch_part(self, name)
|
||||
}
|
||||
|
||||
@@ -472,6 +512,98 @@ impl Mize {
|
||||
crate::platform::load_part(self, name, Some(path))
|
||||
}
|
||||
|
||||
// ===== generate mode accessors =====
|
||||
|
||||
/// Enable or disable generate mode. When enabled, part_smth functions record
|
||||
/// metadata (methods, opts, natives) instead of performing their normal operations.
|
||||
pub fn set_generate_mode(&self, enabled: bool) -> MizeResult<()> {
|
||||
let mut mode = self.generate_mode.write().map_err(|_| mize_err!("generate_mode lock poisoned"))?;
|
||||
*mode = enabled;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Check if generate mode is currently enabled.
|
||||
pub fn is_generate_mode(&self) -> MizeResult<bool> {
|
||||
let mode = self.generate_mode.read().map_err(|_| mize_err!("generate_mode lock poisoned"))?;
|
||||
Ok(*mode)
|
||||
}
|
||||
|
||||
/// Record an opt declaration while in generate mode.
|
||||
pub fn record_opt(&self, name: &str, id: MizeId, default_val: Option<ItemData>) -> MizeResult<()> {
|
||||
let cur_part = self.cur_eval_part.lock()?.clone()
|
||||
.ok_or_else(|| mize_err!("no cur_eval_part set"))?;
|
||||
let mut metadata_map = self.part_metadata.lock()?;
|
||||
let metadata = metadata_map.entry(cur_part.clone()).or_insert_with(|| PartMetadata {
|
||||
part_name: cur_part.dot(),
|
||||
..Default::default()
|
||||
});
|
||||
metadata.opts.push(OptMetadata {
|
||||
name: name.to_string(),
|
||||
id,
|
||||
default_val,
|
||||
});
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Record a method/effect/thread declaration while in generate mode.
|
||||
pub fn record_method(&self, name: &str, id: MizeId, kind: &str) -> MizeResult<()> {
|
||||
let cur_part = self.cur_eval_part.lock()?.clone()
|
||||
.ok_or_else(|| mize_err!("no cur_eval_part set"))?;
|
||||
let mut metadata_map = self.part_metadata.lock()?;
|
||||
let metadata = metadata_map.entry(cur_part.clone()).or_insert_with(|| PartMetadata {
|
||||
part_name: cur_part.dot(),
|
||||
..Default::default()
|
||||
});
|
||||
let m = MethodMetadata {
|
||||
name: name.to_string(),
|
||||
id,
|
||||
kind: kind.to_string(),
|
||||
};
|
||||
match kind {
|
||||
"effect" => metadata.effects.push(m),
|
||||
"thread" => metadata.threads.push(m),
|
||||
_ => metadata.methods.push(m),
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Record a native type inclusion while in generate mode.
|
||||
pub fn record_native(&self, name: &str, type_name: &str, associated: Vec<String>, methods: Vec<String>) -> MizeResult<()> {
|
||||
let cur_part = self.cur_eval_part.lock()?.clone()
|
||||
.ok_or_else(|| mize_err!("no cur_eval_part set"))?;
|
||||
let mut metadata_map = self.part_metadata.lock()?;
|
||||
let metadata = metadata_map.entry(cur_part.clone()).or_insert_with(|| PartMetadata {
|
||||
part_name: cur_part.dot(),
|
||||
..Default::default()
|
||||
});
|
||||
metadata.natives.push(NativeMetadata {
|
||||
name: name.to_string(),
|
||||
type_name: type_name.to_string(),
|
||||
associated,
|
||||
methods,
|
||||
});
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Get recorded metadata for a specific part.
|
||||
pub fn get_metadata(&self, part_id: &MizeId) -> MizeResult<Option<PartMetadata>> {
|
||||
let metadata_map = self.part_metadata.lock()?;
|
||||
Ok(metadata_map.get(part_id).cloned())
|
||||
}
|
||||
|
||||
/// Get all recorded metadata (for all parts).
|
||||
pub fn get_all_metadata(&self) -> MizeResult<Vec<PartMetadata>> {
|
||||
let metadata_map = self.part_metadata.lock()?;
|
||||
Ok(metadata_map.values().cloned().collect())
|
||||
}
|
||||
|
||||
/// Clear all recorded metadata.
|
||||
pub fn clear_metadata(&self) -> MizeResult<()> {
|
||||
let mut metadata_map = self.part_metadata.lock()?;
|
||||
metadata_map.clear();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn namespace_from_string(&self, ns_str: String) -> MizeResult<Namespace> {
|
||||
let namespace_pool_inner = self.namespace_pool.lock()?;
|
||||
let namespace = Namespace(namespace_pool_inner.get(ns_str));
|
||||
@@ -812,32 +944,65 @@ impl Mize {
|
||||
pub fn get_config(&mut self, name: &str) -> MizeResult<ItemData> {
|
||||
let mut config_opts = self.config_opts.lock().unwrap();
|
||||
|
||||
let opt = config_opts
|
||||
.get_mut(&self.clone().id_new_dots(name)?)
|
||||
.ok_or_else(|| mize_err!("Config opt for {name} not in config_opts"))?;
|
||||
|
||||
// Evaluate the fun if present
|
||||
if let Some(fun) = &opt.fun {
|
||||
let result = fun()?;
|
||||
return Ok(ItemData::from(result));
|
||||
let mut id;
|
||||
let mut split = name.split(".");
|
||||
if split.nth(0).unwrap() == "self" {
|
||||
let cur_part = self.cur_eval_part.lock()?.clone().ok_or(mize_err!("get_config called but no curr eval part set"))?;
|
||||
id = cur_part.clone();
|
||||
for el in split {
|
||||
id = id.join(el)?;
|
||||
}
|
||||
} else {
|
||||
id = self.clone().id_new_dots(name)?;
|
||||
}
|
||||
|
||||
// Return cached value
|
||||
let mut opt = config_opts
|
||||
.get_mut(&id)
|
||||
.ok_or_else(|| mize_err!("Config opt for {name} not in config_opts"))?;
|
||||
|
||||
if let Some(val) = opt.val.clone() {
|
||||
return Ok(val);
|
||||
}
|
||||
|
||||
// in case it is not cached run the fun if it exists
|
||||
if let Some(fun) = &opt.fun {
|
||||
let res = fun()?;
|
||||
opt.val = Some(res.clone());
|
||||
return Ok(res);
|
||||
}
|
||||
|
||||
Err(mize_err!("ConfigOpt {name} has no fun and no cached value"))
|
||||
}
|
||||
|
||||
pub fn dyn_call(&mut self, part_id: MizeId, method: &str, args: ItemData) -> MizeResult<ItemData> {
|
||||
let mut opts = self.config_opts.lock()?;
|
||||
let part = opts.get_mut(&part_id).ok_or(mize_err!("part {} is not loaded", part_id.dot()))?;
|
||||
let fun = part.val.ok_or(mize_err!("fun has no val"))?.native::<Fn(ItemData) -> MizeResult<ItemData>>()?;
|
||||
println!("dyn_call ing from part, {:?}, {method}, {:?}", part_id, args);
|
||||
fun(args)
|
||||
debug!("dyn call {}.{} with args: {}", part_id, method, args);
|
||||
let mut fun_values = self.fun_values.lock()?;
|
||||
|
||||
let id = part_id.clone().join(method)?;
|
||||
//let part = opts.get_mut(&id).ok_or(mize_err!("part {} is not loaded", part_id.dot()))?;
|
||||
let fun = fun_values.remove(&id).ok_or(mize_err!("fun value for {id} not found"))?;
|
||||
drop(fun_values);
|
||||
|
||||
let calling_part = self.clone().cur_eval_part()?;
|
||||
self.clone().set_cur_eval_part(Some(part_id));
|
||||
let res = fun(args);
|
||||
self.clone().set_cur_eval_part(calling_part);
|
||||
let mut fun_values = self.fun_values.lock()?;
|
||||
fun_values.insert(id, fun);
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
pub fn cur_eval_part(&mut self) -> MizeResult<Option<MizeId>> {
|
||||
Ok(self.cur_eval_part.lock()?.clone())
|
||||
}
|
||||
|
||||
pub fn set_cur_eval_part(&mut self, part: Option<MizeId>) -> MizeResult<()> {
|
||||
let mut guard = self.cur_eval_part.lock()?;
|
||||
*guard = part;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn get_part_gen<T: MizePart + Send + Sync + 'static>(
|
||||
&mut self,
|
||||
name: &str,
|
||||
|
||||
886
core/src/item.rs
886
core/src/item.rs
@@ -1,21 +1,21 @@
|
||||
use crate::get_global_mize;
|
||||
use crate::config::{ConfigOpt};
|
||||
use crate::{config, get_global_mize};
|
||||
use crate::config::{ConfigOpt, FromItemData, MizeFun};
|
||||
use ciborium::into_writer;
|
||||
use colored::Colorize;
|
||||
use serde_json::from_reader;
|
||||
use core::fmt;
|
||||
use serde::Deserialize;
|
||||
use serde::Serialize;
|
||||
use std::any::Any;
|
||||
use std::any::{Any, TypeId};
|
||||
use std::collections::HashMap;
|
||||
use std::fmt::Debug;
|
||||
use std::sync::MutexGuard;
|
||||
use std::io;
|
||||
use std::{boxed, io};
|
||||
use std::fmt::Display;
|
||||
use std::i128;
|
||||
use tracing::{debug, instrument, trace, warn};
|
||||
|
||||
use crate::error::{IntoMizeResult, MizeError, MizeResult};
|
||||
use crate::error::{IntoMizeResult, MizeError, MizeErrorWhichImplementsError, MizeResult};
|
||||
use crate::id::MizeId;
|
||||
use crate::instance::connection::value_raw_con_by_id;
|
||||
use crate::instance::Mize;
|
||||
@@ -56,6 +56,7 @@ impl Item<'_> {
|
||||
.mres_msg(format!("failed to convert raw value from item '{}' into a utf-8 string. raw value was: '{:02X?}'", self.id(), raw))
|
||||
}
|
||||
|
||||
|
||||
pub fn as_data_full(&self) -> MizeResult<ItemData> {
|
||||
let id = self.id();
|
||||
|
||||
@@ -228,7 +229,7 @@ impl ItemData {
|
||||
*next_id += 1;
|
||||
native_values.insert(id, Box::new(val));
|
||||
let mut data = ItemData::new();
|
||||
data.set_path("_native", id.to_string())?;
|
||||
data.set_path("_native", id)?;
|
||||
Ok(data)
|
||||
}
|
||||
|
||||
@@ -238,6 +239,43 @@ impl ItemData {
|
||||
_ => Err(mize_err!("this ItemData has no string value")),
|
||||
}
|
||||
}
|
||||
|
||||
//pub fn value_fun(&self) -> MizeResult<config::MizeFun> {
|
||||
pub fn value_fun(&self) -> MizeResult<()> {
|
||||
let mut mize = get_global_mize()?;
|
||||
let mut fun_values = mize.fun_values.lock()?;
|
||||
let fun_id = self.get_path("_func")?.value_u32()?;
|
||||
//let fun = fun_values.remove(fun_id).ok_or(mize_err!("fun with id {fun_id} not in fun_values"))?;
|
||||
// TODO: finish
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn call(&self, args: ItemData) -> MizeResult<ItemData> {
|
||||
let mut mize = get_global_mize()?;
|
||||
let mut fun_values = mize.fun_values.lock()?;
|
||||
let fun_num = self.get_path("_fun_num")?.value_u32()?;
|
||||
let fun_id = mize.clone().id_new_dots("mize.fun")?.join(format!("{fun_num}").as_str())?;
|
||||
let fun = fun_values.remove(&fun_id).ok_or(mize_err!("fun with id {fun_id} not in fun_values"))?;
|
||||
let res = fun(args)?;
|
||||
return Ok(res);
|
||||
}
|
||||
|
||||
pub fn fun(fun: MizeFun) -> MizeResult<ItemData> {
|
||||
let mut mize = get_global_mize()?;
|
||||
let mut fun_values = mize.fun_values.lock()?;
|
||||
let mut next_fun_values_id = mize.next_fun_values_id.lock()?;
|
||||
let fun_num = next_fun_values_id.clone();
|
||||
*next_fun_values_id = *next_fun_values_id +1;
|
||||
let fun_id = mize.clone().id_new_dots("mize.fun")?.join(format!("{fun_num}").as_str())?;
|
||||
fun_values.insert(fun_id, fun);
|
||||
let mut ret = ItemData::new();
|
||||
ret.set_path("_fun_num", fun_num)?;
|
||||
Ok(ret)
|
||||
}
|
||||
|
||||
pub fn value_u32(&self) -> MizeResult<u32> {
|
||||
Ok(u32::try_from(self.cbor().as_integer().ok_or("value_u32 but data wos not a cbor Integer")?)?)
|
||||
}
|
||||
|
||||
pub fn value_bool(self) -> MizeResult<bool> {
|
||||
match self.cbor() {
|
||||
@@ -352,132 +390,503 @@ impl ItemData {
|
||||
Ok(ItemData::from_cbor(value.clone()))
|
||||
}
|
||||
|
||||
/// Try to get the native id from the `_native` field.
|
||||
/// Returns `Some(id)` if the field exists and is a valid u32 string.
|
||||
/// Returns `None` if the field doesn't exist or is not a valid id.
|
||||
fn try_get_native_id(&self) -> Option<u32> {
|
||||
let id_data = self.get_path("_native").ok()?;
|
||||
if id_data.cbor().is_null() {
|
||||
return None;
|
||||
}
|
||||
if let Some(integer) = id_data.cbor().as_integer() {
|
||||
if let Ok(val) = u32::try_from(integer) {
|
||||
return Some(val)
|
||||
}
|
||||
}
|
||||
return None;
|
||||
}
|
||||
|
||||
/// Get an immutable reference to the native value stored in this ItemData.
|
||||
/// The native value must have been stored via from_native().
|
||||
/// Returns a guard that holds the lock on the native_values registry.
|
||||
///
|
||||
/// If the ItemData has a `_native` field (stored via `from_native()`), returns a guard
|
||||
/// that takes the value out of the global registry and provides an immutable reference to it.
|
||||
///
|
||||
/// Otherwise, for supported hardcoded types (String, bool, Vec<String>, Vec<u8>,
|
||||
/// integer types, float types), constructs the value directly from the CBOR content,
|
||||
/// stores it in the registry, and returns a guard to it.
|
||||
///
|
||||
/// The guard temporarily removes the value from the registry to allow the mutex lock to be
|
||||
/// dropped, improving concurrency. The value is automatically reinserted when the guard is dropped.
|
||||
pub fn native<T: Any + Send + Sync + 'static>(&self) -> MizeResult<NativeRefGuard<T>> {
|
||||
let mize = get_global_mize()?;
|
||||
let native_values = mize.native_values.lock().map_err(|_| mize_err!("Failed to lock native_values"))?;
|
||||
|
||||
let id_data = self.get_path("_native")
|
||||
.map_err(|_| mize_err!("No _native field in ItemData"))?;
|
||||
let id_str = id_data.value_string()
|
||||
.map_err(|_| mize_err!("_native field is not a string"))?;
|
||||
let id: u32 = id_str.parse()
|
||||
.map_err(|_| mize_err!("_native field is not a valid u32: {}", id_str))?;
|
||||
|
||||
let boxed = native_values.get(&id)
|
||||
let mut native_values = mize.native_values.lock().map_err(|_| mize_err!("Failed to lock native_values"))?;
|
||||
|
||||
let id = match self.try_get_native_id() {
|
||||
Some(id) => id,
|
||||
None => {
|
||||
// No _native field, try to construct from CBOR
|
||||
let value = try_construct_native::<T>(self.cbor())?
|
||||
.ok_or_else(|| mize_err!(
|
||||
"ItemData has no _native field and type {} is not supported for direct construction from CBOR. Value: {}",
|
||||
std::any::type_name::<T>(),
|
||||
self
|
||||
))?;
|
||||
|
||||
// Store in registry and use the new id
|
||||
let mut next_id = mize.next_native_value_id.lock()
|
||||
.map_err(|_| mize_err!("Failed to lock next_native_value_id"))?;
|
||||
let new_id = *next_id;
|
||||
*next_id += 1;
|
||||
native_values.insert(new_id, Box::new(value));
|
||||
new_id
|
||||
}
|
||||
};
|
||||
|
||||
let boxed = native_values.remove(&id)
|
||||
.ok_or(mize_err!("No native value with id {}", id))?;
|
||||
|
||||
// SAFETY: The reference is valid as long as the guard exists.
|
||||
// The guard holds the MutexGuard which keeps the lock on native_values.
|
||||
// The data in the HashMap is owned by the global Mize instance.
|
||||
// We extract the raw pointer first, then move the guard.
|
||||
let ref_t_ptr = boxed.downcast_ref::<T>()
|
||||
.ok_or(mize_err!("Failed to downcast native value to {}", std::any::type_name::<T>()))? as *const T;
|
||||
|
||||
let guard = std::mem::ManuallyDrop::new(native_values);
|
||||
|
||||
|
||||
let typed = boxed.downcast::<T>()
|
||||
.map_err(|_| mize_err!("Failed to downcast native value to {}", std::any::type_name::<T>()))?;
|
||||
|
||||
// Drop the mutex guard before returning - the value is now owned by the guard
|
||||
drop(native_values);
|
||||
|
||||
Ok(NativeRefGuard {
|
||||
_guard: unsafe { std::mem::transmute(guard) },
|
||||
value: unsafe { &*ref_t_ptr },
|
||||
value: std::mem::ManuallyDrop::new(*typed),
|
||||
id,
|
||||
needs_reinsert: true,
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
/// Get an immutable reference to a native value of type `T`, using [`FromItemData`]
|
||||
/// to construct it when it is not already stored in the registry as a `T`.
|
||||
///
|
||||
/// If the ItemData has a `_native` field (stored via `from_native()`) and the registry
|
||||
/// value is a `T`, returns a guard to it (the value is temporarily removed from the
|
||||
/// registry and reinserted when the guard is dropped).
|
||||
///
|
||||
/// Otherwise, constructs the value via `T::from_itemdata(self)`, stores it in the
|
||||
/// registry, and returns a guard to it.
|
||||
pub fn native_from<T: FromItemData + Any + Send + Sync + 'static>(&self) -> MizeResult<NativeRefGuard<T>> {
|
||||
// Fast path: the registry already holds a T for this ItemData
|
||||
if let Some(id) = self.try_get_native_id() {
|
||||
let mize = get_global_mize()?;
|
||||
let mut native_values = mize.native_values.lock()
|
||||
.map_err(|_| mize_err!("Failed to lock native_values"))?;
|
||||
if native_values.get(&id).map(|b| b.downcast_ref::<T>().is_some()).unwrap_or(false) {
|
||||
let boxed = native_values.remove(&id)
|
||||
.ok_or(mize_err!("No native value with id {}", id))?;
|
||||
let typed = boxed.downcast::<T>()
|
||||
.map_err(|_| mize_err!("Failed to downcast native value to {}", std::any::type_name::<T>()))?;
|
||||
drop(native_values);
|
||||
return Ok(NativeRefGuard {
|
||||
value: std::mem::ManuallyDrop::new(*typed),
|
||||
id,
|
||||
needs_reinsert: true,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Fallback: let T construct itself from this ItemData (without holding the registry lock)
|
||||
let value = T::from_itemdata(self.clone())?;
|
||||
|
||||
// Store in registry and hand out a guard to it
|
||||
let mize = get_global_mize()?;
|
||||
let mut native_values = mize.native_values.lock()
|
||||
.map_err(|_| mize_err!("Failed to lock native_values"))?;
|
||||
let new_id = {
|
||||
let mut next_id = mize.next_native_value_id.lock()
|
||||
.map_err(|_| mize_err!("Failed to lock next_native_value_id"))?;
|
||||
let new_id = *next_id;
|
||||
*next_id += 1;
|
||||
new_id
|
||||
};
|
||||
native_values.insert(new_id, Box::new(value));
|
||||
let boxed = native_values.remove(&new_id)
|
||||
.ok_or(mize_err!("No native value with id {}", new_id))?;
|
||||
let typed = boxed.downcast::<T>()
|
||||
.map_err(|_| mize_err!("Failed to downcast native value to {}", std::any::type_name::<T>()))?;
|
||||
drop(native_values);
|
||||
Ok(NativeRefGuard {
|
||||
value: std::mem::ManuallyDrop::new(*typed),
|
||||
id: new_id,
|
||||
needs_reinsert: true,
|
||||
})
|
||||
}
|
||||
|
||||
/// Get a mutable reference to the native value stored in this ItemData.
|
||||
/// The native value must have been stored via from_native().
|
||||
/// Returns a guard that holds the lock on the native_values registry.
|
||||
///
|
||||
/// If the ItemData has a `_native` field (stored via `from_native()`), returns a guard
|
||||
/// that takes the value out of the global registry and provides a mutable reference to it.
|
||||
///
|
||||
/// Otherwise, for supported hardcoded types (String, bool, Vec<String>, Vec<u8>,
|
||||
/// integer types, float types), constructs the value directly from the CBOR content,
|
||||
/// stores it in the registry, and returns a mutable guard to it.
|
||||
///
|
||||
/// The guard temporarily removes the value from the registry to allow the mutex lock to be
|
||||
/// dropped, improving concurrency. The value is automatically reinserted when the guard is dropped.
|
||||
pub fn native_mut<T: Any + Send + Sync + 'static>(&self) -> MizeResult<NativeMutRefGuard<T>> {
|
||||
let mize = get_global_mize()?;
|
||||
let mut native_values = mize.native_values.lock().map_err(|_| mize_err!("Failed to lock native_values"))?;
|
||||
|
||||
let id_data = self.get_path("_native")
|
||||
.map_err(|_| mize_err!("No _native field in ItemData"))?;
|
||||
let id_str = id_data.value_string()
|
||||
.map_err(|_| mize_err!("_native field is not a string"))?;
|
||||
let id: u32 = id_str.parse()
|
||||
.map_err(|_| mize_err!("_native field is not a valid u32: {}", id_str))?;
|
||||
|
||||
let boxed = native_values.get_mut(&id)
|
||||
.ok_or(mize_err!("No native value with id {}", id))?;
|
||||
|
||||
// SAFETY: The reference is valid as long as the guard exists.
|
||||
// The guard holds the MutexGuard which keeps the lock on native_values.
|
||||
// The data in the HashMap is owned by the global Mize instance.
|
||||
// We extract the raw pointer first, then move the guard.
|
||||
let ref_mut_t_ptr = boxed.downcast_mut::<T>()
|
||||
.ok_or(mize_err!("Failed to downcast native value to {}", std::any::type_name::<T>()))? as *mut T;
|
||||
|
||||
let guard = std::mem::ManuallyDrop::new(native_values);
|
||||
|
||||
Ok(NativeMutRefGuard {
|
||||
_guard: unsafe { std::mem::transmute(guard) },
|
||||
value: unsafe { &mut *ref_mut_t_ptr },
|
||||
})
|
||||
}
|
||||
|
||||
/// Get a clone of the native value stored in this ItemData.
|
||||
/// The native value must have been stored via from_native().
|
||||
/// This is useful when you need to pass the value by ownership (e.g., as a function argument).
|
||||
pub fn native_clone<T: Any + Send + Sync + Clone + 'static>(&self) -> MizeResult<T> {
|
||||
let guard = self.native::<T>()?;
|
||||
Ok(guard.clone())
|
||||
}
|
||||
|
||||
/// Take the native value from this ItemData, removing it from the registry.
|
||||
/// The native value must have been stored via from_native().
|
||||
pub fn native_take<T: Any + Send + Sync + 'static>(&self) -> MizeResult<T> {
|
||||
let mize = get_global_mize()?;
|
||||
let mut native_values = mize.native_values.lock().map_err(|_| mize_err!("Failed to lock native_values"))?;
|
||||
|
||||
let id_data = self.get_path("_native")
|
||||
.map_err(|_| mize_err!("No _native field in ItemData"))?;
|
||||
let id_str = id_data.value_string()
|
||||
.map_err(|_| mize_err!("_native field is not a string"))?;
|
||||
let id: u32 = id_str.parse()
|
||||
.map_err(|_| mize_err!("_native field is not a valid u32: {}", id_str))?;
|
||||
|
||||
|
||||
let id = match self.try_get_native_id() {
|
||||
Some(id) => id,
|
||||
None => {
|
||||
// No _native field, try to construct from CBOR
|
||||
let value = try_construct_native::<T>(self.cbor())?
|
||||
.ok_or_else(|| mize_err!(
|
||||
"ItemData has no _native field and type {} is not supported for direct construction from CBOR. Value: {}",
|
||||
std::any::type_name::<T>(),
|
||||
self
|
||||
))?;
|
||||
|
||||
// Store in registry and use the new id
|
||||
let mut next_id = mize.next_native_value_id.lock()
|
||||
.map_err(|_| mize_err!("Failed to lock next_native_value_id"))?;
|
||||
let new_id = *next_id;
|
||||
*next_id += 1;
|
||||
native_values.insert(new_id, Box::new(value));
|
||||
new_id
|
||||
}
|
||||
};
|
||||
|
||||
let boxed = native_values.remove(&id)
|
||||
.ok_or(mize_err!("No native value with id {}", id))?;
|
||||
|
||||
|
||||
let typed = boxed.downcast::<T>()
|
||||
.map_err(|_| mize_err!("Failed to downcast native value to {}", std::any::type_name::<T>()))?;
|
||||
|
||||
Ok(*typed)
|
||||
|
||||
// Drop the mutex guard before returning - the value is now owned by the guard
|
||||
drop(native_values);
|
||||
|
||||
Ok(NativeMutRefGuard {
|
||||
value: std::mem::ManuallyDrop::new(*typed),
|
||||
id,
|
||||
needs_reinsert: true,
|
||||
})
|
||||
}
|
||||
|
||||
/// Get a mutable reference to a native value of type `T`, using [`FromItemData`]
|
||||
/// to construct it when it is not already stored in the registry as a `T`.
|
||||
///
|
||||
/// If the ItemData has a `_native` field (stored via `from_native()`) and the registry
|
||||
/// value is a `T`, returns a mutable guard to it (the value is temporarily removed
|
||||
/// from the registry and reinserted when the guard is dropped).
|
||||
///
|
||||
/// Otherwise, constructs the value via `T::from_itemdata(self)`, stores it in the
|
||||
/// registry, and returns a mutable guard to it.
|
||||
pub fn native_mut_from<T: FromItemData + Any + Send + Sync + 'static>(&self) -> MizeResult<NativeMutRefGuard<T>> {
|
||||
// Fast path: the registry already holds a T for this ItemData
|
||||
if let Some(id) = self.try_get_native_id() {
|
||||
let mize = get_global_mize()?;
|
||||
let mut native_values = mize.native_values.lock()
|
||||
.map_err(|_| mize_err!("Failed to lock native_values"))?;
|
||||
if native_values.get(&id).map(|b| b.downcast_ref::<T>().is_some()).unwrap_or(false) {
|
||||
let boxed = native_values.remove(&id)
|
||||
.ok_or(mize_err!("No native value with id {}", id))?;
|
||||
let typed = boxed.downcast::<T>()
|
||||
.map_err(|_| mize_err!("Failed to downcast native value to {}", std::any::type_name::<T>()))?;
|
||||
drop(native_values);
|
||||
return Ok(NativeMutRefGuard {
|
||||
value: std::mem::ManuallyDrop::new(*typed),
|
||||
id,
|
||||
needs_reinsert: true,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Fallback: let T construct itself from this ItemData (without holding the registry lock)
|
||||
let value = T::from_itemdata(self.clone())?;
|
||||
|
||||
// Store in registry and hand out a mutable guard to it
|
||||
let mize = get_global_mize()?;
|
||||
let mut native_values = mize.native_values.lock()
|
||||
.map_err(|_| mize_err!("Failed to lock native_values"))?;
|
||||
let new_id = {
|
||||
let mut next_id = mize.next_native_value_id.lock()
|
||||
.map_err(|_| mize_err!("Failed to lock next_native_value_id"))?;
|
||||
let new_id = *next_id;
|
||||
*next_id += 1;
|
||||
new_id
|
||||
};
|
||||
native_values.insert(new_id, Box::new(value));
|
||||
let boxed = native_values.remove(&new_id)
|
||||
.ok_or(mize_err!("No native value with id {}", new_id))?;
|
||||
let typed = boxed.downcast::<T>()
|
||||
.map_err(|_| mize_err!("Failed to downcast native value to {}", std::any::type_name::<T>()))?;
|
||||
drop(native_values);
|
||||
Ok(NativeMutRefGuard {
|
||||
value: std::mem::ManuallyDrop::new(*typed),
|
||||
id: new_id,
|
||||
needs_reinsert: true,
|
||||
})
|
||||
}
|
||||
|
||||
/// Get a clone of the native value stored in this ItemData.
|
||||
///
|
||||
/// If the ItemData has a `_native` field (stored via `from_native()`), clones the value
|
||||
/// from the global registry.
|
||||
///
|
||||
/// Otherwise, for supported hardcoded types (String, bool, Vec<String>, Vec<u8>,
|
||||
/// integer types, float types), constructs the value directly from the CBOR content.
|
||||
///
|
||||
/// This is useful when you need to pass the value by ownership (e.g., as a function argument).
|
||||
pub fn native_clone<T: Any + Send + Sync + Clone + 'static>(&self) -> MizeResult<T> {
|
||||
// Try registry first
|
||||
if let Some(id) = self.try_get_native_id() {
|
||||
let mize = get_global_mize()?;
|
||||
let native_values = mize.native_values.lock()
|
||||
.map_err(|_| mize_err!("Failed to lock native_values"))?;
|
||||
let boxed = native_values.get(&id)
|
||||
.ok_or(mize_err!("No native value with id {}", id))?;
|
||||
let val = boxed.downcast_ref::<T>()
|
||||
.ok_or(mize_err!("Failed to downcast native value to {}", std::any::type_name::<T>()))?;
|
||||
return Ok(val.clone());
|
||||
}
|
||||
|
||||
// Fallback: construct directly from CBOR
|
||||
try_construct_native::<T>(self.cbor())?
|
||||
.ok_or_else(|| mize_err!(
|
||||
"ItemData has no _native field and type {} is not supported for direct construction from CBOR. Value: {}",
|
||||
std::any::type_name::<T>(),
|
||||
self
|
||||
))
|
||||
}
|
||||
|
||||
/// Take the native value from this ItemData, removing it from the registry.
|
||||
///
|
||||
/// If the ItemData has a `_native` field (stored via `from_native()`), removes and
|
||||
/// returns the value from the global registry.
|
||||
///
|
||||
/// Otherwise, for supported hardcoded types (String, bool, Vec<String>, Vec<u8>,
|
||||
/// integer types, float types), constructs the value directly from the CBOR content.
|
||||
pub fn native_take<T: Any + Send + Sync + 'static>(&self) -> MizeResult<T> {
|
||||
// Try registry first
|
||||
if let Some(id) = self.try_get_native_id() {
|
||||
let mize = get_global_mize()?;
|
||||
let mut native_values = mize.native_values.lock()
|
||||
.map_err(|_| mize_err!("Failed to lock native_values"))?;
|
||||
let boxed = native_values.remove(&id)
|
||||
.ok_or(mize_err!("No native value with id {}", id))?;
|
||||
let typed = boxed.downcast::<T>()
|
||||
.map_err(|_| mize_err!("Failed to downcast native value to {}", std::any::type_name::<T>()))?;
|
||||
return Ok(*typed);
|
||||
}
|
||||
|
||||
// Fallback: construct directly from CBOR
|
||||
try_construct_native::<T>(self.cbor())?
|
||||
.ok_or_else(|| mize_err!(
|
||||
"ItemData has no _native field and type {} is not supported for direct construction from CBOR. Value: {}",
|
||||
std::any::type_name::<T>(),
|
||||
self
|
||||
))
|
||||
}
|
||||
|
||||
/// Take a native value of type `T` from this ItemData, using [`FromItemData`] as fallback.
|
||||
///
|
||||
/// If the ItemData has a `_native` field (stored via `from_native()`) and the registry
|
||||
/// value is a `T`, removes and returns it from the global registry.
|
||||
///
|
||||
/// Otherwise, constructs and returns the value via `T::from_itemdata(self)`.
|
||||
pub fn native_take_from<T: FromItemData + Any + Send + Sync + 'static>(&self) -> MizeResult<T> {
|
||||
// Try registry first: only take the value out if it actually is a T
|
||||
if let Some(id) = self.try_get_native_id() {
|
||||
let mize = get_global_mize()?;
|
||||
let mut native_values = mize.native_values.lock()
|
||||
.map_err(|_| mize_err!("Failed to lock native_values"))?;
|
||||
if native_values.get(&id).map(|b| b.downcast_ref::<T>().is_some()).unwrap_or(false) {
|
||||
let boxed = native_values.remove(&id)
|
||||
.ok_or(mize_err!("No native value with id {}", id))?;
|
||||
let typed = boxed.downcast::<T>()
|
||||
.map_err(|_| mize_err!("Failed to downcast native value to {}", std::any::type_name::<T>()))?;
|
||||
return Ok(*typed);
|
||||
}
|
||||
}
|
||||
|
||||
// Fallback: let T construct itself from this ItemData
|
||||
T::from_itemdata(self.clone())
|
||||
}
|
||||
}
|
||||
|
||||
/// Guard that holds a lock on the native_values registry and provides an immutable reference to a native value.
|
||||
/// Try to construct a native value of type `T` from a `CborValue`.
|
||||
///
|
||||
/// Supports hardcoded types:
|
||||
/// - `String` from `CborValue::Text`
|
||||
/// - `bool` from `CborValue::Bool`
|
||||
/// - `Vec<u8>` from `CborValue::Bytes`
|
||||
/// - `Vec<String>` from `CborValue::Array` of `Text`
|
||||
/// - Integer types (`i8`, `i16`, `i32`, `i64`, `i128`, `isize`,
|
||||
/// `u8`, `u16`, `u32`, `u64`, `u128`, `usize`) from `CborValue::Integer`
|
||||
/// - Float types (`f32`, `f64`) from `CborValue::Float` or `CborValue::Integer`
|
||||
///
|
||||
/// Returns `Ok(Some(value))` if `T` is a supported type and conversion succeeds.
|
||||
/// Returns `Ok(None)` if `T` is not a supported hardcoded type.
|
||||
/// Returns `Err` if `T` is supported but the CBOR value cannot be converted.
|
||||
fn try_construct_native<T: Any + Send + Sync + 'static>(cbor: &CborValue) -> MizeResult<Option<T>> {
|
||||
let tid = TypeId::of::<T>();
|
||||
|
||||
macro_rules! try_convert {
|
||||
($target:ty, $conv:expr) => {
|
||||
if tid == TypeId::of::<$target>() {
|
||||
let val: $target = $conv?;
|
||||
// SAFETY: TypeId verified that $target == T, so the transmute is sound.
|
||||
return Ok(Some(*unsafe {
|
||||
std::mem::transmute::<Box<$target>, Box<T>>(Box::new(val))
|
||||
}));
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// String from Text
|
||||
try_convert!(String,
|
||||
cbor.as_text()
|
||||
.map(|s| s.to_owned())
|
||||
.ok_or_else(|| mize_err!("ItemData is not a Text value, cannot construct String"))
|
||||
);
|
||||
|
||||
// bool from Bool
|
||||
try_convert!(bool,
|
||||
cbor.as_bool()
|
||||
.ok_or_else(|| mize_err!("ItemData is not a Bool value, cannot construct bool"))
|
||||
);
|
||||
|
||||
// Vec<u8> from Bytes
|
||||
try_convert!(Vec<u8>,
|
||||
cbor.as_bytes()
|
||||
.map(|b| b.clone())
|
||||
.ok_or_else(|| mize_err!("ItemData is not a Bytes value, cannot construct Vec<u8>"))
|
||||
);
|
||||
|
||||
// Vec<String> from Array of Text
|
||||
try_convert!(Vec<String>, {
|
||||
let arr = cbor.as_array()
|
||||
.ok_or_else(|| mize_err!("ItemData is not an Array value, cannot construct Vec<String>"))?;
|
||||
arr.iter()
|
||||
.map(|v| {
|
||||
v.as_text()
|
||||
.map(|s| s.to_owned())
|
||||
.ok_or_else(|| mize_err!("Array element is not a Text value, cannot construct Vec<String>"))
|
||||
})
|
||||
.collect::<MizeResult<Vec<String>>>()
|
||||
});
|
||||
|
||||
// Integer types from Integer
|
||||
macro_rules! try_int {
|
||||
($target:ty) => {
|
||||
try_convert!($target, {
|
||||
let int = cbor.as_integer()
|
||||
.ok_or_else(|| mize_err!("ItemData is not an Integer value, cannot construct {}", std::any::type_name::<$target>()))?;
|
||||
let i128_val: i128 = int.into();
|
||||
<$target>::try_from(i128_val)
|
||||
.map_err(|_| mize_err!("Integer value {} does not fit in {}", i128_val, std::any::type_name::<$target>()))
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
try_int!(i8);
|
||||
try_int!(i16);
|
||||
try_int!(i32);
|
||||
try_int!(i64);
|
||||
try_int!(i128);
|
||||
try_int!(isize);
|
||||
try_int!(u8);
|
||||
try_int!(u16);
|
||||
try_int!(u32);
|
||||
try_int!(u64);
|
||||
try_int!(u128);
|
||||
try_int!(usize);
|
||||
|
||||
// Float types from Float or Integer
|
||||
macro_rules! try_float {
|
||||
($target:ty) => {
|
||||
try_convert!($target, {
|
||||
match cbor {
|
||||
CborValue::Float(f) => Ok(*f as $target),
|
||||
CborValue::Integer(i) => {
|
||||
let i128_val: i128 = (*i).into();
|
||||
Ok(i128_val as $target)
|
||||
}
|
||||
_ => Err(mize_err!(
|
||||
"ItemData is neither Float nor Integer, cannot construct {}",
|
||||
std::any::type_name::<$target>()
|
||||
)),
|
||||
}
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
try_float!(f32);
|
||||
try_float!(f64);
|
||||
|
||||
// Type not supported for direct construction
|
||||
Ok(None)
|
||||
}
|
||||
|
||||
/// Guard that holds a native value taken from the registry.
|
||||
///
|
||||
/// The value is temporarily removed from the registry to allow the mutex lock to be dropped.
|
||||
/// When the guard is dropped, the value is automatically reinserted into the registry.
|
||||
pub struct NativeRefGuard<T: Any + Send + Sync + 'static> {
|
||||
_guard: MutexGuard<'static, HashMap<u32, Box<dyn Any + Send + Sync>>>,
|
||||
value: &'static T,
|
||||
value: std::mem::ManuallyDrop<T>,
|
||||
id: u32,
|
||||
needs_reinsert: bool,
|
||||
}
|
||||
|
||||
impl<T: Any + Send + Sync + 'static> std::ops::Deref for NativeRefGuard<T> {
|
||||
type Target = T;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
self.value
|
||||
&*self.value
|
||||
}
|
||||
}
|
||||
|
||||
/// Guard that holds a lock on the native_values registry and provides a mutable reference to a native value.
|
||||
impl<T: Any + Send + Sync + 'static> Drop for NativeRefGuard<T> {
|
||||
fn drop(&mut self) {
|
||||
if self.needs_reinsert {
|
||||
let mize = get_global_mize().expect("Failed to get global mize in NativeRefGuard drop");
|
||||
let mut native_values = mize.native_values.lock().expect("Failed to lock native_values in NativeRefGuard drop");
|
||||
let value = unsafe { std::mem::ManuallyDrop::take(&mut self.value) };
|
||||
native_values.insert(self.id, Box::new(value));
|
||||
} else {
|
||||
unsafe { std::mem::ManuallyDrop::drop(&mut self.value) };
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Guard that holds a mutable native value taken from the registry.
|
||||
///
|
||||
/// The value is temporarily removed from the registry to allow the mutex lock to be dropped.
|
||||
/// When the guard is dropped, the value is automatically reinserted into the registry.
|
||||
pub struct NativeMutRefGuard<T: Any + Send + Sync + 'static> {
|
||||
_guard: MutexGuard<'static, HashMap<u32, Box<dyn Any + Send + Sync>>>,
|
||||
value: &'static mut T,
|
||||
value: std::mem::ManuallyDrop<T>,
|
||||
id: u32,
|
||||
needs_reinsert: bool,
|
||||
}
|
||||
|
||||
impl<T: Any + Send + Sync + 'static> std::ops::Deref for NativeMutRefGuard<T> {
|
||||
type Target = T;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
self.value
|
||||
&*self.value
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Any + Send + Sync + 'static> std::ops::DerefMut for NativeMutRefGuard<T> {
|
||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||
self.value
|
||||
&mut *self.value
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Any + Send + Sync + 'static> Drop for NativeMutRefGuard<T> {
|
||||
fn drop(&mut self) {
|
||||
if self.needs_reinsert {
|
||||
let mize = get_global_mize().expect("Failed to get global mize in NativeMutRefGuard drop");
|
||||
let mut native_values = mize.native_values.lock().expect("Failed to lock native_values in NativeMutRefGuard drop");
|
||||
let value = unsafe { std::mem::ManuallyDrop::take(&mut self.value) };
|
||||
native_values.insert(self.id, Box::new(value));
|
||||
} else {
|
||||
unsafe { std::mem::ManuallyDrop::drop(&mut self.value) };
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -561,8 +970,6 @@ pub fn data_from_string(data_string: String) -> MizeResult<ItemData> {
|
||||
|
||||
pub fn item_data_get_path(data: &CborValue, path: Vec<String>) -> MizeResult<&CborValue> {
|
||||
let mut path_iter = path.clone().into_iter();
|
||||
//println!("data: {:?}", data);
|
||||
//println!("path: {:?}", path);
|
||||
|
||||
let path_el = match path_iter.nth(0) {
|
||||
Some(val) => val,
|
||||
@@ -573,8 +980,6 @@ pub fn item_data_get_path(data: &CborValue, path: Vec<String>) -> MizeResult<&Cb
|
||||
match data {
|
||||
CborValue::Map(map) => {
|
||||
for (key, val) in map {
|
||||
//println!("key: {:?}", key);
|
||||
//println!("path_el: {}", path_el);
|
||||
if let CborValue::Text(key_str) = key {
|
||||
if key_str == &path_el {
|
||||
sub_data = val;
|
||||
@@ -635,12 +1040,6 @@ pub fn item_data_set_path(
|
||||
//trace!("[ {} ] path: {:?}", "ARG".yellow(), path);
|
||||
//trace!("[ {} ] data_to_set: {}", "ARG".yellow(), data_to_set.clone().into_item_data());
|
||||
|
||||
//test_println!("item_data_set_path ###########################");
|
||||
//test_println!("data: {}", ItemData::from_cbor(data.clone()));
|
||||
//test_println!("path: {:?}", path);
|
||||
//test_println!("data_to_set: {}", ItemData::from_cbor(data_to_set.clone()));
|
||||
//test_println!("###########################");
|
||||
|
||||
let mut path_iter = path.clone().into_iter();
|
||||
let path_el = match path_iter.nth(0) {
|
||||
Some(val) => val,
|
||||
@@ -850,6 +1249,11 @@ impl IntoPath for &str {
|
||||
self.to_owned().split("/").map(|v| v.to_owned()).collect()
|
||||
}
|
||||
}
|
||||
impl IntoPath for usize {
|
||||
fn into_path(self) -> Vec<String> {
|
||||
vec![format!("{}", self)]
|
||||
}
|
||||
}
|
||||
|
||||
impl IntoPath for &[&str] {
|
||||
fn into_path(self) -> Vec<String> {
|
||||
@@ -984,3 +1388,281 @@ pub fn set_config_from_data(mize: &mut Mize, mut data: ItemData) -> MizeResult<(
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::set_global_mize;
|
||||
use crate::instance::Mize;
|
||||
use std::sync::{Mutex, MutexGuard};
|
||||
|
||||
/// Mutex to serialize tests that use the global Mize instance.
|
||||
static TEST_MUTEX: Mutex<()> = Mutex::new(());
|
||||
|
||||
fn setup() -> MizeResult<MutexGuard<'static, ()>> {
|
||||
let guard = TEST_MUTEX.lock().map_err(|_| mize_err!("Failed to lock test mutex"))?;
|
||||
let mize = Mize::empty()?;
|
||||
set_global_mize(mize)?;
|
||||
Ok(guard)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_native_clone_string_from_cbor() -> MizeResult<()> {
|
||||
let _guard = setup()?;
|
||||
let data = ItemData::from_string("hello world");
|
||||
let s: String = data.native_clone()?;
|
||||
assert_eq!(s, "hello world");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_native_take_string_from_cbor() -> MizeResult<()> {
|
||||
let _guard = setup()?;
|
||||
let data = ItemData::from_string("take me");
|
||||
let s: String = data.native_take()?;
|
||||
assert_eq!(s, "take me");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_native_string_from_cbor() -> MizeResult<()> {
|
||||
let _guard = setup()?;
|
||||
let data = ItemData::from_string("guard test");
|
||||
let guard = data.native::<String>()?;
|
||||
assert_eq!(&*guard, "guard test");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_native_mut_string_from_cbor() -> MizeResult<()> {
|
||||
let _guard = setup()?;
|
||||
let data = ItemData::from_string("mutable");
|
||||
{
|
||||
let mut guard = data.native_mut::<String>()?;
|
||||
guard.push_str(" updated");
|
||||
assert_eq!(&*guard, "mutable updated");
|
||||
}
|
||||
// Note: each call to native/native_mut on an ItemData without _native
|
||||
// constructs a fresh value from the CBOR content. The ItemData itself
|
||||
// is not modified, so the original CBOR value is used each time.
|
||||
let guard2 = data.native::<String>()?;
|
||||
assert_eq!(&*guard2, "mutable");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_native_clone_bool_from_cbor() -> MizeResult<()> {
|
||||
let _guard = setup()?;
|
||||
let data = ItemData::from_cbor(CborValue::Bool(true));
|
||||
let b: bool = data.native_clone()?;
|
||||
assert_eq!(b, true);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_native_clone_i32_from_cbor() -> MizeResult<()> {
|
||||
let _guard = setup()?;
|
||||
let data = ItemData::from_cbor(CborValue::Integer(42.into()));
|
||||
let i: i32 = data.native_clone()?;
|
||||
assert_eq!(i, 42);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_native_clone_u64_from_cbor() -> MizeResult<()> {
|
||||
let _guard = setup()?;
|
||||
let data = ItemData::from_cbor(CborValue::Integer(123456789u64.into()));
|
||||
let u: u64 = data.native_clone()?;
|
||||
assert_eq!(u, 123456789);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_native_clone_i128_from_cbor() -> MizeResult<()> {
|
||||
let _guard = setup()?;
|
||||
let data = ItemData::from_cbor(CborValue::from(9999999999i128));
|
||||
let i: i128 = data.native_clone()?;
|
||||
assert_eq!(i, 9999999999);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_native_clone_f64_from_cbor() -> MizeResult<()> {
|
||||
let _guard = setup()?;
|
||||
let data = ItemData::from_cbor(CborValue::Float(3.14159));
|
||||
let f: f64 = data.native_clone()?;
|
||||
assert!((f - 3.14159).abs() < 0.00001);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_native_clone_f64_from_integer() -> MizeResult<()> {
|
||||
let _guard = setup()?;
|
||||
let data = ItemData::from_cbor(CborValue::Integer(42.into()));
|
||||
let f: f64 = data.native_clone()?;
|
||||
assert_eq!(f, 42.0);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_native_clone_vec_string_from_cbor() -> MizeResult<()> {
|
||||
let _guard = setup()?;
|
||||
let data = ItemData::from_cbor(CborValue::Array(vec![
|
||||
CborValue::Text("a".to_string()),
|
||||
CborValue::Text("b".to_string()),
|
||||
CborValue::Text("c".to_string()),
|
||||
]));
|
||||
let v: Vec<String> = data.native_clone()?;
|
||||
assert_eq!(v, vec!["a".to_string(), "b".to_string(), "c".to_string()]);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_native_clone_vec_u8_from_cbor() -> MizeResult<()> {
|
||||
let _guard = setup()?;
|
||||
let data = ItemData::from_cbor(CborValue::Bytes(vec![1, 2, 3, 4, 5]));
|
||||
let v: Vec<u8> = data.native_clone()?;
|
||||
assert_eq!(v, vec![1, 2, 3, 4, 5]);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_native_clone_usize_from_cbor() -> MizeResult<()> {
|
||||
let _guard = setup()?;
|
||||
let data = ItemData::from_cbor(CborValue::Integer(99.into()));
|
||||
let u: usize = data.native_clone()?;
|
||||
assert_eq!(u, 99);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_native_registry_still_works() -> MizeResult<()> {
|
||||
let _guard = setup()?;
|
||||
// Store a custom type in the registry
|
||||
let data = ItemData::from_native("registry value".to_string())?;
|
||||
let s: String = data.native_clone()?;
|
||||
assert_eq!(s, "registry value");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_native_take_registry() -> MizeResult<()> {
|
||||
let _guard = setup()?;
|
||||
let data = ItemData::from_native(777i32)?;
|
||||
let i: i32 = data.native_take()?;
|
||||
assert_eq!(i, 777);
|
||||
// Second take should fail (already removed from registry)
|
||||
let result: MizeResult<i32> = data.native_take();
|
||||
assert!(result.is_err());
|
||||
Ok(())
|
||||
}
|
||||
|
||||
struct TestNative {
|
||||
value: i32,
|
||||
}
|
||||
|
||||
impl FromItemData for TestNative {
|
||||
fn from_itemdata(data: ItemData) -> MizeResult<Self> {
|
||||
let value = data.get_path("value")?.value_u32()? as i32;
|
||||
Ok(TestNative { value })
|
||||
}
|
||||
}
|
||||
|
||||
struct IdWrapper {
|
||||
native_id: u32,
|
||||
}
|
||||
|
||||
impl FromItemData for IdWrapper {
|
||||
fn from_itemdata(data: ItemData) -> MizeResult<Self> {
|
||||
Ok(IdWrapper { native_id: data.get_path("_native")?.value_u32()? })
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_native_take_from_registry() -> MizeResult<()> {
|
||||
let _guard = setup()?;
|
||||
let data = ItemData::from_native(TestNative { value: 5 })?;
|
||||
let v: TestNative = data.native_take_from()?;
|
||||
assert_eq!(v.value, 5);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_native_take_from_itemdata() -> MizeResult<()> {
|
||||
let _guard = setup()?;
|
||||
let mut data = ItemData::new();
|
||||
data.set_path("value", 42u32)?;
|
||||
let v: TestNative = data.native_take_from()?;
|
||||
assert_eq!(v.value, 42);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_native_take_from_wrapper_keeps_registry_value() -> MizeResult<()> {
|
||||
let _guard = setup()?;
|
||||
// registry holds a u32, but we take an IdWrapper which builds itself from the _native field
|
||||
let data = ItemData::from_native(777u32)?;
|
||||
let expected_id = data.get_path("_native")?.value_u32()?;
|
||||
let w: IdWrapper = data.native_take_from()?;
|
||||
assert_eq!(w.native_id, expected_id);
|
||||
// the u32 was not consumed and can still be taken
|
||||
let v: u32 = data.native_take()?;
|
||||
assert_eq!(v, 777);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_native_from_registry() -> MizeResult<()> {
|
||||
let _guard = setup()?;
|
||||
let data = ItemData::from_native(TestNative { value: 9 })?;
|
||||
let guard = data.native_from::<TestNative>()?;
|
||||
assert_eq!((*guard).value, 9);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_native_mut_from_itemdata() -> MizeResult<()> {
|
||||
let _guard = setup()?;
|
||||
let mut data = ItemData::new();
|
||||
data.set_path("value", 1u32)?;
|
||||
{
|
||||
let mut guard = data.native_mut_from::<TestNative>()?;
|
||||
(*guard).value += 10;
|
||||
assert_eq!((*guard).value, 11);
|
||||
}
|
||||
// a fresh guard is constructed from the CBOR content again
|
||||
let guard2 = data.native_from::<TestNative>()?;
|
||||
assert_eq!((*guard2).value, 1);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_native_clone_unsupported_type_fails() -> MizeResult<()> {
|
||||
let _guard = setup()?;
|
||||
// HashMap is not a supported hardcoded type
|
||||
let data = ItemData::from_string("test");
|
||||
let result: MizeResult<std::collections::HashMap<String, String>> = data.native_clone();
|
||||
assert!(result.is_err());
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_native_clone_wrong_type_fails() -> MizeResult<()> {
|
||||
let _guard = setup()?;
|
||||
// ItemData has Text, but we ask for bool
|
||||
let data = ItemData::from_string("not a bool");
|
||||
let result: MizeResult<bool> = data.native_clone();
|
||||
assert!(result.is_err());
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_native_clone_integer_overflow_fails() -> MizeResult<()> {
|
||||
let _guard = setup()?;
|
||||
// i128 value too large for u8
|
||||
let data = ItemData::from_cbor(CborValue::Integer(300.into()));
|
||||
let result: MizeResult<u8> = data.native_clone();
|
||||
assert!(result.is_err());
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -79,6 +79,7 @@ pub mod prelude {
|
||||
pub use error::MizeError;
|
||||
pub use crate::mize_err;
|
||||
pub use error::MizeResult;
|
||||
pub use error::IntoMizeResult;
|
||||
pub use item::{Item, ItemData, IntoItemData};
|
||||
pub use config::*;
|
||||
pub use api::*;
|
||||
@@ -107,7 +108,7 @@ pub use error::MizeError;
|
||||
pub use error::MizeResult;
|
||||
pub use instance::Mize;
|
||||
pub use instance::{
|
||||
MizePart,
|
||||
MizePart, PartMetadata, OptMetadata, MethodMetadata, NativeMetadata,
|
||||
};
|
||||
pub use mize_macros::*;
|
||||
use std::path::PathBuf;
|
||||
|
||||
@@ -33,7 +33,7 @@ use crate::MizeResult;
|
||||
static DEFAULT_LOG_LEVEL: LevelFilter = LevelFilter::WARN;
|
||||
|
||||
pub fn init_logger(cli_matches: &ArgMatches) {
|
||||
println!("init_logger called");
|
||||
trace!("init_logger called");
|
||||
// to save log messages, before the logger is setup
|
||||
let mut log_messages: Vec<(Level, String)> = Vec::new();
|
||||
|
||||
@@ -69,7 +69,7 @@ pub fn init_logger(cli_matches: &ArgMatches) {
|
||||
// check env LOG
|
||||
let mut val_mize_log = level_from_env("LOG", log_level);
|
||||
if let Some(level_mize_log) = val_mize_log.0 {
|
||||
println!("set log level from the Variable LOG to {}", level_mize_log);
|
||||
debug!("set log level from the Variable LOG to {}", level_mize_log);
|
||||
log_level = level_mize_log;
|
||||
log_messages.push((
|
||||
Level::TRACE,
|
||||
@@ -172,7 +172,7 @@ pub fn init_logger(cli_matches: &ArgMatches) {
|
||||
|
||||
//MinimalTracer::register();
|
||||
|
||||
println!("log_level: {}", log_level);
|
||||
debug!("log_level: {}", log_level);
|
||||
let field_formatter = tracing_subscriber::fmt::format::debug_fn(|writer, field, value| {
|
||||
if field.name() == "message" {
|
||||
write!(writer, "{:?}", value)?;
|
||||
@@ -207,7 +207,7 @@ fn level_from_env(
|
||||
) -> (Option<LevelFilter>, Vec<(Level, String)>) {
|
||||
let mut log_messages: Vec<(Level, String)> = Vec::new();
|
||||
let var_value = env::var(var_name);
|
||||
println!("checking env var: {}={:?}", var_name, var_value);
|
||||
debug!("checking env var: {}={:?}", var_name, var_value);
|
||||
|
||||
match var_value {
|
||||
Ok(val) => {
|
||||
|
||||
@@ -173,8 +173,8 @@ pub fn get_part_hash(
|
||||
) -> MizeResult<(String, String)> {
|
||||
let mut selector_data = mize.get("self/config/selector")?.as_data_full()?;
|
||||
|
||||
// handle the case, when we want a module for not the system we are running on...
|
||||
println!("get_module_hash: name: {}", name);
|
||||
// handle the case, when we want a part for not the system we are running on...
|
||||
debug!("get_part_hash: name: {}", name);
|
||||
let mut name_parts = name.split(".");
|
||||
if name_parts
|
||||
.next()
|
||||
@@ -185,7 +185,7 @@ pub fn get_part_hash(
|
||||
.next()
|
||||
.ok_or(mize_err!("no 1th element in the modName"))?;
|
||||
let mod_name: String = name_parts.collect();
|
||||
println!("get_module_hash: mod_name: {}", mod_name);
|
||||
println!("get_part_hash: mod_name: {}", mod_name);
|
||||
if mod_name == "" {
|
||||
return Err(mize_err!("no 2nd element in the modName"));
|
||||
}
|
||||
@@ -317,12 +317,9 @@ pub fn load_part(mize: &mut Mize, part_name: &str, path: Option<String>) -> Mize
|
||||
unsafe extern "C" fn() -> MizeResult<()>,
|
||||
> = unsafe { lib.get(format!("part_{}", part_name).as_bytes())? };
|
||||
|
||||
let mut cur_eval_part = mize.cur_eval_part.lock()?;
|
||||
*cur_eval_part = Some(mize.clone().id_new_dots(part_name)?);
|
||||
println!("running part: {}", cur_eval_part.clone().unwrap());
|
||||
drop(cur_eval_part);
|
||||
|
||||
mize.clone().set_cur_eval_part(Some(mize.clone().id_new_dots(part_name)?))?;
|
||||
unsafe { func()? };
|
||||
mize.clone().set_cur_eval_part(None)?;
|
||||
|
||||
let mut cur_eval_part = mize.cur_eval_part.lock()?;
|
||||
*cur_eval_part = None;
|
||||
|
||||
@@ -175,10 +175,7 @@ in {
|
||||
pkgs.pkg-config
|
||||
pkgs.gobject-introspection
|
||||
pkgs.gobject-introspection.dev
|
||||
pkgs.systemd
|
||||
pkgs.rsync
|
||||
pkgs.deno
|
||||
pkgs.openssh
|
||||
|
||||
# thanks to: https://github.com/NixOS/nixpkgs/pull/496279/changes
|
||||
(pkgs.buildWasmBindgenCli rec {
|
||||
|
||||
@@ -215,7 +215,7 @@ pub fn include_native(input: TokenStream) -> TokenStream {
|
||||
fn generate_arg_extraction(args: &[(Ident, Type)]) -> Vec<proc_macro2::TokenStream> {
|
||||
args.iter().enumerate().map(|(i, (arg_name, arg_type))| {
|
||||
quote! {
|
||||
let #arg_name = args.arr_nth(#i)?.native_clone::<#arg_type>()?;
|
||||
let #arg_name = args.get_path(#i)?.native_clone::<#arg_type>()?;
|
||||
}
|
||||
}).collect()
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use clap::{ArgMatches, Command};
|
||||
use mize::item::ItemData;
|
||||
use std::collections::HashMap;
|
||||
use std::{collections::HashMap, ffi::OsString};
|
||||
use mize::prelude::*;
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
@@ -13,7 +13,6 @@ pub extern "C" fn part_cli() -> MizeResult<()> {
|
||||
|
||||
opt("state")
|
||||
.fun(Box::new(|| {
|
||||
println!("hiiiiii");
|
||||
let mize = get_global_mize().unwrap();
|
||||
let state = CliPartState {
|
||||
cmds: HashMap::new(),
|
||||
@@ -22,15 +21,13 @@ pub extern "C" fn part_cli() -> MizeResult<()> {
|
||||
parsers: Some(Vec::new()),
|
||||
mize: mize.clone(),
|
||||
};
|
||||
println!("hooooo");
|
||||
Ok(ItemData::from_native(state)?)
|
||||
}));
|
||||
|
||||
|
||||
method("set_command", |args| {
|
||||
println!("in set command");
|
||||
let mut state = get_config("self.state")?.native_mut::<CliPartState>()?;
|
||||
let cmd = args.arr_nth(0)?.native::<String>()?;
|
||||
let cmd = args.get_path("0")?.native::<String>()?;
|
||||
state.current_cmd_name = cmd.to_owned();
|
||||
if !state.cmds.contains_key(&*cmd) {
|
||||
state.cmds
|
||||
@@ -40,10 +37,9 @@ pub extern "C" fn part_cli() -> MizeResult<()> {
|
||||
});
|
||||
|
||||
method("subcommand", |args| {
|
||||
println!("in subcommand");
|
||||
let mut state = get_config("self.state")?.native_mut::<CliPartState>()?;
|
||||
let subcmd = args.arr_nth(0)?.native_take::<clap::Command>()?;
|
||||
let action = args.arr_nth(1)?.native_take::<Box<dyn Fn(ArgMatches) -> MizeResult<()> + Send + Sync>>()?;
|
||||
let subcmd = args.get_path("0")?.native_take::<clap::Command>()?;
|
||||
let action = args.get_path("1")?;
|
||||
let name = subcmd.get_name().to_string();
|
||||
let cur_cmd_name = state.current_cmd_name.clone();
|
||||
let cmd = state
|
||||
@@ -56,6 +52,23 @@ pub extern "C" fn part_cli() -> MizeResult<()> {
|
||||
Ok(ItemData::new())
|
||||
});
|
||||
|
||||
method("run", |args| {
|
||||
let cmdline = args.get_path("0")?.native_take::<Vec<String>>()?;
|
||||
let mut state = get_config("self.state")?.native_mut::<CliPartState>()?;
|
||||
let cmd_name = cmdline.iter().nth(0).ok_or(mize_err!("empty cmdline"))?;
|
||||
let cmd = state.cmds.remove(cmd_name).ok_or(mize_err!("cmd {} not found in CliPart", cmd_name))?;
|
||||
|
||||
let matches = cmd.get_matches_from(cmdline);
|
||||
let sub_name = matches.subcommand_name().ok_or(mize_err!("subcommand not found"))?;
|
||||
let sub_matches = matches.subcommand_matches(sub_name).ok_or(mize_err!("subcommand matches not found"))?;
|
||||
let action = state.actions.remove(sub_name).ok_or(mize_err!("action for subcommand not found"))?;
|
||||
let mut args = ItemData::new();
|
||||
args.set_path("0", ItemData::from_native(sub_matches.clone())?);
|
||||
action.call(args)?;
|
||||
|
||||
Ok(ItemData::new())
|
||||
});
|
||||
|
||||
// TODO in future....
|
||||
// Register methods from the impl block
|
||||
//CliPartState::methods_from_impl()?;
|
||||
@@ -67,7 +80,7 @@ pub struct CliPartState {
|
||||
cmds: HashMap<String, Command>,
|
||||
current_cmd_name: String,
|
||||
actions:
|
||||
HashMap<String, Box<dyn Fn(ArgMatches) -> MizeResult<()> + Send + Sync + 'static>>,
|
||||
HashMap<String, ItemData>,
|
||||
parsers: Option<Vec<Box<dyn Fn(Mize, Vec<String>) -> MizeResult<()> + Send + Sync + 'static>>>,
|
||||
mize: Mize,
|
||||
}
|
||||
|
||||
167
parts/mize.dev/src/codegen.rs
Normal file
167
parts/mize.dev/src/codegen.rs
Normal file
@@ -0,0 +1,167 @@
|
||||
//! Code generation for mize bindings.
|
||||
//!
|
||||
//! Takes recorded PartMetadata (collected while in generate mode) and produces
|
||||
//! a Rust source file that looks like the hand-written `generated.rs`.
|
||||
|
||||
use mize::prelude::*;
|
||||
use mize::instance::{PartMetadata, MethodMetadata, OptMetadata, NativeMetadata};
|
||||
use std::fmt::Write as _;
|
||||
|
||||
/// Convert a part name like "cli" into a PascalCase struct name like "CliPart".
|
||||
fn part_name_to_struct(part_name: &str) -> String {
|
||||
let mut out = String::new();
|
||||
let mut upper_next = true;
|
||||
for c in part_name.chars() {
|
||||
if c == '.' || c == '-' || c == '_' || c == ' ' {
|
||||
upper_next = true;
|
||||
continue;
|
||||
}
|
||||
if upper_next {
|
||||
out.extend(c.to_uppercase());
|
||||
upper_next = false;
|
||||
} else {
|
||||
out.push(c);
|
||||
}
|
||||
}
|
||||
out.push_str("Part");
|
||||
out
|
||||
}
|
||||
|
||||
/// Generate the full `src/lib.rs` content for the generated bindings package.
|
||||
pub fn generate_lib_rs(all_metadata: &[PartMetadata]) -> String {
|
||||
let mut out = String::new();
|
||||
out.push_str("// AUTO-GENERATED by mize dev gen -- do not edit manually\n");
|
||||
out.push_str("use mize::prelude::*;\n\n\n");
|
||||
|
||||
for meta in all_metadata {
|
||||
generate_part_struct(&mut out, meta);
|
||||
}
|
||||
|
||||
out
|
||||
}
|
||||
|
||||
fn generate_part_struct(out: &mut String, meta: &PartMetadata) {
|
||||
let struct_name = part_name_to_struct(&meta.part_name);
|
||||
let part_id_str = meta.part_name.replace('.', ".");
|
||||
|
||||
// struct definition
|
||||
let _ = writeln!(out, "pub struct {struct_name} {{");
|
||||
let _ = writeln!(out, " id: MizeId");
|
||||
let _ = writeln!(out, "}}");
|
||||
let _ = writeln!(out);
|
||||
|
||||
// MizePart impl
|
||||
let _ = writeln!(out, "impl MizePart for {struct_name} {{");
|
||||
let _ = writeln!(out, " fn id(&self) -> MizeId {{");
|
||||
let _ = writeln!(out, " self.id.clone()");
|
||||
let _ = writeln!(out, " }}");
|
||||
let _ = writeln!(out, "}}");
|
||||
let _ = writeln!(out);
|
||||
|
||||
// inherent impl
|
||||
let _ = writeln!(out, "impl {struct_name} {{");
|
||||
|
||||
// get() constructor
|
||||
let _ = writeln!(out, " pub fn get(name: &str) -> MizeResult<{struct_name}> {{");
|
||||
let _ = writeln!(out, " let mut mize = get_global_mize()?;");
|
||||
let _ = writeln!(out, " let part = mize.get_part(name)?;");
|
||||
let _ = writeln!(out, " Ok({struct_name} {{ id: part.id() }})");
|
||||
let _ = writeln!(out, " }}");
|
||||
|
||||
// one method per recorded method/effect/thread
|
||||
for m in meta.methods.iter().chain(meta.effects.iter()).chain(meta.threads.iter()) {
|
||||
generate_method_fn(out, &struct_name, m);
|
||||
}
|
||||
|
||||
// one getter-ish fn per recorded opt (opts are config values; expose a dyn_call getter)
|
||||
for o in &meta.opts {
|
||||
generate_opt_fn(out, &struct_name, o);
|
||||
}
|
||||
|
||||
let _ = writeln!(out, "}}");
|
||||
let _ = writeln!(out);
|
||||
|
||||
// native type wrappers
|
||||
for n in &meta.natives {
|
||||
generate_native_struct(out, n);
|
||||
}
|
||||
|
||||
let _ = writeln!(out, "// part id string: {part_id_str}");
|
||||
let _ = writeln!(out);
|
||||
}
|
||||
|
||||
fn generate_method_fn(out: &mut String, _struct_name: &str, m: &MethodMetadata) {
|
||||
let fn_name = &m.name;
|
||||
let _ = writeln!(
|
||||
out,
|
||||
" pub fn {fn_name}(&self, args: ItemData) -> MizeResult<ItemData> {{"
|
||||
);
|
||||
let _ = writeln!(out, " dyn_call(self.id.clone(), \"{fn_name}\", args)?;");
|
||||
// note: dyn_call returns ItemData already, so wrap:
|
||||
let _ = writeln!(out, " dyn_call(self.id.clone(), \"{fn_name}\", args)");
|
||||
let _ = writeln!(out, " }}");
|
||||
let _ = writeln!(out);
|
||||
}
|
||||
|
||||
fn generate_opt_fn(out: &mut String, _struct_name: &str, o: &OptMetadata) {
|
||||
let fn_name = &o.name;
|
||||
let _ = writeln!(
|
||||
out,
|
||||
" pub fn opt_{fn_name}(&self) -> MizeResult<ItemData> {{"
|
||||
);
|
||||
let _ = writeln!(out, " get_config(&format!(\"{{}}/{fn_name}\", self.id.dot()))");
|
||||
let _ = writeln!(out, " }}");
|
||||
let _ = writeln!(out);
|
||||
}
|
||||
|
||||
fn generate_native_struct(out: &mut String, n: &NativeMetadata) {
|
||||
let struct_name = &n.name;
|
||||
let _ = writeln!(out, "// native type: {} ({})", n.name, n.type_name);
|
||||
let _ = writeln!(out, "pub struct {struct_name} {{");
|
||||
let _ = writeln!(out, " native_id: u32");
|
||||
let _ = writeln!(out, "}}");
|
||||
let _ = writeln!(out);
|
||||
let _ = writeln!(out, "impl {struct_name} {{");
|
||||
for assoc in &n.associated {
|
||||
let _ = writeln!(
|
||||
out,
|
||||
" pub fn {assoc}(args: ItemData) -> MizeResult<ItemData> {{"
|
||||
);
|
||||
let _ = writeln!(out, " let mut mize = get_global_mize()?;");
|
||||
let _ = writeln!(out, " let id = mize.id_new_dots(\"{}.{assoc}\")?;", n.name);
|
||||
let _ = writeln!(out, " dyn_call(id, \"{assoc}\", args)");
|
||||
let _ = writeln!(out, " }}");
|
||||
}
|
||||
for meth in &n.methods {
|
||||
let _ = writeln!(
|
||||
out,
|
||||
" pub fn {meth}(&self, args: ItemData) -> MizeResult<ItemData> {{"
|
||||
);
|
||||
let _ = writeln!(out, " let mut mize = get_global_mize()?;");
|
||||
let _ = writeln!(out, " let id = mize.id_new_dots(\"{}.{meth}\")?;", n.name);
|
||||
let _ = writeln!(out, " dyn_call(id, \"{meth}\", args)");
|
||||
let _ = writeln!(out, " }}");
|
||||
}
|
||||
let _ = writeln!(out, "}}");
|
||||
let _ = writeln!(out);
|
||||
}
|
||||
|
||||
/// Generate the `Cargo.toml` content for the generated package.
|
||||
pub fn generate_cargo_toml(package_name: &str) -> String {
|
||||
format!(
|
||||
r#"[package]
|
||||
name = "{package_name}"
|
||||
version = "0.1.0"
|
||||
edition = "2024"
|
||||
|
||||
[lib]
|
||||
name = "{lib_name}"
|
||||
path = "src/lib.rs"
|
||||
|
||||
[dependencies]
|
||||
mize = {{ path = "../../core" }}
|
||||
"#,
|
||||
package_name = package_name,
|
||||
lib_name = package_name.replace('-', "_"),
|
||||
)
|
||||
}
|
||||
@@ -34,7 +34,11 @@ impl CliPart {
|
||||
pub fn subcommand<T: Fn(ArgMatches) -> MizeResult<()> + Send + Sync + 'static>(&self, subcmd: Command, action: T) -> MizeResult<()> {
|
||||
let mut subcmd_arg = ItemData::new();
|
||||
subcmd_arg.set_path("_native", subcmd.native_id)?;
|
||||
let mut action_arg = ItemData::from_native(action)?;
|
||||
let mut action_arg = ItemData::fun(Box::new(move |args: ItemData| {
|
||||
let matches = args.clone().get_path("0")?.native_take_from::<ArgMatches>()?;
|
||||
action(matches)?;
|
||||
Ok(ItemData::new())
|
||||
}))?;
|
||||
let mut args = ItemData::new();
|
||||
args.set_path("0", subcmd_arg)?;
|
||||
args.set_path("1", action_arg)?;
|
||||
@@ -52,6 +56,12 @@ pub struct ArgMatches {
|
||||
native_id: u32
|
||||
}
|
||||
|
||||
impl FromItemData for ArgMatches {
|
||||
fn from_itemdata(data: ItemData) -> MizeResult<Self> {
|
||||
Ok(ArgMatches { native_id: data.get_path("_native")?.value_u32()? })
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Action {
|
||||
native_id: u32
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use std::fs::create_dir_all;
|
||||
use std::fs::{create_dir_all, write};
|
||||
use mize::prelude::*;
|
||||
use cmd_lib::run_cmd as sh;
|
||||
use mize::tracing::info;
|
||||
@@ -6,6 +6,7 @@ use mize::tracing::info;
|
||||
// a static version of what would be generated.
|
||||
// as we need mize.dev to generate anything.
|
||||
mod generated;
|
||||
mod codegen;
|
||||
use generated::*;
|
||||
|
||||
// spacetime gen command: sh!(RUST_LOG=error RUST_BACKTRACE=0 spacetime generate --build-options="--lint-dir=" -p $SRC/mize/marts/spacetime -o $SRC/mize/marts/generated/src/spacetime --lang rust)?;
|
||||
@@ -17,13 +18,16 @@ extern "C" fn part_dev() -> MizeResult<()> {
|
||||
cli.set_command("dev");
|
||||
|
||||
cli.subcommand(Command::new("test")?, |_| {println!("hello from test command"); Ok(())})?;
|
||||
//cli.subcommand(Command::new("publish").alias("p"), publish_mize)?;
|
||||
//cli.subcommand(Command::new("genBindings").alias("b"), gen_bindings)?;
|
||||
cli.subcommand(Command::new("generate")?, gen_bindings)?;
|
||||
cli.subcommand(Command::new("gen")?, gen_bindings)?; // alias for generate
|
||||
cli.subcommand(Command::new("publish")?, publish_mize)?;
|
||||
cli.subcommand(Command::new("p")?, publish_mize)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn publish_mize(_ : &ArgMatches, mut mize: Mize) -> MizeResult<()> {
|
||||
pub fn publish_mize(_ : ArgMatches) -> MizeResult<()> {
|
||||
let mut mize = get_global_mize()?;
|
||||
let mize_src = mize.get_config("mize.src")?.value_string()?;
|
||||
let cargo_dir = std::env::home_dir().unwrap().join(".cargo");
|
||||
|
||||
@@ -57,7 +61,77 @@ pub fn publish_mize(_ : &ArgMatches, mut mize: Mize) -> MizeResult<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn gen_bindings(_ : &ArgMatches, mize: Mize) -> MizeResult<()> {
|
||||
|
||||
/// The `dev generate` command (alias `gen` to be added once bindings support it).
|
||||
///
|
||||
/// Reads a `part` TOML file from the current directory, loads the parts listed
|
||||
/// in `gen = [...]` in generate mode (recording metadata instead of executing),
|
||||
/// then writes a full Cargo package with Rust bindings to `<cwd>/generated/`.
|
||||
pub fn gen_bindings(_ : ArgMatches) -> MizeResult<()> {
|
||||
let mut mize = get_global_mize()?;
|
||||
|
||||
// 1. read the "part" file from the current directory
|
||||
let cwd = std::env::current_dir().map_err(|e| mize_err!("could not get current dir: {e}"))?;
|
||||
let part_file = cwd.join("part");
|
||||
info!("reading part description file: {}", part_file.display());
|
||||
let part_toml = std::fs::read_to_string(&part_file)
|
||||
.map_err(|e| mize_err!("could not read part file {}: {e}", part_file.display()))?;
|
||||
let part_data = ItemData::from_toml(&part_toml)?;
|
||||
|
||||
// 2. parse the part name and the gen = [...] list
|
||||
let part_name: String = part_data
|
||||
.get_path("name")
|
||||
.ok()
|
||||
.and_then(|v| v.value_string().ok())
|
||||
.ok_or_else(|| mize_err!("part file must have a 'name' field"))?;
|
||||
info!("part name: {part_name}");
|
||||
|
||||
let gen_data = part_data
|
||||
.get_path("gen")
|
||||
.map_err(|_| mize_err!("part file must have a 'gen' array, eg: gen = [\"cli\", \"auth\"]"))?;
|
||||
let gen_arr = gen_data
|
||||
.cbor()
|
||||
.as_array()
|
||||
.ok_or_else(|| mize_err!("'gen' is not an array"))?;
|
||||
let mut gen_parts: Vec<String> = Vec::new();
|
||||
for v in gen_arr.iter() {
|
||||
let s = v.as_text().ok_or_else(|| mize_err!("'gen' array must contain only strings"))?;
|
||||
gen_parts.push(s.to_owned());
|
||||
}
|
||||
info!("parts to generate bindings for: {:?}", gen_parts);
|
||||
|
||||
// 3. enable generate mode and clear any old metadata
|
||||
mize.set_generate_mode(true)?;
|
||||
mize.clear_metadata()?;
|
||||
|
||||
// 4. load each part — in generate mode their part_smth funcs only record metadata
|
||||
for p in &gen_parts {
|
||||
info!("loading part in generate mode: {p}");
|
||||
mize.load_part(p)?;
|
||||
}
|
||||
|
||||
// 5. disable generate mode again
|
||||
mize.set_generate_mode(false)?;
|
||||
|
||||
// 6. collect all recorded metadata
|
||||
let all_metadata = mize.get_all_metadata()?;
|
||||
info!("recorded metadata for {} part(s)", all_metadata.len());
|
||||
|
||||
// 7. generate the bindings package at <cwd>/generated
|
||||
let gen_dir = cwd.join("generated");
|
||||
let src_dir = gen_dir.join("src");
|
||||
create_dir_all(&src_dir).map_err(|e| mize_err!("could not create generated/src dir: {e}"))?;
|
||||
|
||||
let package_name = format!("mize-generated-{part_name}");
|
||||
let cargo_toml = codegen::generate_cargo_toml(&package_name);
|
||||
write(gen_dir.join("Cargo.toml"), cargo_toml)
|
||||
.map_err(|e| mize_err!("could not write generated/Cargo.toml: {e}"))?;
|
||||
|
||||
let lib_rs = codegen::generate_lib_rs(&all_metadata);
|
||||
write(src_dir.join("lib.rs"), lib_rs)
|
||||
.map_err(|e| mize_err!("could not write generated/src/lib.rs: {e}"))?;
|
||||
|
||||
info!("bindings written to {}", gen_dir.display());
|
||||
println!("generated bindings package: {}", gen_dir.display());
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
6
parts/mize.ui/Cargo.toml
Normal file
6
parts/mize.ui/Cargo.toml
Normal file
@@ -0,0 +1,6 @@
|
||||
[package]
|
||||
name = "mize-part-mize-ui"
|
||||
version = "0.1.0"
|
||||
edition = "2024"
|
||||
|
||||
[dependencies]
|
||||
17
parts/mize.ui/dioxus-components/Cargo.toml
Normal file
17
parts/mize.ui/dioxus-components/Cargo.toml
Normal file
@@ -0,0 +1,17 @@
|
||||
[package]
|
||||
name = "dioxus-components"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
description = "Styled Dioxus UI components (mize palette)"
|
||||
license = "MIT OR Apache-2.0"
|
||||
|
||||
[dependencies]
|
||||
dioxus = { version = "0.7", default-features = false }
|
||||
dioxus-primitives = { git = "https://github.com/DioxusLabs/components", default-features = false }
|
||||
dioxus-code = { version = "0.1.0", default-features = false, features = ["macro", "lang-css"] }
|
||||
dioxus-icons = "0.1.0"
|
||||
|
||||
[features]
|
||||
default = []
|
||||
web = ["dioxus/web", "dioxus-primitives/web"]
|
||||
desktop = ["dioxus/desktop"]
|
||||
7
parts/mize.ui/dioxus-components/PLAN.md
Normal file
7
parts/mize.ui/dioxus-components/PLAN.md
Normal file
@@ -0,0 +1,7 @@
|
||||
# dioxus-components MVP plan (approved)
|
||||
|
||||
Library of styled Dioxus components copied from upstream preview, restyled with the mize palette (light + dark).
|
||||
|
||||
- MVP: button, input, textarea, label, checkbox, switch, dialog, separator, avatar, badge, card, scroll_area
|
||||
- dioxus-primitives via git (DioxusLabs/components) — crates.io is a stub
|
||||
- Plain Rust lib; consumers include theme.css and set `html[data-theme]`
|
||||
44
parts/mize.ui/dioxus-components/assets/theme.css
Normal file
44
parts/mize.ui/dioxus-components/assets/theme.css
Normal file
@@ -0,0 +1,44 @@
|
||||
/* mize UI theme — include once; toggle via html[data-theme="light"|"dark"] */
|
||||
|
||||
html[data-theme="dark"] {
|
||||
--dark: initial;
|
||||
--light: ;
|
||||
}
|
||||
|
||||
html[data-theme="light"] {
|
||||
--dark: ;
|
||||
--light: initial;
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
:root:not([data-theme]) {
|
||||
--dark: initial;
|
||||
--light: ;
|
||||
}
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: light) {
|
||||
:root:not([data-theme]) {
|
||||
--dark: ;
|
||||
--light: initial;
|
||||
}
|
||||
}
|
||||
|
||||
:root {
|
||||
--bg: var(--dark, #171d18) var(--light, #f7f8f4);
|
||||
--surface: var(--dark, #242d25) var(--light, #ffffff);
|
||||
--surface-2: var(--dark, #2c362d) var(--light, #eff2ea);
|
||||
--text: var(--dark, #f2f4ea) var(--light, #20251c);
|
||||
--text-2: var(--dark, #aab5a4) var(--light, #66705e);
|
||||
--border: var(--dark, #3a463a) var(--light, #dfe4d9);
|
||||
--accent: var(--dark, #d7e47d) var(--light, #fec251);
|
||||
--accent-text: var(--dark, #d7e47d) var(--light, #79500f);
|
||||
--success: var(--dark, #b9d987) var(--light, #526f32);
|
||||
--info: var(--dark, #b9e4eb) var(--light, #376777);
|
||||
--warning: var(--dark, #ffd77f) var(--light, #a66b13);
|
||||
--danger: var(--dark, #ff9b8e) var(--light, #b94d42);
|
||||
|
||||
/* On-accent fill text: dark ink on light accent, dark surface on dark accent */
|
||||
--on-accent: var(--dark, #171d18) var(--light, #79500f);
|
||||
--focus-ring: var(--accent);
|
||||
}
|
||||
@@ -0,0 +1,206 @@
|
||||
use dioxus::prelude::*;
|
||||
use dioxus_primitives::dioxus_attributes::attributes;
|
||||
use dioxus_primitives::avatar::{self, AvatarState};
|
||||
use dioxus_primitives::merge_attributes;
|
||||
|
||||
#[css_module("/src/components/avatar/style.css")]
|
||||
struct Styles;
|
||||
|
||||
#[derive(Clone, Copy, PartialEq, Default)]
|
||||
pub enum AvatarImageSize {
|
||||
#[default]
|
||||
Small,
|
||||
Medium,
|
||||
Large,
|
||||
}
|
||||
|
||||
impl AvatarImageSize {
|
||||
fn to_class(self) -> &'static str {
|
||||
match self {
|
||||
AvatarImageSize::Small => Styles::dx_avatar_sm.inner,
|
||||
AvatarImageSize::Medium => Styles::dx_avatar_md.inner,
|
||||
AvatarImageSize::Large => Styles::dx_avatar_lg.inner,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, PartialEq, Default)]
|
||||
pub enum AvatarShape {
|
||||
#[default]
|
||||
Circle,
|
||||
Rounded,
|
||||
}
|
||||
|
||||
impl AvatarShape {
|
||||
fn to_class(self) -> &'static str {
|
||||
match self {
|
||||
AvatarShape::Circle => Styles::dx_avatar_circle.inner,
|
||||
AvatarShape::Rounded => Styles::dx_avatar_rounded.inner,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// The props for the [`Avatar`] root component.
|
||||
#[derive(Props, Clone, PartialEq)]
|
||||
pub struct AvatarProps {
|
||||
/// Callback when image loads successfully.
|
||||
#[props(default)]
|
||||
pub on_load: Option<EventHandler<()>>,
|
||||
|
||||
/// Callback when image fails to load.
|
||||
#[props(default)]
|
||||
pub on_error: Option<EventHandler<()>>,
|
||||
|
||||
/// Callback when the avatar state changes.
|
||||
#[props(default)]
|
||||
pub on_state_change: Option<EventHandler<AvatarState>>,
|
||||
|
||||
#[props(default)]
|
||||
pub size: AvatarImageSize,
|
||||
|
||||
#[props(default)]
|
||||
pub shape: AvatarShape,
|
||||
|
||||
/// Additional attributes for the avatar element.
|
||||
#[props(extends = GlobalAttributes)]
|
||||
pub attributes: Vec<Attribute>,
|
||||
|
||||
/// The fallback content shown while the image is loading or if it fails to load.
|
||||
pub children: Element,
|
||||
}
|
||||
|
||||
#[component]
|
||||
pub fn Avatar(props: AvatarProps) -> Element {
|
||||
let class = format!(
|
||||
"{} {} {}",
|
||||
Styles::dx_avatar,
|
||||
props.size.to_class(),
|
||||
props.shape.to_class()
|
||||
);
|
||||
let base = attributes!(span {
|
||||
class
|
||||
});
|
||||
let merged = merge_attributes(vec![base, props.attributes]);
|
||||
|
||||
rsx! {
|
||||
avatar::Avatar {
|
||||
on_load: props.on_load,
|
||||
on_error: props.on_error,
|
||||
on_state_change: props.on_state_change,
|
||||
attributes: merged,
|
||||
{props.children}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Props, Clone, PartialEq)]
|
||||
pub struct AvatarImageProps {
|
||||
#[props(default)]
|
||||
pub id: ReadSignal<Option<String>>,
|
||||
|
||||
pub src: String,
|
||||
|
||||
#[props(default)]
|
||||
pub alt: String,
|
||||
|
||||
#[props(extends = GlobalAttributes)]
|
||||
pub attributes: Vec<Attribute>,
|
||||
}
|
||||
|
||||
#[component]
|
||||
pub fn AvatarImage(props: AvatarImageProps) -> Element {
|
||||
let base = attributes!(img {
|
||||
class: Styles::dx_avatar_image,
|
||||
draggable: "false",
|
||||
});
|
||||
let merged = merge_attributes(vec![base, props.attributes]);
|
||||
|
||||
rsx! {
|
||||
avatar::AvatarImage {
|
||||
id: props.id,
|
||||
src: props.src,
|
||||
alt: props.alt,
|
||||
attributes: merged,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Props, Clone, PartialEq)]
|
||||
pub struct AvatarFallbackProps {
|
||||
#[props(extends = GlobalAttributes)]
|
||||
pub attributes: Vec<Attribute>,
|
||||
|
||||
pub children: Element,
|
||||
}
|
||||
|
||||
#[component]
|
||||
pub fn AvatarFallback(props: AvatarFallbackProps) -> Element {
|
||||
let base = attributes!(span {
|
||||
class: Styles::dx_avatar_fallback,
|
||||
});
|
||||
let merged = merge_attributes(vec![base, props.attributes]);
|
||||
|
||||
rsx! {
|
||||
avatar::AvatarFallback {
|
||||
attributes: merged,
|
||||
{props.children}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// The props for the [`ImageAvatar`] convenience component.
|
||||
#[derive(Props, Clone, PartialEq)]
|
||||
pub struct ImageAvatarProps {
|
||||
/// The image source URL.
|
||||
pub src: String,
|
||||
|
||||
/// The image alt text.
|
||||
#[props(default)]
|
||||
pub alt: String,
|
||||
|
||||
/// Callback when image loads successfully.
|
||||
#[props(default)]
|
||||
pub on_load: Option<EventHandler<()>>,
|
||||
|
||||
/// Callback when image fails to load.
|
||||
#[props(default)]
|
||||
pub on_error: Option<EventHandler<()>>,
|
||||
|
||||
/// Callback when the avatar state changes.
|
||||
#[props(default)]
|
||||
pub on_state_change: Option<EventHandler<AvatarState>>,
|
||||
|
||||
#[props(default)]
|
||||
pub size: AvatarImageSize,
|
||||
|
||||
#[props(default)]
|
||||
pub shape: AvatarShape,
|
||||
|
||||
/// Additional attributes for the avatar element.
|
||||
#[props(extends = GlobalAttributes)]
|
||||
pub attributes: Vec<Attribute>,
|
||||
|
||||
/// The fallback content shown while the image is loading or if it fails to load.
|
||||
pub children: Element,
|
||||
}
|
||||
|
||||
#[component]
|
||||
pub fn ImageAvatar(props: ImageAvatarProps) -> Element {
|
||||
rsx! {
|
||||
Avatar {
|
||||
on_load: props.on_load,
|
||||
on_error: props.on_error,
|
||||
on_state_change: props.on_state_change,
|
||||
size: props.size,
|
||||
shape: props.shape,
|
||||
attributes: props.attributes,
|
||||
AvatarImage {
|
||||
src: props.src,
|
||||
alt: props.alt,
|
||||
}
|
||||
AvatarFallback {
|
||||
{props.children}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
mod component;
|
||||
pub use component::*;
|
||||
@@ -0,0 +1,84 @@
|
||||
.dx-avatar {
|
||||
position: relative;
|
||||
display: inline-flex;
|
||||
overflow: hidden;
|
||||
width: 64px;
|
||||
height: 64px;
|
||||
flex-shrink: 0;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: var(--text);
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.dx-avatar-image {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
aspect-ratio: 1;
|
||||
object-fit: cover;
|
||||
}
|
||||
|
||||
.dx-avatar-sm {
|
||||
width: 2rem;
|
||||
height: 2rem;
|
||||
font-size: 0.875rem;
|
||||
}
|
||||
|
||||
.dx-avatar-md {
|
||||
width: 2.5rem;
|
||||
height: 2.5rem;
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
.dx-avatar-lg {
|
||||
width: 3.5rem;
|
||||
height: 3.5rem;
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
|
||||
.dx-avatar-circle {
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
.dx-avatar-rounded {
|
||||
border-radius: 0.375rem;
|
||||
}
|
||||
|
||||
.dx-avatar[data-state="loading"] {
|
||||
animation: dx-pulse 1.5s infinite ease-in-out;
|
||||
background: var(--surface-2);
|
||||
}
|
||||
|
||||
.dx-avatar[data-state="empty"] {
|
||||
background: var(--surface-2);
|
||||
}
|
||||
|
||||
@keyframes dx-pulse {
|
||||
0% {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
50% {
|
||||
opacity: 0.7;
|
||||
}
|
||||
|
||||
100% {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
.dx-avatar-fallback {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background: var(--surface-2);
|
||||
color: var(--text-2);
|
||||
font-size: inherit;
|
||||
}
|
||||
|
||||
.dx-avatar[data-state="error"] .dx-avatar-fallback {
|
||||
background: var(--surface-2);
|
||||
color: var(--text-2);
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
use dioxus::prelude::*;
|
||||
use dioxus_icons::lucide::BadgeCheck;
|
||||
|
||||
#[css_module("/src/components/badge/style.css")]
|
||||
struct Styles;
|
||||
|
||||
#[derive(Copy, Clone, PartialEq, Default)]
|
||||
#[non_exhaustive]
|
||||
pub enum BadgeVariant {
|
||||
#[default]
|
||||
Primary,
|
||||
Secondary,
|
||||
Destructive,
|
||||
Outline,
|
||||
}
|
||||
|
||||
impl BadgeVariant {
|
||||
pub fn class(&self) -> &'static str {
|
||||
match self {
|
||||
BadgeVariant::Primary => "primary",
|
||||
BadgeVariant::Secondary => "secondary",
|
||||
BadgeVariant::Destructive => "destructive",
|
||||
BadgeVariant::Outline => "outline",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// The props for the [`Badge`] component.
|
||||
#[derive(Props, Clone, PartialEq)]
|
||||
pub struct BadgeProps {
|
||||
#[props(default)]
|
||||
pub variant: BadgeVariant,
|
||||
|
||||
/// Additional attributes to extend the badge element
|
||||
#[props(extends = GlobalAttributes)]
|
||||
pub attributes: Vec<Attribute>,
|
||||
|
||||
/// The children of the badge element
|
||||
pub children: Element,
|
||||
}
|
||||
|
||||
#[component]
|
||||
pub fn Badge(props: BadgeProps) -> Element {
|
||||
rsx! {
|
||||
BadgeElement {
|
||||
"padding": true,
|
||||
variant: props.variant,
|
||||
attributes: props.attributes,
|
||||
{props.children}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[component]
|
||||
fn BadgeElement(props: BadgeProps) -> Element {
|
||||
rsx! {
|
||||
span {
|
||||
class: Styles::dx_badge,
|
||||
"data-style": props.variant.class(),
|
||||
..props.attributes,
|
||||
{props.children}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[component]
|
||||
pub fn VerifiedIcon() -> Element {
|
||||
rsx! {
|
||||
BadgeCheck {
|
||||
size: "12px",
|
||||
stroke: "var(--text)",
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
mod component;
|
||||
pub use component::*;
|
||||
@@ -0,0 +1,37 @@
|
||||
.dx-badge {
|
||||
display: inline-flex;
|
||||
min-width: 20px;
|
||||
height: 20px;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border-radius: 9999px;
|
||||
font-size: 11px;
|
||||
font-weight: 600;
|
||||
gap: 4px;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.dx-badge[padding="true"] {
|
||||
padding: 0 8px;
|
||||
}
|
||||
|
||||
.dx-badge[data-style="primary"] {
|
||||
background-color: var(--accent);
|
||||
color: var(--on-accent);
|
||||
}
|
||||
|
||||
.dx-badge[data-style="secondary"] {
|
||||
background-color: var(--surface-2);
|
||||
color: var(--text);
|
||||
}
|
||||
|
||||
.dx-badge[data-style="outline"] {
|
||||
border: 1px solid var(--border);
|
||||
background-color: var(--surface);
|
||||
color: var(--text-2);
|
||||
}
|
||||
|
||||
.dx-badge[data-style="destructive"] {
|
||||
background-color: var(--danger);
|
||||
color: var(--dark, #171d18) var(--light, #ffffff);
|
||||
}
|
||||
@@ -0,0 +1,108 @@
|
||||
use dioxus::prelude::*;
|
||||
use dioxus_primitives::dioxus_attributes::attributes;
|
||||
use dioxus_primitives::merge_attributes;
|
||||
|
||||
#[css_module("/src/components/button/style.css")]
|
||||
struct Styles;
|
||||
|
||||
#[derive(Copy, Clone, PartialEq, Default)]
|
||||
#[non_exhaustive]
|
||||
pub enum ButtonVariant {
|
||||
#[default]
|
||||
Primary,
|
||||
Secondary,
|
||||
Destructive,
|
||||
Outline,
|
||||
Ghost,
|
||||
Link,
|
||||
}
|
||||
|
||||
impl ButtonVariant {
|
||||
pub fn class(&self) -> &'static str {
|
||||
match self {
|
||||
ButtonVariant::Primary => "primary",
|
||||
ButtonVariant::Secondary => "secondary",
|
||||
ButtonVariant::Destructive => "destructive",
|
||||
ButtonVariant::Outline => "outline",
|
||||
ButtonVariant::Ghost => "ghost",
|
||||
ButtonVariant::Link => "link",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, PartialEq, Default)]
|
||||
#[non_exhaustive]
|
||||
pub enum ButtonSize {
|
||||
Xs,
|
||||
Sm,
|
||||
#[default]
|
||||
Default,
|
||||
Lg,
|
||||
Icon,
|
||||
IconXs,
|
||||
IconSm,
|
||||
IconLg,
|
||||
}
|
||||
|
||||
impl ButtonSize {
|
||||
pub fn class(&self) -> &'static str {
|
||||
match self {
|
||||
ButtonSize::Xs => "xs",
|
||||
ButtonSize::Sm => "sm",
|
||||
ButtonSize::Default => "default",
|
||||
ButtonSize::Lg => "lg",
|
||||
ButtonSize::Icon => "icon",
|
||||
ButtonSize::IconXs => "icon-xs",
|
||||
ButtonSize::IconSm => "icon-sm",
|
||||
ButtonSize::IconLg => "icon-lg",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[component]
|
||||
pub fn Button(
|
||||
#[props(default)] variant: ButtonVariant,
|
||||
#[props(default)] size: ButtonSize,
|
||||
#[props(extends=GlobalAttributes)]
|
||||
#[props(extends=button)]
|
||||
attributes: Vec<Attribute>,
|
||||
onclick: Option<EventHandler<MouseEvent>>,
|
||||
onmousedown: Option<EventHandler<MouseEvent>>,
|
||||
onmouseup: Option<EventHandler<MouseEvent>>,
|
||||
onkeydown: Option<EventHandler<KeyboardEvent>>,
|
||||
children: Element,
|
||||
) -> Element {
|
||||
let base = attributes!(button {
|
||||
class: Styles::dx_button,
|
||||
"data-style": variant.class(),
|
||||
"data-size": size.class(),
|
||||
});
|
||||
let merged = merge_attributes(vec![base, attributes]);
|
||||
|
||||
rsx! {
|
||||
button {
|
||||
onclick: move |event| {
|
||||
if let Some(f) = &onclick {
|
||||
f.call(event);
|
||||
}
|
||||
},
|
||||
onmousedown: move |event| {
|
||||
if let Some(f) = &onmousedown {
|
||||
f.call(event);
|
||||
}
|
||||
},
|
||||
onmouseup: move |event| {
|
||||
if let Some(f) = &onmouseup {
|
||||
f.call(event);
|
||||
}
|
||||
},
|
||||
onkeydown: move |event| {
|
||||
if let Some(f) = &onkeydown {
|
||||
f.call(event);
|
||||
}
|
||||
},
|
||||
..merged,
|
||||
{children}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
mod component;
|
||||
pub use component::*;
|
||||
195
parts/mize.ui/dioxus-components/src/components/button/style.css
Normal file
195
parts/mize.ui/dioxus-components/src/components/button/style.css
Normal file
@@ -0,0 +1,195 @@
|
||||
.dx-button {
|
||||
display: inline-flex;
|
||||
box-sizing: border-box;
|
||||
flex-shrink: 0;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 0;
|
||||
border: none;
|
||||
border-radius: 0.375rem;
|
||||
cursor: pointer;
|
||||
font-family: inherit;
|
||||
font-size: 0.875rem;
|
||||
font-weight: 500;
|
||||
gap: 0.5rem;
|
||||
line-height: 1.25rem;
|
||||
outline: none;
|
||||
transition-duration: 150ms;
|
||||
transition-property: background-color, color, box-shadow, border-color, opacity;
|
||||
transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.dx-button svg {
|
||||
flex-shrink: 0;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.dx-button svg:not([class*="size-"]) {
|
||||
width: 1rem;
|
||||
height: 1rem;
|
||||
}
|
||||
|
||||
.dx-button[data-size="xs"] {
|
||||
height: 1.5rem;
|
||||
padding: 0 0.5rem;
|
||||
font-size: 0.75rem;
|
||||
gap: 0.25rem;
|
||||
line-height: 1rem;
|
||||
}
|
||||
|
||||
.dx-button[data-size="xs"]:has(> svg) {
|
||||
padding-inline: 0.375rem;
|
||||
}
|
||||
|
||||
.dx-button[data-size="xs"] svg:not([class*="size-"]) {
|
||||
width: 0.75rem;
|
||||
height: 0.75rem;
|
||||
}
|
||||
|
||||
.dx-button[data-size="sm"] {
|
||||
height: 1.875rem;
|
||||
padding: 0 0.625rem;
|
||||
gap: 0.375rem;
|
||||
}
|
||||
|
||||
.dx-button[data-size="sm"]:has(> svg) {
|
||||
padding-inline: 0.5rem;
|
||||
}
|
||||
|
||||
.dx-button[data-size="default"] {
|
||||
height: 2rem;
|
||||
padding: 0.375rem 1rem;
|
||||
}
|
||||
|
||||
.dx-button[data-size="default"]:has(> svg) {
|
||||
padding-inline: 0.75rem;
|
||||
}
|
||||
|
||||
.dx-button[data-size="lg"] {
|
||||
height: 2.375rem;
|
||||
padding: 0 1.25rem;
|
||||
}
|
||||
|
||||
.dx-button[data-size="lg"]:has(> svg) {
|
||||
padding-inline: 1rem;
|
||||
}
|
||||
|
||||
.dx-button[data-size="icon"] {
|
||||
width: 2rem;
|
||||
height: 2rem;
|
||||
}
|
||||
|
||||
.dx-button[data-size="icon"] svg:not([class*="size-"]) {
|
||||
width: 1rem;
|
||||
height: 1rem;
|
||||
}
|
||||
|
||||
.dx-button[data-size="icon-xs"] {
|
||||
width: 1.5rem;
|
||||
height: 1.5rem;
|
||||
}
|
||||
|
||||
.dx-button[data-size="icon-xs"] svg:not([class*="size-"]) {
|
||||
width: 0.75rem;
|
||||
height: 0.75rem;
|
||||
}
|
||||
|
||||
.dx-button[data-size="icon-sm"] {
|
||||
width: 1.875rem;
|
||||
height: 1.875rem;
|
||||
}
|
||||
|
||||
.dx-button[data-size="icon-sm"] svg:not([class*="size-"]) {
|
||||
width: 1rem;
|
||||
height: 1rem;
|
||||
}
|
||||
|
||||
.dx-button[data-size="icon-lg"] {
|
||||
width: 2.375rem;
|
||||
height: 2.375rem;
|
||||
}
|
||||
|
||||
.dx-button[data-size="icon-lg"] svg:not([class*="size-"]) {
|
||||
width: 1rem;
|
||||
height: 1rem;
|
||||
}
|
||||
|
||||
.dx-button:disabled {
|
||||
opacity: 0.5;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.dx-button[data-style="primary"] {
|
||||
background-color: var(--accent);
|
||||
color: var(--on-accent);
|
||||
}
|
||||
|
||||
.dx-button[data-style="primary"]:not(:disabled):hover {
|
||||
background-color: color-mix(in oklab, var(--accent) 88%, var(--text));
|
||||
}
|
||||
|
||||
.dx-button[data-style="secondary"] {
|
||||
background-color: var(--surface-2);
|
||||
color: var(--text);
|
||||
}
|
||||
|
||||
.dx-button[data-style="secondary"]:not(:disabled):hover {
|
||||
background-color: color-mix(in oklab, var(--surface-2) 85%, var(--text));
|
||||
}
|
||||
|
||||
.dx-button[data-style="ghost"] {
|
||||
background-color: transparent;
|
||||
color: var(--text-2);
|
||||
}
|
||||
|
||||
.dx-button[data-style="ghost"]:not(:disabled):hover {
|
||||
background-color: var(--surface-2);
|
||||
color: var(--text);
|
||||
}
|
||||
|
||||
.dx-button[data-style="outline"] {
|
||||
border: 1px solid var(--border);
|
||||
background-color: var(--surface);
|
||||
color: var(--text);
|
||||
}
|
||||
|
||||
.dx-button[data-style="outline"]:not(:disabled):hover {
|
||||
background-color: var(--surface-2);
|
||||
}
|
||||
|
||||
.dx-button[data-style="destructive"] {
|
||||
background-color: var(--danger);
|
||||
color: var(--dark, #171d18) var(--light, #ffffff);
|
||||
}
|
||||
|
||||
.dx-button[data-style="destructive"]:not(:disabled):hover {
|
||||
background-color: color-mix(in oklab, var(--danger) 88%, var(--text));
|
||||
}
|
||||
|
||||
.dx-button[data-style="destructive"]:focus-visible {
|
||||
box-shadow: 0 0 0 3px color-mix(in oklab, var(--danger) 40%, transparent);
|
||||
}
|
||||
|
||||
.dx-button[data-style="link"] {
|
||||
background-color: transparent;
|
||||
color: var(--accent-text);
|
||||
text-decoration: none;
|
||||
text-underline-offset: 0.25rem;
|
||||
}
|
||||
|
||||
.dx-button[data-style="link"]:not(:disabled):hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.dx-button:focus-visible {
|
||||
box-shadow: 0 0 0 3px color-mix(in oklab, var(--focus-ring) 45%, transparent);
|
||||
}
|
||||
|
||||
.dx-button[aria-invalid="true"] {
|
||||
border-color: var(--danger);
|
||||
}
|
||||
|
||||
.dx-button[aria-invalid="true"]:focus-visible {
|
||||
box-shadow: 0 0 0 3px color-mix(in oklab, var(--danger) 40%, transparent);
|
||||
}
|
||||
109
parts/mize.ui/dioxus-components/src/components/card/component.rs
Normal file
109
parts/mize.ui/dioxus-components/src/components/card/component.rs
Normal file
@@ -0,0 +1,109 @@
|
||||
use dioxus::prelude::*;
|
||||
|
||||
#[css_module("/src/components/card/style.css")]
|
||||
struct Styles;
|
||||
|
||||
#[component]
|
||||
pub fn Card(
|
||||
#[props(extends=GlobalAttributes)] attributes: Vec<Attribute>,
|
||||
children: Element,
|
||||
) -> Element {
|
||||
rsx! {
|
||||
div {
|
||||
class: Styles::dx_card,
|
||||
"data-slot": "card",
|
||||
..attributes,
|
||||
{children}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[component]
|
||||
pub fn CardHeader(
|
||||
#[props(extends=GlobalAttributes)] attributes: Vec<Attribute>,
|
||||
children: Element,
|
||||
) -> Element {
|
||||
rsx! {
|
||||
div {
|
||||
class: Styles::dx_card_header,
|
||||
"data-slot": "card-header",
|
||||
..attributes,
|
||||
{children}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[component]
|
||||
pub fn CardTitle(
|
||||
#[props(extends=GlobalAttributes)] attributes: Vec<Attribute>,
|
||||
children: Element,
|
||||
) -> Element {
|
||||
rsx! {
|
||||
div {
|
||||
class: Styles::dx_card_title,
|
||||
"data-slot": "card-title",
|
||||
..attributes,
|
||||
{children}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[component]
|
||||
pub fn CardDescription(
|
||||
#[props(extends=GlobalAttributes)] attributes: Vec<Attribute>,
|
||||
children: Element,
|
||||
) -> Element {
|
||||
rsx! {
|
||||
div {
|
||||
class: Styles::dx_card_description,
|
||||
"data-slot": "card-description",
|
||||
..attributes,
|
||||
{children}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[component]
|
||||
pub fn CardAction(
|
||||
#[props(extends=GlobalAttributes)] attributes: Vec<Attribute>,
|
||||
children: Element,
|
||||
) -> Element {
|
||||
rsx! {
|
||||
div {
|
||||
class: Styles::dx_card_action,
|
||||
"data-slot": "card-action",
|
||||
..attributes,
|
||||
{children}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[component]
|
||||
pub fn CardContent(
|
||||
#[props(extends=GlobalAttributes)] attributes: Vec<Attribute>,
|
||||
children: Element,
|
||||
) -> Element {
|
||||
rsx! {
|
||||
div {
|
||||
class: Styles::dx_card_content,
|
||||
"data-slot": "card-content",
|
||||
..attributes,
|
||||
{children}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[component]
|
||||
pub fn CardFooter(
|
||||
#[props(extends=GlobalAttributes)] attributes: Vec<Attribute>,
|
||||
children: Element,
|
||||
) -> Element {
|
||||
rsx! {
|
||||
div {
|
||||
class: Styles::dx_card_footer,
|
||||
"data-slot": "card-footer",
|
||||
..attributes,
|
||||
{children}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
mod component;
|
||||
pub use component::*;
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
.dx-card {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding: 1rem 0;
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 0.5rem;
|
||||
background-color: var(--surface);
|
||||
box-shadow: 0 1px 0 rgb(0 0 0 / 8%);
|
||||
color: var(--text);
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
.dx-card-header {
|
||||
display: grid;
|
||||
align-items: start;
|
||||
padding: 0 1rem;
|
||||
gap: 0.375rem;
|
||||
grid-auto-rows: min-content;
|
||||
grid-template-rows: auto auto;
|
||||
}
|
||||
|
||||
.dx-card-header:has([data-slot="card-action"]) {
|
||||
grid-template-columns: 1fr auto;
|
||||
}
|
||||
|
||||
.dx-card-title {
|
||||
font-size: 1rem;
|
||||
font-weight: 600;
|
||||
line-height: 1.25;
|
||||
}
|
||||
|
||||
.dx-card-description {
|
||||
color: var(--text-2);
|
||||
font-size: 0.875rem;
|
||||
line-height: 1.25rem;
|
||||
}
|
||||
|
||||
.dx-card-action {
|
||||
grid-column-start: 2;
|
||||
grid-row: 1 / span 2;
|
||||
place-self: start end;
|
||||
}
|
||||
|
||||
.dx-card-content {
|
||||
padding: 0 1rem;
|
||||
}
|
||||
|
||||
.dx-card-footer {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 0 1rem;
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
use dioxus::prelude::*;
|
||||
use dioxus_icons::lucide::Check;
|
||||
use dioxus_primitives::checkbox::{self, CheckboxProps};
|
||||
|
||||
#[css_module("/src/components/checkbox/style.css")]
|
||||
struct Styles;
|
||||
|
||||
#[component]
|
||||
pub fn Checkbox(props: CheckboxProps) -> Element {
|
||||
rsx! {
|
||||
checkbox::Checkbox {
|
||||
class: Styles::dx_checkbox,
|
||||
checked: props.checked,
|
||||
default_checked: props.default_checked,
|
||||
required: props.required,
|
||||
disabled: props.disabled,
|
||||
name: props.name,
|
||||
value: props.value,
|
||||
on_checked_change: props.on_checked_change,
|
||||
attributes: props.attributes,
|
||||
checkbox::CheckboxIndicator { class: Styles::dx_checkbox_indicator,
|
||||
Check { size: "1rem" }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
mod component;
|
||||
pub use component::*;
|
||||
@@ -0,0 +1,34 @@
|
||||
.dx-checkbox {
|
||||
width: 1.125rem;
|
||||
height: 1.125rem;
|
||||
box-sizing: border-box;
|
||||
padding: 0;
|
||||
border: none;
|
||||
border-radius: 0.25rem;
|
||||
margin: 0;
|
||||
background-color: var(--surface-2);
|
||||
box-shadow: inset 0 0 0 1px var(--border);
|
||||
color: var(--on-accent);
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.dx-checkbox-indicator {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.dx-checkbox[data-state="checked"] {
|
||||
background-color: var(--accent);
|
||||
box-shadow: none;
|
||||
color: var(--on-accent);
|
||||
}
|
||||
|
||||
.dx-checkbox:focus-visible {
|
||||
box-shadow: 0 0 0 2px color-mix(in oklab, var(--accent) 45%, transparent);
|
||||
}
|
||||
|
||||
.dx-checkbox[data-disabled="true"] {
|
||||
cursor: not-allowed;
|
||||
opacity: 0.5;
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
use dioxus::prelude::*;
|
||||
use dioxus_primitives::dialog::{self, DialogDescriptionProps, DialogRootProps, DialogTitleProps};
|
||||
use dioxus_primitives::{dioxus_attributes::attributes, merge_attributes};
|
||||
|
||||
#[css_module("/src/components/dialog/style.css")]
|
||||
struct Styles;
|
||||
|
||||
#[component]
|
||||
pub fn Dialog(props: DialogRootProps) -> Element {
|
||||
let base = attributes!(div {
|
||||
class: Styles::dx_dialog,
|
||||
});
|
||||
let merged = merge_attributes(vec![base, props.attributes]);
|
||||
|
||||
rsx! {
|
||||
dialog::DialogRoot {
|
||||
class: Styles::dx_dialog_backdrop,
|
||||
id: props.id,
|
||||
is_modal: props.is_modal,
|
||||
open: props.open,
|
||||
default_open: props.default_open,
|
||||
on_open_change: props.on_open_change,
|
||||
dialog::DialogContent {
|
||||
class: None,
|
||||
attributes: merged,
|
||||
{props.children}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[component]
|
||||
pub fn DialogTitle(props: DialogTitleProps) -> Element {
|
||||
let base = attributes!(h2 {
|
||||
class: Styles::dx_dialog_title,
|
||||
});
|
||||
let merged = merge_attributes(vec![base, props.attributes]);
|
||||
|
||||
rsx! {
|
||||
dialog::DialogTitle {
|
||||
id: props.id,
|
||||
attributes: merged,
|
||||
{props.children}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[component]
|
||||
pub fn DialogDescription(props: DialogDescriptionProps) -> Element {
|
||||
let base = attributes!(p {
|
||||
class: Styles::dx_dialog_description,
|
||||
});
|
||||
let merged = merge_attributes(vec![base, props.attributes]);
|
||||
|
||||
rsx! {
|
||||
dialog::DialogDescription {
|
||||
id: props.id,
|
||||
attributes: merged,
|
||||
{props.children}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
mod component;
|
||||
pub use component::*;
|
||||
100
parts/mize.ui/dioxus-components/src/components/dialog/style.css
Normal file
100
parts/mize.ui/dioxus-components/src/components/dialog/style.css
Normal file
@@ -0,0 +1,100 @@
|
||||
.dx-dialog-backdrop {
|
||||
position: fixed;
|
||||
z-index: 1000;
|
||||
background: rgb(23 29 24 / 55%);
|
||||
inset: 0;
|
||||
opacity: 0;
|
||||
will-change: opacity;
|
||||
}
|
||||
|
||||
.dx-dialog-backdrop[data-state="closed"] {
|
||||
animation: dx-dialog-backdrop-animate-out 150ms ease-in forwards;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
@keyframes dx-dialog-backdrop-animate-out {
|
||||
0% {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
100% {
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.dx-dialog-backdrop[data-state="open"] {
|
||||
animation: dx-dialog-backdrop-animate-in 150ms ease-out forwards;
|
||||
}
|
||||
|
||||
@keyframes dx-dialog-backdrop-animate-in {
|
||||
0% {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
100% {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
.dx-dialog {
|
||||
position: fixed;
|
||||
z-index: 1001;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
display: flex;
|
||||
width: 100%;
|
||||
max-width: calc(100% - 2rem);
|
||||
box-sizing: border-box;
|
||||
flex-direction: column;
|
||||
padding: 1.5rem 1.25rem 1.25rem;
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 0.5rem;
|
||||
margin: 0;
|
||||
background: var(--surface);
|
||||
box-shadow: 0 8px 24px rgb(0 0 0 / 35%);
|
||||
color: var(--text);
|
||||
font-family: inherit;
|
||||
gap: 0.75rem;
|
||||
text-align: center;
|
||||
transform: translate(-50%, -50%);
|
||||
}
|
||||
|
||||
.dx-dialog-title {
|
||||
margin: 0;
|
||||
color: var(--text);
|
||||
font-size: 1.125rem;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.dx-dialog-description {
|
||||
margin: 0;
|
||||
color: var(--text-2);
|
||||
font-size: 0.875rem;
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
@media (width >= 40rem) {
|
||||
.dx-dialog {
|
||||
max-width: 28rem;
|
||||
text-align: left;
|
||||
}
|
||||
}
|
||||
|
||||
.dx-dialog-close {
|
||||
position: absolute;
|
||||
top: 0.75rem;
|
||||
right: 0.75rem;
|
||||
align-self: flex-start;
|
||||
padding: 0;
|
||||
border: none;
|
||||
margin: 0;
|
||||
background: none;
|
||||
color: var(--text-2);
|
||||
cursor: pointer;
|
||||
font-size: 18px;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.dx-dialog-close:hover {
|
||||
color: var(--text);
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
use dioxus::prelude::*;
|
||||
#[css_module("/src/components/input/style.css")]
|
||||
struct Styles;
|
||||
|
||||
#[component]
|
||||
pub fn Input(
|
||||
oninput: Option<EventHandler<FormEvent>>,
|
||||
onchange: Option<EventHandler<FormEvent>>,
|
||||
oninvalid: Option<EventHandler<FormEvent>>,
|
||||
onselect: Option<EventHandler<SelectionEvent>>,
|
||||
onselectionchange: Option<EventHandler<SelectionEvent>>,
|
||||
onfocus: Option<EventHandler<FocusEvent>>,
|
||||
onblur: Option<EventHandler<FocusEvent>>,
|
||||
onfocusin: Option<EventHandler<FocusEvent>>,
|
||||
onfocusout: Option<EventHandler<FocusEvent>>,
|
||||
onkeydown: Option<EventHandler<KeyboardEvent>>,
|
||||
onkeypress: Option<EventHandler<KeyboardEvent>>,
|
||||
onkeyup: Option<EventHandler<KeyboardEvent>>,
|
||||
onwheel: Option<EventHandler<WheelEvent>>,
|
||||
oncompositionstart: Option<EventHandler<CompositionEvent>>,
|
||||
oncompositionupdate: Option<EventHandler<CompositionEvent>>,
|
||||
oncompositionend: Option<EventHandler<CompositionEvent>>,
|
||||
oncopy: Option<EventHandler<ClipboardEvent>>,
|
||||
oncut: Option<EventHandler<ClipboardEvent>>,
|
||||
onpaste: Option<EventHandler<ClipboardEvent>>,
|
||||
#[props(extends=GlobalAttributes)]
|
||||
#[props(extends=input)]
|
||||
attributes: Vec<Attribute>,
|
||||
children: Element,
|
||||
) -> Element {
|
||||
rsx! {
|
||||
input {
|
||||
class: Styles::dx_input,
|
||||
oninput: move |e| _ = oninput.map(|callback| callback(e)),
|
||||
onchange: move |e| _ = onchange.map(|callback| callback(e)),
|
||||
oninvalid: move |e| _ = oninvalid.map(|callback| callback(e)),
|
||||
onselect: move |e| _ = onselect.map(|callback| callback(e)),
|
||||
onselectionchange: move |e| _ = onselectionchange.map(|callback| callback(e)),
|
||||
onfocus: move |e| _ = onfocus.map(|callback| callback(e)),
|
||||
onblur: move |e| _ = onblur.map(|callback| callback(e)),
|
||||
onfocusin: move |e| _ = onfocusin.map(|callback| callback(e)),
|
||||
onfocusout: move |e| _ = onfocusout.map(|callback| callback(e)),
|
||||
onkeydown: move |e| _ = onkeydown.map(|callback| callback(e)),
|
||||
onkeypress: move |e| _ = onkeypress.map(|callback| callback(e)),
|
||||
onkeyup: move |e| _ = onkeyup.map(|callback| callback(e)),
|
||||
onwheel: move |e| _ = onwheel.map(|callback| callback(e)),
|
||||
oncompositionstart: move |e| _ = oncompositionstart.map(|callback| callback(e)),
|
||||
oncompositionupdate: move |e| _ = oncompositionupdate.map(|callback| callback(e)),
|
||||
oncompositionend: move |e| _ = oncompositionend.map(|callback| callback(e)),
|
||||
oncopy: move |e| _ = oncopy.map(|callback| callback(e)),
|
||||
oncut: move |e| _ = oncut.map(|callback| callback(e)),
|
||||
onpaste: move |e| _ = onpaste.map(|callback| callback(e)),
|
||||
..attributes,
|
||||
{children}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
mod component;
|
||||
pub use component::*;
|
||||
@@ -0,0 +1,40 @@
|
||||
.dx-input {
|
||||
position: relative;
|
||||
display: flex;
|
||||
box-sizing: border-box;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 8px 12px;
|
||||
border: none;
|
||||
border-radius: 0.375rem;
|
||||
background-color: var(--surface-2);
|
||||
box-shadow: inset 0 0 0 1px var(--border);
|
||||
color: var(--text);
|
||||
font-family: inherit;
|
||||
font-size: 0.875rem;
|
||||
gap: 0.25rem;
|
||||
outline: none;
|
||||
transition: background-color 100ms ease-out, box-shadow 100ms ease-out;
|
||||
}
|
||||
|
||||
.dx-input::placeholder {
|
||||
color: var(--text-2);
|
||||
}
|
||||
|
||||
.dx-input:disabled {
|
||||
color: var(--text-2);
|
||||
cursor: not-allowed;
|
||||
opacity: 0.6;
|
||||
}
|
||||
|
||||
.dx-input:hover:not(:disabled) {
|
||||
background-color: color-mix(in oklab, var(--surface-2) 90%, var(--text));
|
||||
}
|
||||
|
||||
.dx-input:focus-visible {
|
||||
background-color: var(--surface);
|
||||
box-shadow: inset 0 0 0 1px var(--accent), 0 0 0 2px color-mix(in oklab, var(--accent) 35%, transparent);
|
||||
color: var(--text);
|
||||
outline: none;
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
use dioxus::prelude::*;
|
||||
use dioxus_primitives::label::{self, LabelProps};
|
||||
#[css_module("/src/components/label/style.css")]
|
||||
struct Styles;
|
||||
|
||||
#[component]
|
||||
pub fn Label(props: LabelProps) -> Element {
|
||||
rsx! {
|
||||
label::Label {
|
||||
class: Styles::dx_label,
|
||||
html_for: props.html_for,
|
||||
attributes: props.attributes,
|
||||
{props.children}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
mod component;
|
||||
pub use component::*;
|
||||
@@ -0,0 +1,9 @@
|
||||
.dx-label {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
color: var(--text-2);
|
||||
font-size: 0.75rem;
|
||||
font-weight: 500;
|
||||
letter-spacing: 0.01em;
|
||||
line-height: 1.2;
|
||||
}
|
||||
25
parts/mize.ui/dioxus-components/src/components/mod.rs
Normal file
25
parts/mize.ui/dioxus-components/src/components/mod.rs
Normal file
@@ -0,0 +1,25 @@
|
||||
pub mod avatar;
|
||||
pub mod badge;
|
||||
pub mod button;
|
||||
pub mod card;
|
||||
pub mod checkbox;
|
||||
pub mod dialog;
|
||||
pub mod input;
|
||||
pub mod label;
|
||||
pub mod scroll_area;
|
||||
pub mod separator;
|
||||
pub mod switch;
|
||||
pub mod textarea;
|
||||
|
||||
pub use avatar::*;
|
||||
pub use badge::*;
|
||||
pub use button::*;
|
||||
pub use card::*;
|
||||
pub use checkbox::*;
|
||||
pub use dialog::*;
|
||||
pub use input::*;
|
||||
pub use label::*;
|
||||
pub use scroll_area::*;
|
||||
pub use separator::*;
|
||||
pub use switch::*;
|
||||
pub use textarea::*;
|
||||
@@ -0,0 +1,7 @@
|
||||
use dioxus::prelude::*;
|
||||
use dioxus_primitives::scroll_area::{self, ScrollAreaProps};
|
||||
|
||||
#[component]
|
||||
pub fn ScrollArea(props: ScrollAreaProps) -> Element {
|
||||
scroll_area::ScrollArea(props)
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
mod component;
|
||||
pub use component::*;
|
||||
@@ -0,0 +1 @@
|
||||
/* Scroll area uses unstyled dioxus-primitives; no extra styles for MVP. */
|
||||
@@ -0,0 +1,23 @@
|
||||
use dioxus::prelude::*;
|
||||
use dioxus_primitives::separator::{self, SeparatorProps};
|
||||
use dioxus_primitives::{dioxus_attributes::attributes, merge_attributes};
|
||||
|
||||
#[css_module("/src/components/separator/style.css")]
|
||||
struct Styles;
|
||||
|
||||
#[component]
|
||||
pub fn Separator(props: SeparatorProps) -> Element {
|
||||
let base = attributes!(div {
|
||||
class: Styles::dx_separator,
|
||||
});
|
||||
let merged = merge_attributes(vec![base, props.attributes]);
|
||||
|
||||
rsx! {
|
||||
separator::Separator {
|
||||
horizontal: props.horizontal,
|
||||
decorative: props.decorative,
|
||||
attributes: merged,
|
||||
{props.children}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
mod component;
|
||||
pub use component::*;
|
||||
@@ -0,0 +1,13 @@
|
||||
.dx-separator {
|
||||
background-color: var(--border);
|
||||
}
|
||||
|
||||
.dx-separator[data-orientation="horizontal"] {
|
||||
width: 100%;
|
||||
height: 1px;
|
||||
}
|
||||
|
||||
.dx-separator[data-orientation="vertical"] {
|
||||
width: 1px;
|
||||
height: 100%;
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
use dioxus::prelude::*;
|
||||
use dioxus_primitives::switch::{self, SwitchProps};
|
||||
|
||||
#[css_module("/src/components/switch/style.css")]
|
||||
struct Styles;
|
||||
|
||||
#[component]
|
||||
pub fn Switch(props: SwitchProps) -> Element {
|
||||
rsx! {
|
||||
switch::Switch {
|
||||
class: Styles::dx_switch,
|
||||
checked: props.checked,
|
||||
default_checked: props.default_checked,
|
||||
disabled: props.disabled,
|
||||
required: props.required,
|
||||
name: props.name,
|
||||
value: props.value,
|
||||
on_checked_change: props.on_checked_change,
|
||||
attributes: props.attributes,
|
||||
switch::SwitchThumb { class: Styles::dx_switch_thumb }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
mod component;
|
||||
pub use component::*;
|
||||
@@ -0,0 +1,41 @@
|
||||
.dx-switch {
|
||||
all: unset;
|
||||
position: relative;
|
||||
width: 2rem;
|
||||
height: 1.15rem;
|
||||
border-radius: 9999px;
|
||||
background-color: var(--surface-2);
|
||||
box-shadow: inset 0 0 0 1px var(--border);
|
||||
cursor: pointer;
|
||||
transition: background-color 150ms, box-shadow 150ms;
|
||||
}
|
||||
|
||||
.dx-switch[data-state="checked"] {
|
||||
background-color: var(--accent);
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
.dx-switch:focus-visible {
|
||||
box-shadow: 0 0 0 3px color-mix(in oklab, var(--accent) 45%, transparent);
|
||||
}
|
||||
|
||||
.dx-switch-thumb {
|
||||
display: block;
|
||||
width: calc(1.15rem - 2px);
|
||||
height: calc(1.15rem - 2px);
|
||||
border-radius: 9999px;
|
||||
background-color: var(--text);
|
||||
transform: translateX(1px);
|
||||
transition: transform 150ms, background-color 150ms;
|
||||
will-change: transform;
|
||||
}
|
||||
|
||||
.dx-switch[data-state="checked"] .dx-switch-thumb {
|
||||
background-color: var(--on-accent);
|
||||
transform: translateX(calc(2rem - 1px - (1.15rem - 2px)));
|
||||
}
|
||||
|
||||
.dx-switch[data-disabled="true"] {
|
||||
cursor: not-allowed;
|
||||
opacity: 0.5;
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
use dioxus::prelude::*;
|
||||
|
||||
#[css_module("/src/components/textarea/style.css")]
|
||||
struct Styles;
|
||||
|
||||
#[derive(Copy, Clone, PartialEq, Default)]
|
||||
#[non_exhaustive]
|
||||
pub enum TextareaVariant {
|
||||
#[default]
|
||||
Default,
|
||||
Fade,
|
||||
Outline,
|
||||
Ghost,
|
||||
}
|
||||
|
||||
impl TextareaVariant {
|
||||
pub fn class(&self) -> &'static str {
|
||||
match self {
|
||||
TextareaVariant::Default => "default",
|
||||
TextareaVariant::Fade => "fade",
|
||||
TextareaVariant::Outline => "outline",
|
||||
TextareaVariant::Ghost => "ghost",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[component]
|
||||
pub fn Textarea(
|
||||
oninput: Option<EventHandler<FormEvent>>,
|
||||
onchange: Option<EventHandler<FormEvent>>,
|
||||
oninvalid: Option<EventHandler<FormEvent>>,
|
||||
onselect: Option<EventHandler<SelectionEvent>>,
|
||||
onselectionchange: Option<EventHandler<SelectionEvent>>,
|
||||
onfocus: Option<EventHandler<FocusEvent>>,
|
||||
onblur: Option<EventHandler<FocusEvent>>,
|
||||
onfocusin: Option<EventHandler<FocusEvent>>,
|
||||
onfocusout: Option<EventHandler<FocusEvent>>,
|
||||
onkeydown: Option<EventHandler<KeyboardEvent>>,
|
||||
onkeypress: Option<EventHandler<KeyboardEvent>>,
|
||||
onkeyup: Option<EventHandler<KeyboardEvent>>,
|
||||
oncompositionstart: Option<EventHandler<CompositionEvent>>,
|
||||
oncompositionupdate: Option<EventHandler<CompositionEvent>>,
|
||||
oncompositionend: Option<EventHandler<CompositionEvent>>,
|
||||
oncopy: Option<EventHandler<ClipboardEvent>>,
|
||||
oncut: Option<EventHandler<ClipboardEvent>>,
|
||||
onpaste: Option<EventHandler<ClipboardEvent>>,
|
||||
onmounted: Option<EventHandler<MountedEvent>>,
|
||||
#[props(default)] variant: TextareaVariant,
|
||||
#[props(extends=GlobalAttributes)]
|
||||
#[props(extends=textarea)]
|
||||
attributes: Vec<Attribute>,
|
||||
children: Element,
|
||||
) -> Element {
|
||||
rsx! {
|
||||
textarea {
|
||||
class: Styles::dx_textarea,
|
||||
"data-slot": "textarea",
|
||||
"data-style": variant.class(),
|
||||
oninput: move |e| _ = oninput.map(|callback| callback(e)),
|
||||
onchange: move |e| _ = onchange.map(|callback| callback(e)),
|
||||
oninvalid: move |e| _ = oninvalid.map(|callback| callback(e)),
|
||||
onselect: move |e| _ = onselect.map(|callback| callback(e)),
|
||||
onselectionchange: move |e| _ = onselectionchange.map(|callback| callback(e)),
|
||||
onfocus: move |e| _ = onfocus.map(|callback| callback(e)),
|
||||
onblur: move |e| _ = onblur.map(|callback| callback(e)),
|
||||
onfocusin: move |e| _ = onfocusin.map(|callback| callback(e)),
|
||||
onfocusout: move |e| _ = onfocusout.map(|callback| callback(e)),
|
||||
onkeydown: move |e| _ = onkeydown.map(|callback| callback(e)),
|
||||
onkeypress: move |e| _ = onkeypress.map(|callback| callback(e)),
|
||||
onkeyup: move |e| _ = onkeyup.map(|callback| callback(e)),
|
||||
oncompositionstart: move |e| _ = oncompositionstart.map(|callback| callback(e)),
|
||||
oncompositionupdate: move |e| _ = oncompositionupdate.map(|callback| callback(e)),
|
||||
oncompositionend: move |e| _ = oncompositionend.map(|callback| callback(e)),
|
||||
oncopy: move |e| _ = oncopy.map(|callback| callback(e)),
|
||||
oncut: move |e| _ = oncut.map(|callback| callback(e)),
|
||||
onpaste: move |e| _ = onpaste.map(|callback| callback(e)),
|
||||
onmounted: move |e| _ = onmounted.map(|callback| callback(e)),
|
||||
..attributes,
|
||||
{children}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
mod component;
|
||||
pub use component::*;
|
||||
@@ -0,0 +1,84 @@
|
||||
.dx-textarea {
|
||||
width: 100%;
|
||||
min-height: 4rem;
|
||||
box-sizing: border-box;
|
||||
padding: 8px 12px;
|
||||
border: none;
|
||||
border-radius: 0.375rem;
|
||||
margin: 0;
|
||||
appearance: none;
|
||||
background: none;
|
||||
color: var(--text);
|
||||
font-family: inherit;
|
||||
font-size: 0.875rem;
|
||||
line-height: 1.5;
|
||||
outline: none;
|
||||
resize: vertical;
|
||||
transition: background-color 100ms ease-out, border-color 100ms ease-out, box-shadow 100ms ease-out;
|
||||
}
|
||||
|
||||
.dx-textarea:disabled {
|
||||
color: var(--text-2);
|
||||
cursor: not-allowed;
|
||||
opacity: 0.6;
|
||||
}
|
||||
|
||||
.dx-textarea::placeholder {
|
||||
color: var(--text-2);
|
||||
}
|
||||
|
||||
.dx-textarea[data-style="default"] {
|
||||
background: var(--surface-2);
|
||||
box-shadow: inset 0 0 0 1px var(--border);
|
||||
}
|
||||
|
||||
.dx-textarea[data-style="default"]:hover:not(:disabled),
|
||||
.dx-textarea[data-style="default"]:focus {
|
||||
background: var(--surface);
|
||||
color: var(--text);
|
||||
}
|
||||
|
||||
.dx-textarea[data-style="default"]:focus {
|
||||
box-shadow: inset 0 0 0 1px var(--accent), 0 0 0 2px color-mix(in oklab, var(--accent) 35%, transparent);
|
||||
}
|
||||
|
||||
.dx-textarea[data-style="fade"] {
|
||||
background: var(--surface-2);
|
||||
}
|
||||
|
||||
.dx-textarea[data-style="fade"]:hover:not(:disabled),
|
||||
.dx-textarea[data-style="fade"]:focus {
|
||||
background: var(--surface);
|
||||
color: var(--text);
|
||||
}
|
||||
|
||||
.dx-textarea[data-style="outline"] {
|
||||
border: 1px solid var(--border);
|
||||
background-color: var(--surface);
|
||||
}
|
||||
|
||||
.dx-textarea[data-style="outline"]:hover:not(:disabled, :focus) {
|
||||
border-color: color-mix(in oklab, var(--border) 70%, var(--text));
|
||||
}
|
||||
|
||||
.dx-textarea[data-style="outline"]:focus {
|
||||
border-color: var(--accent);
|
||||
}
|
||||
|
||||
.dx-textarea[data-style="outline"]:invalid,
|
||||
.dx-textarea[data-style="outline"][aria-invalid="true"] {
|
||||
border-color: var(--danger);
|
||||
}
|
||||
|
||||
.dx-textarea[data-style="ghost"] {
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
.dx-textarea[data-style="ghost"]:hover:not(:disabled) {
|
||||
background-color: var(--surface-2);
|
||||
color: var(--text);
|
||||
}
|
||||
|
||||
.dx-textarea[data-style="ghost"]:focus {
|
||||
box-shadow: inset 0 0 0 1px var(--accent);
|
||||
}
|
||||
14
parts/mize.ui/dioxus-components/src/lib.rs
Normal file
14
parts/mize.ui/dioxus-components/src/lib.rs
Normal file
@@ -0,0 +1,14 @@
|
||||
//! Styled Dioxus UI components using the mize color palette.
|
||||
//!
|
||||
//! Include [`THEME`] once at the app root (e.g. a stylesheet link), and set
|
||||
//! `html[data-theme="light"]` or `html[data-theme="dark"]` to switch themes.
|
||||
|
||||
#![allow(non_snake_case)]
|
||||
|
||||
use dioxus::prelude::*;
|
||||
|
||||
pub mod components;
|
||||
pub use components::*;
|
||||
|
||||
/// Global theme stylesheet (`assets/theme.css`). Link this once in your app.
|
||||
pub const THEME: Asset = asset!("/assets/theme.css");
|
||||
3
parts/mize.ui/src/main.rs
Normal file
3
parts/mize.ui/src/main.rs
Normal file
@@ -0,0 +1,3 @@
|
||||
fn main() {
|
||||
println!("Hello, world!");
|
||||
}
|
||||
83
parts/mos/disk_provision.py
Normal file
83
parts/mos/disk_provision.py
Normal file
@@ -0,0 +1,83 @@
|
||||
import subprocess
|
||||
|
||||
config = {
|
||||
"qcow_disk": "/home/me/work/config/mize/parts/mos/build/stick.qcow2",
|
||||
"root_dir": "/home/me/work/config/mize/parts/mos/build/root",
|
||||
"target": "/dev/nbd0",
|
||||
"partitions": [
|
||||
{
|
||||
"type": "fat32" ,
|
||||
"size": "200M",
|
||||
"flags": [ "esp" ]
|
||||
},
|
||||
{
|
||||
"type": "ext4" ,
|
||||
"size": "100%",
|
||||
},
|
||||
],
|
||||
"isofs": {
|
||||
"volid": "archiso-project",
|
||||
},
|
||||
}
|
||||
|
||||
def set_config(path, val):
|
||||
config[path] = val
|
||||
|
||||
|
||||
def get_config(path):
|
||||
return config[path]
|
||||
|
||||
|
||||
def connect_qemu_nbd():
|
||||
disk = get_config("qcow_disk")
|
||||
sh(f"sudo qemu-nbd --connect=/dev/nbd0 -f qcow2 {disk}")
|
||||
|
||||
|
||||
def disconnect_qemu_nbd():
|
||||
sh(f"sudo qemu-nbd --disconnect=/dev/nbd0")
|
||||
|
||||
def make_busybod_root_from_nixpkgs():
|
||||
path = sub
|
||||
pass
|
||||
|
||||
def flash_isofs():
|
||||
volid = get_config("isofs.volid")
|
||||
isolinux = get_config("isofs.linux")
|
||||
boot_cat = get_config("isofs.boot_cat")
|
||||
isohybrid_mbr = get_config("isofs.hybrid_mbr") # /usr/lib/syslinux/bios/isohdpfx.bin"
|
||||
efiboot_img = get_config("isofs.efiboot.img")
|
||||
output = get_config("target")
|
||||
root_fs = get_config("root_dir")
|
||||
subprocess.run([
|
||||
"xorriso", "as", "mkisofs",
|
||||
"-iso-level", "3", "-full-iso9660-filenames",
|
||||
"-volid", volid,
|
||||
"-eltorito-boot", isolinux,
|
||||
"-eltorito-catalog", boot_cat,
|
||||
"-no-emul-boot", "-boot-load-size", "4", "-boot-info-table",
|
||||
"-isohybrid-mbr", isohybrid_mbr,
|
||||
"-eltorito-alt-boot",
|
||||
"-e", efiboot_img,
|
||||
"-no-emul-boot",
|
||||
"-isohybrid-gpt-basdat",
|
||||
"-output", output,
|
||||
root_fs,
|
||||
])
|
||||
|
||||
def sh(cmd):
|
||||
subprocess.run(["sh", "-c", cmd])
|
||||
|
||||
def provision():
|
||||
try:
|
||||
connect_qemu_nbd()
|
||||
|
||||
|
||||
finally:
|
||||
disconnect_qemu_nbd()
|
||||
|
||||
def main():
|
||||
provision()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
313
parts/mos/partition_table.py
Normal file
313
parts/mos/partition_table.py
Normal file
@@ -0,0 +1,313 @@
|
||||
import json
|
||||
import os
|
||||
import re
|
||||
import subprocess
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
FILESYSTEMS = {
|
||||
"fat32": {
|
||||
"mkfs": ["mkfs.fat", "-F", "32"],
|
||||
"parted_type": "fat32",
|
||||
"parted_flags": {"esp": "esp"},
|
||||
},
|
||||
"ext4": {
|
||||
"mkfs": ["mkfs.ext4", "-F"],
|
||||
"parted_type": "ext4",
|
||||
"parted_flags": {},
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
def run(*args, check=True):
|
||||
"""Run a system command and return stdout."""
|
||||
result = subprocess.run(
|
||||
[str(arg) for arg in args],
|
||||
check=check,
|
||||
text=True,
|
||||
capture_output=True,
|
||||
)
|
||||
return result.stdout.strip()
|
||||
|
||||
|
||||
def partition_device(disk, number):
|
||||
"""Return the correct partition path for common Linux devices."""
|
||||
disk = str(disk)
|
||||
suffix = "p" if re.search(r"\d$", disk) else ""
|
||||
return f"{disk}{suffix}{number}"
|
||||
|
||||
|
||||
def parse_size(size, disk_bytes):
|
||||
"""Convert a size such as 200M or 100% into bytes."""
|
||||
if size == "100%":
|
||||
return disk_bytes
|
||||
|
||||
match = re.fullmatch(
|
||||
r"\s*(\d+(?:\.\d+)?)\s*([KMGT]?)B?\s*",
|
||||
size.upper(),
|
||||
)
|
||||
if not match:
|
||||
raise ValueError(f"Invalid partition size: {size!r}")
|
||||
|
||||
value = float(match.group(1))
|
||||
unit = match.group(2)
|
||||
multiplier = {
|
||||
"": 1,
|
||||
"K": 1024,
|
||||
"M": 1024**2,
|
||||
"G": 1024**3,
|
||||
"T": 1024**4,
|
||||
}[unit]
|
||||
|
||||
return int(value * multiplier)
|
||||
|
||||
|
||||
def get_table(disk):
|
||||
"""Read the partition table and return its partitions."""
|
||||
try:
|
||||
data = json.loads(run("sfdisk", "--json", disk))
|
||||
except subprocess.CalledProcessError:
|
||||
return None
|
||||
|
||||
table = data.get("partitiontable")
|
||||
if not table:
|
||||
return None
|
||||
|
||||
return table
|
||||
|
||||
|
||||
def get_filesystem(device):
|
||||
"""Return the detected filesystem type, or None."""
|
||||
result = run(
|
||||
"blkid", "-p", "-s", "TYPE", "-o", "value",
|
||||
device, check=False,
|
||||
)
|
||||
return result.lower() if result else None
|
||||
|
||||
|
||||
def apply_partition_config(
|
||||
disk,
|
||||
config,
|
||||
*,
|
||||
dry_run=True,
|
||||
format_mismatches=False,
|
||||
):
|
||||
"""
|
||||
Apply a partition configuration to a disk.
|
||||
|
||||
Safety behavior:
|
||||
- Dry-run by default.
|
||||
- Creates GPT only if no partition table exists.
|
||||
- Does not automatically resize, delete, or reorder partitions.
|
||||
- Refuses to format filesystems unless format_mismatches=True.
|
||||
- Verifies the expected partition count and filesystem types.
|
||||
|
||||
Requires root privileges and: parted, sfdisk, blkid, blockdev,
|
||||
mkfs.fat, mkfs.ext4, and partprobe.
|
||||
"""
|
||||
disk = str(Path(disk))
|
||||
|
||||
if os.geteuid() != 0:
|
||||
raise PermissionError("Run this function as root.")
|
||||
|
||||
if not os.path.exists(disk):
|
||||
raise FileNotFoundError(disk)
|
||||
|
||||
if run("lsblk", "-dn", "-o", "TYPE", disk) != "disk":
|
||||
raise ValueError(f"{disk} is not a whole-disk device")
|
||||
|
||||
partitions = config["partitions"]
|
||||
if not partitions:
|
||||
raise ValueError("At least one partition is required")
|
||||
|
||||
for p in partitions:
|
||||
fs = p["type"].lower()
|
||||
if fs not in FILESYSTEMS:
|
||||
raise ValueError(f"Unsupported filesystem: {fs}")
|
||||
if not isinstance(p.get("flags", []), list):
|
||||
raise ValueError("'flags' must be a list")
|
||||
|
||||
disk_bytes = int(run("blockdev", "--getsize64", disk))
|
||||
sector_size = int(run("blockdev", "--getss", disk))
|
||||
alignment = 1024 * 1024 # 1 MiB
|
||||
|
||||
def align_up(n):
|
||||
return ((n + alignment - 1) // alignment) * alignment
|
||||
|
||||
# Calculate partition boundaries. Reserve 1 MiB at the start.
|
||||
layout = []
|
||||
start = alignment
|
||||
|
||||
for i, p in enumerate(partitions):
|
||||
requested = p["size"]
|
||||
if requested == "100%":
|
||||
end = disk_bytes - alignment
|
||||
else:
|
||||
size_bytes = parse_size(requested, disk_bytes)
|
||||
end = align_up(start + size_bytes)
|
||||
|
||||
if i == len(partitions) - 1 and requested == "100%":
|
||||
end = disk_bytes - alignment
|
||||
|
||||
if end <= start:
|
||||
raise ValueError(f"Invalid size for partition {i + 1}")
|
||||
|
||||
if end > disk_bytes - alignment:
|
||||
raise ValueError(
|
||||
f"Partition {i + 1} exceeds disk capacity"
|
||||
)
|
||||
|
||||
layout.append({
|
||||
"number": i + 1,
|
||||
"start": start,
|
||||
"end": end,
|
||||
"type": p["type"].lower(),
|
||||
"flags": p.get("flags", []),
|
||||
})
|
||||
start = end
|
||||
|
||||
def parted(*args):
|
||||
if dry_run:
|
||||
print("+ parted", "-s", disk, *args)
|
||||
else:
|
||||
run("parted", "-s", disk, *args)
|
||||
|
||||
table = get_table(disk)
|
||||
|
||||
# Create GPT only when the disk has no partition table.
|
||||
if table is None:
|
||||
print(f"No readable partition table found on {disk}.")
|
||||
parted("mklabel", "gpt")
|
||||
table = None
|
||||
|
||||
elif table.get("label") != "gpt":
|
||||
raise RuntimeError(
|
||||
f"{disk} has a {table.get('label')} table, not GPT. "
|
||||
"Refusing to overwrite it."
|
||||
)
|
||||
|
||||
existing = table.get("partitions", []) if table else []
|
||||
|
||||
# Never guess how to repair an incomplete or mismatched layout.
|
||||
if existing and len(existing) != len(layout):
|
||||
raise RuntimeError(
|
||||
f"Found {len(existing)} partitions; "
|
||||
f"configuration requires {len(layout)}. "
|
||||
"Automatic resizing/deletion is disabled."
|
||||
)
|
||||
|
||||
if not existing:
|
||||
for item in layout:
|
||||
fs = item["type"]
|
||||
start_mib = item["start"] / (1024**2)
|
||||
end_mib = item["end"] / (1024**2)
|
||||
|
||||
# parted accepts MiB boundaries.
|
||||
parted(
|
||||
"mkpart",
|
||||
"primary",
|
||||
FILESYSTEMS[fs]["parted_type"],
|
||||
f"{start_mib:.6f}MiB",
|
||||
f"{end_mib:.6f}MiB",
|
||||
)
|
||||
|
||||
for flag in item["flags"]:
|
||||
if flag not in FILESYSTEMS[fs]["parted_flags"]:
|
||||
raise ValueError(
|
||||
f"Unsupported flag {flag!r} for {fs}"
|
||||
)
|
||||
parted(
|
||||
"set",
|
||||
str(item["number"]),
|
||||
FILESYSTEMS[fs]["parted_flags"][flag],
|
||||
"on",
|
||||
)
|
||||
|
||||
elif not dry_run:
|
||||
# Re-read the on-disk table before inspecting filesystems.
|
||||
table = get_table(disk)
|
||||
existing = table.get("partitions", [])
|
||||
|
||||
# Validate the existing boundaries. Do not modify them.
|
||||
for expected, actual in zip(layout, existing):
|
||||
actual_start = int(actual["start"]) * sector_size
|
||||
actual_end = (
|
||||
int(actual["start"]) + int(actual["size"])
|
||||
) * sector_size
|
||||
|
||||
if (
|
||||
abs(actual_start - expected["start"]) > alignment
|
||||
or abs(actual_end - expected["end"]) > alignment
|
||||
):
|
||||
raise RuntimeError(
|
||||
f"Partition {expected['number']} has incorrect "
|
||||
"boundaries. Automatic resizing is disabled."
|
||||
)
|
||||
|
||||
# Ask the kernel to re-read the partition table.
|
||||
if not dry_run:
|
||||
run("partprobe", disk)
|
||||
run("udevadm", "settle")
|
||||
|
||||
results = []
|
||||
|
||||
for item in layout:
|
||||
device = partition_device(disk, item["number"])
|
||||
expected_fs = item["type"]
|
||||
|
||||
if dry_run:
|
||||
print(
|
||||
f"Would inspect {device}; expected filesystem "
|
||||
f"{expected_fs}"
|
||||
)
|
||||
if format_mismatches:
|
||||
print(f"Would format {device} if needed")
|
||||
results.append({
|
||||
"device": device,
|
||||
"expected": expected_fs,
|
||||
"status": "dry-run",
|
||||
})
|
||||
continue
|
||||
|
||||
if not os.path.exists(device):
|
||||
raise RuntimeError(
|
||||
f"Partition device {device} did not appear"
|
||||
)
|
||||
|
||||
actual_fs = get_filesystem(device)
|
||||
|
||||
if actual_fs != expected_fs:
|
||||
if not format_mismatches:
|
||||
raise RuntimeError(
|
||||
f"{device}: expected {expected_fs}, "
|
||||
f"found {actual_fs!r}. "
|
||||
"Set format_mismatches=True to format it."
|
||||
)
|
||||
|
||||
# Avoid accidentally formatting a mounted filesystem.
|
||||
mounts = run(
|
||||
"lsblk", "-n", "-o", "MOUNTPOINTS", device
|
||||
)
|
||||
if mounts.strip():
|
||||
raise RuntimeError(
|
||||
f"{device} appears to be mounted; refusing "
|
||||
"to format it."
|
||||
)
|
||||
|
||||
fs = FILESYSTEMS[expected_fs]
|
||||
run(*fs["mkfs"], device)
|
||||
|
||||
actual_fs = get_filesystem(device)
|
||||
if actual_fs != expected_fs:
|
||||
raise RuntimeError(
|
||||
f"Filesystem verification failed for {device}"
|
||||
)
|
||||
|
||||
results.append({
|
||||
"device": device,
|
||||
"expected": expected_fs,
|
||||
"actual": actual_fs,
|
||||
"status": "ok",
|
||||
})
|
||||
|
||||
return results
|
||||
6
parts/mos/run_vm.py
Normal file
6
parts/mos/run_vm.py
Normal file
@@ -0,0 +1,6 @@
|
||||
|
||||
def runVm():
|
||||
sh("qemu-system-x86_64 -drive file=disk.img,format=raw -enable-kvm")
|
||||
|
||||
if __name__ == "__main__":
|
||||
runVm()
|
||||
Reference in New Issue
Block a user