<commit_before>//! Intrusive reference types.
//!
//! Use of intrusively-reference-counted objects with matching reference types
//! over containers like `std::rc::Rc` or `std::sync::Arc` trades some
//! compile-time safety checks (and likely performance benefits) for greater
//! flexibility in how those objects are allocated and used.
//!
//! `Rc` and `Arc` are themselves references to underlying *containers*; any
//! object passed to either's `::new` will be moved to a newly-allocated block
//! on the heap.  To support weak references the data's reference count is
//! stored in another heap-allocated block, which is freed *only when the last
//! reference, weak *or* strong, is dropped*.  Because these types have
//! a reference count stored separately from the data, it is impossible to
//! create a new `Rc`, `Arc`, or `Weak` pointer thereto without one of
//! the same.
//!
//! In contrast, the types defined here forgo support for weak references and
//! strong guarantees of lifetime-management and memory-safety to allow the
//! developer more flexibility in data storage and manipulation.
//!
//! Since reference counts are stored alongside the data (in types
//! implementing [`RefCountHolder`](trait.RefCountHolder.html)),
//! a stack-allocated value can safely provide references to itself as long as
//! its refcount is non-zero prior to its doing so.  they support references
//! to stack-allocated values and values in memory not managed directly the by
//! [`Reference`](trait.Reference.html) implementation.
use std;
use std::fmt;
use std::mem;
use std::ops::{Deref,DerefMut};
use std::ptr::PtrExt;
use std::clone::Clone;
use std::hash::{Hash, Hasher};
use std::borrow::Borrow;

/// Abstract interface for references to intrusively-refcounted values.
pub trait Reference<T: ?Sized> where T: RefCounted, Self: Sized {
    /// Create a reference from a raw pointer.  The reference-type
    /// implementation should do no management of the referand's refcount for
    /// this operation.
    fn from_obj<U>(obj: &U) -> Self;

    /// Create an intrusive reference from a trait object.  The reference-type
    /// implementation should do no management of the referand's refcount for
    /// this operation.
    fn from_trait_obj(obj: &T) -> Self;

    /// Move the given value into the heap, and return a reference to it.  The value
    /// should have a refcount of zero prior to this function.
    ///
    /// # Examples
    ///
    /// ```rust
    /// #[macro_use]
    /// use cripes::util::intrusive;
    /// use cripes::util::intrusive::{Ref,Reference};
    ///
    /// #[derive(Debug)]
    /// struct Dog { refcount: usize }
    /// default_refcounted_impl!(Dog);
    ///
    /// let x: Ref<_> = Reference::new_on_heap(Dog{refcount: 0});   // x is an intrusive reference to the heap
    ///
    /// println!("{:?}", x);
    /// drop(x);                  // dropping the sole reference to that location
    ///                           // will free the memory.
    /// ```
    fn new_on_heap<U>(mut obj: U) -> Self where Self: Sized, U: Sized + RefCounted {
        {
            let rc = obj.as_refcounted_mut();
            // Zero the reference count: we're "taking ownership" of the object.
            while rc.get_refcount() != 0 { rc.remove_ref(); }

            rc.add_ref();
        }

        // Move the object onto the heap using Box, then discard the box in
        // favor of a hot, sticky, unsafe mess.
        let heaped: *const () = unsafe { std::boxed::into_raw(Box::new(obj)) as *const () };

        if std::mem::size_of::<&T>() == std::mem::size_of::<usize>() {
            // standard pointer
            unsafe { Reference::from_obj(&*(heaped as *const U)) }
        } else {
            // trait object
            unsafe { Reference::from_trait_obj(&*(heaped as *const T)) }
        }
    }
}

/// Describes a type that holds a reference count.
pub trait RefCountHolder {
    /// Fetch the object's current reference count.
    fn get_refcount(&self) -> usize;

    /// Increment the object's reference count, returning the new value.
    fn add_ref(&mut self) -> usize;

    /// Decrement the object's reference count, returning the new value.
    fn remove_ref(&mut self) -> usize;
}

pub trait RefCounted {
    // /// Create a reference to the current object.
    // fn ref_to_self<T: ?Sized>(&self) -> Ref<T> where T: RefCounted, Self: T {
    //     Reference::from_raw(unsafe { std::mem::transmute::<_,*const Self>(self) })
    // }

    fn as_refcounted(&self) -> &RefCountHolder;
    fn as_refcounted_mut(&mut self) -> &mut RefCountHolder;
}

impl<T> RefCounted for T where T: RefCountHolder {
    fn as_refcounted(&self) -> &RefCountHolder { self as &RefCountHolder }
    fn as_refcounted_mut(&mut self) -> &mut RefCountHolder { self as &mut RefCountHolder }
}

/// Create a simple implementation of util::intrusive::RefCountHolder for a type.
#[macro_export]
macro_rules! default_refcounted_impl {
    ($target:ident) => (default_refcounted_impl!($target, refcount););

    ($target:ident, $($mbr:ident).*) => (
        impl ::cripes::util::intrusive::RefCountHolder for $target {
            fn get_refcount(&self) -> usize { self.$($mbr).* }
            fn add_ref(&mut self) -> usize { self.$($mbr).* += 1; self.$($mbr).* }
            fn remove_ref(&mut self) -> usize { self.$($mbr).* -= 1; self.$($mbr).* }
        });
}

/* ****************************************************************
 * MutRef<T>
 */
// This is disabled for now -- we'd need a refcounted `RwLock` type or
// something to make this properly intrusive.  Until that happens, we
// (!!unsafely) implement MutDeref for Ref<T>.

/* /// Mutable, RwLock-protected reference to an intrusively-refcounted object.
pub struct MutRef<T: ?Sized + RefCounted + Send + Sync>(std::sync::RwLock<*mut T>);

impl<T> Reference<T> for MutRef<T>
where T: RefCounted + Send + Sync {
    fn new(ptr: *mut T) -> MutRef<T> { MutRef(std::sync::RwLock::new(ptr)) }
}

impl<T> Deref for MutRef<T>
    where T: RefCounted + Send + Sync {
    type Target = T;
    fn deref(&self) -> &T {
        if (*self).is_null() { panic!("tried to deref a null pointer") }
        match *self { Ref(p) =>  { let val_ref: &T = unsafe { &*p };
                                   val_ref }
        }
    }
}
impl<T: RefCounted> DerefMut for Ref<T> {
    fn deref_mut(&mut self) -> &mut T {
        if (*self).is_null() { panic!("tried to deref a null pointer") }
        match *self { Ref(p) =>  unsafe { std::mem::transmute(&*p) }
        }
    }
}*/

/* ****************************************************************
 * Ref<T>
 */
/// Immutable reference to an intrusively-refcounted object.
pub struct Ref<T: ?Sized>(RefImpl<T>) where T: RefCounted;

/// Private data type for Ref<T>.
enum RefImpl<T: ?Sized> where T: RefCounted {
    /// Invalid reference.
    Empty,

    /// Simple pointer to a concrete type.
    Pointer(*const T),

    /// A fat pointer/trait object.
    TraitObject(std::raw::TraitObject)
}

impl<T: ?Sized> Reference<T> for Ref<T> where T: RefCounted {
    fn from_trait_obj(obj: &T) -> Self {
        Ref(RefImpl::TraitObject(std::mem::transmute(obj)))
    }

    fn from_obj<U>(obj: &U) -> Ref<T> {
        let ptr: *const T =  unsafe { std::mem::transmute(obj) };
        if ptr == (0 as *const T) { Ref(RefImpl::Empty) }
        else { Ref(RefImpl::Pointer(ptr)) }
    }
}

// impl<T: ?Sized, U> Reference<U> for Ref<T> where T: RefCounted {
//     fn from_trait_obj(obj: &T) -> Self {
//         Ref(RefImpl::TraitObject(std::mem::transmute(obj)))
//     }

//     fn from_obj<U>(obj: &U) -> Ref<T> {
//         let ptr: *const T =  unsafe { std::mem::transmute(obj) };
//         if ptr == (0 as *const T) { Ref(RefImpl::Empty) }
//         else { Ref(RefImpl::Pointer(ptr)) }
//     }
// }

impl<T: ?Sized> Ref<T> where T: RefCounted {
    /// Fetch the reference's data pointer as a `usize`.  This is intended
    /// primarily for implementing hashes over trait objects (the standard
    /// library's Hash is not object-safe) by simply hashing the instance's
    /// data pointer.
    pub fn data_id(&self) -> usize {
        match self.0 {
            RefImpl::Empty => 0,
            RefImpl::Pointer(p) => p as usize,
            RefImpl::TraitObject(ref to) => to.data as usize
        }
    }

    /// Create a null Ref.  This is probably not very safe.
    pub fn null() -> Ref<T> {
        Ref(RefImpl::Empty)
   }

    /// Check if the ref's internal pointer is NULL.
    pub fn is_null(&self) -> bool {
        match self.0 {
            RefImpl::Empty => true,
            _ => false
        }
    }

    /// Get the reference count from the underlying object.
    pub fn get_refcount(&self) -> usize {
        match self.0 {
            RefImpl::Empty => 0,
            _ => self.deref().as_refcounted().get_refcount()
        }
    }

    /// Check if this refers to the same object as another Ref.
    pub fn is_same(&self, other: &Ref<T>) -> bool {
        match self.0 {
            RefImpl::Empty => match other.0 { RefImpl::Empty => true, _ => false },
            RefImpl::Pointer(x) => match other.0 { RefImpl::Pointer(y) => x == y, _ => false },
            RefImpl::TraitObject(ref x) => match other.0 {
                RefImpl::TraitObject(ref y) => { x.data == y.data && x.vtable == y.vtable },
                _ => false },
        }
    }
}

