<commit_before>/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

use devtools_traits::AttrInfo;
use dom::bindings::cell::DOMRefCell;
use dom::bindings::codegen::Bindings::AttrBinding::{self, AttrMethods};
use dom::bindings::codegen::InheritTypes::NodeCast;
use dom::bindings::global::GlobalRef;
use dom::bindings::js::{JS, MutNullableHeap};
use dom::bindings::js::{LayoutJS, Root, RootedReference};
use dom::bindings::utils::{Reflector, reflect_dom_object};
use dom::element::{AttributeMutation, Element};
use dom::virtualmethods::vtable_for;
use dom::window::Window;
use std::borrow::ToOwned;
use std::cell::Ref;
use std::mem;
use std::ops::Deref;
use string_cache::{Atom, Namespace};
use style::values::specified::Length;
use util::str::{DOMString, parse_unsigned_integer, split_html_space_chars, str_join};

#[derive(JSTraceable, PartialEq, Clone, HeapSizeOf)]
pub enum AttrValue {
    String(DOMString),
    TokenList(DOMString, Vec<Atom>),
    UInt(DOMString, u32),
    Atom(Atom),
    Length(DOMString, Option<Length>),
}

impl AttrValue {
    pub fn from_serialized_tokenlist(tokens: DOMString) -> AttrValue {
        let atoms =
            split_html_space_chars(&tokens)
            .map(Atom::from_slice)
            .fold(vec![], |mut acc, atom| {
                if !acc.contains(&atom) { acc.push(atom) }
                acc
            });
        AttrValue::TokenList(tokens, atoms)
    }

    pub fn from_atomic_tokens(atoms: Vec<Atom>) -> AttrValue {
        let tokens = str_join(&atoms, "\x20");
        AttrValue::TokenList(tokens, atoms)
    }

    // https://html.spec.whatwg.org/multipage/#reflecting-content-attributes-in-idl-attributes:idl-unsigned-long
    pub fn from_u32(string: DOMString, default: u32) -> AttrValue {
        let result = parse_unsigned_integer(string.chars()).unwrap_or(default);
        let result = if result > 2147483647 {
            default
        } else {
            result
        };
        AttrValue::UInt(string, result)
    }

    // https://html.spec.whatwg.org/multipage/#limited-to-only-non-negative-numbers-greater-than-zero
    pub fn from_limited_u32(string: DOMString, default: u32) -> AttrValue {
        let result = parse_unsigned_integer(string.chars()).unwrap_or(default);
        let result = if result == 0 || result > 2147483647 {
            default
        } else {
            result
        };
        AttrValue::UInt(string, result)
    }

    pub fn from_atomic(string: DOMString) -> AttrValue {
        let value = Atom::from_slice(&string);
        AttrValue::Atom(value)
    }

    pub fn as_tokens(&self) -> &[Atom] {
        match *self {
            AttrValue::TokenList(_, ref tokens) => tokens,
            _ => panic!("Tokens not found"),
        }
    }

    pub fn as_atom(&self) -> &Atom {
        match *self {
            AttrValue::Atom(ref value) => value,
            _ => panic!("Atom not found"),
        }
    }

    pub fn as_length(&self) -> Option<&Length> {
        match *self {
            AttrValue::Length(_, ref length) => length.as_ref(),
            _ => panic!("Length not found"),
        }
    }

    /// Return the AttrValue as its integer representation, if any.
    /// This corresponds to attribute values returned as `AttrValue::UInt(_)`
    /// by `VirtualMethods::parse_plain_attribute()`.
    pub fn as_uint(&self) -> u32 {
        if let AttrValue::UInt(_, value) = *self {
            value
        } else {
            panic!("Uint not found");
        }
    }
}

impl Deref for AttrValue {
    type Target = str;

    fn deref(&self) -> &str {
        match *self {
            AttrValue::String(ref value) |
                AttrValue::TokenList(ref value, _) |
                AttrValue::UInt(ref value, _) |
                AttrValue::Length(ref value, _) => &value,
            AttrValue::Atom(ref value) => &value,
        }
    }
}

// https://dom.spec.whatwg.org/#interface-attr
#[dom_struct]
pub struct Attr {
    reflector_: Reflector,
    local_name: Atom,
    value: DOMRefCell<AttrValue>,
    name: Atom,
    namespace: Namespace,
    prefix: Option<Atom>,

    /// the element that owns this attribute.
    owner: MutNullableHeap<JS<Element>>,
}

impl Attr {
    fn new_inherited(local_name: Atom, value: AttrValue, name: Atom, namespace: Namespace,
                     prefix: Option<Atom>, owner: Option<&Element>) -> Attr {
        Attr {
            reflector_: Reflector::new(),
            local_name: local_name,
            value: DOMRefCell::new(value),
            name: name,
            namespace: namespace,
            prefix: prefix,
            owner: MutNullableHeap::new(owner.map(JS::from_ref)),
        }
    }

    pub fn new(window: &Window, local_name: Atom, value: AttrValue,
               name: Atom, namespace: Namespace,
               prefix: Option<Atom>, owner: Option<&Element>) -> Root<Attr> {
        reflect_dom_object(
            box Attr::new_inherited(local_name, value, name, namespace, prefix, owner),
            GlobalRef::Window(window),
            AttrBinding::Wrap)
    }

    #[inline]
    pub fn name(&self) -> &Atom {
        &self.name
    }

    #[inline]
    pub fn namespace(&self) -> &Namespace {
        &self.namespace
    }

    #[inline]
    pub fn prefix(&self) -> &Option<Atom> {
        &self.prefix
    }
}

impl AttrMethods for Attr {
    // https://dom.spec.whatwg.org/#dom-attr-localname
    fn LocalName(&self) -> DOMString {
        (**self.local_name()).to_owned()
    }

    // https://dom.spec.whatwg.org/#dom-attr-value
    fn Value(&self) -> DOMString {
        (**self.value()).to_owned()
    }

    // https://dom.spec.whatwg.org/#dom-attr-value
    fn SetValue(&self, value: DOMString) {
        match self.owner() {
            None => *self.value.borrow_mut() = AttrValue::String(value),
            Some(owner) => {
                let value = owner.r().parse_attribute(&self.namespace, self.local_name(), value);
                self.set_value(value, owner.r());
            }
        }
    }

    // https://dom.spec.whatwg.org/#dom-attr-textcontent
    fn TextContent(&self) -> DOMString {
        self.Value()
    }

    // https://dom.spec.whatwg.org/#dom-attr-textcontent
    fn SetTextContent(&self, value: DOMString) {
        self.SetValue(value)
    }

    // https://dom.spec.whatwg.org/#dom-attr-nodevalue
    fn NodeValue(&self) -> DOMString {
        self.Value()
    }

    // https://dom.spec.whatwg.org/#dom-attr-nodevalue
    fn SetNodeValue(&self, value: DOMString) {
        self.SetValue(value)
    }

    // https://dom.spec.whatwg.org/#dom-attr-name
    fn Name(&self) -> DOMString {
        (*self.name).to_owned()
    }

    // https://dom.spec.whatwg.org/#dom-attr-namespaceuri
    fn GetNamespaceURI(&self) -> Option<DOMString> {
        let Namespace(ref atom) = self.namespace;
        match &**atom {
            "" => None,
            url => Some(url.to_owned()),
        }
    }

    // https://dom.spec.whatwg.org/#dom-attr-prefix
    fn GetPrefix(&self) -> Option<DOMString> {
        self.prefix().as_ref().map(|p| (**p).to_owned())
    }

    // https://dom.spec.whatwg.org/#dom-attr-ownerelement
    fn GetOwnerElement(&self) -> Option<Root<Element>> {
        self.owner()
    }

    // https://dom.spec.whatwg.org/#dom-attr-specified
    fn Specified(&self) -> bool {
        true // Always returns true
    }
}

impl Attr {
    pub fn set_value(&self, mut value: AttrValue, owner: &Element) {
        assert!(Some(owner) == self.owner().r());
        mem::swap(&mut *self.value.borrow_mut(), &mut value);
        if self.namespace == ns!("") {
            vtable_for(NodeCast::from_ref(owner)).attribute_mutated(
                self, AttributeMutation::Set(Some(&value)));
        }
    }

    pub fn value(&self) -> Ref<AttrValue> {
        self.value.borrow()
    }

    pub fn local_name(&self) -> &Atom {
        &self.local_name
    }

    /// Sets the owner element. Should be called after the attribute is added
    /// or removed from its older parent.
    pub fn set_owner(&self, owner: Option<&Element>) {
        let ref ns = self.namespace;
        match (self.owner().r(), owner) {
            (None, Some(new)) => {
                // Already in the list of attributes of new owner.
                assert!(new.get_attribute(&ns, &self.local_name) == Some(Root::from_ref(self)))
            }
            (Some(old), None) => {
                // Already gone from the list of attributes of old owner.
                assert!(old.get_attribute(&ns, &self.local_name).is_none())
            }
            (old, new) => assert!(old == new)
        }
        self.owner.set(owner.map(JS::from_ref))
    }

    pub fn owner(&self) -> Option<Root<Element>> {
        self.owner.get().map(Root::from_rooted)
    }

    pub fn summarize(&self) -> AttrInfo {
        let Namespace(ref ns) = self.namespace;
        AttrInfo {
            namespace: (**ns).to_owned(),
            name: self.Name(),
            value: self.Value(),
        }
    }
}

#[allow(unsafe_code)]
pub trait AttrHelpersForLayout {
    unsafe fn value_forever(&self) -> &'static AttrValue;
    unsafe fn value_ref_forever(&self) -> &'static str;
    unsafe fn value_atom_forever(&self) -> Option<Atom>;
    unsafe fn value_tokens_forever(&self) -> Option<&'static [Atom]>;
    unsafe fn local_name_atom_forever(&self) -> Atom;
    unsafe fn value_for_layout(&self) -> &AttrValue;
}

#[allow(unsafe_code)]
impl AttrHelpersForLayout for LayoutJS<Attr> {
    #[inline]
    unsafe fn value_forever(&self) -> &'static AttrValue {
        // This transmute is used to cheat the lifetime restriction.
        mem::transmute::<&AttrValue, &AttrValue>((*self.unsafe_get()).value.borrow_for_layout())
    }

    #[inline]
    unsafe fn value_ref_forever(&self) -> &'static str {
        &**self.value_forever()
    }

    #[inline]
    unsafe fn value_atom_forever(&self) -> Option<Atom> {
        let value = (*self.unsafe_get()).value.borrow_for_layout();
        match *value {
            AttrValue::Atom(ref val) => Some(val.clone()),
            _ => None,
        }
    }

    #[inline]
    unsafe fn value_tokens_forever(&self) -> Option<&'static [Atom]> {
        // This transmute is used to cheat the lifetime restriction.
        match *self.value_forever() {
            AttrValue::TokenList(_, ref tokens) => Some(tokens),
            _ => None,
        }
    }

    #[inline]
    unsafe fn local_name_atom_forever(&self) -> Atom {
        (*self.unsafe_get()).local_name.clone()
    }

    #[inline]
    unsafe fn value_for_layout(&self) -> &AttrValue {
        (*self.unsafe_get()).value.borrow_for_layout()
    }
}
<commit_msg>Add doc-comments about methods that can panic<commit_after>/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

