Editor for papermario-dx mods
at main 261 lines 8.0 kB view raw
1// SPDX-FileCopyrightText: 2026 Alex Bates <alex@bates64.com> 2// 3// SPDX-License-Identifier: AGPL-3.0-or-later 4 5#![allow( 6 clippy::missing_panics_doc, 7 clippy::unwrap_used, 8 reason = "construction asserts containers are attached" 9)] 10 11use std::fmt; 12use std::marker::PhantomData; 13 14use loro::{LoroList, LoroMap, LoroMovableList, LoroTree, LoroValue}; 15 16use crate::FromLoroMap; 17 18/// A typed string-keyed map where every value is of type `V`. 19/// 20/// Use this as a field type in a `#[loroscope]` struct: 21/// 22/// ``` 23/// use loroscope::loroscope; 24/// 25/// #[loroscope] 26/// struct Settings { 27/// pub values: Map<i64>, 28/// } 29/// 30/// let settings = Settings::new(); 31/// settings.values().insert("volume", 80); 32/// assert_eq!(settings.values().get("volume"), Some(80)); 33/// ``` 34/// 35/// `V` can be a `#[loroscope]` struct, a primitive (`f64`, `i64`, `bool`, 36/// `String`), a Loro container type (`LoroText`, `LoroCounter`, `LoroTree`), 37/// or another collection ([`List<T>`](crate::List), 38/// [`MovableList<T>`](crate::MovableList), `Map<V>`). 39pub struct Map<V> { 40 map: LoroMap, 41 _phantom: PhantomData<V>, 42} 43 44impl<V> Clone for Map<V> { 45 fn clone(&self) -> Self { 46 Self { 47 map: self.map.clone(), 48 _phantom: PhantomData, 49 } 50 } 51} 52 53impl<V> fmt::Debug for Map<V> { 54 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 55 f.debug_struct("Map").field("len", &self.len()).finish() 56 } 57} 58 59impl<V> Map<V> { 60 /// Creates a typed view from a [`LoroMap`]. 61 /// 62 /// # Panics 63 /// 64 /// Panics if `map` is not attached to a [`LoroDoc`](loro::LoroDoc). 65 #[must_use] 66 pub fn new(map: LoroMap) -> Self { 67 assert!( 68 map.is_attached(), 69 "Map requires an attached LoroMap (one that belongs to a LoroDoc)" 70 ); 71 Self { 72 map, 73 _phantom: PhantomData, 74 } 75 } 76 77 /// Returns the number of entries. 78 #[must_use] 79 pub fn len(&self) -> usize { 80 self.map.len() 81 } 82 83 /// Returns `true` if the map is empty. 84 #[must_use] 85 pub fn is_empty(&self) -> bool { 86 self.map.is_empty() 87 } 88 89 /// Returns a reference to the underlying [`LoroMap`]. 90 #[must_use] 91 pub fn raw(&self) -> &LoroMap { 92 &self.map 93 } 94 95 /// Deletes the entry for `key`. 96 pub fn delete(&self, key: &str) { 97 self.map.delete(key).unwrap(); 98 } 99 100 /// Removes all entries from the map. 101 pub fn clear(&self) { 102 self.map.clear().unwrap(); 103 } 104 105 /// Returns an iterator over the keys in the map. 106 pub fn keys(&self) -> impl Iterator<Item = String> + '_ { 107 self.map.keys().map(|s| s.to_string()) 108 } 109} 110 111impl<V: FromLoroMap> Map<V> { 112 /// Returns the value for `key`, or `None` if the key is missing. 113 pub fn get(&self, key: &str) -> Option<V> { 114 self.map 115 .get(key) 116 .and_then(|v| v.into_container().ok()) 117 .and_then(|c| c.into_map().ok()) 118 .map(V::from_loro_map) 119 } 120 121 /// Returns the value for `key`, creating it if it doesn't exist. 122 pub fn get_or_create(&self, key: &str) -> V { 123 V::from_loro_map( 124 self.map 125 .get_or_create_container(key, LoroMap::new()) 126 .unwrap(), 127 ) 128 } 129 130 /// Returns an iterator over all key-value pairs. 131 pub fn iter(&self) -> impl Iterator<Item = (String, V)> { 132 let mut items = Vec::with_capacity(self.len()); 133 self.map.for_each(|key, voc| { 134 if let Some(map) = voc.into_container().ok().and_then(|c| c.into_map().ok()) { 135 items.push((key.to_owned(), V::from_loro_map(map))); 136 } 137 }); 138 items.into_iter() 139 } 140} 141 142macro_rules! impl_primitive { 143 ($ty:ty, $variant:pat => $expr:expr) => { 144 impl Map<$ty> { 145 /// Returns the value for `key`, or `None` if missing. 146 pub fn get(&self, key: &str) -> Option<$ty> { 147 self.map 148 .get(key) 149 .and_then(|v| v.into_value().ok()) 150 .and_then(|v| match v { 151 $variant => Some($expr), 152 _ => None, 153 }) 154 } 155 156 /// Returns an iterator over all key-value pairs. 157 pub fn iter(&self) -> impl Iterator<Item = (String, $ty)> { 158 let mut items = Vec::with_capacity(self.len()); 159 self.map.for_each(|key, voc| { 160 if let Some(v) = voc.into_value().ok().and_then(|v| match v { 161 $variant => Some($expr), 162 _ => None, 163 }) { 164 items.push((key.to_owned(), v)); 165 } 166 }); 167 items.into_iter() 168 } 169 } 170 }; 171} 172 173impl_primitive!(f64, LoroValue::Double(d) => d); 174impl_primitive!(i64, LoroValue::I64(i) => i); 175impl_primitive!(bool, LoroValue::Bool(b) => b); 176impl_primitive!(String, LoroValue::String(s) => s.to_string()); 177 178macro_rules! impl_insert { 179 ($($value_ty:ty => $param_ty:ty),* $(,)?) => {$( 180 impl Map<$value_ty> { 181 /// Inserts a value for the given key, overwriting any previous value. 182 pub fn insert(&self, key: &str, val: $param_ty) { 183 self.map.insert(key, val).unwrap(); 184 } 185 } 186 )*}; 187} 188 189impl_insert!(f64 => f64, i64 => i64, bool => bool, String => &str); 190 191macro_rules! impl_container { 192 ($ty:ty, $variant:ident) => { 193 impl Map<$ty> { 194 /// Returns the container for `key`, or `None` if missing. 195 pub fn get(&self, key: &str) -> Option<$ty> { 196 self.map 197 .get(key) 198 .and_then(|v| v.into_container().ok()) 199 .and_then(|c| c.$variant().ok()) 200 } 201 202 /// Returns the container for `key`, creating it if it doesn't exist. 203 pub fn get_or_create(&self, key: &str) -> $ty { 204 self.map.get_or_create_container(key, <$ty>::new()).unwrap() 205 } 206 } 207 }; 208} 209 210impl_container!(loro::LoroText, into_text); 211impl_container!(loro::LoroCounter, into_counter); 212impl_container!(loro::LoroTree, into_tree); 213 214macro_rules! impl_wrapper { 215 ($wrapper:ident < $($param:ident),+ >, $loro_ty:ty, $variant:ident) => { 216 impl<$($param),+> Map<crate::$wrapper<$($param),+>> { 217 /// Returns the value for `key`, or `None` if missing. 218 pub fn get(&self, key: &str) -> Option<crate::$wrapper<$($param),+>> { 219 self.map 220 .get(key) 221 .and_then(|v| v.into_container().ok()) 222 .and_then(|c| c.$variant().ok()) 223 .map(crate::$wrapper::new) 224 } 225 226 /// Returns the value for `key`, creating it if it doesn't exist. 227 pub fn get_or_create(&self, key: &str) -> crate::$wrapper<$($param),+> { 228 crate::$wrapper::new( 229 self.map 230 .get_or_create_container(key, <$loro_ty>::new()) 231 .unwrap(), 232 ) 233 } 234 } 235 }; 236} 237 238impl_wrapper!(List<T>, LoroList, into_list); 239impl_wrapper!(MovableList<T>, LoroMovableList, into_movable_list); 240impl_wrapper!(Map<V>, LoroMap, into_map); 241 242// Tree needs a manual impl because Tree<T> requires T: FromLoroMap. 243impl<T: FromLoroMap> Map<crate::Tree<T>> { 244 /// Returns the value for `key`, or `None` if missing. 245 pub fn get(&self, key: &str) -> Option<crate::Tree<T>> { 246 self.map 247 .get(key) 248 .and_then(|v| v.into_container().ok()) 249 .and_then(|c| c.into_tree().ok()) 250 .map(crate::Tree::new) 251 } 252 253 /// Returns the value for `key`, creating it if it doesn't exist. 254 pub fn get_or_create(&self, key: &str) -> crate::Tree<T> { 255 crate::Tree::new( 256 self.map 257 .get_or_create_container(key, LoroTree::new()) 258 .unwrap(), 259 ) 260 } 261}