/* ================================================================
 * impl Deref
 */
impl<T: ?Sized> Deref for Ref<T>
where T: RefCounted {
    type Target = T;
    fn deref(&self) -> &T {
        match self.0 {
            RefImpl::Empty => panic!("tried to deref from an empty reference"),
            RefImpl::Pointer(ptr) => unsafe { &*ptr },
            RefImpl::TraitObject(ref obj) => unsafe { std::mem::transmute(obj) }
        }
    }
}

impl<T: ?Sized> DerefMut for Ref<T>
where T: RefCounted {
    fn deref_mut(&mut self) -> &mut T {
        match self.0 {
            RefImpl::Empty => panic!("tried to mutably deref from an empty reference"),
            RefImpl::Pointer(ptr) => unsafe { &mut *std::mem::transmute::<_,*mut T>(ptr) },
            RefImpl::TraitObject(ref obj) => unsafe { std::mem::transmute(obj) }
        }
    }
}

/* ================================================================
 * impl std::borrow::Borrow
 */
impl<T: ?Sized>  Borrow<T> for ::cripes::util::intrusive::Ref<T>
where T: RefCounted {
    fn borrow(&self) -> &T {
        self.deref()
    }
}

// impl<T: ?Sized>  Borrow<::cripes::util::intrusive::Ref<T>> for ::cripes::util::intrusive::Ref<T>
// where T: RefCounted {
//     fn borrow(&self) -> &T {
//         self
//     }
// }

/* ================================================================
 * impl fmt::Display
 */
impl<T: ?Sized> fmt::Display for Ref<T>
where T: RefCounted + fmt::Display {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        match self.0 {
            RefImpl::Pointer(p) => { assert!(! (p == 0 as *const T));
                                     let val: &T = (*self).deref();
                                     val.fmt(f) } }
    }
}

/* ================================================================
 * impl fmt::Debug
 */
impl<T: ?Sized> fmt::Debug for Ref<T>
where T:  RefCounted + fmt::Debug {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        let ptr = match self.0 {
            RefImpl::Empty => { return write!(f, "Ref{{Empty}}"); },
            RefImpl::Pointer(p) => p,
            RefImpl::TraitObject(ref obj) => unsafe { std::mem::transmute(obj.data) }};
        let val = (*self).deref();
        try!(write!(f, "Ref {{ 0x{:X}=>", ptr as usize));
        try!(val.fmt(f));
        write!(f, " }}")

    }
}

/* ================================================================
 * impl Clone
 */
impl<T: ?Sized> Clone for Ref<T>
where T: RefCounted {
    fn clone(&self) -> Self {
        Ref(self.0)
    }
}

/* ================================================================
 * impl Drop
 */
#[unsafe_destructor]
impl<T: ?Sized> Drop for Ref<T>
where T: RefCounted {
    fn drop(&mut self) {
        match self.0 {
            RefImpl::Pointer(x) => { assert!((x as usize) != 0);
                                     unsafe {
                                         let _ref =  self.deref_mut().as_refcounted_mut();
                                         if _ref.remove_ref() == 0 {
                                             let boxed = Box::from_raw(mem::transmute::<_,*mut T>(x));
                                             drop(boxed); } } },
            _ => {}
        }
    }
}

/* ================================================================
 * impl Eq, PartialEq<T>, PartialEq<Ref<T>>
 */
impl<T: ?Sized> Eq for Ref<T>
where T: RefCounted + Eq {}

impl<T: ?Sized> PartialEq<T> for Ref<T>
where T: RefCounted + PartialEq<T> {
    fn eq(&self, other: &T) -> bool {
        self.deref() == other
    }
}

impl<T: ?Sized> PartialEq<Ref<T>> for Ref<T>
where T: RefCounted + PartialEq<T> {
    fn eq(&self, other: &Ref<T>) -> bool {
        *self == *other
    }
}

/* ================================================================
 * impl Hash<H>
 */
impl<T: ?Sized> Hash for Ref<T>
where T: RefCounted + Hash {
    fn hash<H: Hasher>(&self, state: &mut H) {
        (*self).deref().hash(state)
    }
}
<commit_msg>util/intrusive: impl Clone correctly for Ref<T><commit_after>//! Intrusive reference types.
//!
//! Use of intrusively-reference-counted objects with matching reference types
//! over containers like `std::rc::Rc` or `std::sync::Arc` trades some
//! compile-time safety checks (and likely performance benefits) for greater
//! flexibility in how those objects are allocated and used.
//!
//! `Rc` and `Arc` are themselves references to underlying *containers*; any
//! object passed to either's `::new` will be moved to a newly-allocated block
//! on the heap.  To support weak references the data's reference count is
//! stored in another heap-allocated block, which is freed *only when the last
//! reference, weak *or* strong, is dropped*.  Because these types have
//! a reference count stored separately from the data, it is impossible to
//! create a new `Rc`, `Arc`, or `Weak` pointer thereto without one of
//! the same.
//!
//! In contrast, the types defined here forgo support for weak references and
//! strong guarantees of lifetime-management and memory-safety to allow the
//! developer more flexibility in data storage and manipulation.
//!
//! Since reference counts are stored alongside the data (in types
//! implementing [`RefCountHolder`](trait.RefCountHolder.html)),
//! a stack-allocated value can safely provide references to itself as long as
//! its refcount is non-zero prior to its doing so.  they support references
//! to stack-allocated values and values in memory not managed directly the by
//! [`Reference`](trait.Reference.html) implementation.
use std;
use std::fmt;
use std::mem;
use std::ops::{Deref,DerefMut};
use std::ptr::PtrExt;
use std::clone::Clone;
use std::hash::{Hash, Hasher};
use std::borrow::Borrow;

/// Abstract interface for references to intrusively-refcounted values.
pub trait Reference<T: ?Sized> where T: RefCounted, Self: Sized {
    /// Create a reference from a raw pointer.  The reference-type
    /// implementation should do no management of the referand's refcount for
    /// this operation.
    fn from_obj<U>(obj: &U) -> Self;

    /// Create an intrusive reference from a trait object.  The reference-type
    /// implementation should do no management of the referand's refcount for
    /// this operation.
    fn from_trait_obj(obj: &T) -> Self;

    /// Move the given value into the heap, and return a reference to it.  The value
    /// should have a refcount of zero prior to this function.
    ///
    /// # Examples
    ///
    /// ```rust
    /// #[macro_use]
    /// use cripes::util::intrusive;
    /// use cripes::util::intrusive::{Ref,Reference};
    ///
    /// #[derive(Debug)]
    /// struct Dog { refcount: usize }
    /// default_refcounted_impl!(Dog);
    ///
    /// let x: Ref<_> = Reference::new_on_heap(Dog{refcount: 0});   // x is an intrusive reference to the heap
    ///
    /// println!("{:?}", x);
    /// drop(x);                  // dropping the sole reference to that location
    ///                           // will free the memory.
    /// ```
    fn new_on_heap<U>(mut obj: U) -> Self where Self: Sized, U: Sized + RefCounted {
        {
            let rc = obj.as_refcounted_mut();
            // Zero the reference count: we're "taking ownership" of the object.
            while rc.get_refcount() != 0 { rc.remove_ref(); }

            rc.add_ref();
        }

        // Move the object onto the heap using Box, then discard the box in
        // favor of a hot, sticky, unsafe mess.
        let heaped: *const () = unsafe { std::boxed::into_raw(Box::new(obj)) as *const () };

        if std::mem::size_of::<&T>() == std::mem::size_of::<usize>() {
            // standard pointer
            unsafe { Reference::from_obj(&*(heaped as *const U)) }
        } else {
            // trait object
            unsafe { Reference::from_trait_obj(&*(heaped as *const T)) }
        }
    }
}

/// Describes a type that holds a reference count.
pub trait RefCountHolder {
    /// Fetch the object's current reference count.
    fn get_refcount(&self) -> usize;

    /// Increment the object's reference count, returning the new value.
    fn add_ref(&mut self) -> usize;

    /// Decrement the object's reference count, returning the new value.
    fn remove_ref(&mut self) -> usize;
}

pub trait RefCounted {
    // /// Create a reference to the current object.
    // fn ref_to_self<T: ?Sized>(&self) -> Ref<T> where T: RefCounted, Self: T {
    //     Reference::from_raw(unsafe { std::mem::transmute::<_,*const Self>(self) })
    // }

    fn as_refcounted(&self) -> &RefCountHolder;
    fn as_refcounted_mut(&mut self) -> &mut RefCountHolder;
}

impl<T> RefCounted for T where T: RefCountHolder {
    fn as_refcounted(&self) -> &RefCountHolder { self as &RefCountHolder }
    fn as_refcounted_mut(&mut self) -> &mut RefCountHolder { self as &mut RefCountHolder }
}

/// Create a simple implementation of util::intrusive::RefCountHolder for a type.
#[macro_export]
macro_rules! default_refcounted_impl {
    ($target:ident) => (default_refcounted_impl!($target, refcount););

    ($target:ident, $($mbr:ident).*) => (
        impl ::cripes::util::intrusive::RefCountHolder for $target {
            fn get_refcount(&self) -> usize { self.$($mbr).* }
            fn add_ref(&mut self) -> usize { self.$($mbr).* += 1; self.$($mbr).* }
            fn remove_ref(&mut self) -> usize { self.$($mbr).* -= 1; self.$($mbr).* }
        });
}

/* ****************************************************************
 * MutRef<T>
 */
// This is disabled for now -- we'd need a refcounted `RwLock` type or
// something to make this properly intrusive.  Until that happens, we
// (!!unsafely) implement MutDeref for Ref<T>.