use devtools_traits::AttrInfo;
use dom::bindings::cell::DOMRefCell;
use dom::bindings::codegen::Bindings::AttrBinding::{self, AttrMethods};
use dom::bindings::codegen::InheritTypes::NodeCast;
use dom::bindings::global::GlobalRef;
use dom::bindings::js::{JS, MutNullableHeap};
use dom::bindings::js::{LayoutJS, Root, RootedReference};
use dom::bindings::utils::{Reflector, reflect_dom_object};
use dom::element::{AttributeMutation, Element};
use dom::virtualmethods::vtable_for;
use dom::window::Window;
use std::borrow::ToOwned;
use std::cell::Ref;
use std::mem;
use std::ops::Deref;
use string_cache::{Atom, Namespace};
use style::values::specified::Length;
use util::str::{DOMString, parse_unsigned_integer, split_html_space_chars, str_join};

#[derive(JSTraceable, PartialEq, Clone, HeapSizeOf)]
pub enum AttrValue {
    String(DOMString),
    TokenList(DOMString, Vec<Atom>),
    UInt(DOMString, u32),
    Atom(Atom),
    Length(DOMString, Option<Length>),
}

impl AttrValue {
    pub fn from_serialized_tokenlist(tokens: DOMString) -> AttrValue {
        let atoms =
            split_html_space_chars(&tokens)
            .map(Atom::from_slice)
            .fold(vec![], |mut acc, atom| {
                if !acc.contains(&atom) { acc.push(atom) }
                acc
            });
        AttrValue::TokenList(tokens, atoms)
    }

    pub fn from_atomic_tokens(atoms: Vec<Atom>) -> AttrValue {
        let tokens = str_join(&atoms, "\x20");
        AttrValue::TokenList(tokens, atoms)
    }

    // https://html.spec.whatwg.org/multipage/#reflecting-content-attributes-in-idl-attributes:idl-unsigned-long
    pub fn from_u32(string: DOMString, default: u32) -> AttrValue {
        let result = parse_unsigned_integer(string.chars()).unwrap_or(default);
        let result = if result > 2147483647 {
            default
        } else {
            result
        };
        AttrValue::UInt(string, result)
    }

    // https://html.spec.whatwg.org/multipage/#limited-to-only-non-negative-numbers-greater-than-zero
    pub fn from_limited_u32(string: DOMString, default: u32) -> AttrValue {
        let result = parse_unsigned_integer(string.chars()).unwrap_or(default);
        let result = if result == 0 || result > 2147483647 {
            default
        } else {
            result
        };
        AttrValue::UInt(string, result)
    }

    pub fn from_atomic(string: DOMString) -> AttrValue {
        let value = Atom::from_slice(&string);
        AttrValue::Atom(value)
    }

    /// Assumes the `AttrValue` is a `TokenList` and returns its tokens
    ///
    /// ## Panics
    ///
    /// Panics if the `AttrValue` is not a `TokenList`
    pub fn as_tokens(&self) -> &[Atom] {
        match *self {
            AttrValue::TokenList(_, ref tokens) => tokens,
            _ => panic!("Tokens not found"),
        }
    }

    /// Assumes the `AttrValue` is an `Atom` and returns its value
    ///
    /// ## Panics
    ///
    /// Panics if the `AttrValue` is not an `Atom`
    pub fn as_atom(&self) -> &Atom {
        match *self {
            AttrValue::Atom(ref value) => value,
            _ => panic!("Atom not found"),
        }
    }

    /// Assumes the `AttrValue` is a `Length` and returns its value
    ///
    /// ## Panics
    ///
    /// Panics if the `AttrValue` is not a `Length`
    pub fn as_length(&self) -> Option<&Length> {
        match *self {
            AttrValue::Length(_, ref length) => length.as_ref(),
            _ => panic!("Length not found"),
        }
    }

    /// Return the AttrValue as its integer representation, if any.
    /// This corresponds to attribute values returned as `AttrValue::UInt(_)`
    /// by `VirtualMethods::parse_plain_attribute()`.
    ///
    /// ## Panics
    ///
    /// Panics if the `AttrValue` is not a `UInt`
    pub fn as_uint(&self) -> u32 {
        if let AttrValue::UInt(_, value) = *self {
            value
        } else {
            panic!("Uint not found");
        }
    }
}

impl Deref for AttrValue {
    type Target = str;

    fn deref(&self) -> &str {
        match *self {
            AttrValue::String(ref value) |
                AttrValue::TokenList(ref value, _) |
                AttrValue::UInt(ref value, _) |
                AttrValue::Length(ref value, _) => &value,
            AttrValue::Atom(ref value) => &value,
        }
    }
}

// https://dom.spec.whatwg.org/#interface-attr
#[dom_struct]
pub struct Attr {
    reflector_: Reflector,
    local_name: Atom,
    value: DOMRefCell<AttrValue>,
    name: Atom,
    namespace: Namespace,
    prefix: Option<Atom>,

    /// the element that owns this attribute.
    owner: MutNullableHeap<JS<Element>>,
}

impl Attr {
    fn new_inherited(local_name: Atom, value: AttrValue, name: Atom, namespace: Namespace,
                     prefix: Option<Atom>, owner: Option<&Element>) -> Attr {
        Attr {
            reflector_: Reflector::new(),
            local_name: local_name,
            value: DOMRefCell::new(value),
            name: name,
            namespace: namespace,
            prefix: prefix,
            owner: MutNullableHeap::new(owner.map(JS::from_ref)),
        }
    }

    pub fn new(window: &Window, local_name: Atom, value: AttrValue,
               name: Atom, namespace: Namespace,
               prefix: Option<Atom>, owner: Option<&Element>) -> Root<Attr> {
        reflect_dom_object(
            box Attr::new_inherited(local_name, value, name, namespace, prefix, owner),
            GlobalRef::Window(window),
            AttrBinding::Wrap)
    }

    #[inline]
    pub fn name(&self) -> &Atom {
        &self.name
    }

    #[inline]
    pub fn namespace(&self) -> &Namespace {
        &self.namespace
    }

    #[inline]
    pub fn prefix(&self) -> &Option<Atom> {
        &self.prefix
    }
}

impl AttrMethods for Attr {
    // https://dom.spec.whatwg.org/#dom-attr-localname
    fn LocalName(&self) -> DOMString {
        (**self.local_name()).to_owned()
    }

    // https://dom.spec.whatwg.org/#dom-attr-value
    fn Value(&self) -> DOMString {
        (**self.value()).to_owned()
    }

    // https://dom.spec.whatwg.org/#dom-attr-value
    fn SetValue(&self, value: DOMString) {
        match self.owner() {
            None => *self.value.borrow_mut() = AttrValue::String(value),
            Some(owner) => {
                let value = owner.r().parse_attribute(&self.namespace, self.local_name(), value);
                self.set_value(value, owner.r());
            }
        }
    }

    // https://dom.spec.whatwg.org/#dom-attr-textcontent
    fn TextContent(&self) -> DOMString {
        self.Value()
    }

    // https://dom.spec.whatwg.org/#dom-attr-textcontent
    fn SetTextContent(&self, value: DOMString) {
        self.SetValue(value)
    }

    // https://dom.spec.whatwg.org/#dom-attr-nodevalue
    fn NodeValue(&self) -> DOMString {
        self.Value()
    }

    // https://dom.spec.whatwg.org/#dom-attr-nodevalue
    fn SetNodeValue(&self, value: DOMString) {
        self.SetValue(value)
    }

    // https://dom.spec.whatwg.org/#dom-attr-name
    fn Name(&self) -> DOMString {
        (*self.name).to_owned()
    }

    // https://dom.spec.whatwg.org/#dom-attr-namespaceuri
    fn GetNamespaceURI(&self) -> Option<DOMString> {
        let Namespace(ref atom) = self.namespace;
        match &**atom {
            "" => None,
            url => Some(url.to_owned()),
        }
    }

    // https://dom.spec.whatwg.org/#dom-attr-prefix
    fn GetPrefix(&self) -> Option<DOMString> {
        self.prefix().as_ref().map(|p| (**p).to_owned())
    }

    // https://dom.spec.whatwg.org/#dom-attr-ownerelement
    fn GetOwnerElement(&self) -> Option<Root<Element>> {
        self.owner()
    }

    // https://dom.spec.whatwg.org/#dom-attr-specified
    fn Specified(&self) -> bool {
        true // Always returns true
    }
}

impl Attr {
    pub fn set_value(&self, mut value: AttrValue, owner: &Element) {
        assert!(Some(owner) == self.owner().r());
        mem::swap(&mut *self.value.borrow_mut(), &mut value);
        if self.namespace == ns!("") {
            vtable_for(NodeCast::from_ref(owner)).attribute_mutated(
                self, AttributeMutation::Set(Some(&value)));
        }
    }

    pub fn value(&self) -> Ref<AttrValue> {
        self.value.borrow()
    }

    pub fn local_name(&self) -> &Atom {
        &self.local_name
    }

    /// Sets the owner element. Should be called after the attribute is added
    /// or removed from its older parent.
    pub fn set_owner(&self, owner: Option<&Element>) {
        let ref ns = self.namespace;
        match (self.owner().r(), owner) {
            (None, Some(new)) => {
                // Already in the list of attributes of new owner.
                assert!(new.get_attribute(&ns, &self.local_name) == Some(Root::from_ref(self)))
            }
            (Some(old), None) => {
                // Already gone from the list of attributes of old owner.
                assert!(old.get_attribute(&ns, &self.local_name).is_none())
            }
            (old, new) => assert!(old == new)
        }
        self.owner.set(owner.map(JS::from_ref))
    }

    pub fn owner(&self) -> Option<Root<Element>> {
        self.owner.get().map(Root::from_rooted)
    }

    pub fn summarize(&self) -> AttrInfo {
        let Namespace(ref ns) = self.namespace;
        AttrInfo {
            namespace: (**ns).to_owned(),
            name: self.Name(),
            value: self.Value(),
        }
    }
}

#[allow(unsafe_code)]
pub trait AttrHelpersForLayout {
    unsafe fn value_forever(&self) -> &'static AttrValue;
    unsafe fn value_ref_forever(&self) -> &'static str;
    unsafe fn value_atom_forever(&self) -> Option<Atom>;
    unsafe fn value_tokens_forever(&self) -> Option<&'static [Atom]>;
    unsafe fn local_name_atom_forever(&self) -> Atom;
    unsafe fn value_for_layout(&self) -> &AttrValue;
}

