#[macro_export]
macro_rules! xpc {
($($xpc:tt)+) => {
$crate::xpc_internal!($($xpc)+)
};
}
#[macro_export]
#[doc(hidden)]
macro_rules! xpc_internal {
(@array [$($elems:expr,)*]) => {
vec![$($elems,)*]
};
(@array [$($elems:expr),*]) => {
vec![$($elems),*]
};
(@array [$($elems:expr,)*] null $($rest:tt)*) => {
$crate::xpc_internal!(@array [$($elems,)* $crate::xpc_internal!(null)] $($rest)*)
};
(@array [$($elems:expr,)*] true $($rest:tt)*) => {
$crate::xpc_internal!(@array [$($elems,)* $crate::xpc_internal!(true)] $($rest)*)
};
(@array [$($elems:expr,)*] false $($rest:tt)*) => {
$crate::xpc_internal!(@array [$($elems,)* $crate::xpc_internal!(false)] $($rest)*)
};
(@array [$($elems:expr,)*] [$($array:tt)*] $($rest:tt)*) => {
$crate::xpc_internal!(@array [$($elems,)* $crate::xpc_internal!([$($array)*])] $($rest)*)
};
(@array [$($elems:expr,)*] {$($map:tt)*} $($rest:tt)*) => {
$crate::xpc_internal!(@array [$($elems,)* $crate::xpc_internal!({$($map)*})] $($rest)*)
};
(@array [$($elems:expr,)*] ? $maybe:expr, $($rest:tt)*) => {
if let Some(__v) = $crate::xpc::xpc_macro::xpc_maybe($maybe) {
$crate::xpc_internal!(@array [$($elems,)* __v,] $($rest)*)
} else {
$crate::xpc_internal!(@array [$($elems,)*] $($rest)*)
}
};
(@array [$($elems:expr,)*] ? $maybe:expr) => {
if let Some(__v) = $crate::xpc_macro::xpc_maybe($maybe) {
$crate::xpc_internal!(@array [$($elems,)* __v])
} else {
$crate::xpc_internal!(@array [$($elems,)*])
}
};
(@array [$($elems:expr,)*] $next:expr, $($rest:tt)*) => {
$crate::xpc_internal!(@array [$($elems,)* $crate::xpc_internal!($next),] $($rest)*)
};
(@array [$($elems:expr,)*] $last:expr) => {
$crate::xpc_internal!(@array [$($elems,)* $crate::xpc_internal!($last)])
};
(@array [$($elems:expr),*] , $($rest:tt)*) => {
$crate::xpc_internal!(@array [$($elems,)*] $($rest)*)
};
(@array [$($elems:expr),*] $unexpected:tt $($rest:tt)*) => {
$crate::xpc_unexpected!($unexpected)
};
(@object $object:ident () () ()) => {};
(@object $object:ident [$($key:tt)+] ($value:expr) , $($rest:tt)*) => {
let _ = $object.insert(($($key)+).into(), $value);
$crate::xpc_internal!(@object $object () ($($rest)*) ($($rest)*));
};
(@object $object:ident [$($key:tt)+] ($value:expr) $unexpected:tt $($rest:tt)*) => {
$crate::xpc_unexpected!($unexpected);
};
(@object $object:ident [$($key:tt)+] ($value:expr)) => {
let _ = $object.insert(($($key)+).into(), $value);
};
(@object $object:ident ($($key:tt)+) (: null $($rest:tt)*) $copy:tt) => {
$crate::xpc_internal!(@object $object [$($key)+] ($crate::xpc_internal!(null)) $($rest)*);
};
(@object $object:ident ($($key:tt)+) (: true $($rest:tt)*) $copy:tt) => {
$crate::xpc_internal!(@object $object [$($key)+] ($crate::xpc_internal!(true)) $($rest)*);
};
(@object $object:ident ($($key:tt)+) (: false $($rest:tt)*) $copy:tt) => {
$crate::xpc_internal!(@object $object [$($key)+] ($crate::xpc_internal!(false)) $($rest)*);
};
(@object $object:ident ($($key:tt)+) (: [$($array:tt)*] $($rest:tt)*) $copy:tt) => {
$crate::xpc_internal!(@object $object [$($key)+] ($crate::xpc_internal!([$($array)*])) $($rest)*);
};
(@object $object:ident ($($key:tt)+) (: {$($map:tt)*} $($rest:tt)*) $copy:tt) => {
$crate::xpc_internal!(@object $object [$($key)+] ($crate::xpc_internal!({$($map)*})) $($rest)*);
};
(@object $object:ident ($($key:tt)+) (:? $value:expr, $($rest:tt)*) $copy:tt) => {
if let Some(__v) = $crate::xpc::xpc_macro::xpc_maybe($value) {
let _ = $object.insert(($($key)+).into(), __v);
}
$crate::xpc_internal!(@object $object () ($($rest)*) ($($rest)*));
};
(@object $object:ident ($($key:tt)+) (:? $value:expr) $copy:tt) => {
if let Some(__v) = $crate::xpc_macro::xpc_maybe($value) {
let _ = $object.insert(($($key)+).into(), __v);
}
};
(@object $object:ident () (:< $value:expr, $($rest:tt)*) $copy:tt) => {
{
let __v = $crate::xpc_internal!($value);
let __dict = $crate::xpc::xpc_macro::IntoXpcDict::into_xpc_dict(__v);
for (__k, __val) in __dict {
let _ = $object.insert(__k, __val);
}
}
$crate::xpc_internal!(@object $object () ($($rest)*) ($($rest)*));
};
(@object $object:ident () (:< $value:expr) $copy:tt) => {
{
let __v = $crate::xpc_internal!($value);
let __dict = $crate::xpc_macro::IntoXpcDict::into_xpc_dict(__v);
for (__k, __val) in __dict {
let _ = $object.insert(__k, __val);
}
}
};
(@object $object:ident () (:< ? $value:expr, $($rest:tt)*) $copy:tt) => {
if let Some(__dict) = $crate::xpc::xpc_macro::maybe_into_xpc_dict($value) {
for (__k, __val) in __dict {
let _ = $object.insert(__k, __val);
}
}
$crate::xpc_internal!(@object $object () ($($rest)*) ($($rest)*));
};
(@object $object:ident () (:< ? $value:expr) $copy:tt) => {
if let Some(__dict) = $crate::xpc_macro::maybe_into_xpc_dict($value) {
for (__k, __val) in __dict {
let _ = $object.insert(__k, __val);
}
}
};
(@object $object:ident ($($key:tt)+) (: $value:expr, $($rest:tt)*) $copy:tt) => {
$crate::xpc_internal!(@object $object [$($key)+] ($crate::xpc_internal!($value)), $($rest)*);
};
(@object $object:ident ($($key:tt)+) (: $value:expr) $copy:tt) => {
$crate::xpc_internal!(@object $object [$($key)+] ($crate::xpc_internal!($value)));
};
(@object $object:ident ($($key:tt)+) (:) $copy:tt) => {
$crate::xpc_internal!();
};
(@object $object:ident ($($key:tt)+) () $copy:tt) => {
$crate::xpc_internal!();
};
(@object $object:ident () (: $($rest:tt)*) ($colon:tt $($copy:tt)*)) => {
$crate::xpc_unexpected!($colon);
};
(@object $object:ident ($($key:tt)*) ($tt:tt $($rest:tt)*) $copy:tt) => {
$crate::xpc_internal!(@object $object ($($key)* $tt) ($($rest)*) ($($rest)*));
};
(null) => {
$crate::xpc::XPCObject::String("".to_string())
};
(true) => {
$crate::xpc::XPCObject::Bool(true)
};
(false) => {
$crate::xpc::XPCObject::Bool(false)
};
([ $($tt:tt)+ ]) => {
$crate::xpc::XPCObject::Array($crate::xpc_internal!(@array [] $($tt)+))
};
({ $($tt:tt)+ }) => {
$crate::xpc::XPCObject::Dictionary({
let mut object = $crate::xpc::Dictionary::new();
$crate::xpc_internal!(@object object () ($($tt)+) ($($tt)+));
object
})
};
($other:expr) => {
$crate::xpc::xpc_macro::xpc_to_value($other)
};
}
#[macro_export]
#[doc(hidden)]
macro_rules! xpc_unexpected {
() => {};
}
use crate::xpc::{Dictionary, XPCObject};
use std::collections::{BTreeMap, HashMap};
pub trait XpcConvertible {
fn to_xpc_value(self) -> XPCObject;
}
#[doc(hidden)]
pub fn xpc_to_value<T: XpcConvertible>(value: T) -> XPCObject {
value.to_xpc_value()
}
impl XpcConvertible for XPCObject {
fn to_xpc_value(self) -> XPCObject {
self
}
}
impl XpcConvertible for String {
fn to_xpc_value(self) -> XPCObject {
XPCObject::String(self)
}
}
impl XpcConvertible for &str {
fn to_xpc_value(self) -> XPCObject {
XPCObject::String(self.to_string())
}
}
impl XpcConvertible for bool {
fn to_xpc_value(self) -> XPCObject {
XPCObject::Bool(self)
}
}
macro_rules! impl_xpc_convertible_for_signed {
($($t:ty),*) => {
$(impl XpcConvertible for $t {
fn to_xpc_value(self) -> XPCObject { XPCObject::Int64(self as i64) }
})*
};
}
impl_xpc_convertible_for_signed!(i8, i16, i32, i64, isize);
macro_rules! impl_xpc_convertible_for_unsigned {
($($t:ty),*) => {
$(impl XpcConvertible for $t {
fn to_xpc_value(self) -> XPCObject { XPCObject::UInt64(self as u64) }
})*
};
}
impl_xpc_convertible_for_unsigned!(u16, u32, u64, usize);
impl XpcConvertible for f32 {
fn to_xpc_value(self) -> XPCObject {
XPCObject::Double(self as f64)
}
}
impl XpcConvertible for f64 {
fn to_xpc_value(self) -> XPCObject {
XPCObject::Double(self)
}
}
impl XpcConvertible for Vec<u8> {
fn to_xpc_value(self) -> XPCObject {
XPCObject::Data(self)
}
}
impl XpcConvertible for &[u8] {
fn to_xpc_value(self) -> XPCObject {
XPCObject::Data(self.to_vec())
}
}
impl XpcConvertible for uuid::Uuid {
fn to_xpc_value(self) -> XPCObject {
XPCObject::Uuid(self)
}
}
impl XpcConvertible for std::time::SystemTime {
fn to_xpc_value(self) -> XPCObject {
XPCObject::Date(self)
}
}
impl<T: XpcConvertible> XpcConvertible for Vec<T> {
fn to_xpc_value(self) -> XPCObject {
XPCObject::Array(self.into_iter().map(XpcConvertible::to_xpc_value).collect())
}
}
impl<T: XpcConvertible + Clone> XpcConvertible for &[T] {
fn to_xpc_value(self) -> XPCObject {
XPCObject::Array(
self.iter()
.cloned()
.map(XpcConvertible::to_xpc_value)
.collect(),
)
}
}
impl<K, V> XpcConvertible for HashMap<K, V>
where
K: Into<String>,
V: XpcConvertible,
{
fn to_xpc_value(self) -> XPCObject {
XPCObject::Dictionary(
self.into_iter()
.map(|(k, v)| (k.into(), v.to_xpc_value()))
.collect(),
)
}
}
impl<K, V> XpcConvertible for BTreeMap<K, V>
where
K: Into<String>,
V: XpcConvertible,
{
fn to_xpc_value(self) -> XPCObject {
XPCObject::Dictionary(
self.into_iter()
.map(|(k, v)| (k.into(), v.to_xpc_value()))
.collect(),
)
}
}
pub trait MaybeXpc {
fn into_option_xpc(self) -> Option<XPCObject>;
}
impl<T: XpcConvertible> MaybeXpc for T {
fn into_option_xpc(self) -> Option<XPCObject> {
Some(self.to_xpc_value())
}
}
impl<T: XpcConvertible> MaybeXpc for Option<T> {
fn into_option_xpc(self) -> Option<XPCObject> {
self.map(XpcConvertible::to_xpc_value)
}
}
#[doc(hidden)]
pub fn xpc_maybe<T: MaybeXpc>(v: T) -> Option<XPCObject> {
v.into_option_xpc()
}
pub trait IntoXpcDict {
fn into_xpc_dict(self) -> Dictionary;
}
impl IntoXpcDict for Dictionary {
fn into_xpc_dict(self) -> Dictionary {
self
}
}
impl IntoXpcDict for XPCObject {
fn into_xpc_dict(self) -> Dictionary {
match self {
XPCObject::Dictionary(d) => d,
other => panic!(
"xpc! macro merge `:<` expects a dictionary, found {:?}",
other
),
}
}
}
impl<K, V> IntoXpcDict for HashMap<K, V>
where
K: Into<String>,
V: XpcConvertible,
{
fn into_xpc_dict(self) -> Dictionary {
self.into_iter()
.map(|(k, v)| (k.into(), v.to_xpc_value()))
.collect()
}
}
pub trait MaybeIntoXpcDict {
fn into_option_xpc_dict(self) -> Option<Dictionary>;
}
impl<T: IntoXpcDict> MaybeIntoXpcDict for T {
fn into_option_xpc_dict(self) -> Option<Dictionary> {
Some(self.into_xpc_dict())
}
}
impl<T: IntoXpcDict> MaybeIntoXpcDict for Option<T> {
fn into_option_xpc_dict(self) -> Option<Dictionary> {
self.map(IntoXpcDict::into_xpc_dict)
}
}
#[doc(hidden)]
pub fn maybe_into_xpc_dict<T: MaybeIntoXpcDict>(v: T) -> Option<Dictionary> {
v.into_option_xpc_dict()
}
#[cfg(test)]
mod tests {
use crate::xpc::{Dictionary, XPCObject};
use uuid::Uuid;
#[test]
fn test_xpc_macro_primitives() {
assert_eq!(xpc!(null), XPCObject::String("".to_string()));
assert_eq!(xpc!(true), XPCObject::Bool(true));
assert_eq!(xpc!(-123), XPCObject::Int64(-123));
assert_eq!(xpc!(123), XPCObject::Int64(123));
assert_eq!(xpc!(123u32), XPCObject::UInt64(123));
assert_eq!(xpc!(123u64), XPCObject::UInt64(123));
assert_eq!(xpc!(123.45), XPCObject::Double(123.45));
assert_eq!(xpc!("hello"), XPCObject::String("hello".to_string()));
}
#[test]
fn test_xpc_macro_collections() {
let arr = xpc!([1, "two", true]);
match arr {
XPCObject::Array(vec) => {
assert_eq!(vec.len(), 3);
assert_eq!(vec[0], XPCObject::Int64(1));
assert_eq!(vec[1], XPCObject::String("two".to_string()));
assert_eq!(vec[2], XPCObject::Bool(true));
}
_ => panic!("Expected array"),
}
let dict = xpc!({
"key1": 1u64,
"key2": "value2"
});
match dict {
XPCObject::Dictionary(map) => {
assert_eq!(map.len(), 2);
assert_eq!(map.get("key1"), Some(&XPCObject::UInt64(1)));
assert_eq!(
map.get("key2"),
Some(&XPCObject::String("value2".to_string()))
);
}
_ => panic!("Expected dictionary"),
}
}
#[test]
fn test_xpc_macro_interpolation_and_optional() {
let my_uuid = Uuid::new_v4();
let optional_field: Option<String> = None;
let present_field = Some("I'm here");
let base = xpc!({ "base_field": true });
let optional_base: Option<Dictionary> = None;
let obj = xpc!({
"id": my_uuid,
"optional_field":? optional_field,
"present_field":? present_field,
:< base,
:<? optional_base,
"arr": [?Some(1), ?None::<i32>, 3],
});
if let XPCObject::Dictionary(dict) = obj {
assert_eq!(dict.get("id"), Some(&XPCObject::Uuid(my_uuid)));
assert!(dict.get("optional_field").is_none());
assert_eq!(
dict.get("present_field"),
Some(&XPCObject::String("I'm here".to_string()))
);
assert_eq!(dict.get("base_field"), Some(&XPCObject::Bool(true)));
if let Some(XPCObject::Array(arr)) = dict.get("arr") {
assert_eq!(arr.len(), 2);
assert_eq!(arr[0], XPCObject::Int64(1));
assert_eq!(arr[1], XPCObject::Int64(3));
} else {
panic!("Expected array for 'arr' key");
}
} else {
panic!("Expected dictionary");
}
}
}