/* /// Mutable, RwLock-protected reference to an intrusively-refcounted object.
pub struct MutRef<T: ?Sized + RefCounted + Send + Sync>(std::sync::RwLock<*mut T>);

impl<T> Reference<T> for MutRef<T>
where T: RefCounted + Send + Sync {
    fn new(ptr: *mut T) -> MutRef<T> { MutRef(std::sync::RwLock::new(ptr)) }
}

impl<T> Deref for MutRef<T>
    where T: RefCounted + Send + Sync {
    type Target = T;
    fn deref(&self) -> &T {
        if (*self).is_null() { panic!("tried to deref a null pointer") }
        match *self { Ref(p) =>  { let val_ref: &T = unsafe { &*p };
                                   val_ref }
        }
    }
}
impl<T: RefCounted> DerefMut for Ref<T> {
    fn deref_mut(&mut self) -> &mut T {
        if (*self).is_null() { panic!("tried to deref a null pointer") }
        match *self { Ref(p) =>  unsafe { std::mem::transmute(&*p) }
        }
    }
}*/

/* ****************************************************************
 * Ref<T>
 */
/// Immutable reference to an intrusively-refcounted object.
pub struct Ref<T: ?Sized>(RefImpl<T>) where T: RefCounted;

/// Private data type for Ref<T>.
enum RefImpl<T: ?Sized> where T: RefCounted {
    /// Invalid reference.
    Empty,

    /// Simple pointer to a concrete type.
    Pointer(*const T),

    /// A fat pointer/trait object.
    TraitObject(std::raw::TraitObject)
}

impl<T: ?Sized> Reference<T> for Ref<T> where T: RefCounted {
    fn from_trait_obj(obj: &T) -> Self {
        Ref(RefImpl::TraitObject(std::mem::transmute(obj)))
    }

    fn from_obj<U>(obj: &U) -> Ref<T> {
        let ptr: *const T =  unsafe { std::mem::transmute(obj) };
        if ptr == (0 as *const T) { Ref(RefImpl::Empty) }
        else { Ref(RefImpl::Pointer(ptr)) }
    }
}

// impl<T: ?Sized, U> Reference<U> for Ref<T> where T: RefCounted {
//     fn from_trait_obj(obj: &T) -> Self {
//         Ref(RefImpl::TraitObject(std::mem::transmute(obj)))
//     }

//     fn from_obj<U>(obj: &U) -> Ref<T> {
//         let ptr: *const T =  unsafe { std::mem::transmute(obj) };
//         if ptr == (0 as *const T) { Ref(RefImpl::Empty) }
//         else { Ref(RefImpl::Pointer(ptr)) }
//     }
// }

impl<T: ?Sized> Ref<T> where T: RefCounted {
    /// Fetch the reference's data pointer as a `usize`.  This is intended
    /// primarily for implementing hashes over trait objects (the standard
    /// library's Hash is not object-safe) by simply hashing the instance's
    /// data pointer.
    pub fn data_id(&self) -> usize {
        match self.0 {
            RefImpl::Empty => 0,
            RefImpl::Pointer(p) => p as usize,
            RefImpl::TraitObject(ref to) => to.data as usize
        }
    }

    /// Create a null Ref.  This is probably not very safe.
    pub fn null() -> Ref<T> {
        Ref(RefImpl::Empty)
   }

    /// Check if the ref's internal pointer is NULL.
    pub fn is_null(&self) -> bool {
        match self.0 {
            RefImpl::Empty => true,
            _ => false
        }
    }

    /// Get the reference count from the underlying object.
    pub fn get_refcount(&self) -> usize {
        match self.0 {
            RefImpl::Empty => 0,
            _ => self.deref().as_refcounted().get_refcount()
        }
    }

    /// Check if this refers to the same object as another Ref.
    pub fn is_same(&self, other: &Ref<T>) -> bool {
        match self.0 {
            RefImpl::Empty => match other.0 { RefImpl::Empty => true, _ => false },
            RefImpl::Pointer(x) => match other.0 { RefImpl::Pointer(y) => x == y, _ => false },
            RefImpl::TraitObject(ref x) => match other.0 {
                RefImpl::TraitObject(ref y) => { x.data == y.data && x.vtable == y.vtable },
                _ => false },
        }
    }
}

/* ================================================================
 * impl Deref
 */
impl<T: ?Sized> Deref for Ref<T>
where T: RefCounted {
    type Target = T;
    fn deref(&self) -> &T {
        match self.0 {
            RefImpl::Empty => panic!("tried to deref from an empty reference"),
            RefImpl::Pointer(ptr) => unsafe { &*ptr },
            RefImpl::TraitObject(ref obj) => unsafe { std::mem::transmute(obj) }
        }
    }
}

impl<T: ?Sized> DerefMut for Ref<T>
where T: RefCounted {
    fn deref_mut(&mut self) -> &mut T {
        match self.0 {
            RefImpl::Empty => panic!("tried to mutably deref from an empty reference"),
            RefImpl::Pointer(ptr) => unsafe { &mut *std::mem::transmute::<_,*mut T>(ptr) },
            RefImpl::TraitObject(ref obj) => unsafe { std::mem::transmute(obj) }
        }
    }
}

/* ================================================================
 * impl std::borrow::Borrow
 */
impl<T: ?Sized>  Borrow<T> for ::cripes::util::intrusive::Ref<T>
where T: RefCounted {
    fn borrow(&self) -> &T {
        self.deref()
    }
}

// impl<T: ?Sized>  Borrow<::cripes::util::intrusive::Ref<T>> for ::cripes::util::intrusive::Ref<T>
// where T: RefCounted {
//     fn borrow(&self) -> &T {
//         self
//     }
// }

/* ================================================================
 * impl fmt::Display
 */
impl<T: ?Sized> fmt::Display for Ref<T>
where T: RefCounted + fmt::Display {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        match self.0 {
            RefImpl::Pointer(p) => { assert!(! (p == 0 as *const T));
                                     let val: &T = (*self).deref();
                                     val.fmt(f) } }
    }
}

/* ================================================================
 * impl fmt::Debug
 */
impl<T: ?Sized> fmt::Debug for Ref<T>
where T:  RefCounted + fmt::Debug {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        let ptr = match self.0 {
            RefImpl::Empty => { return write!(f, "Ref{{Empty}}"); },
            RefImpl::Pointer(p) => p,
            RefImpl::TraitObject(ref obj) => unsafe { std::mem::transmute(obj.data) }};
        let val = (*self).deref();
        try!(write!(f, "Ref {{ 0x{:X}=>", ptr as usize));
        try!(val.fmt(f));
        write!(f, " }}")

    }
}

/* ================================================================
 * impl Clone
 */
impl<T: ?Sized> Clone for Ref<T>
where T: RefCounted {
    fn clone(&self) -> Self {
        match self.0 {
            RefImpl::Empty => Ref(RefImpl::Empty),
            RefImpl::Pointer(p) => Ref(RefImpl::Pointer(p)),
            RefImpl::TraitObject(to) => Ref(RefImpl::TraitObject(to))
        }
    }
}

/* ================================================================
 * impl Drop
 */
#[unsafe_destructor]
impl<T: ?Sized> Drop for Ref<T>
where T: RefCounted {
    fn drop(&mut self) {
        match self.0 {
            RefImpl::Pointer(x) => { assert!((x as usize) != 0);
                                     unsafe {
                                         let _ref =  self.deref_mut().as_refcounted_mut();
                                         if _ref.remove_ref() == 0 {
                                             let boxed = Box::from_raw(mem::transmute::<_,*mut T>(x));
                                             drop(boxed); } } },
            _ => {}
        }
    }
}

/* ================================================================
 * impl Eq, PartialEq<T>, PartialEq<Ref<T>>
 */
impl<T: ?Sized> Eq for Ref<T>
where T: RefCounted + Eq {}

impl<T: ?Sized> PartialEq<T> for Ref<T>
where T: RefCounted + PartialEq<T> {
    fn eq(&self, other: &T) -> bool {
        self.deref() == other
    }
}

impl<T: ?Sized> PartialEq<Ref<T>> for Ref<T>
where T: RefCounted + PartialEq<T> {
    fn eq(&self, other: &Ref<T>) -> bool {
        *self == *other
    }
}

/* ================================================================
 * impl Hash<H>
 */
impl<T: ?Sized> Hash for Ref<T>
where T: RefCounted + Hash {
    fn hash<H: Hasher>(&self, state: &mut H) {
        (*self).deref().hash(state)
    }
}
<|endoftext|>


<commit_before>use std::env;
use std::io;
use std::path::Path;
use std::process;
use std::str;

use console::strip_ansi_codes;
use failure::{err_msg, Error};
use glob::{glob_with, MatchOptions};
use serde_json;

use utils::releases::{get_xcode_release_name, infer_gradle_release_name};
use utils::xcode::{InfoPlist, XcodeProjectInfo};

static CODEPUSH_BIN_PATH: &'static str = "code-push";
static CODEPUSH_NPM_PATH: &'static str = "node_modules/.bin/code-push";

#[derive(Debug, Deserialize)]
pub struct CodePushPackage {
    pub label: String,
}

#[derive(Debug, Deserialize)]
pub struct CodePushDeployment {
    pub name: String,
    pub package: Option<CodePushPackage>,
}

fn get_codepush_error(output: process::Output) -> Error {
    if let Ok(message) = str::from_utf8(&output.stderr) {
        let stripped = strip_ansi_codes(message);
        err_msg(
            if stripped.starts_with("[Error]  ") {
                &stripped[9..]
            } else if stripped.starts_with("[Error] ") {
                &stripped[8..]
            } else {
                &stripped
            }.to_string(),
        )
    } else {
        err_msg("Unknown Error")
    }
}