#[allow(unsafe_code)]
impl AttrHelpersForLayout for LayoutJS<Attr> {
    #[inline]
    unsafe fn value_forever(&self) -> &'static AttrValue {
        // This transmute is used to cheat the lifetime restriction.
        mem::transmute::<&AttrValue, &AttrValue>((*self.unsafe_get()).value.borrow_for_layout())
    }

    #[inline]
    unsafe fn value_ref_forever(&self) -> &'static str {
        &**self.value_forever()
    }

    #[inline]
    unsafe fn value_atom_forever(&self) -> Option<Atom> {
        let value = (*self.unsafe_get()).value.borrow_for_layout();
        match *value {
            AttrValue::Atom(ref val) => Some(val.clone()),
            _ => None,
        }
    }

    #[inline]
    unsafe fn value_tokens_forever(&self) -> Option<&'static [Atom]> {
        // This transmute is used to cheat the lifetime restriction.
        match *self.value_forever() {
            AttrValue::TokenList(_, ref tokens) => Some(tokens),
            _ => None,
        }
    }

    #[inline]
    unsafe fn local_name_atom_forever(&self) -> Atom {
        (*self.unsafe_get()).local_name.clone()
    }

    #[inline]
    unsafe fn value_for_layout(&self) -> &AttrValue {
        (*self.unsafe_get()).value.borrow_for_layout()
    }
}
<|endoftext|>


<commit_before>/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at https://mozilla.org/MPL/2.0/. */

use crate::dom::bindings::cell::{DomRefCell, Ref};
use crate::dom::bindings::codegen::Bindings::AttrBinding::AttrMethods;
use crate::dom::bindings::inheritance::Castable;
use crate::dom::bindings::root::{DomRoot, LayoutDom, MutNullableDom};
use crate::dom::bindings::str::DOMString;
use crate::dom::customelementregistry::CallbackReaction;
use crate::dom::document::Document;
use crate::dom::element::{AttributeMutation, Element};
use crate::dom::mutationobserver::{Mutation, MutationObserver};
use crate::dom::node::Node;
use crate::dom::virtualmethods::vtable_for;
use crate::script_thread::ScriptThread;
use devtools_traits::AttrInfo;
use dom_struct::dom_struct;
use html5ever::{LocalName, Namespace, Prefix};
use servo_atoms::Atom;
use std::borrow::ToOwned;
use std::mem;
use style::attr::{AttrIdentifier, AttrValue};

// https://dom.spec.whatwg.org/#interface-attr
#[dom_struct]
pub struct Attr {
    node_: Node,
    identifier: AttrIdentifier,
    value: DomRefCell<AttrValue>,

    /// the element that owns this attribute.
    owner: MutNullableDom<Element>,
}

impl Attr {
    fn new_inherited(
        document: &Document,
        local_name: LocalName,
        value: AttrValue,
        name: LocalName,
        namespace: Namespace,
        prefix: Option<Prefix>,
        owner: Option<&Element>,
    ) -> Attr {
        Attr {
            node_: Node::new_inherited(document),
            identifier: AttrIdentifier {
                local_name: local_name,
                name: name,
                namespace: namespace,
                prefix: prefix,
            },
            value: DomRefCell::new(value),
            owner: MutNullableDom::new(owner),
        }
    }

    pub fn new(
        document: &Document,
        local_name: LocalName,
        value: AttrValue,
        name: LocalName,
        namespace: Namespace,
        prefix: Option<Prefix>,
        owner: Option<&Element>,
    ) -> DomRoot<Attr> {
        Node::reflect_node(
            Box::new(Attr::new_inherited(
                document, local_name, value, name, namespace, prefix, owner,
            )),
            document,
        )
    }

    #[inline]
    pub fn name(&self) -> &LocalName {
        &self.identifier.name
    }

    #[inline]
    pub fn namespace(&self) -> &Namespace {
        &self.identifier.namespace
    }

    #[inline]
    pub fn prefix(&self) -> Option<&Prefix> {
        self.identifier.prefix.as_ref()
    }
}

impl AttrMethods for Attr {
    // https://dom.spec.whatwg.org/#dom-attr-localname
    fn LocalName(&self) -> DOMString {
        // FIXME(ajeffrey): convert directly from LocalName to DOMString
        DOMString::from(&**self.local_name())
    }

    // https://dom.spec.whatwg.org/#dom-attr-value
    fn Value(&self) -> DOMString {
        // FIXME(ajeffrey): convert directly from AttrValue to DOMString
        DOMString::from(&**self.value())
    }

    // https://dom.spec.whatwg.org/#dom-attr-value
    fn SetValue(&self, value: DOMString) {
        if let Some(owner) = self.owner() {
            let value = owner.parse_attribute(&self.identifier.namespace, self.local_name(), value);
            self.set_value(value, &owner);
        } else {
            *self.value.borrow_mut() = AttrValue::String(value.into());
        }
    }

    // https://dom.spec.whatwg.org/#dom-attr-name
    fn Name(&self) -> DOMString {
        // FIXME(ajeffrey): convert directly from LocalName to DOMString
        DOMString::from(&*self.identifier.name)
    }

    // https://dom.spec.whatwg.org/#dom-attr-namespaceuri
    fn GetNamespaceURI(&self) -> Option<DOMString> {
        match self.identifier.namespace {
            ns!() => None,
            ref url => Some(DOMString::from(&**url)),
        }
    }

    // https://dom.spec.whatwg.org/#dom-attr-prefix
    fn GetPrefix(&self) -> Option<DOMString> {
        // FIXME(ajeffrey): convert directly from LocalName to DOMString
        self.prefix().map(|p| DOMString::from(&**p))
    }

    // https://dom.spec.whatwg.org/#dom-attr-ownerelement
    fn GetOwnerElement(&self) -> Option<DomRoot<Element>> {
        self.owner()
    }

    // https://dom.spec.whatwg.org/#dom-attr-specified
    fn Specified(&self) -> bool {
        true // Always returns true
    }
}

impl Attr {
    pub fn set_value(&self, mut value: AttrValue, owner: &Element) {
        let name = self.local_name().clone();
        let namespace = self.namespace().clone();
        let old_value = DOMString::from(&**self.value());
        let new_value = DOMString::from(&*value);
        let mutation = Mutation::Attribute {
            name: name.clone(),
            namespace: namespace.clone(),
            old_value: Some(old_value.clone()),
        };

        MutationObserver::queue_a_mutation_record(owner.upcast::<Node>(), mutation);

        if owner.get_custom_element_definition().is_some() {
            let reaction = CallbackReaction::AttributeChanged(
                name,
                Some(old_value),
                Some(new_value),
                namespace,
            );
            ScriptThread::enqueue_callback_reaction(owner, reaction, None);
        }

        assert_eq!(Some(owner), self.owner().as_deref());
        owner.will_mutate_attr(self);
        self.swap_value(&mut value);
        if self.identifier.namespace == ns!() {
            vtable_for(owner.upcast())
                .attribute_mutated(self, AttributeMutation::Set(Some(&value)));
        }
    }

    /// Used to swap the attribute's value without triggering mutation events
    pub fn swap_value(&self, value: &mut AttrValue) {
        mem::swap(&mut *self.value.borrow_mut(), value);
    }

    pub fn identifier(&self) -> &AttrIdentifier {
        &self.identifier
    }

    pub fn value(&self) -> Ref<AttrValue> {
        self.value.borrow()
    }

    pub fn local_name(&self) -> &LocalName {
        &self.identifier.local_name
    }

    /// Sets the owner element. Should be called after the attribute is added
    /// or removed from its older parent.
    pub fn set_owner(&self, owner: Option<&Element>) {
        let ns = &self.identifier.namespace;
        match (self.owner(), owner) {
            (Some(old), None) => {
                // Already gone from the list of attributes of old owner.
                assert!(
                    old.get_attribute(&ns, &self.identifier.local_name)
                        .as_deref() !=
                        Some(self)
                )
            },
            (Some(old), Some(new)) => assert_eq!(&*old, new),
            _ => {},
        }
        self.owner.set(owner);
    }

    pub fn owner(&self) -> Option<DomRoot<Element>> {
        self.owner.get()
    }

    pub fn summarize(&self) -> AttrInfo {
        AttrInfo {
            namespace: (*self.identifier.namespace).to_owned(),
            name: String::from(self.Name()),
            value: String::from(self.Value()),
        }
    }

    pub fn qualified_name(&self) -> DOMString {
        match self.prefix() {
            Some(ref prefix) => DOMString::from(format!("{}:{}", prefix, &**self.local_name())),
            None => DOMString::from(&**self.local_name()),
        }
    }
}

#[allow(unsafe_code)]
pub trait AttrHelpersForLayout<'dom> {
    fn value(self) -> &'dom AttrValue;
    fn as_str(self) -> &'dom str;
    fn as_tokens(self) -> Option<&'dom [Atom]>;
    fn local_name(self) -> &'dom LocalName;
    fn namespace(self) -> &'dom Namespace;
}

#[allow(unsafe_code)]
impl<'dom> AttrHelpersForLayout<'dom> for LayoutDom<'dom, Attr> {
    #[inline]
    fn value(self) -> &'dom AttrValue {
        unsafe { self.unsafe_get().value.borrow_for_layout() }
    }

    #[inline]
    fn as_str(self) -> &'dom str {
        &**self.value()
    }

    #[inline]
    fn as_tokens(self) -> Option<&'dom [Atom]> {
        // This transmute is used to cheat the lifetime restriction.
        match *self.value() {
            AttrValue::TokenList(_, ref tokens) => Some(tokens),
            _ => None,
        }
    }

    #[inline]
    fn local_name(self) -> &'dom LocalName {
        unsafe { &self.unsafe_get().identifier.local_name }
    }

    #[inline]
    fn namespace(self) -> &'dom Namespace {
        unsafe { &self.unsafe_get().identifier.namespace }
    }
}
<commit_msg>Remove an obsolete comment<commit_after>/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at https://mozilla.org/MPL/2.0/. */

use crate::dom::bindings::cell::{DomRefCell, Ref};
use crate::dom::bindings::codegen::Bindings::AttrBinding::AttrMethods;
use crate::dom::bindings::inheritance::Castable;
use crate::dom::bindings::root::{DomRoot, LayoutDom, MutNullableDom};
use crate::dom::bindings::str::DOMString;
use crate::dom::customelementregistry::CallbackReaction;
use crate::dom::document::Document;
use crate::dom::element::{AttributeMutation, Element};
use crate::dom::mutationobserver::{Mutation, MutationObserver};
use crate::dom::node::Node;
use crate::dom::virtualmethods::vtable_for;
use crate::script_thread::ScriptThread;
use devtools_traits::AttrInfo;
use dom_struct::dom_struct;
use html5ever::{LocalName, Namespace, Prefix};
use servo_atoms::Atom;
use std::borrow::ToOwned;
use std::mem;
use style::attr::{AttrIdentifier, AttrValue};

// https://dom.spec.whatwg.org/#interface-attr
#[dom_struct]
pub struct Attr {
    node_: Node,
    identifier: AttrIdentifier,
    value: DomRefCell<AttrValue>,

    /// the element that owns this attribute.
    owner: MutNullableDom<Element>,
}

impl Attr {
    fn new_inherited(
        document: &Document,
        local_name: LocalName,
        value: AttrValue,
        name: LocalName,
        namespace: Namespace,
        prefix: Option<Prefix>,
        owner: Option<&Element>,
    ) -> Attr {
        Attr {
            node_: Node::new_inherited(document),
            identifier: AttrIdentifier {
                local_name: local_name,
                name: name,
                namespace: namespace,
                prefix: prefix,
            },
            value: DomRefCell::new(value),
            owner: MutNullableDom::new(owner),
        }
    }