pub fn get_codepush_deployments(app: &str) -> Result<Vec<CodePushDeployment>, Error> {
    let codepush_bin = if Path::new(CODEPUSH_NPM_PATH).exists() {
        CODEPUSH_NPM_PATH
    } else {
        CODEPUSH_BIN_PATH
    };

    let output = process::Command::new(codepush_bin)
        .arg("deployment")
        .arg("ls")
        .arg(app)
        .arg("--format")
        .arg("json")
        .output()
        .map_err(|e| {
            if e.kind() == io::ErrorKind::NotFound {
                "Codepush not found. Is it installed and configured on the PATH?".into()
            } else {
                Error::from(e).context("Failed to run codepush")
            }
        })?;

    if output.status.success() {
        Ok(serde_json::from_slice(&output.stdout)?)
    } else {
        Err(get_codepush_error(output)
            .context("Failed to get codepush deployments")
            .into())
    }
}

pub fn get_codepush_package(app: &str, deployment: &str) -> Result<CodePushPackage, Error> {
    let deployments = get_codepush_deployments(app)?;
    for dep in deployments {
        if_chain! {
            if &dep.name == deployment;
            if let Some(pkg) = dep.package;
            then {
                return Ok(pkg);
            }
        }
    }

    bail!("Could not find deployment {} for {}", deployment, app)
}

pub fn get_react_native_codepush_release(
    package: &CodePushPackage,
    platform: &str,
    bundle_id_override: Option<&str>,
) -> Result<String, Error> {
    if let Some(bundle_id) = bundle_id_override {
        return Ok(format!("{}-codepush:{}", bundle_id, package.label));
    }

    if platform == "ios" {
        if !cfg!(target_os = "macos") {
            bail!("Codepush releases for iOS require OS X if no bundle ID is specified");
        }
        let mut opts = MatchOptions::new();
        opts.case_sensitive = false;
        for entry_rv in glob_with("ios/*.xcodeproj", &opts)? {
            if let Ok(entry) = entry_rv {
                let pi = XcodeProjectInfo::from_path(&entry)?;
                if let Some(ipl) = InfoPlist::from_project_info(&pi)? {
                    if let Some(release_name) = get_xcode_release_name(Some(ipl))? {
                        return Ok(format!("{}-codepush:{}", release_name, package.label));
                    }
                }
            }
        }
        bail!("Could not find plist");
    } else if platform == "android" {
        if_chain! {
            if let Ok(here) = env::current_dir();
            if let Ok(android_folder) = here.join("android").metadata();
            if android_folder.is_dir();
            then {
                if let Some(release_name) = infer_gradle_release_name(Some(here.join("android")))? {
                    return Ok(format!("{}-codepush:{}", release_name, package.label));
                } else {
                    bail!("Could not parse app id from build.gradle");
                }
            }
        }
        bail!("Could not find AndroidManifest.xml");
    }
    bail!("Unsupported platform '{}'", platform);
}
<commit_msg>feat: Switch from codepush to App Center CLI (#325)<commit_after>use std::env;
use std::io;
use std::path::Path;
use std::process;
use std::str;

use console::strip_ansi_codes;
use failure::{err_msg, Error};
use glob::{glob_with, MatchOptions};
use serde_json;

use utils::releases::{get_xcode_release_name, infer_gradle_release_name};
use utils::xcode::{InfoPlist, XcodeProjectInfo};

static APPCENTER_BIN_PATH: &'static str = "appcenter";
static APPCENTER_NPM_PATH: &'static str = "node_modules/.bin/appcenter";

#[derive(Debug, Deserialize)]
pub struct CodePushPackage {
    pub label: String,
}

#[derive(Debug, Deserialize)]
pub struct CodePushDeployment {
    pub name: String,
    pub package: Option<CodePushPackage>,
}

fn get_codepush_error(output: process::Output) -> Error {
    if let Ok(message) = str::from_utf8(&output.stderr) {
        let stripped = strip_ansi_codes(message);
        err_msg(
            if stripped.starts_with("[Error]  ") {
                &stripped[9..]
            } else if stripped.starts_with("[Error] ") {
                &stripped[8..]
            } else {
                &stripped
            }.to_string(),
        )
    } else {
        err_msg("Unknown Error")
    }
}

pub fn get_codepush_deployments(app: &str) -> Result<Vec<CodePushDeployment>, Error> {
    let appcenter_bin = if Path::new(APPCENTER_NPM_PATH).exists() {
        APPCENTER_NPM_PATH
    } else {
        APPCENTER_BIN_PATH
    };

    let output = process::Command::new(appcenter_bin)
        .arg("codepush")
        .arg("deployment")
        .arg("list")
        .arg("--app")
        .arg(app)
        .arg("--output")
        .arg("json")
        .output()
        .map_err(|e| {
            if e.kind() == io::ErrorKind::NotFound {
                "App Center CLI not found. Is it installed and configured on the PATH?".into()
            } else {
                Error::from(e).context("Failed to run appcenter")
            }
        })?;

    if output.status.success() {
        let raw_data: Vec<Vec<String>> = serde_json::from_slice(&output.stdout)?;

        let mapped: Vec<CodePushDeployment> = raw_data
            .iter()
            .map(|&ref pair| CodePushDeployment {
                name: pair[0].clone(),
                package: if let Some(lbl) = pair.get(1) {
                    Some(CodePushPackage { label: lbl.clone() })
                } else {
                    None
                },
            })
            .collect();

        Ok(mapped)
    } else {
        Err(get_codepush_error(output)
            .context("Failed to get codepush deployments")
            .into())
    }
}

pub fn get_codepush_package(app: &str, deployment: &str) -> Result<CodePushPackage, Error> {
    let deployments = get_codepush_deployments(app)?;
    for dep in deployments {
        if_chain! {
            if &dep.name == deployment;
            if let Some(pkg) = dep.package;
            then {
                return Ok(pkg);
            }
        }
    }

    bail!("Could not find deployment {} for {}", deployment, app)
}

pub fn get_react_native_codepush_release(
    package: &CodePushPackage,
    platform: &str,
    bundle_id_override: Option<&str>,
) -> Result<String, Error> {
    if let Some(bundle_id) = bundle_id_override {
        return Ok(format!("{}-codepush:{}", bundle_id, package.label));
    }

    if platform == "ios" {
        if !cfg!(target_os = "macos") {
            bail!("Codepush releases for iOS require OS X if no bundle ID is specified");
        }
        let mut opts = MatchOptions::new();
        opts.case_sensitive = false;
        for entry_rv in glob_with("ios/*.xcodeproj", &opts)? {
            if let Ok(entry) = entry_rv {
                let pi = XcodeProjectInfo::from_path(&entry)?;
                if let Some(ipl) = InfoPlist::from_project_info(&pi)? {
                    if let Some(release_name) = get_xcode_release_name(Some(ipl))? {
                        return Ok(format!("{}-codepush:{}", release_name, package.label));
                    }
                }
            }
        }
        bail!("Could not find plist");
    } else if platform == "android" {
        if_chain! {
            if let Ok(here) = env::current_dir();
            if let Ok(android_folder) = here.join("android").metadata();
            if android_folder.is_dir();
            then {
                if let Some(release_name) = infer_gradle_release_name(Some(here.join("android")))? {
                    return Ok(format!("{}-codepush:{}", release_name, package.label));
                } else {
                    bail!("Could not parse app id from build.gradle");
                }
            }
        }
        bail!("Could not find AndroidManifest.xml");
    }
    bail!("Unsupported platform '{}'", platform);
}
<|endoftext|>


<commit_before>// This file is part of the uutils coreutils package.
//
// (c) Jordi Boggiano <j.boggiano@seld.be>
//
// For the full copyright and license information, please view the LICENSE
// file that was distributed with this source code.

/* last synced with: env (GNU coreutils) 8.13 */

// spell-checker:ignore (ToDO) chdir execvp progname subcommand subcommands unsets

#[macro_use]
extern crate clap;

use clap::{App, AppSettings, Arg};
use ini::Ini;
use std::borrow::Cow;
use std::env;
use std::io::{self, Write};
use std::iter::Iterator;
use std::process::Command;

const USAGE: &str = "env [OPTION]... [-] [NAME=VALUE]... [COMMAND [ARG]...]";
const AFTER_HELP: &str = "\
A mere - implies -i. If no COMMAND, print the resulting environment.
";

struct Options<'a> {
    ignore_env: bool,
    null: bool,
    running_directory: Option<&'a str>,
    files: Vec<&'a str>,
    unsets: Vec<&'a str>,
    sets: Vec<(&'a str, &'a str)>,
    program: Vec<&'a str>,
}

// print name=value env pairs on screen
// if null is true, separate pairs with a \0, \n otherwise
fn print_env(null: bool) {
    let stdout_raw = io::stdout();
    let mut stdout = stdout_raw.lock();
    for (n, v) in env::vars() {
        write!(stdout, "{}={}{}", n, v, if null { '\0' } else { '\n' }).unwrap();
    }
}

fn parse_name_value_opt<'a>(opts: &mut Options<'a>, opt: &'a str) -> Result<bool, i32> {
    // is it a NAME=VALUE like opt ?
    if let Some(idx) = opt.find('=') {
        // yes, so push name, value pair
        let (name, value) = opt.split_at(idx);
        opts.sets.push((name, &value['='.len_utf8()..]));

        Ok(false)
    } else {
        // no, it's a program-like opt
        parse_program_opt(opts, opt).map(|_| true)
    }
}

fn parse_program_opt<'a>(opts: &mut Options<'a>, opt: &'a str) -> Result<(), i32> {
    if opts.null {
        eprintln!("{}: cannot specify --null (-0) with command", crate_name!());
        eprintln!("Type \"{} --help\" for detailed information", crate_name!());
        Err(1)
    } else {
        opts.program.push(opt);
        Ok(())
    }
}

fn load_config_file(opts: &mut Options) -> Result<(), i32> {
    // NOTE: config files are parsed using an INI parser b/c it's available and compatible with ".env"-style files
    //   ... * but support for actual INI files, although working, is not intended, nor claimed
    for &file in &opts.files {
        let conf = if file == "-" {
            let stdin = io::stdin();
            let mut stdin_locked = stdin.lock();
            Ini::read_from(&mut stdin_locked)
        } else {
            Ini::load_from_file(file)
        };

        let conf = match conf {
            Ok(config) => config,
            Err(error) => {
                eprintln!("env: error: \"{}\": {}", file, error);
                return Err(1);
            }
        };

        for (_, prop) in &conf {
            // ignore all INI section lines (treat them as comments)
            for (key, value) in prop {
                env::set_var(key, value);
            }
        }
    }

    Ok(())
}

#[cfg(not(windows))]
fn build_command<'a, 'b>(args: &'a mut Vec<&'b str>) -> (Cow<'b, str>, &'a [&'b str]) {
    let progname = Cow::from(args[0]);
    (progname, &args[1..])
}

#[cfg(windows)]
fn build_command<'a, 'b>(args: &'a mut Vec<&'b str>) -> (Cow<'b, str>, &'a [&'b str]) {
    args.insert(0, "/d/c");
    let progname = env::var("ComSpec")
        .map(Cow::from)
        .unwrap_or_else(|_| Cow::from("cmd"));

    (progname, &args[..])
}

fn create_app() -> App<'static, 'static> {
    App::new(crate_name!())
        .version(crate_version!())
        .author(crate_authors!())
        .about(crate_description!())
        .usage(USAGE)
        .after_help(AFTER_HELP)
        .setting(AppSettings::AllowExternalSubcommands)
        .arg(Arg::with_name("ignore-environment")
            .short("i")
            .long("ignore-environment")
            .help("start with an empty environment"))
        .arg(Arg::with_name("chdir")
            .short("c")
            .long("chdir")
            .takes_value(true)
            .number_of_values(1)
            .value_name("DIR")
            .help("change working directory to DIR"))
        .arg(Arg::with_name("null")
            .short("0")
            .long("null")
            .help("end each output line with a 0 byte rather than a newline (only valid when \
                    printing the environment)"))
        .arg(Arg::with_name("file")
            .short("f")
            .long("file")
            .takes_value(true)
            .number_of_values(1)
            .value_name("PATH")
            .multiple(true)
            .help("read and set variables from a \".env\"-style configuration file (prior to any \
                    unset and/or set)"))
        .arg(Arg::with_name("unset")
            .short("u")
            .long("unset")
            .takes_value(true)
            .number_of_values(1)
            .value_name("NAME")
            .multiple(true)
            .help("remove variable from the environment"))
}

fn run_env(args: impl uucore::Args) -> Result<(), i32> {
    let app = create_app();
    let matches = app.get_matches_from(args);

    let ignore_env = matches.is_present("ignore-environment");
    let null = matches.is_present("null");
    let running_directory = matches.value_of("chdir");
    let files = matches
        .values_of("file")
        .map(Iterator::collect)
        .unwrap_or_else(|| Vec::with_capacity(0));
    let unsets = matches
        .values_of("unset")
        .map(Iterator::collect)
        .unwrap_or_else(|| Vec::with_capacity(0));

    let mut opts = Options {
        ignore_env,
        null,
        running_directory,
        files,
        unsets,
        sets: vec![],
        program: vec![],
    };

    // change directory
    if let Some(d) = opts.running_directory {
        match env::set_current_dir(d) {
            Ok(()) => d,
            Err(error) => {
                eprintln!("env: cannot change directory to \"{}\": {}", d, error);
                return Err(125);
            }
        };
    }

    // we handle the name, value pairs and the program to be executed by treating them as external
    // subcommands in clap
    if let (external, Some(matches)) = matches.subcommand() {
        let mut begin_prog_opts = false;

        if external == "-" {
            // "-" implies -i and stop parsing opts
            opts.ignore_env = true;
        } else {
            begin_prog_opts = parse_name_value_opt(&mut opts, external)?;
        }

        if let Some(mut iter) = matches.values_of("") {
            // read NAME=VALUE arguments (and up to a single program argument)
            while !begin_prog_opts {
                if let Some(opt) = iter.next() {
                    begin_prog_opts = parse_name_value_opt(&mut opts, opt)?;
                } else {
                    break;
                }
            }

            // read any leftover program arguments
            for opt in iter {
                parse_program_opt(&mut opts, opt)?;
            }
        }
    }

    // NOTE: we manually set and unset the env vars below rather than using Command::env() to more
    //       easily handle the case where no command is given

    // remove all env vars if told to ignore presets
    if opts.ignore_env {
        for (ref name, _) in env::vars() {
            env::remove_var(name);
        }
    }

    // load .env-style config file prior to those given on the command-line
    load_config_file(&mut opts)?;

    // unset specified env vars
    for name in &opts.unsets {
        env::remove_var(name);
    }

    // set specified env vars
    for &(name, val) in &opts.sets {
        // FIXME: set_var() panics if name is an empty string
        env::set_var(name, val);
    }

    if !opts.program.is_empty() {
        // we need to execute a command
        let (prog, args) = build_command(&mut opts.program);

        // FIXME: this should just use execvp() (no fork()) on Unix-like systems
        match Command::new(&*prog).args(args).status() {
            Ok(exit) => {
                if !exit.success() {
                    return Err(exit.code().unwrap());
                }
            }
            Err(ref err) if err.kind() == io::ErrorKind::NotFound => return Err(127),
            Err(_) => return Err(126),
        }
    } else {
        // no program provided, so just dump all env vars to stdout
        print_env(opts.null);
    }

    Ok(())
}

pub fn uumain(args: impl uucore::Args) -> i32 {
    match run_env(args) {
        Ok(()) => 0,
        Err(code) => code,
    }
}
<commit_msg>env: clean up returning Err<commit_after>// This file is part of the uutils coreutils package.
//
// (c) Jordi Boggiano <j.boggiano@seld.be>
//
// For the full copyright and license information, please view the LICENSE
// file that was distributed with this source code.

/* last synced with: env (GNU coreutils) 8.13 */

// spell-checker:ignore (ToDO) chdir execvp progname subcommand subcommands unsets

#[macro_use]
extern crate clap;

use clap::{App, AppSettings, Arg};
use ini::Ini;
use std::borrow::Cow;
use std::env;
use std::io::{self, Write};
use std::iter::Iterator;
use std::process::Command;

const USAGE: &str = "env [OPTION]... [-] [NAME=VALUE]... [COMMAND [ARG]...]";
const AFTER_HELP: &str = "\
A mere - implies -i. If no COMMAND, print the resulting environment.
";

struct Options<'a> {
    ignore_env: bool,
    null: bool,
    running_directory: Option<&'a str>,
    files: Vec<&'a str>,
    unsets: Vec<&'a str>,
    sets: Vec<(&'a str, &'a str)>,
    program: Vec<&'a str>,
}

// print name=value env pairs on screen
// if null is true, separate pairs with a \0, \n otherwise
fn print_env(null: bool) {
    let stdout_raw = io::stdout();
    let mut stdout = stdout_raw.lock();
    for (n, v) in env::vars() {
        write!(stdout, "{}={}{}", n, v, if null { '\0' } else { '\n' }).unwrap();
    }
}

fn parse_name_value_opt<'a>(opts: &mut Options<'a>, opt: &'a str) -> Result<bool, i32> {
    // is it a NAME=VALUE like opt ?
    if let Some(idx) = opt.find('=') {
        // yes, so push name, value pair
        let (name, value) = opt.split_at(idx);
        opts.sets.push((name, &value['='.len_utf8()..]));

        Ok(false)
    } else {
        // no, it's a program-like opt
        parse_program_opt(opts, opt).map(|_| true)
    }
}

fn parse_program_opt<'a>(opts: &mut Options<'a>, opt: &'a str) -> Result<(), i32> {
    if opts.null {
        eprintln!("{}: cannot specify --null (-0) with command", crate_name!());
        eprintln!("Type \"{} --help\" for detailed information", crate_name!());
        Err(1)
    } else {
        opts.program.push(opt);
        Ok(())
    }
}

fn load_config_file(opts: &mut Options) -> Result<(), i32> {
    // NOTE: config files are parsed using an INI parser b/c it's available and compatible with ".env"-style files
    //   ... * but support for actual INI files, although working, is not intended, nor claimed
    for &file in &opts.files {
        let conf = if file == "-" {
            let stdin = io::stdin();
            let mut stdin_locked = stdin.lock();
            Ini::read_from(&mut stdin_locked)
        } else {
            Ini::load_from_file(file)
        };

        let conf = conf.map_err(|error| {
            eprintln!("env: error: \"{}\": {}", file, error);
            1
        })?;

        for (_, prop) in &conf {
            // ignore all INI section lines (treat them as comments)
            for (key, value) in prop {
                env::set_var(key, value);
            }
        }
    }

    Ok(())
}