    pub fn new(
        document: &Document,
        local_name: LocalName,
        value: AttrValue,
        name: LocalName,
        namespace: Namespace,
        prefix: Option<Prefix>,
        owner: Option<&Element>,
    ) -> DomRoot<Attr> {
        Node::reflect_node(
            Box::new(Attr::new_inherited(
                document, local_name, value, name, namespace, prefix, owner,
            )),
            document,
        )
    }

    #[inline]
    pub fn name(&self) -> &LocalName {
        &self.identifier.name
    }

    #[inline]
    pub fn namespace(&self) -> &Namespace {
        &self.identifier.namespace
    }

    #[inline]
    pub fn prefix(&self) -> Option<&Prefix> {
        self.identifier.prefix.as_ref()
    }
}

impl AttrMethods for Attr {
    // https://dom.spec.whatwg.org/#dom-attr-localname
    fn LocalName(&self) -> DOMString {
        // FIXME(ajeffrey): convert directly from LocalName to DOMString
        DOMString::from(&**self.local_name())
    }

    // https://dom.spec.whatwg.org/#dom-attr-value
    fn Value(&self) -> DOMString {
        // FIXME(ajeffrey): convert directly from AttrValue to DOMString
        DOMString::from(&**self.value())
    }

    // https://dom.spec.whatwg.org/#dom-attr-value
    fn SetValue(&self, value: DOMString) {
        if let Some(owner) = self.owner() {
            let value = owner.parse_attribute(&self.identifier.namespace, self.local_name(), value);
            self.set_value(value, &owner);
        } else {
            *self.value.borrow_mut() = AttrValue::String(value.into());
        }
    }

    // https://dom.spec.whatwg.org/#dom-attr-name
    fn Name(&self) -> DOMString {
        // FIXME(ajeffrey): convert directly from LocalName to DOMString
        DOMString::from(&*self.identifier.name)
    }

    // https://dom.spec.whatwg.org/#dom-attr-namespaceuri
    fn GetNamespaceURI(&self) -> Option<DOMString> {
        match self.identifier.namespace {
            ns!() => None,
            ref url => Some(DOMString::from(&**url)),
        }
    }

    // https://dom.spec.whatwg.org/#dom-attr-prefix
    fn GetPrefix(&self) -> Option<DOMString> {
        // FIXME(ajeffrey): convert directly from LocalName to DOMString
        self.prefix().map(|p| DOMString::from(&**p))
    }

    // https://dom.spec.whatwg.org/#dom-attr-ownerelement
    fn GetOwnerElement(&self) -> Option<DomRoot<Element>> {
        self.owner()
    }

    // https://dom.spec.whatwg.org/#dom-attr-specified
    fn Specified(&self) -> bool {
        true // Always returns true
    }
}

impl Attr {
    pub fn set_value(&self, mut value: AttrValue, owner: &Element) {
        let name = self.local_name().clone();
        let namespace = self.namespace().clone();
        let old_value = DOMString::from(&**self.value());
        let new_value = DOMString::from(&*value);
        let mutation = Mutation::Attribute {
            name: name.clone(),
            namespace: namespace.clone(),
            old_value: Some(old_value.clone()),
        };

        MutationObserver::queue_a_mutation_record(owner.upcast::<Node>(), mutation);

        if owner.get_custom_element_definition().is_some() {
            let reaction = CallbackReaction::AttributeChanged(
                name,
                Some(old_value),
                Some(new_value),
                namespace,
            );
            ScriptThread::enqueue_callback_reaction(owner, reaction, None);
        }

        assert_eq!(Some(owner), self.owner().as_deref());
        owner.will_mutate_attr(self);
        self.swap_value(&mut value);
        if self.identifier.namespace == ns!() {
            vtable_for(owner.upcast())
                .attribute_mutated(self, AttributeMutation::Set(Some(&value)));
        }
    }

    /// Used to swap the attribute's value without triggering mutation events
    pub fn swap_value(&self, value: &mut AttrValue) {
        mem::swap(&mut *self.value.borrow_mut(), value);
    }

    pub fn identifier(&self) -> &AttrIdentifier {
        &self.identifier
    }

    pub fn value(&self) -> Ref<AttrValue> {
        self.value.borrow()
    }

    pub fn local_name(&self) -> &LocalName {
        &self.identifier.local_name
    }

    /// Sets the owner element. Should be called after the attribute is added
    /// or removed from its older parent.
    pub fn set_owner(&self, owner: Option<&Element>) {
        let ns = &self.identifier.namespace;
        match (self.owner(), owner) {
            (Some(old), None) => {
                // Already gone from the list of attributes of old owner.
                assert!(
                    old.get_attribute(&ns, &self.identifier.local_name)
                        .as_deref() !=
                        Some(self)
                )
            },
            (Some(old), Some(new)) => assert_eq!(&*old, new),
            _ => {},
        }
        self.owner.set(owner);
    }

    pub fn owner(&self) -> Option<DomRoot<Element>> {
        self.owner.get()
    }

    pub fn summarize(&self) -> AttrInfo {
        AttrInfo {
            namespace: (*self.identifier.namespace).to_owned(),
            name: String::from(self.Name()),
            value: String::from(self.Value()),
        }
    }

    pub fn qualified_name(&self) -> DOMString {
        match self.prefix() {
            Some(ref prefix) => DOMString::from(format!("{}:{}", prefix, &**self.local_name())),
            None => DOMString::from(&**self.local_name()),
        }
    }
}

#[allow(unsafe_code)]
pub trait AttrHelpersForLayout<'dom> {
    fn value(self) -> &'dom AttrValue;
    fn as_str(self) -> &'dom str;
    fn as_tokens(self) -> Option<&'dom [Atom]>;
    fn local_name(self) -> &'dom LocalName;
    fn namespace(self) -> &'dom Namespace;
}

#[allow(unsafe_code)]
impl<'dom> AttrHelpersForLayout<'dom> for LayoutDom<'dom, Attr> {
    #[inline]
    fn value(self) -> &'dom AttrValue {
        unsafe { self.unsafe_get().value.borrow_for_layout() }
    }

    #[inline]
    fn as_str(self) -> &'dom str {
        &**self.value()
    }

    #[inline]
    fn as_tokens(self) -> Option<&'dom [Atom]> {
        match *self.value() {
            AttrValue::TokenList(_, ref tokens) => Some(tokens),
            _ => None,
        }
    }

    #[inline]
    fn local_name(self) -> &'dom LocalName {
        unsafe { &self.unsafe_get().identifier.local_name }
    }

    #[inline]
    fn namespace(self) -> &'dom Namespace {
        unsafe { &self.unsafe_get().identifier.namespace }
    }
}
<|endoftext|>


<commit_before>#[cfg(test)]
mod pages_test {
    use params::{Map, Value};
    use super::super::*;
    use super::super::pages::*;
    #[test]
    // Test validation for current model
    fn validate_title_test() {
        // Test valid result
        let mut map = Map::new();
        map.assign("pages[title]", Value::String("Test Title".into())).unwrap();
        let validator = validate(&map);
        assert!(validator.get_errors().is_none());

        // test unvalid result
        let mut map = Map::new();
        map.assign("title", Value::String("".into())).unwrap();
        let validator = validate(&map);
        assert!(validator.get_errors().is_some());

        // test unvalid result
        let mut map = Map::new();
        map.assign("title", Value::String("  ".into())).unwrap();
        let validator = validate(&map);
        assert!(validator.get_errors().is_some());
    }

    #[test]
    fn validate_published_test() {
        let mut map = Map::new();
        // Should setrequired field
        map.assign("pages[title]", Value::String("Test".into())).unwrap();
        map.assign("published", Value::Boolean(true)).unwrap();
        let validator = validate(&map);
        assert!(validator.get_errors().is_none());

        let mut map = Map::new();
        map.assign("pages[title]", Value::String("Test".into())).unwrap();
        map.assign("published", Value::String("on".into())).unwrap();
        let validator = validate(&map);
        assert!(validator.get_errors().is_none());

        let mut map = Map::new();
        map.assign("title", Value::String("Test".into())).unwrap();
        map.assign("published", Value::String("off".into())).unwrap();
        let validator = validate(&map);
        assert!(validator.get_errors().is_none());

        let mut map = Map::new();
        map.assign("title", Value::String("Test".into())).unwrap();
        map.assign("published", Value::I64(1)).unwrap();
        let validator = validate(&map);
        assert!(validator.get_errors().is_none());

        let mut map = Map::new();
        map.assign("title", Value::String("Test".into())).unwrap();
        map.assign("published", Value::I64(0)).unwrap();
        let validator = validate(&map);
        assert!(validator.get_errors().is_none());

        let mut map = Map::new();
        map.assign("title", Value::String("Test".into())).unwrap();
        map.assign("published", Value::String("test".into())).unwrap();
        let validator = validate(&map);
        assert!(validator.get_errors().is_some());

        let mut map = Map::new();
        map.assign("title", Value::String("Test".into())).unwrap();
        map.assign("published", Value::I64(-1)).unwrap();
        let validator = validate(&map);
        assert!(validator.get_errors().is_some());

        let mut map = Map::new();
        map.assign("title", Value::String("Test".into())).unwrap();
        map.assign("published", Value::I64(2)).unwrap();
        let validator = validate(&map);
        assert!(validator.get_errors().is_some());
    }

    #[test]
    fn validate_published_default_test() {
        let mut map = Map::new();
        // Should setrequired field
        map.assign("pages[title]", Value::String("Test".into())).unwrap();
        let validator = validate(&map);
        assert!(validator.get_errors().is_none());

        let page = init(validator.get_values());
        assert_eq!(page.published, false);
    }

    #[test]
    #[should_panic]
    fn wrong_type_test() {
        let mut map = Map::new();
        //map.assign("title", Value::String("Test Title".into())).unwrap();
        map.assign("title", Value::Boolean(true)).unwrap();

        // Weong type declaration
        let validator = ValidateResults(vec!(
            Validator::<bool>::new(btreemap! {
                "requiered".to_string() => true.to_json(),
                "vtype".to_string() => "bool".to_json(),
            }).validate("title".to_string(), map.find(&["title"])),
        ));
        // Should paniced
        assert!(validator.get_errors().is_none());
        // Should paniced
        // cause Page stuct `title` field is String not `bool`
        let _ = init(validator.get_values());
    }
}
<commit_msg>admin.model.pages - Fix test<commit_after>#[cfg(test)]
mod pages_test {
    use params::{Map, Value};
    use super::super::*;
    use super::super::pages::*;
    #[test]
    // Test validation for current model
    fn validate_title_test() {
        // Test valid result
        let mut map = Map::new();
        map.assign("pages[title]", Value::String("Test Title".into())).unwrap();
        let validator = validate(&map);
        assert!(validator.get_errors().is_none());

        // test unvalid result
        let mut map = Map::new();
        map.assign("pages[title]", Value::String("".into())).unwrap();
        let validator = validate(&map);
        assert!(validator.get_errors().is_some());

        // test unvalid result
        let mut map = Map::new();
        map.assign("pages[title]", Value::String("  ".into())).unwrap();
        let validator = validate(&map);
        assert!(validator.get_errors().is_some());
    }