#[cfg(not(windows))]
fn build_command<'a, 'b>(args: &'a mut Vec<&'b str>) -> (Cow<'b, str>, &'a [&'b str]) {
    let progname = Cow::from(args[0]);
    (progname, &args[1..])
}

#[cfg(windows)]
fn build_command<'a, 'b>(args: &'a mut Vec<&'b str>) -> (Cow<'b, str>, &'a [&'b str]) {
    args.insert(0, "/d/c");
    let progname = env::var("ComSpec")
        .map(Cow::from)
        .unwrap_or_else(|_| Cow::from("cmd"));

    (progname, &args[..])
}

fn create_app() -> App<'static, 'static> {
    App::new(crate_name!())
        .version(crate_version!())
        .author(crate_authors!())
        .about(crate_description!())
        .usage(USAGE)
        .after_help(AFTER_HELP)
        .setting(AppSettings::AllowExternalSubcommands)
        .arg(Arg::with_name("ignore-environment")
            .short("i")
            .long("ignore-environment")
            .help("start with an empty environment"))
        .arg(Arg::with_name("chdir")
            .short("c")
            .long("chdir")
            .takes_value(true)
            .number_of_values(1)
            .value_name("DIR")
            .help("change working directory to DIR"))
        .arg(Arg::with_name("null")
            .short("0")
            .long("null")
            .help("end each output line with a 0 byte rather than a newline (only valid when \
                    printing the environment)"))
        .arg(Arg::with_name("file")
            .short("f")
            .long("file")
            .takes_value(true)
            .number_of_values(1)
            .value_name("PATH")
            .multiple(true)
            .help("read and set variables from a \".env\"-style configuration file (prior to any \
                    unset and/or set)"))
        .arg(Arg::with_name("unset")
            .short("u")
            .long("unset")
            .takes_value(true)
            .number_of_values(1)
            .value_name("NAME")
            .multiple(true)
            .help("remove variable from the environment"))
}

fn run_env(args: impl uucore::Args) -> Result<(), i32> {
    let app = create_app();
    let matches = app.get_matches_from(args);

    let ignore_env = matches.is_present("ignore-environment");
    let null = matches.is_present("null");
    let running_directory = matches.value_of("chdir");
    let files = matches
        .values_of("file")
        .map(Iterator::collect)
        .unwrap_or_else(|| Vec::with_capacity(0));
    let unsets = matches
        .values_of("unset")
        .map(Iterator::collect)
        .unwrap_or_else(|| Vec::with_capacity(0));

    let mut opts = Options {
        ignore_env,
        null,
        running_directory,
        files,
        unsets,
        sets: vec![],
        program: vec![],
    };

    // change directory
    if let Some(d) = opts.running_directory {
        match env::set_current_dir(d) {
            Ok(()) => d,
            Err(error) => {
                eprintln!("env: cannot change directory to \"{}\": {}", d, error);
                return Err(125);
            }
        };
    }

    // we handle the name, value pairs and the program to be executed by treating them as external
    // subcommands in clap
    if let (external, Some(matches)) = matches.subcommand() {
        let mut begin_prog_opts = false;

        if external == "-" {
            // "-" implies -i and stop parsing opts
            opts.ignore_env = true;
        } else {
            begin_prog_opts = parse_name_value_opt(&mut opts, external)?;
        }

        if let Some(mut iter) = matches.values_of("") {
            // read NAME=VALUE arguments (and up to a single program argument)
            while !begin_prog_opts {
                if let Some(opt) = iter.next() {
                    begin_prog_opts = parse_name_value_opt(&mut opts, opt)?;
                } else {
                    break;
                }
            }

            // read any leftover program arguments
            for opt in iter {
                parse_program_opt(&mut opts, opt)?;
            }
        }
    }

    // NOTE: we manually set and unset the env vars below rather than using Command::env() to more
    //       easily handle the case where no command is given

    // remove all env vars if told to ignore presets
    if opts.ignore_env {
        for (ref name, _) in env::vars() {
            env::remove_var(name);
        }
    }

    // load .env-style config file prior to those given on the command-line
    load_config_file(&mut opts)?;

    // unset specified env vars
    for name in &opts.unsets {
        env::remove_var(name);
    }

    // set specified env vars
    for &(name, val) in &opts.sets {
        // FIXME: set_var() panics if name is an empty string
        env::set_var(name, val);
    }

    if !opts.program.is_empty() {
        // we need to execute a command
        let (prog, args) = build_command(&mut opts.program);

        // FIXME: this should just use execvp() (no fork()) on Unix-like systems
        match Command::new(&*prog).args(args).status() {
            Ok(exit) if !exit.success() => return Err(exit.code().unwrap()),
            Err(ref err) if err.kind() == io::ErrorKind::NotFound => return Err(127),
            Err(_) => return Err(126),
            Ok(_) => (),
        }
    } else {
        // no program provided, so just dump all env vars to stdout
        print_env(opts.null);
    }

    Ok(())
}

pub fn uumain(args: impl uucore::Args) -> i32 {
    match run_env(args) {
        Ok(()) => 0,
        Err(code) => code,
    }
}
<|endoftext|>


<commit_before>// Copyright (c) Aptos
// SPDX-License-Identifier: Apache-2.0

use crate::common::types::{CliCommand, CliError, CliResult, CliTypedResult, CONFIG_FOLDER};
use crate::common::utils::{
    create_dir_if_not_exist, current_dir, read_from_file, write_to_user_only_file,
};
use crate::genesis::git::{from_yaml, to_yaml};
use crate::Tool;
use async_trait::async_trait;
use clap::ArgEnum;
use clap::CommandFactory;
use clap::Parser;
use clap_complete::{generate, Shell};
use serde::Deserialize;
use serde::Serialize;
use std::fmt::Formatter;
use std::path::PathBuf;
use std::str::FromStr;

/// Tool for configuration of the CLI tool
///
#[derive(Parser)]
pub enum ConfigTool {
    Init(crate::common::init::InitTool),
    GenerateShellCompletions(GenerateShellCompletions),
    SetGlobalConfig(SetGlobalConfig),
}

impl ConfigTool {
    pub async fn execute(self) -> CliResult {
        match self {
            ConfigTool::Init(tool) => tool.execute_serialized_success().await,
            ConfigTool::GenerateShellCompletions(tool) => tool.execute_serialized_success().await,
            ConfigTool::SetGlobalConfig(tool) => tool.execute_serialized_success().await,
        }
    }
}

/// Generates shell completion files
///
/// First generate the completion file, then follow the shell specific directions on how
/// to install the completion file.
#[derive(Parser)]
pub struct GenerateShellCompletions {
    /// Shell to generate completions for one of [bash, elvish, powershell, zsh]
    #[clap(long)]
    shell: Shell,
    /// File to output shell completions to
    #[clap(long, parse(from_os_str))]
    output_file: PathBuf,
}

#[async_trait]
impl CliCommand<()> for GenerateShellCompletions {
    fn command_name(&self) -> &'static str {
        "GenerateShellCompletions"
    }

    async fn execute(self) -> CliTypedResult<()> {
        let mut command = Tool::command();
        let mut file = std::fs::File::create(self.output_file.as_path())
            .map_err(|err| CliError::IO(self.output_file.display().to_string(), err))?;
        generate(self.shell, &mut command, "aptos".to_string(), &mut file);
        Ok(())
    }
}

/// Set global configuration settings
///
/// Any configuration flags that are not provided will not be changed
#[derive(Parser, Debug)]
pub struct SetGlobalConfig {
    /// A configuration for where to place and use the config
    ///
    /// Workspace allows for multiple configs based on location, where
    /// Global allows for one config for every part of the code
    #[clap(long)]
    config_type: Option<ConfigType>,
}

#[async_trait]
impl CliCommand<()> for SetGlobalConfig {
    fn command_name(&self) -> &'static str {
        "SetGlobalConfig"
    }

    async fn execute(self) -> CliTypedResult<()> {
        // Load the global config
        let mut config = GlobalConfig::load()?;

        // Enable all features that are actually listed
        if let Some(config_type) = self.config_type {
            config.config_type = config_type;
        }

        config.save()
    }
}

const GLOBAL_CONFIG_FILE: &str = "global_config.yaml";

/// A global configuration for global settings related to a user
#[derive(Serialize, Deserialize, Debug, Default)]
pub struct GlobalConfig {
    /// Whether to be using Global or Workspace mode
    pub config_type: ConfigType,
}

impl GlobalConfig {
    pub fn load() -> CliTypedResult<Self> {
        let path = global_folder()?.join(GLOBAL_CONFIG_FILE);
        if path.exists() {
            from_yaml(&String::from_utf8(read_from_file(path.as_path())?)?)
        } else {
            // If we don't have a config, let's load the default
            Ok(GlobalConfig::default())
        }
    }

    /// Get the config location based on the type
    pub fn get_config_location(&self) -> CliTypedResult<PathBuf> {
        match self.config_type {
            ConfigType::Global => global_folder(),
            ConfigType::Workspace => Ok(current_dir()?.join(CONFIG_FOLDER)),
        }
    }

    fn save(&self) -> CliTypedResult<()> {
        let global_folder = global_folder()?;
        create_dir_if_not_exist(global_folder.as_path())?;

        write_to_user_only_file(
            global_folder.join(GLOBAL_CONFIG_FILE).as_path(),
            "Global Config",
            &to_yaml(&self)?.into_bytes(),
        )
    }
}

fn global_folder() -> CliTypedResult<PathBuf> {
    if let Some(dir) = dirs::home_dir() {
        Ok(dir.join(CONFIG_FOLDER))
    } else {
        Err(CliError::UnexpectedError(
            "Unable to retrieve home directory".to_string(),
        ))
    }
}

const GLOBAL: &str = "global";
const WORKSPACE: &str = "workspace";

/// A configuration for where to place and use the config
///
/// Workspace allows for multiple configs based on location, where
/// Global allows for one config for every part of the code
#[derive(Debug, Copy, Clone, Serialize, Deserialize, ArgEnum)]
pub enum ConfigType {
    /// Per system user configuration put in `<HOME>/.aptos`
    Global,
    /// Per directory configuration put in `<CURRENT_DIR>/.aptos`
    Workspace,
}

impl Default for ConfigType {
    fn default() -> Self {
        // TODO: When we version up, we can change this to global
        Self::Workspace
    }
}

impl std::fmt::Display for ConfigType {
    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
        f.write_str(match self {
            ConfigType::Global => GLOBAL,
            ConfigType::Workspace => WORKSPACE,
        })
    }
}

impl FromStr for ConfigType {
    type Err = CliError;

    fn from_str(s: &str) -> Result<Self, Self::Err> {
        match s.to_lowercase().trim() {
            GLOBAL => Ok(Self::Global),
            WORKSPACE => Ok(Self::Workspace),
            _ => Err(CliError::CommandArgumentError(
                "Invalid config type, must be one of [global, workspace]".to_string(),
            )),
        }
    }
}
<commit_msg>[aptos-cli] Add ability to show global config<commit_after>// Copyright (c) Aptos
// SPDX-License-Identifier: Apache-2.0

use crate::common::types::{CliCommand, CliError, CliResult, CliTypedResult, CONFIG_FOLDER};
use crate::common::utils::{
    create_dir_if_not_exist, current_dir, read_from_file, write_to_user_only_file,
};
use crate::genesis::git::{from_yaml, to_yaml};
use crate::Tool;
use async_trait::async_trait;
use clap::ArgEnum;
use clap::CommandFactory;
use clap::Parser;
use clap_complete::{generate, Shell};
use serde::Deserialize;
use serde::Serialize;
use std::fmt::Formatter;
use std::path::PathBuf;
use std::str::FromStr;

/// Tool for configuration of the CLI tool
///
#[derive(Parser)]
pub enum ConfigTool {
    Init(crate::common::init::InitTool),
    GenerateShellCompletions(GenerateShellCompletions),
    SetGlobalConfig(SetGlobalConfig),
    ShowGlobalConfig(ShowGlobalConfig),
}

impl ConfigTool {
    pub async fn execute(self) -> CliResult {
        match self {
            ConfigTool::Init(tool) => tool.execute_serialized_success().await,
            ConfigTool::GenerateShellCompletions(tool) => tool.execute_serialized_success().await,
            ConfigTool::SetGlobalConfig(tool) => tool.execute_serialized_success().await,
            ConfigTool::ShowGlobalConfig(tool) => tool.execute_serialized().await,
        }
    }
}

/// Generates shell completion files
///
/// First generate the completion file, then follow the shell specific directions on how
/// to install the completion file.
#[derive(Parser)]
pub struct GenerateShellCompletions {
    /// Shell to generate completions for one of [bash, elvish, powershell, zsh]
    #[clap(long)]
    shell: Shell,
    /// File to output shell completions to
    #[clap(long, parse(from_os_str))]
    output_file: PathBuf,
}

#[async_trait]
impl CliCommand<()> for GenerateShellCompletions {
    fn command_name(&self) -> &'static str {
        "GenerateShellCompletions"
    }

    async fn execute(self) -> CliTypedResult<()> {
        let mut command = Tool::command();
        let mut file = std::fs::File::create(self.output_file.as_path())
            .map_err(|err| CliError::IO(self.output_file.display().to_string(), err))?;
        generate(self.shell, &mut command, "aptos".to_string(), &mut file);
        Ok(())
    }
}

/// Set global configuration settings
///
/// Any configuration flags that are not provided will not be changed
#[derive(Parser, Debug)]
pub struct SetGlobalConfig {
    /// A configuration for where to place and use the config
    ///
    /// Workspace allows for multiple configs based on location, where
    /// Global allows for one config for every part of the code
    #[clap(long)]
    config_type: Option<ConfigType>,
}

#[async_trait]
impl CliCommand<()> for SetGlobalConfig {
    fn command_name(&self) -> &'static str {
        "SetGlobalConfig"
    }

    async fn execute(self) -> CliTypedResult<()> {
        // Load the global config
        let mut config = GlobalConfig::load()?;

        // Enable all features that are actually listed
        if let Some(config_type) = self.config_type {
            config.config_type = config_type;
        }

        config.save()
    }
}

/// Shows the properties in the global config
#[derive(Parser, Debug)]
pub struct ShowGlobalConfig {}

#[async_trait]
impl CliCommand<GlobalConfig> for ShowGlobalConfig {
    fn command_name(&self) -> &'static str {
        "ShowGlobalConfig"
    }

    async fn execute(self) -> CliTypedResult<GlobalConfig> {
        // Load the global config
        GlobalConfig::load()
    }
}

const GLOBAL_CONFIG_FILE: &str = "global_config.yaml";

/// A global configuration for global settings related to a user
#[derive(Serialize, Deserialize, Debug, Default)]
pub struct GlobalConfig {
    /// Whether to be using Global or Workspace mode
    pub config_type: ConfigType,
}

impl GlobalConfig {
    pub fn load() -> CliTypedResult<Self> {
        let path = global_folder()?.join(GLOBAL_CONFIG_FILE);
        if path.exists() {
            from_yaml(&String::from_utf8(read_from_file(path.as_path())?)?)
        } else {
            // If we don't have a config, let's load the default
            Ok(GlobalConfig::default())
        }
    }

    /// Get the config location based on the type
    pub fn get_config_location(&self) -> CliTypedResult<PathBuf> {
        match self.config_type {
            ConfigType::Global => global_folder(),
            ConfigType::Workspace => Ok(current_dir()?.join(CONFIG_FOLDER)),
        }
    }

    fn save(&self) -> CliTypedResult<()> {
        let global_folder = global_folder()?;
        create_dir_if_not_exist(global_folder.as_path())?;

        write_to_user_only_file(
            global_folder.join(GLOBAL_CONFIG_FILE).as_path(),
            "Global Config",
            &to_yaml(&self)?.into_bytes(),
        )
    }
}

fn global_folder() -> CliTypedResult<PathBuf> {
    if let Some(dir) = dirs::home_dir() {
        Ok(dir.join(CONFIG_FOLDER))
    } else {
        Err(CliError::UnexpectedError(
            "Unable to retrieve home directory".to_string(),
        ))
    }
}

const GLOBAL: &str = "global";
const WORKSPACE: &str = "workspace";

/// A configuration for where to place and use the config
///
/// Workspace allows for multiple configs based on location, where
/// Global allows for one config for every part of the code
#[derive(Debug, Copy, Clone, Serialize, Deserialize, ArgEnum)]
pub enum ConfigType {
    /// Per system user configuration put in `<HOME>/.aptos`
    Global,
    /// Per directory configuration put in `<CURRENT_DIR>/.aptos`
    Workspace,
}

impl Default for ConfigType {
    fn default() -> Self {
        // TODO: When we version up, we can change this to global
        Self::Workspace
    }
}

impl std::fmt::Display for ConfigType {
    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
        f.write_str(match self {
            ConfigType::Global => GLOBAL,
            ConfigType::Workspace => WORKSPACE,
        })
    }
}

impl FromStr for ConfigType {
    type Err = CliError;

    fn from_str(s: &str) -> Result<Self, Self::Err> {
        match s.to_lowercase().trim() {
            GLOBAL => Ok(Self::Global),
            WORKSPACE => Ok(Self::Workspace),
            _ => Err(CliError::CommandArgumentError(
                "Invalid config type, must be one of [global, workspace]".to_string(),
            )),
        }
    }
}
<|endoftext|>


<commit_before>//! Store XML element attributes and their values.

use std::slice;
use std::str;

use markup5ever::{namespace_url, LocalName, Namespace, Prefix, QualName};
use string_cache::DefaultAtom;

use crate::util::{opt_utf8_cstr, utf8_cstr};

/// Type used to store attribute values.
///
/// Attribute values are often repeated in an SVG file, so we intern them using the
/// string_cache crate.
pub type AttributeValue = DefaultAtom;

/// Iterable wrapper for libxml2's representation of attribute/value.
///
/// See the [`new_from_xml2_attributes`] function for information.
///
/// [`new_from_xml2_attributes`]: #method.new_from_xml2_attributes
#[derive(Clone)]
pub struct Attributes(Vec<(QualName, AttributeValue)>);

/// Iterator from `Attributes.iter`.
pub struct AttributesIter<'a>(slice::Iter<'a, (QualName, AttributeValue)>);

impl Attributes {
    #[cfg(test)]
    pub fn new() -> Attributes {
        Attributes(Vec::new())
    }

    /// Creates an iterable `Attributes` from the C array of borrowed C strings.
    ///
    /// With libxml2's SAX parser, the caller's startElementNsSAX2Func
    /// callback gets passed a `xmlChar **` for attributes, which
    /// comes in groups of (localname/prefix/URI/value_start/value_end).
    /// In those, localname/prefix/URI are NUL-terminated strings;
    /// value_start and value_end point to the start-inclusive and
    /// end-exclusive bytes in the attribute's value.
    ///
    /// # Safety
    ///
    /// This function is unsafe because the caller must guarantee the following:
    ///
    /// * `attrs` is a valid pointer, with (n_attributes * 5) elements.
    ///
    /// * All strings are valid UTF-8.
    pub unsafe fn new_from_xml2_attributes(
        n_attributes: usize,
        attrs: *const *const libc::c_char,
    ) -> Attributes {
        let mut array = Vec::new();

        if n_attributes > 0 && !attrs.is_null() {
            for attr in slice::from_raw_parts(attrs, n_attributes * 5).chunks_exact(5) {
                let localname = attr[0];
                let prefix = attr[1];
                let uri = attr[2];
                let value_start = attr[3];
                let value_end = attr[4];

                assert!(!localname.is_null());

                let localname = utf8_cstr(localname);

                let prefix = opt_utf8_cstr(prefix);
                let uri = opt_utf8_cstr(uri);
                let qual_name = QualName::new(
                    prefix.map(Prefix::from),
                    uri.map(Namespace::from)
                        .unwrap_or_else(|| namespace_url!("")),
                    LocalName::from(localname),
                );

                if !value_start.is_null() && !value_end.is_null() {
                    assert!(value_end >= value_start);

                    // FIXME: ptr::offset_from() is nightly-only.
                    // We'll do the computation of the length by hand.
                    let start = value_start as usize;
                    let end = value_end as usize;
                    let len = end - start;

                    let value_slice = slice::from_raw_parts(value_start as *const u8, len);
                    let value_str = str::from_utf8_unchecked(value_slice);
                    let value_atom = DefaultAtom::from(value_str);

                    array.push((qual_name, value_atom));
                }
            }
        }

        Attributes(array)
    }