    #[test]
    fn validate_published_test() {
        let mut map = Map::new();
        // Should setrequired field
        map.assign("pages[title]", Value::String("Test".into())).unwrap();
        map.assign("published", Value::Boolean(true)).unwrap();
        let validator = validate(&map);
        assert!(validator.get_errors().is_none());

        let mut map = Map::new();
        map.assign("pages[title]", Value::String("Test".into())).unwrap();
        map.assign("published", Value::String("on".into())).unwrap();
        let validator = validate(&map);
        assert!(validator.get_errors().is_none());

        let mut map = Map::new();
        map.assign("pages[title]", Value::String("Test".into())).unwrap();
        map.assign("published", Value::String("off".into())).unwrap();
        let validator = validate(&map);
        assert!(validator.get_errors().is_none());

        let mut map = Map::new();
        map.assign("pages[title]", Value::String("Test".into())).unwrap();
        map.assign("published", Value::I64(1)).unwrap();
        let validator = validate(&map);
        assert!(validator.get_errors().is_none());

        let mut map = Map::new();
        map.assign("pages[title]", Value::String("Test".into())).unwrap();
        map.assign("published", Value::I64(0)).unwrap();
        let validator = validate(&map);
        assert!(validator.get_errors().is_none());

        let mut map = Map::new();
        map.assign("pages[title]", Value::String("Test".into())).unwrap();
        map.assign("published", Value::String("test".into())).unwrap();
        let validator = validate(&map);
        assert!(validator.get_errors().is_some());

        let mut map = Map::new();
        map.assign("pages[title]", Value::String("Test".into())).unwrap();
        map.assign("published", Value::I64(-1)).unwrap();
        let validator = validate(&map);
        assert!(validator.get_errors().is_some());

        let mut map = Map::new();
        map.assign("pages[title]", Value::String("Test".into())).unwrap();
        map.assign("published", Value::I64(2)).unwrap();
        let validator = validate(&map);
        assert!(validator.get_errors().is_some());
    }

    #[test]
    fn validate_published_default_test() {
        let mut map = Map::new();
        // Should setrequired field
        map.assign("pages[title]", Value::String("Test".into())).unwrap();
        let validator = validate(&map);
        assert!(validator.get_errors().is_none());

        let page = init(validator.get_values());
        assert_eq!(page.published, false);
    }

    #[test]
    #[should_panic]
    fn wrong_type_test() {
        let mut map = Map::new();
        //map.assign("pages[title]", Value::String("Test Title".into())).unwrap();
        map.assign("pages[title]", Value::Boolean(true)).unwrap();

        // Weong type declaration
        let validator = ValidateResults(vec!(
            Validator::<bool>::new(btreemap! {
                "requiered".to_string() => true.to_json(),
                "vtype".to_string() => "bool".to_json(),
            }).validate("title".to_string(), map.find(&["pages", "title"])),
        ));
        // Should paniced
        assert!(validator.get_errors().is_none());
        // Should paniced
        // cause Page stuct `title` field is String not `bool`
        let _ = init(validator.get_values());
    }
}
<|endoftext|>


<commit_before>use common::util::*;

extern crate uu_stat;
pub use self::uu_stat::*;

static UTIL_NAME: &'static str = "stat";

#[cfg(test)]
mod test_fsext {
    use super::*;

    #[test]
    fn test_access() {
        assert_eq!("drwxr-xr-x", pretty_access(S_IFDIR | 0o755));
        assert_eq!("-rw-r--r--", pretty_access(S_IFREG | 0o644));
        assert_eq!("srw-r-----", pretty_access(S_IFSOCK | 0o640));
        assert_eq!("lrw-r-xr-x", pretty_access(S_IFLNK | 0o655));
        assert_eq!("?rw-r-xr-x", pretty_access(0o655));

        assert_eq!("brwSr-xr-x",
                   pretty_access(S_IFBLK | S_ISUID as mode_t | 0o655));
        assert_eq!("brwsr-xr-x",
                   pretty_access(S_IFBLK | S_ISUID as mode_t | 0o755));

        assert_eq!("prw---sr--",
                   pretty_access(S_IFIFO | S_ISGID as mode_t | 0o614));
        assert_eq!("prw---Sr--",
                   pretty_access(S_IFIFO | S_ISGID as mode_t | 0o604));

        assert_eq!("c---r-xr-t",
                   pretty_access(S_IFCHR | S_ISVTX as mode_t | 0o055));
        assert_eq!("c---r-xr-T",
                   pretty_access(S_IFCHR | S_ISVTX as mode_t | 0o054));
    }

    #[test]
    fn test_file_type() {
        assert_eq!("block special file", pretty_filetype(S_IFBLK, 0));
        assert_eq!("character special file", pretty_filetype(S_IFCHR, 0));
        assert_eq!("regular file", pretty_filetype(S_IFREG, 1));
        assert_eq!("regular empty file", pretty_filetype(S_IFREG, 0));
        assert_eq!("weird file", pretty_filetype(0, 0));
    }

    #[test]
    fn test_fs_type() {
        assert_eq!("ext2/ext3", pretty_fstype(0xEF53));
        assert_eq!("tmpfs", pretty_fstype(0x01021994));
        assert_eq!("nfs", pretty_fstype(0x6969));
        assert_eq!("btrfs", pretty_fstype(0x9123683e));
        assert_eq!("xfs", pretty_fstype(0x58465342));
        assert_eq!("zfs", pretty_fstype(0x2FC12FC1));
        assert_eq!("ntfs", pretty_fstype(0x5346544e));
        assert_eq!("fat", pretty_fstype(0x4006));
        assert_eq!("UNKNOWN (0x1234)", pretty_fstype(0x1234));
    }
}

#[test]
fn test_scanutil() {
    assert_eq!(Some((-5, 2)), "-5zxc".scan_num::<i32>());
    assert_eq!(Some((51, 2)), "51zxc".scan_num::<u32>());
    assert_eq!(Some((192, 4)), "+192zxc".scan_num::<i32>());
    assert_eq!(None, "z192zxc".scan_num::<i32>());

    assert_eq!(Some(('a', 3)), "141zxc".scan_char(8));
    assert_eq!(Some(('\n', 2)), "12qzxc".scan_char(8));
    assert_eq!(Some(('\r', 1)), "dqzxc".scan_char(16));
    assert_eq!(None, "z2qzxc".scan_char(8));
}

#[test]
fn test_groupnum() {
    assert_eq!("12,379,821,234", group_num("12379821234"));
    assert_eq!("21,234", group_num("21234"));
    assert_eq!("821,234", group_num("821234"));
    assert_eq!("1,821,234", group_num("1821234"));
    assert_eq!("1,234", group_num("1234"));
    assert_eq!("234", group_num("234"));
    assert_eq!("24", group_num("24"));
    assert_eq!("4", group_num("4"));
    assert_eq!("", group_num(""));
}

#[cfg(test)]
mod test_generate_tokens {
    use super::*;

    #[test]
    fn normal_format() {
        let s = "%'010.2ac%-#5.w\n";
        let expected = vec![Token::Directive {
                                flag: F_GROUP | F_ZERO,
                                width: 10,
                                precision: 2,
                                format: 'a',
                            },
                            Token::Char('c'),
                            Token::Directive {
                                flag: F_LEFT | F_ALTER,
                                width: 5,
                                precision: 0,
                                format: 'w',
                            },
                            Token::Char('\n')];
        assert_eq!(&expected, &Stater::generate_tokens(s, false).unwrap());
    }

    #[test]
    fn printf_format() {
        let s = "%-# 15a\\r\\\"\\\\\\a\\b\\e\\f\\v%+020.-23w\\x12\\167\\132\\112\\n";
        let expected = vec![Token::Directive {
                                flag: F_LEFT | F_ALTER | F_SPACE,
                                width: 15,
                                precision: -1,
                                format: 'a',
                            },
                            Token::Char('\r'),
                            Token::Char('"'),
                            Token::Char('\\'),
                            Token::Char('\x07'),
                            Token::Char('\x08'),
                            Token::Char('\x1B'),
                            Token::Char('\x0C'),
                            Token::Char('\x0B'),
                            Token::Directive {
                                flag: F_SIGN | F_ZERO,
                                width: 20,
                                precision: -1,
                                format: 'w',
                            },
                            Token::Char('\x12'),
                            Token::Char('w'),
                            Token::Char('Z'),
                            Token::Char('J'),
                            Token::Char('\n')];
        assert_eq!(&expected, &Stater::generate_tokens(s, true).unwrap());
    }
}

#[test]
fn test_invalid_option() {
    let (_, mut ucmd) = testing(UTIL_NAME);
    ucmd.arg("-w").arg("-q").arg("/");
    ucmd.fails();
}

#[allow(unused_variable)]
const NORMAL_FMTSTR: &'static str = "%a %A %b %B %d %D %f %F %g %G %h %i %m %n %o %s %u %U %w %W %x %X %y %Y %z %Z";
#[allow(unused_variable)]
const DEV_FMTSTR: &'static str = "%a %A %b %B %d %D %f %F %g %G %h %i %m %n %o %s (%t/%T) %u %U %w %W %x %X %y %Y %z %Z";
#[allow(unused_variable)]
const FS_FMTSTR: &'static str = "%a %b %c %d %f %i %l %n %s %S %t %T";

#[test]
#[cfg(target_os = "linux")]
fn test_terse_fs_format() {
    let (_, mut ucmd) = testing(UTIL_NAME);
    let args = ["-f", "-t", "/proc"];
    ucmd.args(&args);
    assert_eq!(ucmd.run().stdout, expected_result(&args));
}

#[test]
#[cfg(target_os = "linux")]
fn test_fs_format() {
    let (_, mut ucmd) = testing(UTIL_NAME);
    let args = ["-f", "-c", FS_FMTSTR, "/dev/shm"];
    ucmd.args(&args);
    assert_eq!(ucmd.run().stdout, expected_result(&args));
}

#[test]
#[cfg(target_os = "linux")]
fn test_terse_normal_format() {
    let (_, mut ucmd) = testing(UTIL_NAME);
    let args = ["-t", "/"];
    ucmd.args(&args);
    assert_eq!(ucmd.run().stdout, expected_result(&args));
}

#[test]
#[cfg(target_os = "linux")]
fn test_normal_format() {
    let (_, mut ucmd) = testing(UTIL_NAME);
    let args = ["-c", NORMAL_FMTSTR, "/boot"];
    ucmd.args(&args);
    assert_eq!(ucmd.run().stdout, expected_result(&args));
}

#[test]
#[cfg(target_os = "linux")]
fn test_follow_symlink() {
    let (_, mut ucmd) = testing(UTIL_NAME);
    let args = ["-L", "-c", DEV_FMTSTR, "/dev/cdrom"];
    ucmd.args(&args);
    assert_eq!(ucmd.run().stdout, expected_result(&args));
}

#[test]
#[cfg(target_os = "linux")]
fn test_symlink() {
    let (_, mut ucmd) = testing(UTIL_NAME);
    let args = ["-c", DEV_FMTSTR, "/dev/cdrom"];
    ucmd.args(&args);
    assert_eq!(ucmd.run().stdout, expected_result(&args));
}