    /// Returns the number of attributes.
    pub fn len(&self) -> usize {
        self.0.len()
    }

    /// Creates an iterator that yields `(QualName, &'a str)` tuples.
    pub fn iter(&self) -> AttributesIter<'_> {
        AttributesIter(self.0.iter())
    }
}

impl<'a> Iterator for AttributesIter<'a> {
    type Item = (QualName, &'a str);

    fn next(&mut self) -> Option<Self::Item> {
        self.0.next().map(|(a, v)| (a.clone(), v.as_ref()))
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use markup5ever::{expanded_name, local_name, namespace_url, ns};
    use std::ffi::CString;
    use std::ptr;

    #[test]
    fn empty_attributes() {
        let map = unsafe { Attributes::new_from_xml2_attributes(0, ptr::null()) };
        assert_eq!(map.len(), 0);
    }

    #[test]
    fn attributes_with_namespaces() {
        let attrs = [
            (
                CString::new("href").unwrap(),
                Some(CString::new("xlink").unwrap()),
                Some(CString::new("http://www.w3.org/1999/xlink").unwrap()),
                CString::new("1").unwrap(),
            ),
            (
                CString::new("ry").unwrap(),
                None,
                None,
                CString::new("2").unwrap(),
            ),
            (
                CString::new("d").unwrap(),
                None,
                None,
                CString::new("").unwrap(),
            ),
        ];

        let mut v: Vec<*const libc::c_char> = Vec::new();

        for (localname, prefix, uri, val) in &attrs {
            v.push(localname.as_ptr());
            v.push(
                prefix
                    .as_ref()
                    .map(|p: &CString| p.as_ptr())
                    .unwrap_or_else(|| ptr::null()),
            );
            v.push(
                uri.as_ref()
                    .map(|p: &CString| p.as_ptr())
                    .unwrap_or_else(|| ptr::null()),
            );

            let val_start = val.as_ptr();
            let val_end = unsafe { val_start.offset(val.as_bytes().len() as isize) };
            v.push(val_start); // value_start
            v.push(val_end); // value_end
        }

        let attrs = unsafe { Attributes::new_from_xml2_attributes(3, v.as_ptr()) };

        let mut had_href: bool = false;
        let mut had_ry: bool = false;
        let mut had_d: bool = false;

        for (a, v) in attrs.iter() {
            match a.expanded() {
                expanded_name!(xlink "href") => {
                    assert!(v == "1");
                    had_href = true;
                }

                expanded_name!("", "ry") => {
                    assert!(v == "2");
                    had_ry = true;
                }

                expanded_name!("", "d") => {
                    assert!(v == "");
                    had_d = true;
                }

                _ => unreachable!(),
            }
        }

        assert!(had_href);
        assert!(had_ry);
        assert!(had_d);
    }
}
<commit_msg>micro-optimization: use Box slice instead of Vec for Attributes<commit_after>//! Store XML element attributes and their values.

use std::slice;
use std::str;

use markup5ever::{namespace_url, LocalName, Namespace, Prefix, QualName};
use string_cache::DefaultAtom;

use crate::util::{opt_utf8_cstr, utf8_cstr};

/// Type used to store attribute values.
///
/// Attribute values are often repeated in an SVG file, so we intern them using the
/// string_cache crate.
pub type AttributeValue = DefaultAtom;

/// Iterable wrapper for libxml2's representation of attribute/value.
///
/// See the [`new_from_xml2_attributes`] function for information.
///
/// [`new_from_xml2_attributes`]: #method.new_from_xml2_attributes
#[derive(Clone)]
pub struct Attributes(Box<[(QualName, AttributeValue)]>);

/// Iterator from `Attributes.iter`.
pub struct AttributesIter<'a>(slice::Iter<'a, (QualName, AttributeValue)>);

impl Attributes {
    #[cfg(test)]
    pub fn new() -> Attributes {
        Attributes([].into())
    }

    /// Creates an iterable `Attributes` from the C array of borrowed C strings.
    ///
    /// With libxml2's SAX parser, the caller's startElementNsSAX2Func
    /// callback gets passed a `xmlChar **` for attributes, which
    /// comes in groups of (localname/prefix/URI/value_start/value_end).
    /// In those, localname/prefix/URI are NUL-terminated strings;
    /// value_start and value_end point to the start-inclusive and
    /// end-exclusive bytes in the attribute's value.
    ///
    /// # Safety
    ///
    /// This function is unsafe because the caller must guarantee the following:
    ///
    /// * `attrs` is a valid pointer, with (n_attributes * 5) elements.
    ///
    /// * All strings are valid UTF-8.
    pub unsafe fn new_from_xml2_attributes(
        n_attributes: usize,
        attrs: *const *const libc::c_char,
    ) -> Attributes {
        let mut array = Vec::new();

        if n_attributes > 0 && !attrs.is_null() {
            for attr in slice::from_raw_parts(attrs, n_attributes * 5).chunks_exact(5) {
                let localname = attr[0];
                let prefix = attr[1];
                let uri = attr[2];
                let value_start = attr[3];
                let value_end = attr[4];

                assert!(!localname.is_null());

                let localname = utf8_cstr(localname);

                let prefix = opt_utf8_cstr(prefix);
                let uri = opt_utf8_cstr(uri);
                let qual_name = QualName::new(
                    prefix.map(Prefix::from),
                    uri.map(Namespace::from)
                        .unwrap_or_else(|| namespace_url!("")),
                    LocalName::from(localname),
                );

                if !value_start.is_null() && !value_end.is_null() {
                    assert!(value_end >= value_start);

                    // FIXME: ptr::offset_from() is nightly-only.
                    // We'll do the computation of the length by hand.
                    let start = value_start as usize;
                    let end = value_end as usize;
                    let len = end - start;

                    let value_slice = slice::from_raw_parts(value_start as *const u8, len);
                    let value_str = str::from_utf8_unchecked(value_slice);
                    let value_atom = DefaultAtom::from(value_str);

                    array.push((qual_name, value_atom));
                }
            }
        }

        Attributes(array.into())
    }

    /// Returns the number of attributes.
    pub fn len(&self) -> usize {
        self.0.len()
    }

    /// Creates an iterator that yields `(QualName, &'a str)` tuples.
    pub fn iter(&self) -> AttributesIter<'_> {
        AttributesIter(self.0.iter())
    }
}

impl<'a> Iterator for AttributesIter<'a> {
    type Item = (QualName, &'a str);

    fn next(&mut self) -> Option<Self::Item> {
        self.0.next().map(|(a, v)| (a.clone(), v.as_ref()))
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use markup5ever::{expanded_name, local_name, namespace_url, ns};
    use std::ffi::CString;
    use std::ptr;

    #[test]
    fn empty_attributes() {
        let map = unsafe { Attributes::new_from_xml2_attributes(0, ptr::null()) };
        assert_eq!(map.len(), 0);
    }

    #[test]
    fn attributes_with_namespaces() {
        let attrs = [
            (
                CString::new("href").unwrap(),
                Some(CString::new("xlink").unwrap()),
                Some(CString::new("http://www.w3.org/1999/xlink").unwrap()),
                CString::new("1").unwrap(),
            ),
            (
                CString::new("ry").unwrap(),
                None,
                None,
                CString::new("2").unwrap(),
            ),
            (
                CString::new("d").unwrap(),
                None,
                None,
                CString::new("").unwrap(),
            ),
        ];

        let mut v: Vec<*const libc::c_char> = Vec::new();

        for (localname, prefix, uri, val) in &attrs {
            v.push(localname.as_ptr());
            v.push(
                prefix
                    .as_ref()
                    .map(|p: &CString| p.as_ptr())
                    .unwrap_or_else(|| ptr::null()),
            );
            v.push(
                uri.as_ref()
                    .map(|p: &CString| p.as_ptr())
                    .unwrap_or_else(|| ptr::null()),
            );

            let val_start = val.as_ptr();
            let val_end = unsafe { val_start.offset(val.as_bytes().len() as isize) };
            v.push(val_start); // value_start
            v.push(val_end); // value_end
        }

        let attrs = unsafe { Attributes::new_from_xml2_attributes(3, v.as_ptr()) };

        let mut had_href: bool = false;
        let mut had_ry: bool = false;
        let mut had_d: bool = false;

        for (a, v) in attrs.iter() {
            match a.expanded() {
                expanded_name!(xlink "href") => {
                    assert!(v == "1");
                    had_href = true;
                }

                expanded_name!("", "ry") => {
                    assert!(v == "2");
                    had_ry = true;
                }

                expanded_name!("", "d") => {
                    assert!(v == "");
                    had_d = true;
                }

                _ => unreachable!(),
            }
        }

        assert!(had_href);
        assert!(had_ry);
        assert!(had_d);
    }
}
<|endoftext|>