#[test]
#[cfg(target_os = "linux")]
fn test_char() {
    let (_, mut ucmd) = testing(UTIL_NAME);
    let args = ["-c", DEV_FMTSTR, "/dev/zero"];
    ucmd.args(&args);
    assert_eq!(ucmd.run().stdout, expected_result(&args));
}

#[test]
#[cfg(target_os = "linux")]
fn test_multi_files() {
    let (_, mut ucmd) = testing(UTIL_NAME);
    let args = ["-c", NORMAL_FMTSTR, "/dev", "/usr/lib", "/etc/fstab", "/var"];
    ucmd.args(&args);
    assert_eq!(ucmd.run().stdout, expected_result(&args));
}

#[test]
#[cfg(target_os = "linux")]
fn test_printf() {
    let (_, mut ucmd) = testing(UTIL_NAME);
    let args = ["--printf=123%-# 15q\\r\\\"\\\\\\a\\b\\e\\f\\v%+020.23m\\x12\\167\\132\\112\\n", "/"];
    ucmd.args(&args);
    assert_eq!(ucmd.run().stdout, "123?\r\"\\\x07\x08\x1B\x0C\x0B                   /\x12wZJ\n");
}

#[allow(dead_code)]
fn expected_result(args: &[&str]) -> String {
    use std::process::Command;

    let output = Command::new(UTIL_NAME).args(args).output().unwrap();
    String::from_utf8_lossy(&output.stdout).into_owned()
}
<commit_msg>stat: remove warning in test<commit_after>use common::util::*;

extern crate uu_stat;
pub use self::uu_stat::*;

static UTIL_NAME: &'static str = "stat";

#[cfg(test)]
mod test_fsext {
    use super::*;

    #[test]
    fn test_access() {
        assert_eq!("drwxr-xr-x", pretty_access(S_IFDIR | 0o755));
        assert_eq!("-rw-r--r--", pretty_access(S_IFREG | 0o644));
        assert_eq!("srw-r-----", pretty_access(S_IFSOCK | 0o640));
        assert_eq!("lrw-r-xr-x", pretty_access(S_IFLNK | 0o655));
        assert_eq!("?rw-r-xr-x", pretty_access(0o655));

        assert_eq!("brwSr-xr-x",
                   pretty_access(S_IFBLK | S_ISUID as mode_t | 0o655));
        assert_eq!("brwsr-xr-x",
                   pretty_access(S_IFBLK | S_ISUID as mode_t | 0o755));

        assert_eq!("prw---sr--",
                   pretty_access(S_IFIFO | S_ISGID as mode_t | 0o614));
        assert_eq!("prw---Sr--",
                   pretty_access(S_IFIFO | S_ISGID as mode_t | 0o604));

        assert_eq!("c---r-xr-t",
                   pretty_access(S_IFCHR | S_ISVTX as mode_t | 0o055));
        assert_eq!("c---r-xr-T",
                   pretty_access(S_IFCHR | S_ISVTX as mode_t | 0o054));
    }

    #[test]
    fn test_file_type() {
        assert_eq!("block special file", pretty_filetype(S_IFBLK, 0));
        assert_eq!("character special file", pretty_filetype(S_IFCHR, 0));
        assert_eq!("regular file", pretty_filetype(S_IFREG, 1));
        assert_eq!("regular empty file", pretty_filetype(S_IFREG, 0));
        assert_eq!("weird file", pretty_filetype(0, 0));
    }

    #[test]
    fn test_fs_type() {
        assert_eq!("ext2/ext3", pretty_fstype(0xEF53));
        assert_eq!("tmpfs", pretty_fstype(0x01021994));
        assert_eq!("nfs", pretty_fstype(0x6969));
        assert_eq!("btrfs", pretty_fstype(0x9123683e));
        assert_eq!("xfs", pretty_fstype(0x58465342));
        assert_eq!("zfs", pretty_fstype(0x2FC12FC1));
        assert_eq!("ntfs", pretty_fstype(0x5346544e));
        assert_eq!("fat", pretty_fstype(0x4006));
        assert_eq!("UNKNOWN (0x1234)", pretty_fstype(0x1234));
    }
}

#[test]
fn test_scanutil() {
    assert_eq!(Some((-5, 2)), "-5zxc".scan_num::<i32>());
    assert_eq!(Some((51, 2)), "51zxc".scan_num::<u32>());
    assert_eq!(Some((192, 4)), "+192zxc".scan_num::<i32>());
    assert_eq!(None, "z192zxc".scan_num::<i32>());

    assert_eq!(Some(('a', 3)), "141zxc".scan_char(8));
    assert_eq!(Some(('\n', 2)), "12qzxc".scan_char(8));
    assert_eq!(Some(('\r', 1)), "dqzxc".scan_char(16));
    assert_eq!(None, "z2qzxc".scan_char(8));
}

#[test]
fn test_groupnum() {
    assert_eq!("12,379,821,234", group_num("12379821234"));
    assert_eq!("21,234", group_num("21234"));
    assert_eq!("821,234", group_num("821234"));
    assert_eq!("1,821,234", group_num("1821234"));
    assert_eq!("1,234", group_num("1234"));
    assert_eq!("234", group_num("234"));
    assert_eq!("24", group_num("24"));
    assert_eq!("4", group_num("4"));
    assert_eq!("", group_num(""));
}

#[cfg(test)]
mod test_generate_tokens {
    use super::*;

    #[test]
    fn normal_format() {
        let s = "%'010.2ac%-#5.w\n";
        let expected = vec![Token::Directive {
                                flag: F_GROUP | F_ZERO,
                                width: 10,
                                precision: 2,
                                format: 'a',
                            },
                            Token::Char('c'),
                            Token::Directive {
                                flag: F_LEFT | F_ALTER,
                                width: 5,
                                precision: 0,
                                format: 'w',
                            },
                            Token::Char('\n')];
        assert_eq!(&expected, &Stater::generate_tokens(s, false).unwrap());
    }

    #[test]
    fn printf_format() {
        let s = "%-# 15a\\r\\\"\\\\\\a\\b\\e\\f\\v%+020.-23w\\x12\\167\\132\\112\\n";
        let expected = vec![Token::Directive {
                                flag: F_LEFT | F_ALTER | F_SPACE,
                                width: 15,
                                precision: -1,
                                format: 'a',
                            },
                            Token::Char('\r'),
                            Token::Char('"'),
                            Token::Char('\\'),
                            Token::Char('\x07'),
                            Token::Char('\x08'),
                            Token::Char('\x1B'),
                            Token::Char('\x0C'),
                            Token::Char('\x0B'),
                            Token::Directive {
                                flag: F_SIGN | F_ZERO,
                                width: 20,
                                precision: -1,
                                format: 'w',
                            },
                            Token::Char('\x12'),
                            Token::Char('w'),
                            Token::Char('Z'),
                            Token::Char('J'),
                            Token::Char('\n')];
        assert_eq!(&expected, &Stater::generate_tokens(s, true).unwrap());
    }
}

#[test]
fn test_invalid_option() {
    let (_, mut ucmd) = testing(UTIL_NAME);
    ucmd.arg("-w").arg("-q").arg("/");
    ucmd.fails();
}

#[cfg(target_os = "linux")]
const NORMAL_FMTSTR: &'static str = "%a %A %b %B %d %D %f %F %g %G %h %i %m %n %o %s %u %U %w %W %x %X %y %Y %z %Z";
#[cfg(target_os = "linux")]
const DEV_FMTSTR: &'static str = "%a %A %b %B %d %D %f %F %g %G %h %i %m %n %o %s (%t/%T) %u %U %w %W %x %X %y %Y %z %Z";
#[cfg(target_os = "linux")]
const FS_FMTSTR: &'static str = "%a %b %c %d %f %i %l %n %s %S %t %T";

#[test]
#[cfg(target_os = "linux")]
fn test_terse_fs_format() {
    let (_, mut ucmd) = testing(UTIL_NAME);
    let args = ["-f", "-t", "/proc"];
    ucmd.args(&args);
    assert_eq!(ucmd.run().stdout, expected_result(&args));
}

#[test]
#[cfg(target_os = "linux")]
fn test_fs_format() {
    let (_, mut ucmd) = testing(UTIL_NAME);
    let args = ["-f", "-c", FS_FMTSTR, "/dev/shm"];
    ucmd.args(&args);
    assert_eq!(ucmd.run().stdout, expected_result(&args));
}

#[test]
#[cfg(target_os = "linux")]
fn test_terse_normal_format() {
    let (_, mut ucmd) = testing(UTIL_NAME);
    let args = ["-t", "/"];
    ucmd.args(&args);
    assert_eq!(ucmd.run().stdout, expected_result(&args));
}

#[test]
#[cfg(target_os = "linux")]
fn test_normal_format() {
    let (_, mut ucmd) = testing(UTIL_NAME);
    let args = ["-c", NORMAL_FMTSTR, "/boot"];
    ucmd.args(&args);
    assert_eq!(ucmd.run().stdout, expected_result(&args));
}

#[test]
#[cfg(target_os = "linux")]
fn test_follow_symlink() {
    let (_, mut ucmd) = testing(UTIL_NAME);
    let args = ["-L", "-c", DEV_FMTSTR, "/dev/cdrom"];
    ucmd.args(&args);
    assert_eq!(ucmd.run().stdout, expected_result(&args));
}

#[test]
#[cfg(target_os = "linux")]
fn test_symlink() {
    let (_, mut ucmd) = testing(UTIL_NAME);
    let args = ["-c", DEV_FMTSTR, "/dev/cdrom"];
    ucmd.args(&args);
    assert_eq!(ucmd.run().stdout, expected_result(&args));
}

#[test]
#[cfg(target_os = "linux")]
fn test_char() {
    let (_, mut ucmd) = testing(UTIL_NAME);
    let args = ["-c", DEV_FMTSTR, "/dev/zero"];
    ucmd.args(&args);
    assert_eq!(ucmd.run().stdout, expected_result(&args));
}

#[test]
#[cfg(target_os = "linux")]
fn test_multi_files() {
    let (_, mut ucmd) = testing(UTIL_NAME);
    let args = ["-c", NORMAL_FMTSTR, "/dev", "/usr/lib", "/etc/fstab", "/var"];
    ucmd.args(&args);
    assert_eq!(ucmd.run().stdout, expected_result(&args));
}

#[test]
#[cfg(target_os = "linux")]
fn test_printf() {
    let (_, mut ucmd) = testing(UTIL_NAME);
    let args = ["--printf=123%-# 15q\\r\\\"\\\\\\a\\b\\e\\f\\v%+020.23m\\x12\\167\\132\\112\\n", "/"];
    ucmd.args(&args);
    assert_eq!(ucmd.run().stdout, "123?\r\"\\\x07\x08\x1B\x0C\x0B                   /\x12wZJ\n");
}

#[cfg(target_os = "linux")]
fn expected_result(args: &[&str]) -> String {
    use std::process::Command;

    let output = Command::new(UTIL_NAME).args(args).output().unwrap();
    String::from_utf8_lossy(&output.stdout).into_owned()
}
<|endoftext|>


<commit_before>use std::fs::{self, File};
use std::io::SeekFrom;
use std::io::prelude::*;
use std::path::{self, Path};

use flate2::read::GzDecoder;
use flate2::{GzBuilder, Compression};
use git2;
use tar::{Archive, Builder, Header};

use core::{SourceId, Package, PackageId, Workspace, Source};
use sources::PathSource;
use util::{self, CargoResult, human, internal, ChainError, Config, FileLock};
use ops;

pub struct PackageOpts<'cfg> {
    pub config: &'cfg Config,
    pub list: bool,
    pub check_metadata: bool,
    pub allow_dirty: bool,
    pub verify: bool,
    pub jobs: Option<u32>,
}

pub fn package(ws: &Workspace,
               opts: &PackageOpts) -> CargoResult<Option<FileLock>> {
    let pkg = try!(ws.current());
    let config = ws.config();
    let mut src = PathSource::new(pkg.root(),
                                  pkg.package_id().source_id(),
                                  config);
    try!(src.update());

    if opts.check_metadata {
        try!(check_metadata(pkg, config));
    }

    if opts.list {
        let root = pkg.root();
        let mut list: Vec<_> = try!(src.list_files(&pkg)).iter().map(|file| {
            util::without_prefix(&file, &root).unwrap().to_path_buf()
        }).collect();
        list.sort();
        for file in list.iter() {
            println!("{}", file.display());
        }
        return Ok(None)
    }

    if !opts.allow_dirty {
        try!(check_not_dirty(&pkg, &src));
    }

    let filename = format!("{}-{}.crate", pkg.name(), pkg.version());
    let dir = ws.target_dir().join("package");
    let mut dst = {
        let tmp = format!(".{}", filename);
        try!(dir.open_rw(&tmp, config, "package scratch space"))
    };

    // Package up and test a temporary tarball and only move it to the final
    // location if it actually passes all our tests. Any previously existing
    // tarball can be assumed as corrupt or invalid, so we just blow it away if
    // it exists.
    try!(config.shell().status("Packaging", pkg.package_id().to_string()));
    try!(dst.file().set_len(0));
    try!(tar(ws, &src, dst.file(), &filename).chain_error(|| {
        human("failed to prepare local package for uploading")
    }));
    if opts.verify {
        try!(dst.seek(SeekFrom::Start(0)));
        try!(run_verify(ws, dst.file(), opts).chain_error(|| {
            human("failed to verify package tarball")
        }))
    }
    try!(dst.seek(SeekFrom::Start(0)));
    {
        let src_path = dst.path();
        let dst_path = dst.parent().join(&filename);
        try!(fs::rename(&src_path, &dst_path).chain_error(|| {
            human("failed to move temporary tarball into final location")
        }));
    }
    Ok(Some(dst))
}

// check that the package has some piece of metadata that a human can
// use to tell what the package is about.
fn check_metadata(pkg: &Package, config: &Config) -> CargoResult<()> {
    let md = pkg.manifest().metadata();

    let mut missing = vec![];

    macro_rules! lacking {
        ($( $($field: ident)||* ),*) => {{
            $(
                if $(md.$field.as_ref().map_or(true, |s| s.is_empty()))&&* {
                    $(missing.push(stringify!($field).replace("_", "-"));)*
                }
            )*
        }}
    }
    lacking!(description, license || license_file, documentation || homepage || repository);

    if !missing.is_empty() {
        let mut things = missing[..missing.len() - 1].join(", ");
        // things will be empty if and only if length == 1 (i.e. the only case
        // to have no `or`).
        if !things.is_empty() {
            things.push_str(" or ");
        }
        things.push_str(&missing.last().unwrap());

        try!(config.shell().warn(
            &format!("manifest has no {things}. \
                    See http://doc.crates.io/manifest.html#package-metadata for more info.",
                    things = things)))
    }
    Ok(())
}

fn check_not_dirty(p: &Package, src: &PathSource) -> CargoResult<()> {
    if let Ok(repo) = git2::Repository::discover(p.root()) {
        if let Some(workdir) = repo.workdir() {
            debug!("found a git repo at {:?}, checking if index present",
                   workdir);
            let path = p.manifest_path();
            let path = path.strip_prefix(workdir).unwrap_or(path);
            if let Ok(status) = repo.status_file(path) {
                if (status & git2::STATUS_IGNORED).is_empty() {
                    debug!("Cargo.toml found in repo, checking if dirty");
                    return git(p, src, &repo)
                }
            }
        }
    }

    // No VCS recognized, we don't know if the directory is dirty or not, so we
    // have to assume that it's clean.
    return Ok(());

    fn git(p: &Package,
           src: &PathSource,
           repo: &git2::Repository) -> CargoResult<()> {
        let workdir = repo.workdir().unwrap();
        let dirty = try!(src.list_files(p)).iter().filter(|file| {
            let relative = file.strip_prefix(workdir).unwrap();
            if let Ok(status) = repo.status_file(relative) {
                status != git2::STATUS_CURRENT
            } else {
                false
            }
        }).map(|path| {
            path.strip_prefix(p.root()).unwrap_or(path).display().to_string()
        }).collect::<Vec<_>>();
        if dirty.is_empty() {
            Ok(())
        } else {
            bail!("{} dirty files found in the working directory:\n\n{}\n\n\
                   to publish despite this, pass `--allow-dirty` to \
                   `cargo publish`",
                  dirty.len(), dirty.join("\n"))
        }
    }
}

fn tar(ws: &Workspace,
       src: &PathSource,
       dst: &File,
       filename: &str) -> CargoResult<()> {
    // Prepare the encoder and its header
    let filename = Path::new(filename);
    let encoder = GzBuilder::new().filename(try!(util::path2bytes(filename)))
                                  .write(dst, Compression::Best);

    // Put all package files into a compressed archive
    let mut ar = Builder::new(encoder);
    let pkg = try!(ws.current());
    let config = ws.config();
    let root = pkg.root();
    for file in try!(src.list_files(pkg)).iter() {
        let relative = util::without_prefix(&file, &root).unwrap();
        try!(check_filename(relative));
        let relative = try!(relative.to_str().chain_error(|| {
            human(format!("non-utf8 path in source directory: {}",
                          relative.display()))
        }));
        let mut file = try!(File::open(file).chain_error(|| {
            human(format!("failed to open for archiving: `{}`", file.display()))
        }));
        try!(config.shell().verbose(|shell| {
            shell.status("Archiving", &relative)
        }));
        let path = format!("{}-{}{}{}", pkg.name(), pkg.version(),
                           path::MAIN_SEPARATOR, relative);

        // The tar::Builder type by default will build GNU archives, but
        // unfortunately we force it here to use UStar archives instead. The
        // UStar format has more limitations on the length of path name that it
        // can encode, so it's not quite as nice to use.
        //
        // Older cargos, however, had a bug where GNU archives were interpreted
        // as UStar archives. This bug means that if we publish a GNU archive
        // which has fully filled out metadata it'll be corrupt when unpacked by
        // older cargos.
        //
        // Hopefully in the future after enough cargos have been running around
        // with the bugfixed tar-rs library we'll be able to switch this over to
        // GNU archives, but for now we'll just say that you can't encode paths
        // in archives that are *too* long.
        //
        // For an instance of this in the wild, use the tar-rs 0.3.3 library to
        // unpack the selectors 0.4.0 crate on crates.io. Either that or take a
        // look at rust-lang/cargo#2326
        let mut header = Header::new_ustar();
        let metadata = try!(file.metadata().chain_error(|| {
            human(format!("could not learn metadata for: `{}`", relative))
        }));
        try!(header.set_path(&path).chain_error(|| {
            human(format!("failed to add to archive: `{}`", relative))
        }));
        header.set_metadata(&metadata);
        header.set_cksum();

        try!(ar.append(&header, &mut file).chain_error(|| {
            internal(format!("could not archive source file `{}`", relative))
        }));
    }
    let encoder = try!(ar.into_inner());
    try!(encoder.finish());
    Ok(())
}

fn run_verify(ws: &Workspace, tar: &File, opts: &PackageOpts) -> CargoResult<()> {
    let config = ws.config();
    let pkg = try!(ws.current());

    try!(config.shell().status("Verifying", pkg));

    let f = try!(GzDecoder::new(tar));
    let dst = pkg.root().join(&format!("target/package/{}-{}",
                                       pkg.name(), pkg.version()));
    if fs::metadata(&dst).is_ok() {
        try!(fs::remove_dir_all(&dst));
    }
    let mut archive = Archive::new(f);
    try!(archive.unpack(dst.parent().unwrap()));
    let manifest_path = dst.join("Cargo.toml");

    // When packages are uploaded to a registry, all path dependencies are
    // implicitly converted to registry dependencies, so we rewrite those
    // dependencies here.
    //
    // We also make sure to point all paths at `dst` instead of the previous
    // location that the package was originally read from. In locking the
    // `SourceId` we're telling it that the corresponding `PathSource` will be
    // considered updated and we won't actually read any packages.
    let cratesio = try!(SourceId::crates_io(config));
    let precise = Some("locked".to_string());
    let new_src = try!(SourceId::for_path(&dst)).with_precise(precise);
    let new_pkgid = try!(PackageId::new(pkg.name(), pkg.version(), &new_src));
    let new_summary = pkg.summary().clone().map_dependencies(|d| {
        if !d.source_id().is_path() { return d }
        d.clone_inner().set_source_id(cratesio.clone()).into_dependency()
    });
    let mut new_manifest = pkg.manifest().clone();
    new_manifest.set_summary(new_summary.override_id(new_pkgid));
    let new_pkg = Package::new(new_manifest, &manifest_path);

    // Now that we've rewritten all our path dependencies, compile it!
    let ws = try!(Workspace::one(new_pkg, config, None));
    try!(ops::compile_ws(&ws, None, &ops::CompileOptions {
        config: config,
        jobs: opts.jobs,
        target: None,
        features: &[],
        no_default_features: false,
        all_features: false,
        spec: &[],
        filter: ops::CompileFilter::Everything,
        exec_engine: None,
        release: false,
        mode: ops::CompileMode::Build,
        target_rustdoc_args: None,
        target_rustc_args: None,
    }));

    Ok(())
}

// It can often be the case that files of a particular name on one platform
// can't actually be created on another platform. For example files with colons
// in the name are allowed on Unix but not on Windows.
//
// To help out in situations like this, issue about weird filenames when
// packaging as a "heads up" that something may not work on other platforms.
fn check_filename(file: &Path) -> CargoResult<()> {
    let name = match file.file_name() {
        Some(name) => name,
        None => return Ok(()),
    };
    let name = match name.to_str() {
        Some(name) => name,
        None => {
            bail!("path does not have a unicode filename which may not unpack \
                   on all platforms: {}", file.display())
        }
    };
    let bad_chars = ['/', '\\', '<', '>', ':', '"', '|', '?', '*'];
    for c in bad_chars.iter().filter(|c| name.contains(**c)) {
        bail!("cannot package a filename with a special character `{}`: {}",
              c, file.display())
    }
    Ok(())
}
<commit_msg>Check for path dependencies.<commit_after>use std::fs::{self, File};
use std::io::SeekFrom;
use std::io::prelude::*;
use std::path::{self, Path};

use flate2::read::GzDecoder;
use flate2::{GzBuilder, Compression};
use git2;
use tar::{Archive, Builder, Header};

use core::{SourceId, Package, PackageId, Workspace, Source};
use sources::PathSource;
use util::{self, CargoResult, human, internal, ChainError, Config, FileLock};
use ops;

pub struct PackageOpts<'cfg> {
    pub config: &'cfg Config,
    pub list: bool,
    pub check_metadata: bool,
    pub allow_dirty: bool,
    pub verify: bool,
    pub jobs: Option<u32>,
}

pub fn package(ws: &Workspace,
               opts: &PackageOpts) -> CargoResult<Option<FileLock>> {
    let pkg = try!(ws.current());
    let config = ws.config();
    let mut src = PathSource::new(pkg.root(),
                                  pkg.package_id().source_id(),
                                  config);
    try!(src.update());

    if opts.check_metadata {
        try!(check_metadata(pkg, config));
    }

    try!(verify_dependencies(&pkg));

    if opts.list {
        let root = pkg.root();
        let mut list: Vec<_> = try!(src.list_files(&pkg)).iter().map(|file| {
            util::without_prefix(&file, &root).unwrap().to_path_buf()
        }).collect();
        list.sort();
        for file in list.iter() {
            println!("{}", file.display());
        }
        return Ok(None)
    }

    if !opts.allow_dirty {
        try!(check_not_dirty(&pkg, &src));
    }

    let filename = format!("{}-{}.crate", pkg.name(), pkg.version());
    let dir = ws.target_dir().join("package");
    let mut dst = {
        let tmp = format!(".{}", filename);
        try!(dir.open_rw(&tmp, config, "package scratch space"))
    };

    // Package up and test a temporary tarball and only move it to the final
    // location if it actually passes all our tests. Any previously existing
    // tarball can be assumed as corrupt or invalid, so we just blow it away if
    // it exists.
    try!(config.shell().status("Packaging", pkg.package_id().to_string()));
    try!(dst.file().set_len(0));
    try!(tar(ws, &src, dst.file(), &filename).chain_error(|| {
        human("failed to prepare local package for uploading")
    }));
    if opts.verify {
        try!(dst.seek(SeekFrom::Start(0)));
        try!(run_verify(ws, dst.file(), opts).chain_error(|| {
            human("failed to verify package tarball")
        }))
    }
    try!(dst.seek(SeekFrom::Start(0)));
    {
        let src_path = dst.path();
        let dst_path = dst.parent().join(&filename);
        try!(fs::rename(&src_path, &dst_path).chain_error(|| {
            human("failed to move temporary tarball into final location")
        }));
    }
    Ok(Some(dst))
}

// check that the package has some piece of metadata that a human can
// use to tell what the package is about.
fn check_metadata(pkg: &Package, config: &Config) -> CargoResult<()> {
    let md = pkg.manifest().metadata();

    let mut missing = vec![];

    macro_rules! lacking {
        ($( $($field: ident)||* ),*) => {{
            $(
                if $(md.$field.as_ref().map_or(true, |s| s.is_empty()))&&* {
                    $(missing.push(stringify!($field).replace("_", "-"));)*
                }
            )*
        }}
    }
    lacking!(description, license || license_file, documentation || homepage || repository);

    if !missing.is_empty() {
        let mut things = missing[..missing.len() - 1].join(", ");
        // things will be empty if and only if length == 1 (i.e. the only case
        // to have no `or`).
        if !things.is_empty() {
            things.push_str(" or ");
        }
        things.push_str(&missing.last().unwrap());

        try!(config.shell().warn(
            &format!("manifest has no {things}. \
                    See http://doc.crates.io/manifest.html#package-metadata for more info.",
                    things = things)))
    }
    Ok(())
}

// check that the package dependencies are safe to deploy.
fn verify_dependencies(pkg: &Package) -> CargoResult<()> {
    for dep in pkg.dependencies() {
        if dep.source_id().is_path() {
            if !dep.specified_req() {
                bail!("all path dependencies must have a version specified \
                       when packaging.\ndependency `{}` does not specify \
                       a version.", dep.name())
            }
        }
    }
    Ok(())
}

fn check_not_dirty(p: &Package, src: &PathSource) -> CargoResult<()> {
    if let Ok(repo) = git2::Repository::discover(p.root()) {
        if let Some(workdir) = repo.workdir() {
            debug!("found a git repo at {:?}, checking if index present",
                   workdir);
            let path = p.manifest_path();
            let path = path.strip_prefix(workdir).unwrap_or(path);
            if let Ok(status) = repo.status_file(path) {
                if (status & git2::STATUS_IGNORED).is_empty() {
                    debug!("Cargo.toml found in repo, checking if dirty");
                    return git(p, src, &repo)
                }
            }
        }
    }

    // No VCS recognized, we don't know if the directory is dirty or not, so we
    // have to assume that it's clean.
    return Ok(());

    fn git(p: &Package,
           src: &PathSource,
           repo: &git2::Repository) -> CargoResult<()> {
        let workdir = repo.workdir().unwrap();
        let dirty = try!(src.list_files(p)).iter().filter(|file| {
            let relative = file.strip_prefix(workdir).unwrap();
            if let Ok(status) = repo.status_file(relative) {
                status != git2::STATUS_CURRENT
            } else {
                false
            }
        }).map(|path| {
            path.strip_prefix(p.root()).unwrap_or(path).display().to_string()
        }).collect::<Vec<_>>();
        if dirty.is_empty() {
            Ok(())
        } else {
            bail!("{} dirty files found in the working directory:\n\n{}\n\n\
                   to publish despite this, pass `--allow-dirty` to \
                   `cargo publish`",
                  dirty.len(), dirty.join("\n"))
        }
    }
}

fn tar(ws: &Workspace,
       src: &PathSource,
       dst: &File,
       filename: &str) -> CargoResult<()> {
    // Prepare the encoder and its header
    let filename = Path::new(filename);
    let encoder = GzBuilder::new().filename(try!(util::path2bytes(filename)))
                                  .write(dst, Compression::Best);

    // Put all package files into a compressed archive
    let mut ar = Builder::new(encoder);
    let pkg = try!(ws.current());
    let config = ws.config();
    let root = pkg.root();
    for file in try!(src.list_files(pkg)).iter() {
        let relative = util::without_prefix(&file, &root).unwrap();
        try!(check_filename(relative));
        let relative = try!(relative.to_str().chain_error(|| {
            human(format!("non-utf8 path in source directory: {}",
                          relative.display()))
        }));
        let mut file = try!(File::open(file).chain_error(|| {
            human(format!("failed to open for archiving: `{}`", file.display()))
        }));
        try!(config.shell().verbose(|shell| {
            shell.status("Archiving", &relative)
        }));
        let path = format!("{}-{}{}{}", pkg.name(), pkg.version(),
                           path::MAIN_SEPARATOR, relative);

        // The tar::Builder type by default will build GNU archives, but
        // unfortunately we force it here to use UStar archives instead. The
        // UStar format has more limitations on the length of path name that it
        // can encode, so it's not quite as nice to use.
        //
        // Older cargos, however, had a bug where GNU archives were interpreted
        // as UStar archives. This bug means that if we publish a GNU archive
        // which has fully filled out metadata it'll be corrupt when unpacked by
        // older cargos.
        //
        // Hopefully in the future after enough cargos have been running around
        // with the bugfixed tar-rs library we'll be able to switch this over to
        // GNU archives, but for now we'll just say that you can't encode paths
        // in archives that are *too* long.
        //
        // For an instance of this in the wild, use the tar-rs 0.3.3 library to
        // unpack the selectors 0.4.0 crate on crates.io. Either that or take a
        // look at rust-lang/cargo#2326
        let mut header = Header::new_ustar();
        let metadata = try!(file.metadata().chain_error(|| {
            human(format!("could not learn metadata for: `{}`", relative))
        }));
        try!(header.set_path(&path).chain_error(|| {
            human(format!("failed to add to archive: `{}`", relative))
        }));
        header.set_metadata(&metadata);
        header.set_cksum();

        try!(ar.append(&header, &mut file).chain_error(|| {
            internal(format!("could not archive source file `{}`", relative))
        }));
    }
    let encoder = try!(ar.into_inner());
    try!(encoder.finish());
    Ok(())
}

fn run_verify(ws: &Workspace, tar: &File, opts: &PackageOpts) -> CargoResult<()> {
    let config = ws.config();
    let pkg = try!(ws.current());

    try!(config.shell().status("Verifying", pkg));

    let f = try!(GzDecoder::new(tar));
    let dst = pkg.root().join(&format!("target/package/{}-{}",
                                       pkg.name(), pkg.version()));
    if fs::metadata(&dst).is_ok() {
        try!(fs::remove_dir_all(&dst));
    }
    let mut archive = Archive::new(f);
    try!(archive.unpack(dst.parent().unwrap()));
    let manifest_path = dst.join("Cargo.toml");

    // When packages are uploaded to a registry, all path dependencies are
    // implicitly converted to registry dependencies, so we rewrite those
    // dependencies here.
    //
    // We also make sure to point all paths at `dst` instead of the previous
    // location that the package was originally read from. In locking the
    // `SourceId` we're telling it that the corresponding `PathSource` will be
    // considered updated and we won't actually read any packages.
    let cratesio = try!(SourceId::crates_io(config));
    let precise = Some("locked".to_string());
    let new_src = try!(SourceId::for_path(&dst)).with_precise(precise);
    let new_pkgid = try!(PackageId::new(pkg.name(), pkg.version(), &new_src));
    let new_summary = pkg.summary().clone().map_dependencies(|d| {
        if !d.source_id().is_path() { return d }
        d.clone_inner().set_source_id(cratesio.clone()).into_dependency()
    });
    let mut new_manifest = pkg.manifest().clone();
    new_manifest.set_summary(new_summary.override_id(new_pkgid));
    let new_pkg = Package::new(new_manifest, &manifest_path);

    // Now that we've rewritten all our path dependencies, compile it!
    let ws = try!(Workspace::one(new_pkg, config, None));
    try!(ops::compile_ws(&ws, None, &ops::CompileOptions {
        config: config,
        jobs: opts.jobs,
        target: None,
        features: &[],
        no_default_features: false,
        all_features: false,
        spec: &[],
        filter: ops::CompileFilter::Everything,
        exec_engine: None,
        release: false,
        mode: ops::CompileMode::Build,
        target_rustdoc_args: None,
        target_rustc_args: None,
    }));

    Ok(())
}

// It can often be the case that files of a particular name on one platform
// can't actually be created on another platform. For example files with colons
// in the name are allowed on Unix but not on Windows.
//
// To help out in situations like this, issue about weird filenames when
// packaging as a "heads up" that something may not work on other platforms.
fn check_filename(file: &Path) -> CargoResult<()> {
    let name = match file.file_name() {
        Some(name) => name,
        None => return Ok(()),
    };
    let name = match name.to_str() {
        Some(name) => name,
        None => {
            bail!("path does not have a unicode filename which may not unpack \
                   on all platforms: {}", file.display())
        }
    };
    let bad_chars = ['/', '\\', '<', '>', ':', '"', '|', '?', '*'];
    for c in bad_chars.iter().filter(|c| name.contains(**c)) {
        bail!("cannot package a filename with a special character `{}`: {}",
              c, file.display())
    }
    Ok(())
}
<|endoftext|>