<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 dom::bindings::codegen::Bindings::CSSStyleDeclarationBinding::{self, CSSStyleDeclarationMethods};
use dom::bindings::error::{Error, ErrorResult, Fallible};
use dom::bindings::global::GlobalRef;
use dom::bindings::inheritance::Castable;
use dom::bindings::js::{JS, Root};
use dom::bindings::reflector::{Reflector, reflect_dom_object};
use dom::bindings::str::DOMString;
use dom::element::{Element, StylePriority};
use dom::node::{Node, window_from_node};
use dom::window::Window;
use std::ascii::AsciiExt;
use std::cell::Ref;
use std::slice;
use string_cache::Atom;
use style::parser::ParserContextExtraData;
use style::properties::{PropertyDeclaration, Shorthand};
use style::properties::{is_supported_property, parse_one_declaration};
use style::selector_impl::PseudoElement;

// http://dev.w3.org/csswg/cssom/#the-cssstyledeclaration-interface
#[dom_struct]
pub struct CSSStyleDeclaration {
    reflector_: Reflector,
    owner: JS<Element>,
    readonly: bool,
    pseudo: Option<PseudoElement>,
}

#[derive(PartialEq, HeapSizeOf)]
pub enum CSSModificationAccess {
    ReadWrite,
    Readonly,
}

macro_rules! css_properties(
    ( $([$getter:ident, $setter:ident, $cssprop:expr]),* ) => (
        $(
            fn $getter(&self) -> DOMString {
                self.GetPropertyValue(DOMString::from($cssprop))
            }
            fn $setter(&self, value: DOMString) -> ErrorResult {
                self.SetPropertyValue(DOMString::from($cssprop), value)
            }
        )*
    );
);

impl CSSStyleDeclaration {
    pub fn new_inherited(owner: &Element,
                         pseudo: Option<PseudoElement>,
                         modification_access: CSSModificationAccess)
                         -> CSSStyleDeclaration {
        CSSStyleDeclaration {
            reflector_: Reflector::new(),
            owner: JS::from_ref(owner),
            readonly: modification_access == CSSModificationAccess::Readonly,
            pseudo: pseudo,
        }
    }

    pub fn new(global: &Window,
               owner: &Element,
               pseudo: Option<PseudoElement>,
               modification_access: CSSModificationAccess)
               -> Root<CSSStyleDeclaration> {
        reflect_dom_object(box CSSStyleDeclaration::new_inherited(owner,
                                                                  pseudo,
                                                                  modification_access),
                           GlobalRef::Window(global),
                           CSSStyleDeclarationBinding::Wrap)
    }

    fn get_computed_style(&self, property: &Atom) -> Option<DOMString> {
        let node = self.owner.upcast::<Node>();
        if !node.is_in_doc() {
            // TODO: Node should be matched against the style rules of this window.
            // Firefox is currently the only browser to implement this.
            return None;
        }
        let addr = node.to_trusted_node_address();
        window_from_node(&*self.owner).resolved_style_query(addr, self.pseudo.clone(), property)
    }
}

impl CSSStyleDeclarationMethods for CSSStyleDeclaration {
    // https://dev.w3.org/csswg/cssom/#dom-cssstyledeclaration-length
    fn Length(&self) -> u32 {
        let elem = self.owner.upcast::<Element>();
        let len = match *elem.style_attribute().borrow() {
            Some(ref declarations) => declarations.normal.len() + declarations.important.len(),
            None => 0,
        };
        len as u32
    }

    // https://dev.w3.org/csswg/cssom/#dom-cssstyledeclaration-item
    fn Item(&self, index: u32) -> DOMString {
        let index = index as usize;
        let elem = self.owner.upcast::<Element>();
        let style_attribute = elem.style_attribute().borrow();
        let result = style_attribute.as_ref().and_then(|declarations| {
            if index > declarations.normal.len() {
                declarations.important
                            .get(index - declarations.normal.len())
                            .map(|decl| format!("{:?} !important", decl))
            } else {
                declarations.normal
                            .get(index)
                            .map(|decl| format!("{:?}", decl))
            }
        });

        result.map_or(DOMString::new(), DOMString::from)
    }

    // https://dev.w3.org/csswg/cssom/#dom-cssstyledeclaration-getpropertyvalue
    fn GetPropertyValue(&self, mut property: DOMString) -> DOMString {
        let owner = &self.owner;

        // Step 1
        property.make_ascii_lowercase();
        let property = Atom::from(property);

        if self.readonly {
            // Readonly style declarations are used for getComputedStyle.
            return self.get_computed_style(&property).unwrap_or(DOMString::new());
        }

        // Step 2
        if let Some(shorthand) = Shorthand::from_name(&property) {
            // Step 2.1
            let mut list = vec![];

            // Step 2.2
            for longhand in shorthand.longhands() {
                // Step 2.2.1
                let declaration = owner.get_inline_style_declaration(&Atom::from(*longhand));

                // Step 2.2.2 & 2.2.3
                match declaration {
                    Some(declaration) => list.push(declaration),
                    None => return DOMString::new(),
                }
            }

            // Step 2.3
            // Work around closures not being Clone
            #[derive(Clone)]
            struct Map<'a, 'b: 'a>(slice::Iter<'a, Ref<'b, PropertyDeclaration>>);
            impl<'a, 'b> Iterator for Map<'a, 'b> {
                type Item = &'a PropertyDeclaration;
                fn next(&mut self) -> Option<Self::Item> {
                    self.0.next().map(|r| &**r)
                }
            }

            // TODO: important is hardcoded to false because method does not implement it yet
            let serialized_value = shorthand.serialize_shorthand_value_to_string(Map(list.iter()), false);
            return DOMString::from(serialized_value);
        }

        // Step 3 & 4
        match owner.get_inline_style_declaration(&property) {
            Some(declaration) => DOMString::from(declaration.value()),
            None => DOMString::new(),
        }
    }

    // https://dev.w3.org/csswg/cssom/#dom-cssstyledeclaration-getpropertypriority
    fn GetPropertyPriority(&self, mut property: DOMString) -> DOMString {
        // Step 1
        property.make_ascii_lowercase();
        let property = Atom::from(property);

        // Step 2
        if let Some(shorthand) = Shorthand::from_name(&property) {
            // Step 2.1 & 2.2 & 2.3
            if shorthand.longhands().iter()
                                    .map(|&longhand| self.GetPropertyPriority(DOMString::from(longhand)))
                                    .all(|priority| priority == "important") {

                return DOMString::from("important");
            }
        // Step 3
        } else {
            if self.owner.get_important_inline_style_declaration(&property).is_some() {
                return DOMString::from("important");
            }
        }

        // Step 4
        DOMString::new()
    }

    // https://dev.w3.org/csswg/cssom/#dom-cssstyledeclaration-setproperty
    fn SetProperty(&self,
                   mut property: DOMString,
                   value: DOMString,
                   priority: DOMString)
                   -> ErrorResult {
        // Step 1
        if self.readonly {
            return Err(Error::NoModificationAllowed);
        }

        // Step 2
        property.make_ascii_lowercase();

        // Step 3
        if !is_supported_property(&property) {
            return Ok(());
        }

        // Step 4
        if value.is_empty() {
            return self.RemoveProperty(property).map(|_| ());
        }

        // Step 5
        let priority = match &*priority {
            "" => StylePriority::Normal,
            p if p.eq_ignore_ascii_case("important") => StylePriority::Important,
            _ => return Ok(()),
        };

        // Step 6
        let window = window_from_node(&*self.owner);
        let declarations =
            parse_one_declaration(&property, &value, &window.get_url(), window.css_error_reporter(),
                                  ParserContextExtraData::default());

        // Step 7
        let declarations = if let Ok(declarations) = declarations {
            declarations
        } else {
            return Ok(());
        };

        let element = self.owner.upcast::<Element>();

        // Step 8
        // Step 9
        element.update_inline_style(declarations, priority);

        Ok(())
    }

    // https://dev.w3.org/csswg/cssom/#dom-cssstyledeclaration-setpropertypriority
    fn SetPropertyPriority(&self, property: DOMString, priority: DOMString) -> ErrorResult {
        // Step 1
        if self.readonly {
            return Err(Error::NoModificationAllowed);
        }

        // Step 2 & 3
        if !is_supported_property(&property) {
            return Ok(());
        }

        // Step 4
        let priority = match &*priority {
            "" => StylePriority::Normal,
            p if p.eq_ignore_ascii_case("important") => StylePriority::Important,
            _ => return Ok(()),
        };

        let element = self.owner.upcast::<Element>();

        // Step 5 & 6
        match Shorthand::from_name(&property) {
            Some(shorthand) => {
                element.set_inline_style_property_priority(shorthand.longhands(), priority)
            }
            None => element.set_inline_style_property_priority(&[&*property], priority),
        }

        Ok(())
    }

    // https://dev.w3.org/csswg/cssom/#dom-cssstyledeclaration-setpropertyvalue
    fn SetPropertyValue(&self, property: DOMString, value: DOMString) -> ErrorResult {
        self.SetProperty(property, value, DOMString::new())
    }

    // https://dev.w3.org/csswg/cssom/#dom-cssstyledeclaration-removeproperty
    fn RemoveProperty(&self, mut property: DOMString) -> Fallible<DOMString> {
        // Step 1
        if self.readonly {
            return Err(Error::NoModificationAllowed);
        }

        // Step 2
        property.make_ascii_lowercase();

        // Step 3
        let value = self.GetPropertyValue(property.clone());

        let elem = self.owner.upcast::<Element>();

        match Shorthand::from_name(&property) {
            // Step 4
            Some(shorthand) => {
                for longhand in shorthand.longhands() {
                    elem.remove_inline_style_property(longhand)
                }
            }
            // Step 5
            None => elem.remove_inline_style_property(&property),
        }

        // Step 6
        Ok(value)
    }

    // https://dev.w3.org/csswg/cssom/#dom-cssstyledeclaration-cssfloat
    fn CssFloat(&self) -> DOMString {
        self.GetPropertyValue(DOMString::from("float"))
    }

    // https://dev.w3.org/csswg/cssom/#dom-cssstyledeclaration-cssfloat
    fn SetCssFloat(&self, value: DOMString) -> ErrorResult {
        self.SetPropertyValue(DOMString::from("float"), value)
    }

    // https://dev.w3.org/csswg/cssom/#the-cssstyledeclaration-interface
    fn IndexedGetter(&self, index: u32, found: &mut bool) -> DOMString {
        let rval = self.Item(index);
        *found = index < self.Length();
        rval
    }

    // https://drafts.csswg.org/cssom/#cssstyledeclaration
    css_properties_accessors!(css_properties);
}
<commit_msg>Restoring node dirty calls after properties are set to trigger mutations<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 dom::bindings::codegen::Bindings::CSSStyleDeclarationBinding::{self, CSSStyleDeclarationMethods};
use dom::bindings::error::{Error, ErrorResult, Fallible};
use dom::bindings::global::GlobalRef;
use dom::bindings::inheritance::Castable;
use dom::bindings::js::{JS, Root};
use dom::bindings::reflector::{Reflector, reflect_dom_object};
use dom::bindings::str::DOMString;
use dom::element::{Element, StylePriority};
use dom::node::{Node, NodeDamage, window_from_node};
use dom::window::Window;
use std::ascii::AsciiExt;
use std::cell::Ref;
use std::slice;
use string_cache::Atom;
use style::parser::ParserContextExtraData;
use style::properties::{PropertyDeclaration, Shorthand};
use style::properties::{is_supported_property, parse_one_declaration};
use style::selector_impl::PseudoElement;

// http://dev.w3.org/csswg/cssom/#the-cssstyledeclaration-interface
#[dom_struct]
pub struct CSSStyleDeclaration {
    reflector_: Reflector,
    owner: JS<Element>,
    readonly: bool,
    pseudo: Option<PseudoElement>,
}

#[derive(PartialEq, HeapSizeOf)]
pub enum CSSModificationAccess {
    ReadWrite,
    Readonly,
}

macro_rules! css_properties(
    ( $([$getter:ident, $setter:ident, $cssprop:expr]),* ) => (
        $(
            fn $getter(&self) -> DOMString {
                self.GetPropertyValue(DOMString::from($cssprop))
            }
            fn $setter(&self, value: DOMString) -> ErrorResult {
                self.SetPropertyValue(DOMString::from($cssprop), value)
            }
        )*
    );
);

impl CSSStyleDeclaration {
    pub fn new_inherited(owner: &Element,
                         pseudo: Option<PseudoElement>,
                         modification_access: CSSModificationAccess)
                         -> CSSStyleDeclaration {
        CSSStyleDeclaration {
            reflector_: Reflector::new(),
            owner: JS::from_ref(owner),
            readonly: modification_access == CSSModificationAccess::Readonly,
            pseudo: pseudo,
        }
    }

    pub fn new(global: &Window,
               owner: &Element,
               pseudo: Option<PseudoElement>,
               modification_access: CSSModificationAccess)
               -> Root<CSSStyleDeclaration> {
        reflect_dom_object(box CSSStyleDeclaration::new_inherited(owner,
                                                                  pseudo,
                                                                  modification_access),
                           GlobalRef::Window(global),
                           CSSStyleDeclarationBinding::Wrap)
    }

    fn get_computed_style(&self, property: &Atom) -> Option<DOMString> {
        let node = self.owner.upcast::<Node>();
        if !node.is_in_doc() {
            // TODO: Node should be matched against the style rules of this window.
            // Firefox is currently the only browser to implement this.
            return None;
        }
        let addr = node.to_trusted_node_address();
        window_from_node(&*self.owner).resolved_style_query(addr, self.pseudo.clone(), property)
    }
}

impl CSSStyleDeclarationMethods for CSSStyleDeclaration {
    // https://dev.w3.org/csswg/cssom/#dom-cssstyledeclaration-length
    fn Length(&self) -> u32 {
        let elem = self.owner.upcast::<Element>();
        let len = match *elem.style_attribute().borrow() {
            Some(ref declarations) => declarations.normal.len() + declarations.important.len(),
            None => 0,
        };
        len as u32
    }

    // https://dev.w3.org/csswg/cssom/#dom-cssstyledeclaration-item
    fn Item(&self, index: u32) -> DOMString {
        let index = index as usize;
        let elem = self.owner.upcast::<Element>();
        let style_attribute = elem.style_attribute().borrow();
        let result = style_attribute.as_ref().and_then(|declarations| {
            if index > declarations.normal.len() {
                declarations.important
                            .get(index - declarations.normal.len())
                            .map(|decl| format!("{:?} !important", decl))
            } else {
                declarations.normal
                            .get(index)
                            .map(|decl| format!("{:?}", decl))
            }
        });

        result.map_or(DOMString::new(), DOMString::from)
    }

    // https://dev.w3.org/csswg/cssom/#dom-cssstyledeclaration-getpropertyvalue
    fn GetPropertyValue(&self, mut property: DOMString) -> DOMString {
        let owner = &self.owner;

        // Step 1
        property.make_ascii_lowercase();
        let property = Atom::from(property);

        if self.readonly {
            // Readonly style declarations are used for getComputedStyle.
            return self.get_computed_style(&property).unwrap_or(DOMString::new());
        }

        // Step 2
        if let Some(shorthand) = Shorthand::from_name(&property) {
            // Step 2.1
            let mut list = vec![];

            // Step 2.2
            for longhand in shorthand.longhands() {
                // Step 2.2.1
                let declaration = owner.get_inline_style_declaration(&Atom::from(*longhand));

                // Step 2.2.2 & 2.2.3
                match declaration {
                    Some(declaration) => list.push(declaration),
                    None => return DOMString::new(),
                }
            }

            // Step 2.3
            // Work around closures not being Clone
            #[derive(Clone)]
            struct Map<'a, 'b: 'a>(slice::Iter<'a, Ref<'b, PropertyDeclaration>>);
            impl<'a, 'b> Iterator for Map<'a, 'b> {
                type Item = &'a PropertyDeclaration;
                fn next(&mut self) -> Option<Self::Item> {
                    self.0.next().map(|r| &**r)
                }
            }

            // TODO: important is hardcoded to false because method does not implement it yet
            let serialized_value = shorthand.serialize_shorthand_value_to_string(Map(list.iter()), false);
            return DOMString::from(serialized_value);
        }

        // Step 3 & 4
        match owner.get_inline_style_declaration(&property) {
            Some(declaration) => DOMString::from(declaration.value()),
            None => DOMString::new(),
        }
    }

    // https://dev.w3.org/csswg/cssom/#dom-cssstyledeclaration-getpropertypriority
    fn GetPropertyPriority(&self, mut property: DOMString) -> DOMString {
        // Step 1
        property.make_ascii_lowercase();
        let property = Atom::from(property);

        // Step 2
        if let Some(shorthand) = Shorthand::from_name(&property) {
            // Step 2.1 & 2.2 & 2.3
            if shorthand.longhands().iter()
                                    .map(|&longhand| self.GetPropertyPriority(DOMString::from(longhand)))
                                    .all(|priority| priority == "important") {

                return DOMString::from("important");
            }
        // Step 3
        } else {
            if self.owner.get_important_inline_style_declaration(&property).is_some() {
                return DOMString::from("important");
            }
        }

        // Step 4
        DOMString::new()
    }

    // https://dev.w3.org/csswg/cssom/#dom-cssstyledeclaration-setproperty
    fn SetProperty(&self,
                   mut property: DOMString,
                   value: DOMString,
                   priority: DOMString)
                   -> ErrorResult {
        // Step 1
        if self.readonly {
            return Err(Error::NoModificationAllowed);
        }

        // Step 2
        property.make_ascii_lowercase();

        // Step 3
        if !is_supported_property(&property) {
            return Ok(());
        }

        // Step 4
        if value.is_empty() {
            return self.RemoveProperty(property).map(|_| ());
        }

        // Step 5
        let priority = match &*priority {
            "" => StylePriority::Normal,
            p if p.eq_ignore_ascii_case("important") => StylePriority::Important,
            _ => return Ok(()),
        };

        // Step 6
        let window = window_from_node(&*self.owner);
        let declarations =
            parse_one_declaration(&property, &value, &window.get_url(), window.css_error_reporter(),
                                  ParserContextExtraData::default());

        // Step 7
        let declarations = if let Ok(declarations) = declarations {
            declarations
        } else {
            return Ok(());
        };

        let element = self.owner.upcast::<Element>();

        // Step 8
        // Step 9
        element.update_inline_style(declarations, priority);

        let node = element.upcast::<Node>();
        node.dirty(NodeDamage::NodeStyleDamaged);
        Ok(())
    }

    // https://dev.w3.org/csswg/cssom/#dom-cssstyledeclaration-setpropertypriority
    fn SetPropertyPriority(&self, property: DOMString, priority: DOMString) -> ErrorResult {
        // Step 1
        if self.readonly {
            return Err(Error::NoModificationAllowed);
        }

        // Step 2 & 3
        if !is_supported_property(&property) {
            return Ok(());
        }

        // Step 4
        let priority = match &*priority {
            "" => StylePriority::Normal,
            p if p.eq_ignore_ascii_case("important") => StylePriority::Important,
            _ => return Ok(()),
        };

        let element = self.owner.upcast::<Element>();

        // Step 5 & 6
        match Shorthand::from_name(&property) {
            Some(shorthand) => {
                element.set_inline_style_property_priority(shorthand.longhands(), priority)
            }
            None => element.set_inline_style_property_priority(&[&*property], priority),
        }

        let node = element.upcast::<Node>();
        node.dirty(NodeDamage::NodeStyleDamaged);
        Ok(())
    }

    // https://dev.w3.org/csswg/cssom/#dom-cssstyledeclaration-setpropertyvalue
    fn SetPropertyValue(&self, property: DOMString, value: DOMString) -> ErrorResult {
        self.SetProperty(property, value, DOMString::new())
    }

    // https://dev.w3.org/csswg/cssom/#dom-cssstyledeclaration-removeproperty
    fn RemoveProperty(&self, mut property: DOMString) -> Fallible<DOMString> {
        // Step 1
        if self.readonly {
            return Err(Error::NoModificationAllowed);
        }

        // Step 2
        property.make_ascii_lowercase();

        // Step 3
        let value = self.GetPropertyValue(property.clone());

        let element = self.owner.upcast::<Element>();

        match Shorthand::from_name(&property) {
            // Step 4
            Some(shorthand) => {
                for longhand in shorthand.longhands() {
                    element.remove_inline_style_property(longhand)
                }
            }
            // Step 5
            None => element.remove_inline_style_property(&property),
        }

        let node = element.upcast::<Node>();
        node.dirty(NodeDamage::NodeStyleDamaged);

        // Step 6
        Ok(value)
    }

    // https://dev.w3.org/csswg/cssom/#dom-cssstyledeclaration-cssfloat
    fn CssFloat(&self) -> DOMString {
        self.GetPropertyValue(DOMString::from("float"))
    }

    // https://dev.w3.org/csswg/cssom/#dom-cssstyledeclaration-cssfloat
    fn SetCssFloat(&self, value: DOMString) -> ErrorResult {
        self.SetPropertyValue(DOMString::from("float"), value)
    }

    // https://dev.w3.org/csswg/cssom/#the-cssstyledeclaration-interface
    fn IndexedGetter(&self, index: u32, found: &mut bool) -> DOMString {
        let rval = self.Item(index);
        *found = index < self.Length();
        rval
    }

    // https://drafts.csswg.org/cssom/#cssstyledeclaration
    css_properties_accessors!(css_properties);
}
<|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/. */

//! Specified types for CSS values related to effects.

use crate::parser::{Parse, ParserContext};
use crate::values::computed::effects::BoxShadow as ComputedBoxShadow;
use crate::values::computed::effects::SimpleShadow as ComputedSimpleShadow;
use crate::values::computed::NonNegativeNumber as ComputedNonNegativeNumber;
use crate::values::computed::{Context, ToComputedValue};
use crate::values::generics::effects::BoxShadow as GenericBoxShadow;
use crate::values::generics::effects::Filter as GenericFilter;
use crate::values::generics::effects::SimpleShadow as GenericSimpleShadow;
use crate::values::generics::NonNegative;
use crate::values::specified::color::Color;
use crate::values::specified::length::{Length, NonNegativeLength};
#[cfg(feature = "gecko")]
use crate::values::specified::url::SpecifiedUrl;
use crate::values::specified::{Angle, NumberOrPercentage};
#[cfg(not(feature = "gecko"))]
use crate::values::Impossible;
use crate::Zero;
use cssparser::{self, BasicParseErrorKind, Parser, Token};
use style_traits::{ParseError, StyleParseErrorKind, ValueParseErrorKind};

/// A specified value for a single shadow of the `box-shadow` property.
pub type BoxShadow =
    GenericBoxShadow<Option<Color>, Length, Option<NonNegativeLength>, Option<Length>>;

/// A specified value for a single `filter`.
#[cfg(feature = "gecko")]
pub type Filter = GenericFilter<Angle, Factor, NonNegativeLength, SimpleShadow, SpecifiedUrl>;

/// A specified value for a single `filter`.
#[cfg(not(feature = "gecko"))]
pub type Filter = GenericFilter<Angle, Factor, NonNegativeLength, Impossible, Impossible>;

/// A value for the `<factor>` parts in `Filter`.
#[derive(Clone, Debug, MallocSizeOf, PartialEq, SpecifiedValueInfo, ToCss, ToShmem)]
pub struct Factor(NumberOrPercentage);

impl Factor {
    /// Parse this factor but clamp to one if the value is over 100%.
    #[inline]
    pub fn parse_with_clamping_to_one<'i, 't>(
        context: &ParserContext,
        input: &mut Parser<'i, 't>,
    ) -> Result<Self, ParseError<'i>> {
        Factor::parse(context, input).map(|v| v.clamp_to_one())
    }

    /// Clamp the value to 1 if the value is over 100%.
    #[inline]
    fn clamp_to_one(self) -> Self {
        match self.0 {
            NumberOrPercentage::Percentage(percent) => {
                Factor(NumberOrPercentage::Percentage(percent.clamp_to_hundred()))
            },
            NumberOrPercentage::Number(number) => {
                Factor(NumberOrPercentage::Number(number.clamp_to_one()))
            },
        }
    }
}

impl Parse for Factor {
    #[inline]
    fn parse<'i, 't>(
        context: &ParserContext,
        input: &mut Parser<'i, 't>,
    ) -> Result<Self, ParseError<'i>> {
        NumberOrPercentage::parse_non_negative(context, input).map(Factor)
    }
}

impl ToComputedValue for Factor {
    type ComputedValue = ComputedNonNegativeNumber;

    #[inline]
    fn to_computed_value(&self, context: &Context) -> Self::ComputedValue {
        use crate::values::computed::NumberOrPercentage;
        match self.0.to_computed_value(context) {
            NumberOrPercentage::Number(n) => n.into(),
            NumberOrPercentage::Percentage(p) => p.0.into(),
        }
    }

    #[inline]
    fn from_computed_value(computed: &Self::ComputedValue) -> Self {
        Factor(NumberOrPercentage::Number(
            ToComputedValue::from_computed_value(&computed.0),
        ))
    }
}

/// A specified value for the `drop-shadow()` filter.
pub type SimpleShadow = GenericSimpleShadow<Option<Color>, Length, Option<NonNegativeLength>>;

impl Parse for BoxShadow {
    fn parse<'i, 't>(
        context: &ParserContext,
        input: &mut Parser<'i, 't>,
    ) -> Result<Self, ParseError<'i>> {
        let mut lengths = None;
        let mut color = None;
        let mut inset = false;

        loop {
            if !inset {
                if input
                    .try(|input| input.expect_ident_matching("inset"))
                    .is_ok()
                {
                    inset = true;
                    continue;
                }
            }
            if lengths.is_none() {
                let value = input.try::<_, _, ParseError>(|i| {
                    let horizontal = Length::parse(context, i)?;
                    let vertical = Length::parse(context, i)?;
                    let (blur, spread) = match i
                        .try::<_, _, ParseError>(|i| Length::parse_non_negative(context, i))
                    {
                        Ok(blur) => {
                            let spread = i.try(|i| Length::parse(context, i)).ok();
                            (Some(blur.into()), spread)
                        },
                        Err(_) => (None, None),
                    };
                    Ok((horizontal, vertical, blur, spread))
                });
                if let Ok(value) = value {
                    lengths = Some(value);
                    continue;
                }
            }
            if color.is_none() {
                if let Ok(value) = input.try(|i| Color::parse(context, i)) {
                    color = Some(value);
                    continue;
                }
            }
            break;
        }

        let lengths =
            lengths.ok_or(input.new_custom_error(StyleParseErrorKind::UnspecifiedError))?;
        Ok(BoxShadow {
            base: SimpleShadow {
                color: color,
                horizontal: lengths.0,
                vertical: lengths.1,
                blur: lengths.2,
            },
            spread: lengths.3,
            inset: inset,
        })
    }
}

impl ToComputedValue for BoxShadow {
    type ComputedValue = ComputedBoxShadow;

    #[inline]
    fn to_computed_value(&self, context: &Context) -> Self::ComputedValue {
        ComputedBoxShadow {
            base: self.base.to_computed_value(context),
            spread: self
                .spread
                .as_ref()
                .unwrap_or(&Length::zero())
                .to_computed_value(context),
            inset: self.inset,
        }
    }

    #[inline]
    fn from_computed_value(computed: &ComputedBoxShadow) -> Self {
        BoxShadow {
            base: ToComputedValue::from_computed_value(&computed.base),
            spread: Some(ToComputedValue::from_computed_value(&computed.spread)),
            inset: computed.inset,
        }
    }
}

impl Parse for Filter {
    #[inline]
    fn parse<'i, 't>(
        context: &ParserContext,
        input: &mut Parser<'i, 't>,
    ) -> Result<Self, ParseError<'i>> {
        #[cfg(feature = "gecko")]
        {
            if let Ok(url) = input.try(|i| SpecifiedUrl::parse(context, i)) {
                return Ok(GenericFilter::Url(url));
            }
        }
        let location = input.current_source_location();
        let function = match input.expect_function() {
            Ok(f) => f.clone(),
            Err(cssparser::BasicParseError {
                kind: BasicParseErrorKind::UnexpectedToken(t),
                location,
            }) => return Err(location.new_custom_error(ValueParseErrorKind::InvalidFilter(t))),
            Err(e) => return Err(e.into()),
        };
        input.parse_nested_block(|i| {
            match_ignore_ascii_case! { &*function,
                "blur" => Ok(GenericFilter::Blur((Length::parse_non_negative(context, i)?).into())),
                "brightness" => Ok(GenericFilter::Brightness(Factor::parse(context, i)?)),
                "contrast" => Ok(GenericFilter::Contrast(Factor::parse(context, i)?)),
                "grayscale" => {
                    // Values of amount over 100% are allowed but UAs must clamp the values to 1.
                    // https://drafts.fxtf.org/filter-effects/#funcdef-filter-grayscale
                    Ok(GenericFilter::Grayscale(Factor::parse_with_clamping_to_one(context, i)?))
                },
                "hue-rotate" => {
                    // We allow unitless zero here, see:
                    // https://github.com/w3c/fxtf-drafts/issues/228
                    Ok(GenericFilter::HueRotate(Angle::parse_with_unitless(context, i)?))
                },
                "invert" => {
                    // Values of amount over 100% are allowed but UAs must clamp the values to 1.
                    // https://drafts.fxtf.org/filter-effects/#funcdef-filter-invert
                    Ok(GenericFilter::Invert(Factor::parse_with_clamping_to_one(context, i)?))
                },
                "opacity" => {
                    // Values of amount over 100% are allowed but UAs must clamp the values to 1.
                    // https://drafts.fxtf.org/filter-effects/#funcdef-filter-opacity
                    Ok(GenericFilter::Opacity(Factor::parse_with_clamping_to_one(context, i)?))
                },
                "saturate" => Ok(GenericFilter::Saturate(Factor::parse(context, i)?)),
                "sepia" => {
                    // Values of amount over 100% are allowed but UAs must clamp the values to 1.
                    // https://drafts.fxtf.org/filter-effects/#funcdef-filter-sepia
                    Ok(GenericFilter::Sepia(Factor::parse_with_clamping_to_one(context, i)?))
                },
                "drop-shadow" => Ok(GenericFilter::DropShadow(Parse::parse(context, i)?)),
                _ => Err(location.new_custom_error(
                    ValueParseErrorKind::InvalidFilter(Token::Function(function.clone()))
                )),
            }
        })
    }
}

impl Parse for SimpleShadow {
    #[inline]
    fn parse<'i, 't>(
        context: &ParserContext,
        input: &mut Parser<'i, 't>,
    ) -> Result<Self, ParseError<'i>> {
        let color = input.try(|i| Color::parse(context, i)).ok();
        let horizontal = Length::parse(context, input)?;
        let vertical = Length::parse(context, input)?;
        let blur = input.try(|i| Length::parse_non_negative(context, i)).ok();
        let blur = blur.map(NonNegative::<Length>);
        let color = color.or_else(|| input.try(|i| Color::parse(context, i)).ok());

        Ok(SimpleShadow {
            color,
            horizontal,
            vertical,
            blur,
        })
    }
}

impl ToComputedValue for SimpleShadow {
    type ComputedValue = ComputedSimpleShadow;

    #[inline]
    fn to_computed_value(&self, context: &Context) -> Self::ComputedValue {
        ComputedSimpleShadow {
            color: self
                .color
                .as_ref()
                .unwrap_or(&Color::currentcolor())
                .to_computed_value(context),
            horizontal: self.horizontal.to_computed_value(context),
            vertical: self.vertical.to_computed_value(context),
            blur: self
                .blur
                .as_ref()
                .unwrap_or(&NonNegativeLength::zero())
                .to_computed_value(context),
        }
    }

    #[inline]
    fn from_computed_value(computed: &Self::ComputedValue) -> Self {
        SimpleShadow {
            color: Some(ToComputedValue::from_computed_value(&computed.color)),
            horizontal: ToComputedValue::from_computed_value(&computed.horizontal),
            vertical: ToComputedValue::from_computed_value(&computed.vertical),
            blur: Some(ToComputedValue::from_computed_value(&computed.blur)),
        }
    }
}
<commit_msg>style: Accept empty argument for some filters.<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/. */

//! Specified types for CSS values related to effects.

use crate::parser::{Parse, ParserContext};
use crate::values::computed::effects::BoxShadow as ComputedBoxShadow;
use crate::values::computed::effects::SimpleShadow as ComputedSimpleShadow;
use crate::values::computed::NonNegativeNumber as ComputedNonNegativeNumber;
use crate::values::computed::{Context, ToComputedValue};
use crate::values::generics::effects::BoxShadow as GenericBoxShadow;
use crate::values::generics::effects::Filter as GenericFilter;
use crate::values::generics::effects::SimpleShadow as GenericSimpleShadow;
use crate::values::generics::NonNegative;
use crate::values::specified::color::Color;
use crate::values::specified::length::{Length, NonNegativeLength};
#[cfg(feature = "gecko")]
use crate::values::specified::url::SpecifiedUrl;
use crate::values::specified::{Angle, Number, NumberOrPercentage};
#[cfg(not(feature = "gecko"))]
use crate::values::Impossible;
use crate::Zero;
use cssparser::{self, BasicParseErrorKind, Parser, Token};
use style_traits::{ParseError, StyleParseErrorKind, ValueParseErrorKind};

/// A specified value for a single shadow of the `box-shadow` property.
pub type BoxShadow =
    GenericBoxShadow<Option<Color>, Length, Option<NonNegativeLength>, Option<Length>>;

/// A specified value for a single `filter`.
#[cfg(feature = "gecko")]
pub type Filter = GenericFilter<Angle, Factor, NonNegativeLength, SimpleShadow, SpecifiedUrl>;

/// A specified value for a single `filter`.
#[cfg(not(feature = "gecko"))]
pub type Filter = GenericFilter<Angle, Factor, NonNegativeLength, Impossible, Impossible>;

/// A value for the `<factor>` parts in `Filter`.
#[derive(Clone, Debug, MallocSizeOf, PartialEq, SpecifiedValueInfo, ToCss, ToShmem)]
pub struct Factor(NumberOrPercentage);

impl Factor {
    /// Parse this factor but clamp to one if the value is over 100%.
    #[inline]
    pub fn parse_with_clamping_to_one<'i, 't>(
        context: &ParserContext,
        input: &mut Parser<'i, 't>,
    ) -> Result<Self, ParseError<'i>> {
        Factor::parse(context, input).map(|v| v.clamp_to_one())
    }

    /// Clamp the value to 1 if the value is over 100%.
    #[inline]
    fn clamp_to_one(self) -> Self {
        match self.0 {
            NumberOrPercentage::Percentage(percent) => {
                Factor(NumberOrPercentage::Percentage(percent.clamp_to_hundred()))
            },
            NumberOrPercentage::Number(number) => {
                Factor(NumberOrPercentage::Number(number.clamp_to_one()))
            },
        }
    }

    fn one() -> Self {
        Factor(NumberOrPercentage::Number(Number::new(1.0)))
    }
}

impl Parse for Factor {
    #[inline]
    fn parse<'i, 't>(
        context: &ParserContext,
        input: &mut Parser<'i, 't>,
    ) -> Result<Self, ParseError<'i>> {
        NumberOrPercentage::parse_non_negative(context, input).map(Factor)
    }
}

impl ToComputedValue for Factor {
    type ComputedValue = ComputedNonNegativeNumber;

    #[inline]
    fn to_computed_value(&self, context: &Context) -> Self::ComputedValue {
        use crate::values::computed::NumberOrPercentage;
        match self.0.to_computed_value(context) {
            NumberOrPercentage::Number(n) => n.into(),
            NumberOrPercentage::Percentage(p) => p.0.into(),
        }
    }

    #[inline]
    fn from_computed_value(computed: &Self::ComputedValue) -> Self {
        Factor(NumberOrPercentage::Number(
            ToComputedValue::from_computed_value(&computed.0),
        ))
    }
}

/// A specified value for the `drop-shadow()` filter.
pub type SimpleShadow = GenericSimpleShadow<Option<Color>, Length, Option<NonNegativeLength>>;

impl Parse for BoxShadow {
    fn parse<'i, 't>(
        context: &ParserContext,
        input: &mut Parser<'i, 't>,
    ) -> Result<Self, ParseError<'i>> {
        let mut lengths = None;
        let mut color = None;
        let mut inset = false;

        loop {
            if !inset {
                if input
                    .try(|input| input.expect_ident_matching("inset"))
                    .is_ok()
                {
                    inset = true;
                    continue;
                }
            }
            if lengths.is_none() {
                let value = input.try::<_, _, ParseError>(|i| {
                    let horizontal = Length::parse(context, i)?;
                    let vertical = Length::parse(context, i)?;
                    let (blur, spread) = match i
                        .try::<_, _, ParseError>(|i| Length::parse_non_negative(context, i))
                    {
                        Ok(blur) => {
                            let spread = i.try(|i| Length::parse(context, i)).ok();
                            (Some(blur.into()), spread)
                        },
                        Err(_) => (None, None),
                    };
                    Ok((horizontal, vertical, blur, spread))
                });
                if let Ok(value) = value {
                    lengths = Some(value);
                    continue;
                }
            }
            if color.is_none() {
                if let Ok(value) = input.try(|i| Color::parse(context, i)) {
                    color = Some(value);
                    continue;
                }
            }
            break;
        }

        let lengths =
            lengths.ok_or(input.new_custom_error(StyleParseErrorKind::UnspecifiedError))?;
        Ok(BoxShadow {
            base: SimpleShadow {
                color: color,
                horizontal: lengths.0,
                vertical: lengths.1,
                blur: lengths.2,
            },
            spread: lengths.3,
            inset: inset,
        })
    }
}

impl ToComputedValue for BoxShadow {
    type ComputedValue = ComputedBoxShadow;

    #[inline]
    fn to_computed_value(&self, context: &Context) -> Self::ComputedValue {
        ComputedBoxShadow {
            base: self.base.to_computed_value(context),
            spread: self
                .spread
                .as_ref()
                .unwrap_or(&Length::zero())
                .to_computed_value(context),
            inset: self.inset,
        }
    }

    #[inline]
    fn from_computed_value(computed: &ComputedBoxShadow) -> Self {
        BoxShadow {
            base: ToComputedValue::from_computed_value(&computed.base),
            spread: Some(ToComputedValue::from_computed_value(&computed.spread)),
            inset: computed.inset,
        }
    }
}

impl Parse for Filter {
    #[inline]
    fn parse<'i, 't>(
        context: &ParserContext,
        input: &mut Parser<'i, 't>,
    ) -> Result<Self, ParseError<'i>> {
        #[cfg(feature = "gecko")]
        {
            if let Ok(url) = input.try(|i| SpecifiedUrl::parse(context, i)) {
                return Ok(GenericFilter::Url(url));
            }
        }
        let location = input.current_source_location();
        let function = match input.expect_function() {
            Ok(f) => f.clone(),
            Err(cssparser::BasicParseError {
                kind: BasicParseErrorKind::UnexpectedToken(t),
                location,
            }) => return Err(location.new_custom_error(ValueParseErrorKind::InvalidFilter(t))),
            Err(e) => return Err(e.into()),
        };
        input.parse_nested_block(|i| {
            match_ignore_ascii_case! { &*function,
                "blur" => Ok(GenericFilter::Blur(
                    i.try(|i| NonNegativeLength::parse(context, i))
                     .unwrap_or(Zero::zero()),
                )),
                "brightness" => Ok(GenericFilter::Brightness(
                    i.try(|i| Factor::parse(context, i))
                     .unwrap_or(Factor::one()),
                )),
                "contrast" => Ok(GenericFilter::Contrast(
                    i.try(|i| Factor::parse(context, i))
                     .unwrap_or(Factor::one()),
                )),
                "grayscale" => {
                    // Values of amount over 100% are allowed but UAs must clamp the values to 1.
                    // https://drafts.fxtf.org/filter-effects/#funcdef-filter-grayscale
                    Ok(GenericFilter::Grayscale(
                        i.try(|i| Factor::parse_with_clamping_to_one(context, i))
                         .unwrap_or(Factor::one()),
                    ))
                },
                "hue-rotate" => {
                    // We allow unitless zero here, see:
                    // https://github.com/w3c/fxtf-drafts/issues/228
                    Ok(GenericFilter::HueRotate(
                        i.try(|i| Angle::parse_with_unitless(context, i))
                         .unwrap_or(Zero::zero()),
                    ))
                },
                "invert" => {
                    // Values of amount over 100% are allowed but UAs must clamp the values to 1.
                    // https://drafts.fxtf.org/filter-effects/#funcdef-filter-invert
                    Ok(GenericFilter::Invert(
                        i.try(|i| Factor::parse_with_clamping_to_one(context, i))
                         .unwrap_or(Factor::one()),
                    ))
                },
                "opacity" => {
                    // Values of amount over 100% are allowed but UAs must clamp the values to 1.
                    // https://drafts.fxtf.org/filter-effects/#funcdef-filter-opacity
                    Ok(GenericFilter::Opacity(
                        i.try(|i| Factor::parse_with_clamping_to_one(context, i))
                         .unwrap_or(Factor::one()),
                    ))
                },
                "saturate" => Ok(GenericFilter::Saturate(
                    i.try(|i| Factor::parse(context, i))
                     .unwrap_or(Factor::one()),
                )),
                "sepia" => {
                    // Values of amount over 100% are allowed but UAs must clamp the values to 1.
                    // https://drafts.fxtf.org/filter-effects/#funcdef-filter-sepia
                    Ok(GenericFilter::Sepia(
                        i.try(|i| Factor::parse_with_clamping_to_one(context, i))
                         .unwrap_or(Factor::one()),
                    ))
                },
                "drop-shadow" => Ok(GenericFilter::DropShadow(Parse::parse(context, i)?)),
                _ => Err(location.new_custom_error(
                    ValueParseErrorKind::InvalidFilter(Token::Function(function.clone()))
                )),
            }
        })
    }
}

impl Parse for SimpleShadow {
    #[inline]
    fn parse<'i, 't>(
        context: &ParserContext,
        input: &mut Parser<'i, 't>,
    ) -> Result<Self, ParseError<'i>> {
        let color = input.try(|i| Color::parse(context, i)).ok();
        let horizontal = Length::parse(context, input)?;
        let vertical = Length::parse(context, input)?;
        let blur = input.try(|i| Length::parse_non_negative(context, i)).ok();
        let blur = blur.map(NonNegative::<Length>);
        let color = color.or_else(|| input.try(|i| Color::parse(context, i)).ok());

        Ok(SimpleShadow {
            color,
            horizontal,
            vertical,
            blur,
        })
    }
}

impl ToComputedValue for SimpleShadow {
    type ComputedValue = ComputedSimpleShadow;

    #[inline]
    fn to_computed_value(&self, context: &Context) -> Self::ComputedValue {
        ComputedSimpleShadow {
            color: self
                .color
                .as_ref()
                .unwrap_or(&Color::currentcolor())
                .to_computed_value(context),
            horizontal: self.horizontal.to_computed_value(context),
            vertical: self.vertical.to_computed_value(context),
            blur: self
                .blur
                .as_ref()
                .unwrap_or(&NonNegativeLength::zero())
                .to_computed_value(context),
        }
    }

    #[inline]
    fn from_computed_value(computed: &Self::ComputedValue) -> Self {
        SimpleShadow {
            color: Some(ToComputedValue::from_computed_value(&computed.color)),
            horizontal: ToComputedValue::from_computed_value(&computed.horizontal),
            vertical: ToComputedValue::from_computed_value(&computed.vertical),
            blur: Some(ToComputedValue::from_computed_value(&computed.blur)),
        }
    }
}
<|endoftext|>


<commit_before>/// Filters operating on array
use std::collections::HashMap;
use std::cmp::Ordering;

use serde_json::value::{Value, to_value};
use context::{ValueRender, get_json_pointer};
use errors::Result;

/// Returns the first value of an array
/// If the array is empty, returns empty string
pub fn first(value: Value, _: HashMap<String, Value>) -> Result<Value> {
    let mut arr = try_get_value!("first", "value", Vec<Value>, value);

    if arr.is_empty() {
        Ok(to_value("").unwrap())
    } else {
        Ok(arr.swap_remove(0))
    }
}

/// Returns the last value of an array
/// If the array is empty, returns empty string
pub fn last(value: Value, _: HashMap<String, Value>) -> Result<Value> {
    let mut arr = try_get_value!("last", "value", Vec<Value>, value);

    Ok(arr.pop().unwrap_or_else(|| to_value("").unwrap()))
}

/// Joins all values in the array by the `sep` argument given
/// If no separator is given, it will use `""` (empty string) as separator
/// If the array is empty, returns empty string
pub fn join(value: Value, args: HashMap<String, Value>) -> Result<Value> {
    let arr = try_get_value!("join", "value", Vec<Value>, value);
    let sep = match args.get("sep") {
        Some(val) => try_get_value!("truncate", "sep", String, val),
        None => "".to_string(),
    };

    // Convert all the values to strings before we join them together.
    let rendered = arr.iter().map(|val| val.render()).collect::<Vec<_>>();
    Ok(to_value(&rendered.join(&sep))?)
}

/// Sorts the array in ascending order.
/// Use the 'attribute' argument to define a field to sort by.
/// Set the 'reverse' argument to sort in descending order.
pub fn sort(value: Value, args: HashMap<String, Value>) -> Result<Value> {
    let mut arr = try_get_value!("sort", "value", Vec<Value>, value);
    let reverse = try_get_value!("sort", "reverse", bool, args.get("reverse").unwrap_or(&false.into()));
    let attribute = try_get_value!("sort", "attribute", String, args.get("attribute").unwrap_or(&"".into()));
    let attribute = get_json_pointer(&attribute);

    arr.sort_unstable_by(|a, b| {
        value_cmp(
            a.pointer(&attribute).unwrap_or(&Value::Null),
            b.pointer(&attribute).unwrap_or(&Value::Null)
        )
    });
    if reverse {
        arr.reverse();
    }

    Ok(to_value(arr)?)
}

fn value_cmp(a: &Value, b: &Value) -> Ordering {
    use Value::*;
    use self::Ordering::*;
    match (a, b) {
        (&Null, &Null) => Equal,
        (&Bool(ref a), &Bool(ref b)) => a.cmp(b),
        (&Number(ref a), &Number(ref b)) => a.as_f64().unwrap().partial_cmp(&b.as_f64().unwrap()).unwrap(),
        (&String(ref a), &String(ref b)) => a.cmp(b),
        (&Array(ref a), &Array(ref b)) => a.len().cmp(&b.len()),
        (&Object(ref a), &Object(ref b)) => a.len().cmp(&b.len()),
        (a, b) => type_of(a).cmp(&type_of(b))
    }
}

fn type_of(val: &Value) -> usize {
    use Value::*;
    match *val {
        Null => 0,
        Bool(_) => 1,
        Number(_) => 2,
        String(_) => 3,
        Array(_) => 4, Object(_) => 5
    }
}

#[cfg(test)]
mod tests {
    use std::collections::HashMap;
    use serde_json::value::{Value, to_value};
    use super::*;

    #[test]
    fn test_first() {
        let result = first(to_value(&vec![1, 2, 3, 4]).unwrap(), HashMap::new());
        assert!(result.is_ok());
        assert_eq!(result.unwrap(), to_value(&1).unwrap());
    }

    #[test]
    fn test_first_empty() {
        let v: Vec<Value> = Vec::new();

        let result = first(to_value(&v).unwrap(), HashMap::new());
        assert!(result.is_ok());
        assert_eq!(result.ok().unwrap(), to_value("").unwrap());
    }

    #[test]
    fn test_last() {
        let result = last(to_value(&vec!["Hello", "World"]).unwrap(), HashMap::new());
        assert!(result.is_ok());
        assert_eq!(result.unwrap(), to_value("World").unwrap());
    }

    #[test]
    fn test_last_empty() {
        let v: Vec<Value> = Vec::new();

        let result = last(to_value(&v).unwrap(), HashMap::new());
        assert!(result.is_ok());
        assert_eq!(result.ok().unwrap(), to_value("").unwrap());
    }

    #[test]
    fn test_join_sep() {
        let mut args = HashMap::new();
        args.insert("sep".to_owned(), to_value(&"==").unwrap());

        let result = join(to_value(&vec!["Cats", "Dogs"]).unwrap(), args);
        assert!(result.is_ok());
        assert_eq!(result.unwrap(), to_value(&"Cats==Dogs").unwrap());
    }

    #[test]
    fn test_join_sep_omitted() {
        let result = join(to_value(&vec![1.2, 3.4]).unwrap(), HashMap::new());
        assert!(result.is_ok());
        assert_eq!(result.unwrap(), to_value(&"1.23.4").unwrap());
    }

    #[test]
    fn test_join_empty() {
        let v: Vec<Value> = Vec::new();
        let mut args = HashMap::new();
        args.insert("sep".to_owned(), to_value(&"==").unwrap());

        let result = join(to_value(&v).unwrap(), args);
        assert!(result.is_ok());
        assert_eq!(result.unwrap(), to_value(&"").unwrap());
    }

    #[test]
    fn test_sort() {
        let v = to_value(vec![3, 1, 2, 5, 4]).unwrap();
        let args = HashMap::new();
        let result = sort(v, args);
        assert!(result.is_ok());
        assert_eq!(result.unwrap(), to_value(vec![1, 2, 3, 4, 5]).unwrap());
    }

    #[test]
    fn test_sort_descending() {
        let v = to_value(vec![3, 1, 2, 5, 4]).unwrap();
        let mut args = HashMap::new();
        args.insert("reverse".to_string(), to_value(true).unwrap());

        let result = sort(v, args);
        assert!(result.is_ok());
        assert_eq!(result.unwrap(), to_value(vec![5, 4, 3, 2, 1]).unwrap());
    }

    #[derive(Serialize)]
    struct Foo {
        a: i32,
        b: i32
    }

    #[test]
    fn test_sort_attribute() {
        let v = to_value(vec![
            Foo {a: 3, b: 5},
            Foo {a: 2, b: 8},
            Foo {a: 4, b: 7},
            Foo {a: 1, b: 6},
        ]).unwrap();
        let mut args = HashMap::new();
        args.insert("attribute".to_string(), to_value(&"a").unwrap());

        let result = sort(v, args);
        assert!(result.is_ok());
        assert_eq!(result.unwrap(), to_value(vec![
            Foo {a: 1, b: 6},
            Foo {a: 2, b: 8},
            Foo {a: 3, b: 5},
            Foo {a: 4, b: 7},
        ]).unwrap());
    }
}
<commit_msg>Remove 'reverse' attribute in sort filter<commit_after>/// Filters operating on array
use std::collections::HashMap;
use std::cmp::Ordering;

use serde_json::value::{Value, to_value};
use context::{ValueRender, get_json_pointer};
use errors::Result;

/// Returns the first value of an array
/// If the array is empty, returns empty string
pub fn first(value: Value, _: HashMap<String, Value>) -> Result<Value> {
    let mut arr = try_get_value!("first", "value", Vec<Value>, value);

    if arr.is_empty() {
        Ok(to_value("").unwrap())
    } else {
        Ok(arr.swap_remove(0))
    }
}

/// Returns the last value of an array
/// If the array is empty, returns empty string
pub fn last(value: Value, _: HashMap<String, Value>) -> Result<Value> {
    let mut arr = try_get_value!("last", "value", Vec<Value>, value);

    Ok(arr.pop().unwrap_or_else(|| to_value("").unwrap()))
}

/// Joins all values in the array by the `sep` argument given
/// If no separator is given, it will use `""` (empty string) as separator
/// If the array is empty, returns empty string
pub fn join(value: Value, args: HashMap<String, Value>) -> Result<Value> {
    let arr = try_get_value!("join", "value", Vec<Value>, value);
    let sep = match args.get("sep") {
        Some(val) => try_get_value!("truncate", "sep", String, val),
        None => "".to_string(),
    };

    // Convert all the values to strings before we join them together.
    let rendered = arr.iter().map(|val| val.render()).collect::<Vec<_>>();
    Ok(to_value(&rendered.join(&sep))?)
}

/// Sorts the array in ascending order.
/// Use the 'attribute' argument to define a field to sort by.
pub fn sort(value: Value, args: HashMap<String, Value>) -> Result<Value> {
    let mut arr = try_get_value!("sort", "value", Vec<Value>, value);
    let attribute = try_get_value!("sort", "attribute", String, args.get("attribute").unwrap_or(&"".into()));
    let attribute = get_json_pointer(&attribute);

    arr.sort_unstable_by(|a, b| {
        value_cmp(
            a.pointer(&attribute).unwrap_or(&Value::Null),
            b.pointer(&attribute).unwrap_or(&Value::Null)
        )
    });

    Ok(to_value(arr)?)
}

fn value_cmp(a: &Value, b: &Value) -> Ordering {
    use Value::*;
    use self::Ordering::*;
    match (a, b) {
        (&Null, &Null) => Equal,
        (&Bool(ref a), &Bool(ref b)) => a.cmp(b),
        (&Number(ref a), &Number(ref b)) => a.as_f64().unwrap().partial_cmp(&b.as_f64().unwrap()).unwrap(),
        (&String(ref a), &String(ref b)) => a.cmp(b),
        (&Array(ref a), &Array(ref b)) => a.len().cmp(&b.len()),
        (&Object(ref a), &Object(ref b)) => a.len().cmp(&b.len()),
        (a, b) => type_of(a).cmp(&type_of(b))
    }
}

fn type_of(val: &Value) -> usize {
    use Value::*;
    match *val {
        Null => 0,
        Bool(_) => 1,
        Number(_) => 2,
        String(_) => 3,
        Array(_) => 4, Object(_) => 5
    }
}

#[cfg(test)]
mod tests {
    use std::collections::HashMap;
    use serde_json::value::{Value, to_value};
    use super::*;

    #[test]
    fn test_first() {
        let result = first(to_value(&vec![1, 2, 3, 4]).unwrap(), HashMap::new());
        assert!(result.is_ok());
        assert_eq!(result.unwrap(), to_value(&1).unwrap());
    }

    #[test]
    fn test_first_empty() {
        let v: Vec<Value> = Vec::new();

        let result = first(to_value(&v).unwrap(), HashMap::new());
        assert!(result.is_ok());
        assert_eq!(result.ok().unwrap(), to_value("").unwrap());
    }

    #[test]
    fn test_last() {
        let result = last(to_value(&vec!["Hello", "World"]).unwrap(), HashMap::new());
        assert!(result.is_ok());
        assert_eq!(result.unwrap(), to_value("World").unwrap());
    }

    #[test]
    fn test_last_empty() {
        let v: Vec<Value> = Vec::new();

        let result = last(to_value(&v).unwrap(), HashMap::new());
        assert!(result.is_ok());
        assert_eq!(result.ok().unwrap(), to_value("").unwrap());
    }

    #[test]
    fn test_join_sep() {
        let mut args = HashMap::new();
        args.insert("sep".to_owned(), to_value(&"==").unwrap());

        let result = join(to_value(&vec!["Cats", "Dogs"]).unwrap(), args);
        assert!(result.is_ok());
        assert_eq!(result.unwrap(), to_value(&"Cats==Dogs").unwrap());
    }

    #[test]
    fn test_join_sep_omitted() {
        let result = join(to_value(&vec![1.2, 3.4]).unwrap(), HashMap::new());
        assert!(result.is_ok());
        assert_eq!(result.unwrap(), to_value(&"1.23.4").unwrap());
    }

    #[test]
    fn test_join_empty() {
        let v: Vec<Value> = Vec::new();
        let mut args = HashMap::new();
        args.insert("sep".to_owned(), to_value(&"==").unwrap());

        let result = join(to_value(&v).unwrap(), args);
        assert!(result.is_ok());
        assert_eq!(result.unwrap(), to_value(&"").unwrap());
    }

    #[test]
    fn test_sort() {
        let v = to_value(vec![3, 1, 2, 5, 4]).unwrap();
        let args = HashMap::new();
        let result = sort(v, args);
        assert!(result.is_ok());
        assert_eq!(result.unwrap(), to_value(vec![1, 2, 3, 4, 5]).unwrap());
    }

    #[derive(Serialize)]
    struct Foo {
        a: i32,
        b: i32
    }

    #[test]
    fn test_sort_attribute() {
        let v = to_value(vec![
            Foo {a: 3, b: 5},
            Foo {a: 2, b: 8},
            Foo {a: 4, b: 7},
            Foo {a: 1, b: 6},
        ]).unwrap();
        let mut args = HashMap::new();
        args.insert("attribute".to_string(), to_value(&"a").unwrap());

        let result = sort(v, args);
        assert!(result.is_ok());
        assert_eq!(result.unwrap(), to_value(vec![
            Foo {a: 1, b: 6},
            Foo {a: 2, b: 8},
            Foo {a: 3, b: 5},
            Foo {a: 4, b: 7},
        ]).unwrap());
    }
}
<|endoftext|>


<commit_before>use std::io;
use std::path::Path;

use clap::{App, Arg, ArgMatches};
use console::style;
use failure::Error;

use crate::utils::dif::DifFile;
use crate::utils::system::QuietExit;

pub fn make_app<'a, 'b: 'a>(app: App<'a, 'b>) -> App<'a, 'b> {
    app.about("Check the debug info file at a given path.")
        .arg(
            Arg::with_name("type")
                .long("type")
                .short("t")
                .value_name("TYPE")
                .possible_values(&["dsym", "elf", "proguard", "breakpad"])
                .help(
                    "Explicitly set the type of the debug info file. \
                     This should not be needed as files are auto detected.",
                ),
        )
        .arg(
            Arg::with_name("json")
                .long("json")
                .help("Format outputs as JSON."),
        )
        .arg(
            Arg::with_name("path")
                .index(1)
                .required(true)
                .help("The path to the debug info file."),
        )
}

pub fn execute<'a>(matches: &ArgMatches<'a>) -> Result<(), Error> {
    let path = Path::new(matches.value_of("path").unwrap());

    // which types should we consider?
    let ty = matches.value_of("type").map(|t| t.parse().unwrap());
    let dif = DifFile::open_path(path, ty)?;

    if matches.is_present("json") {
        serde_json::to_writer_pretty(&mut io::stdout(), &dif)?;
        println!();
        return if dif.is_usable() {
            Ok(())
        } else {
            Err(QuietExit(1).into())
        };
    }

    println!("{}", style("Debug Info File Check").dim().bold());
    match dif.kind() {
        Some(class) => println!(
            "  Type: {} {:#}",
            style(dif.ty()).cyan(),
            style(class).cyan()
        ),
        None => println!("  Type: {}", style(dif.ty()).cyan()),
    }

    println!("  Contained debug identifiers:");
    for variant in dif.variants() {
        match variant.arch {
            Some(ref cpu_type) => println!(
                "    > debug_id: {} ({})",
                style(variant.debug_id).dim(),
                style(cpu_type).cyan()
            ),
            None => println!("    > debug_id: {}", style(variant.debug_id).dim()),
        }
        match (variant.code_id, variant.arch) {
            (Some(ref code_id), Some(ref cpu_type)) => println!(
                "    > code_id: {} ({})",
                style(code_id).dim(),
                style(cpu_type).cyan()
            ),
            (Some(ref code_id), None) => println!("    > code_id: {}", style(code_id).dim()),
            _ => {}
        }
    }

    println!("  Contained debug information:");
    println!("    > {}", dif.features());

    if let Some(msg) = dif.get_note() {
        println!("  Note: {}", msg);
    }

    if let Some(prob) = dif.get_problem() {
        println!("  Usable: {} ({})", style("no").red(), prob);
        Err(QuietExit(1).into())
    } else {
        println!("  Usable: {}", style("yes").green());
        Ok(())
    }
}
<commit_msg>fix(difutil): Render variants as list in difutil check (#857)<commit_after>use std::io;
use std::path::Path;

use clap::{App, Arg, ArgMatches};
use console::style;
use failure::Error;

use crate::utils::dif::DifFile;
use crate::utils::system::QuietExit;

pub fn make_app<'a, 'b: 'a>(app: App<'a, 'b>) -> App<'a, 'b> {
    app.about("Check the debug info file at a given path.")
        .arg(
            Arg::with_name("type")
                .long("type")
                .short("t")
                .value_name("TYPE")
                .possible_values(&["dsym", "elf", "proguard", "breakpad"])
                .help(
                    "Explicitly set the type of the debug info file. \
                     This should not be needed as files are auto detected.",
                ),
        )
        .arg(
            Arg::with_name("json")
                .long("json")
                .help("Format outputs as JSON."),
        )
        .arg(
            Arg::with_name("path")
                .index(1)
                .required(true)
                .help("The path to the debug info file."),
        )
}

pub fn execute<'a>(matches: &ArgMatches<'a>) -> Result<(), Error> {
    let path = Path::new(matches.value_of("path").unwrap());

    // which types should we consider?
    let ty = matches.value_of("type").map(|t| t.parse().unwrap());
    let dif = DifFile::open_path(path, ty)?;

    if matches.is_present("json") {
        serde_json::to_writer_pretty(&mut io::stdout(), &dif)?;
        println!();
        return if dif.is_usable() {
            Ok(())
        } else {
            Err(QuietExit(1).into())
        };
    }

    println!("{}", style("Debug Info File Check").dim().bold());
    match dif.kind() {
        Some(class) => println!(
            "  Type: {} {:#}",
            style(dif.ty()).cyan(),
            style(class).cyan()
        ),
        None => println!("  Type: {}", style(dif.ty()).cyan()),
    }

    println!("  Contained debug identifiers:");
    for variant in dif.variants() {
        println!("    > Debug ID: {}", style(variant.debug_id).dim());
        if let Some(code_id) = variant.code_id {
            println!("      Code ID:  {}", style(code_id).dim());
        }
        if let Some(arch) = variant.arch {
            println!("      Arch:     {}", style(arch).dim());
        }
    }

    println!("  Contained debug information:");
    println!("    > {}", dif.features());

    if let Some(msg) = dif.get_note() {
        println!("  Note: {}", msg);
    }

    if let Some(prob) = dif.get_problem() {
        println!("  Usable: {} ({})", style("no").red(), prob);
        Err(QuietExit(1).into())
    } else {
        println!("  Usable: {}", style("yes").green());
        Ok(())
    }
}
<|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 http://mozilla.org/MPL/2.0/.

use std::collections::HashMap;

use dbus::arg::{RefArg, Variant};

use crate::{
    dbus_api::util::result_option_to_tuple,
    engine::{LockedPoolInfo, PoolUuid, StoppedPoolInfo},
};

/// D-Bus representation of locked pools.
pub type StoppedOrLockedPools = HashMap<String, HashMap<String, Variant<Box<dyn RefArg>>>>;

/// Convert a locked pool data structure to a property format.
pub fn locked_pools_to_prop(
    pools: &HashMap<PoolUuid, LockedPoolInfo>,
) -> HashMap<String, HashMap<String, Variant<Box<dyn RefArg>>>> {
    pools
        .iter()
        .map(|(u, locked)| {
            (
                uuid_to_string!(u),
                vec![
                    (
                        "key_description".to_string(),
                        Variant(Box::new(result_option_to_tuple(
                            locked
                                .info
                                .key_description()
                                .map(|opt| opt.map(|kd| kd.as_application_str().to_string())),
                            String::new(),
                        )) as Box<dyn RefArg>),
                    ),
                    (
                        "clevis_info".to_string(),
                        Variant(Box::new(result_option_to_tuple(
                            locked
                                .info
                                .clevis_info()
                                .map(|opt| opt.map(|(pin, cfg)| (pin.to_owned(), cfg.to_string()))),
                            (String::new(), String::new()),
                        )) as Box<dyn RefArg>),
                    ),
                    (
                        "devs".to_string(),
                        Variant(Box::new(
                            locked
                                .devices
                                .iter()
                                .map(|d| {
                                    let mut map = HashMap::new();
                                    map.insert(
                                        "devnode".to_string(),
                                        d.devnode.display().to_string(),
                                    );
                                    map.insert("uuid".to_string(), uuid_to_string!(d.uuid));
                                    map
                                })
                                .collect::<Vec<_>>(),
                        )),
                    ),
                ]
                .into_iter()
                .collect::<HashMap<_, _>>(),
            )
        })
        .collect()
}

/// Convert a stopped pool data structure to a property format.
pub fn stopped_pools_to_prop(
    pools: &HashMap<PoolUuid, StoppedPoolInfo>,
) -> HashMap<String, HashMap<String, Variant<Box<dyn RefArg>>>> {
    pools
        .iter()
        .map(|(u, stopped)| {
            let mut map = HashMap::new();
            if let Some(enc_info) = stopped.info.as_ref() {
                map.insert(
                    "key_description".to_string(),
                    Variant(Box::new(result_option_to_tuple(
                        enc_info
                            .key_description()
                            .map(|opt| opt.map(|kd| kd.as_application_str().to_string())),
                        String::new(),
                    )) as Box<dyn RefArg>),
                );
                map.insert(
                    "clevis_info".to_string(),
                    Variant(Box::new(result_option_to_tuple(
                        enc_info
                            .clevis_info()
                            .map(|opt| opt.map(|(pin, cfg)| (pin.to_owned(), cfg.to_string()))),
                        (String::new(), String::new()),
                    )) as Box<dyn RefArg>),
                );
            }
            map.insert(
                "devs".to_string(),
                Variant(Box::new(
                    stopped
                        .devices
                        .iter()
                        .map(|d| {
                            let mut map = HashMap::new();
                            map.insert("devnode".to_string(), d.devnode.display().to_string());
                            map.insert("uuid".to_string(), uuid_to_string!(d.uuid));
                            map
                        })
                        .collect::<Vec<_>>(),
                )),
            );
            (uuid_to_string!(u), map)
        })
        .collect()
}
<commit_msg>Use type alias instead of longer HashMap type<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 std::collections::HashMap;

use dbus::arg::{RefArg, Variant};

use crate::{
    dbus_api::util::result_option_to_tuple,
    engine::{LockedPoolInfo, PoolUuid, StoppedPoolInfo},
};

/// D-Bus representation of locked pools.
pub type StoppedOrLockedPools = HashMap<String, HashMap<String, Variant<Box<dyn RefArg>>>>;

/// Convert a locked pool data structure to a property format.
pub fn locked_pools_to_prop(pools: &HashMap<PoolUuid, LockedPoolInfo>) -> StoppedOrLockedPools {
    pools
        .iter()
        .map(|(u, locked)| {
            (
                uuid_to_string!(u),
                vec![
                    (
                        "key_description".to_string(),
                        Variant(Box::new(result_option_to_tuple(
                            locked
                                .info
                                .key_description()
                                .map(|opt| opt.map(|kd| kd.as_application_str().to_string())),
                            String::new(),
                        )) as Box<dyn RefArg>),
                    ),
                    (
                        "clevis_info".to_string(),
                        Variant(Box::new(result_option_to_tuple(
                            locked
                                .info
                                .clevis_info()
                                .map(|opt| opt.map(|(pin, cfg)| (pin.to_owned(), cfg.to_string()))),
                            (String::new(), String::new()),
                        )) as Box<dyn RefArg>),
                    ),
                    (
                        "devs".to_string(),
                        Variant(Box::new(
                            locked
                                .devices
                                .iter()
                                .map(|d| {
                                    let mut map = HashMap::new();
                                    map.insert(
                                        "devnode".to_string(),
                                        d.devnode.display().to_string(),
                                    );
                                    map.insert("uuid".to_string(), uuid_to_string!(d.uuid));
                                    map
                                })
                                .collect::<Vec<_>>(),
                        )),
                    ),
                ]
                .into_iter()
                .collect::<HashMap<_, _>>(),
            )
        })
        .collect()
}

/// Convert a stopped pool data structure to a property format.
pub fn stopped_pools_to_prop(pools: &HashMap<PoolUuid, StoppedPoolInfo>) -> StoppedOrLockedPools {
    pools
        .iter()
        .map(|(u, stopped)| {
            let mut map = HashMap::new();
            if let Some(enc_info) = stopped.info.as_ref() {
                map.insert(
                    "key_description".to_string(),
                    Variant(Box::new(result_option_to_tuple(
                        enc_info
                            .key_description()
                            .map(|opt| opt.map(|kd| kd.as_application_str().to_string())),
                        String::new(),
                    )) as Box<dyn RefArg>),
                );
                map.insert(
                    "clevis_info".to_string(),
                    Variant(Box::new(result_option_to_tuple(
                        enc_info
                            .clevis_info()
                            .map(|opt| opt.map(|(pin, cfg)| (pin.to_owned(), cfg.to_string()))),
                        (String::new(), String::new()),
                    )) as Box<dyn RefArg>),
                );
            }
            map.insert(
                "devs".to_string(),
                Variant(Box::new(
                    stopped
                        .devices
                        .iter()
                        .map(|d| {
                            let mut map = HashMap::new();
                            map.insert("devnode".to_string(), d.devnode.display().to_string());
                            map.insert("uuid".to_string(), uuid_to_string!(d.uuid));
                            map
                        })
                        .collect::<Vec<_>>(),
                )),
            );
            (uuid_to_string!(u), map)
        })
        .collect()
}
<|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 http://mozilla.org/MPL/2.0/.

//! This module is the main module for the Ligature project.
//! It represents to common types and traits used by Ligature.

#![deny(missing_docs)]
//#![deny(missing_doc_example)] <-- for later, when I'm swole

#[macro_use]
extern crate lazy_static;

use regex::Regex;
use serde::{Deserialize, Serialize};
use uuid::Uuid;

/// A simple type alias for Bytes.
pub type Bytes = Vec<u8>;

/// A string that represents a Dataset by name.
/// Currently can only be ASCII text separated by /
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Serialize, Deserialize)]
pub struct Dataset(String);

impl Dataset {
    /// Creates a new Dataset and returns a Result based on if it is valid or not.
    pub fn new(name: &str) -> Result<Dataset, LigatureError> {
        lazy_static! {
            static ref RE: Regex =
                Regex::new(r"^([a-zA-Z_]{1}[a-zA-Z0-9_]*)(/[a-zA-Z_]{1}[a-zA-Z0-9_]*)*$").unwrap();
        }

        if RE.is_match(name) {
            Ok(Dataset(name.to_string()))
        } else {
            Err(LigatureError(format!("Invalid Dataset name {}", name)))
        }
    }

    /// Returns the name of the given Dataset.
    pub fn name(&self) -> &str {
        &self.0
    }
}

/// Check if a given identifier is valid.
pub fn validate_identifier(id: &str) -> bool {
    lazy_static! {
        static ref RE: Regex =
            Regex::new(r"^[a-zA-Z_][a-zA-Z0-9-._~:/?#\[\]@!$&'()*+,;%=]*$").unwrap();
    }

    RE.is_match(id)
}

/// Check if a given str contains only valid identifier characters.
pub fn validate_identifier_characters(id: &str) -> bool {
    lazy_static! {
        static ref RE: Regex =
            Regex::new(r"^[a-zA-Z0-9-._~:/?#\[\]@!$&'()*+,;%=]*$").unwrap();
    }

    RE.is_match(id)
}

/// An Entity that is identified by a unique String id.
#[derive(Debug, Clone, PartialOrd, Ord, PartialEq, Eq)]
pub struct Entity(String);

impl Entity {
    /// Creates a new Arrow and returns a Result based on if it is valid or not.
    pub fn new(name: &str) -> Result<Self, LigatureError> {
        if validate_identifier(name) {
            Ok(Self(name.to_string()))
        } else {
            Err(LigatureError(format!("Invalid Entity id {}", name)))
        }
    }

    /// Returns the name of the given Arrow.
    pub fn id(&self) -> &str {
        &self.0
    }
}

/// Creates a new Entity with an optional prefix.
/// To assure this Entity is unique within a Dataset use the version located in WriteTx.
pub fn new_entity(prefix: Option<String>) -> Result<Entity, LigatureError> {
    let uuid = Uuid::new_v4().to_hyphenated().to_string();
    let p = match prefix {
        Some(s) => s + &uuid,
        None => String::from("_:") + &uuid,
    };
    Entity::new(p.as_str())
}

/// A named connection between an Entity and a Value.
#[derive(Debug, Clone, PartialOrd, Ord, PartialEq, Eq)]
pub struct Attribute(String);

impl Attribute {
    /// Creates a new Arrow and returns a Result based on if it is valid or not.
    pub fn new(name: &str) -> Result<Self, LigatureError> {
        if validate_identifier(name) {
            Ok(Self(name.to_string()))
        } else {
            Err(LigatureError(format!("Invalid Attribute name {}", name)))
        }
    }

    /// Returns the name of the given Arrow.
    pub fn name(&self) -> &str {
        &self.0
    }
}

/// An enum that represents all the currently supported Value types.
#[derive(Debug, Clone, PartialOrd, Ord, PartialEq, Eq)]
pub enum Value {
    /// An Entity.
    Entity(Entity),
    /// A String used for a Ligature literal
    StringLiteral(String),
    /// An i64 used for a Ligature literal
    IntegerLiteral(i64),
    // /// An f64 used for a Ligature literal
    // FloatLiteral(f64),
    /// An array of bytes
    BytesLiteral(Bytes),
}

/// A set of enums used to express range queries when it makes sense for that type.
#[derive(Debug)]
pub enum Range {
    /// Represents a String range using basic String comparisons.
    StringLiteralRange {
        /// The starting String (inclusive)
        start: String,
        /// The end String (exclusive)
        end: String,
    },
    /// Represents a String range using basic i64 comparisons.
    IntegerLiteralRange {
        /// The starting i64 (inclusive)
        start: i64,
        /// The end i64 (exclusive)
        end: i64,
    },
    // /// Represents a String range using basic f64 comparisons.
    // FloatLiteralRange {
    //     /// The starting f64 (inclusive)
    //     start: f64,
    //     /// The end f64 (exclusive)
    //     end: f64,
    // },
    /// Represents a String range using basic Bytes comparisons.
    BytesLiteralRange {
        /// The starting Byte array (inclusive)
        start: Bytes,
        /// The end Byte array (exclusive)
        end: Bytes,
    },
}

/// A Statement is a grouping of an Entity, an Attribute, and a Value.
#[derive(Debug, Clone, PartialOrd, Ord, PartialEq, Eq)]
pub struct Statement {
    /// The Entity of a Statement
    pub entity: Entity,
    /// The Attribute of a Statement
    pub attribute: Attribute,
    /// The Value of a Statement
    pub value: Value,
    /// The Context of this Statement
    pub context: Entity,
}

/// A general struct for representing errors involving Ligature.
/// TODO should probably be an enum with a bunch of specific cases
#[derive(Debug, PartialEq, Eq)]
pub struct LigatureError(pub String);

/// A trait that all Ligature implementations implement.
pub trait Ligature {
    /// Returns all Datasets in a Ligature instance.
    fn all_datasets(&self) -> Box<dyn Iterator<Item = Result<Dataset, LigatureError>>>;

    /// Check if a given Dataset exists.
    fn dataset_exists(&self, dataset: &Dataset) -> Result<bool, LigatureError>;

    /// Returns all Datasets in a Ligature instance that start with the given prefix.
    fn match_datasets_prefix(
        &self,
        prefix: &str,
    ) -> Box<dyn Iterator<Item = Result<Dataset, LigatureError>>>;

    /// Returns all Datasets in a Ligature instance that are in a given range (inclusive, exclusive].
    fn match_datasets_range(
        &self,
        start: &str,
        end: &str,
    ) -> Box<dyn Iterator<Item = Result<Dataset, LigatureError>>>;

    /// Creates a dataset with the given name.
    /// TODO should probably return its own error type { InvalidDataset, DatasetExists, CouldNotCreateDataset }
    fn create_dataset(&self, dataset: &Dataset) -> Result<(), LigatureError>;

    /// Deletes a dataset with the given name.
    /// TODO should probably return its own error type { InvalidDataset, CouldNotDeleteDataset }
    fn delete_dataset(&self, dataset: &Dataset) -> Result<(), LigatureError>;

    /// Initializes a QueryTx
    /// TODO should probably return its own error type CouldNotInitializeQueryTx
    fn query<T>(&self, dataset: &Dataset, f: QueryFn<T>) -> Result<T, LigatureError>;

    /// Initializes a WriteTx
    /// TODO should probably return its own error type CouldNotInitializeWriteTx
    fn write<T>(&self, dataset: &Dataset, f: WriteFn<T>) -> Result<T, LigatureError>;
}

/// An Fn that is used when making a Query transaction.
pub type QueryFn<T> = Box<dyn Fn(Box<&dyn QueryTx>) -> Result<T, LigatureError>>;

/// An Fn that is used when making a Write transaction.
pub type WriteFn<T> = Box<dyn Fn(Box<&dyn WriteTx>) -> Result<T, LigatureError>>;

/// Represents a QueryTx within the context of a Ligature instance and a single Dataset
pub trait QueryTx {
    /// Returns all Statements in this Dataset.
    fn all_statements(&self) -> Box<dyn Iterator<Item = Result<Statement, LigatureError>> + '_>;

    /// Returns all Statements that match the given criteria.
    /// If a parameter is None then it matches all, so passing all Nones is the same as calling all_statements.
    fn match_statements(
        &self,
        source: Option<Entity>,
        arrow: Option<Attribute>,
        target: Option<Value>,
    ) -> Box<dyn Iterator<Item = Result<Statement, LigatureError>> + '_>;

    /// Returns all Statements that match the given criteria.
    /// If a parameter is None then it matches all.
    fn match_statements_range(
        &self,
        source: Option<Entity>,
        arrow: Option<Attribute>,
        target: Range,
    ) -> Box<dyn Iterator<Item = Result<Statement, LigatureError>> + '_>;

    /// Returns the Statement for the given context.
    fn statement_for_context(&self, context: &Entity) -> Result<Option<Statement>, LigatureError>;
}

/// Represents a WriteTx within the context of a Ligature instance and a single Dataset
pub trait WriteTx {
    /// Creates a new, unique Entity within this Dataset with an optional prefix.
    /// This version of the function enforces that the new entity is unique in this Dataset.
    fn new_entity(&self, prefix: Option<String>) -> Result<Entity, LigatureError>;

    /// Adds a given Statement to this Dataset.
    /// If the Statement already exists nothing happens (TODO maybe add it with a new context?).
    /// Note: Potentially could trigger a ValidationError
    fn add_statement(&self, statement: &Statement) -> Result<Statement, LigatureError>;

    /// Removes a given Statement from this Dataset.
    /// If the Statement doesn't exist nothing happens and returns Ok(false).
    /// This function returns Ok(true) only if the given Statement was found and removed.
    /// Note: Potentially could trigger a ValidationError.
    fn remove_statement(&self, persisted_statement: &Statement) -> Result<bool, LigatureError>;

    /// Cancels this transaction so that none of the changes made so far will be stored.
    /// This also closes this transaction so no other methods can be called without returning a LigatureError.
    fn cancel(&self) -> Result<(), LigatureError>;
}
<commit_msg>simplify model, #210<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/.

//! This module is the main module for the Ligature project.
//! It represents to common types and traits used by Ligature.

#![deny(missing_docs)]
//#![deny(missing_doc_example)] <-- for later, when I'm swole

#[macro_use]
extern crate lazy_static;

use regex::Regex;
use serde::{Deserialize, Serialize};
use uuid::Uuid;

/// A simple type alias for Bytes.
pub type Bytes = Vec<u8>;

/// A string that represents a Dataset by name.
/// Currently can only be ASCII text separated by /
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Serialize, Deserialize)]
pub struct Dataset(String);

impl Dataset {
    /// Creates a new Dataset and returns a Result based on if it is valid or not.
    pub fn new(name: &str) -> Result<Dataset, LigatureError> {
        lazy_static! {
            static ref RE: Regex =
                Regex::new(r"^([a-zA-Z_]{1}[a-zA-Z0-9_]*)(/[a-zA-Z_]{1}[a-zA-Z0-9_]*)*$").unwrap();
        }

        if RE.is_match(name) {
            Ok(Dataset(name.to_string()))
        } else {
            Err(LigatureError(format!("Invalid Dataset name {}", name)))
        }
    }

    /// Returns the name of the given Dataset.
    pub fn name(&self) -> &str {
        &self.0
    }
}

/// Check if a given identifier is valid.
pub fn validate_identifier(id: &str) -> bool {
    lazy_static! {
        static ref RE: Regex =
            Regex::new(r"^[a-zA-Z0-9-._~:/?#\[\]@!$&'()*+,;%=]*$").unwrap();
    }

    RE.is_match(id)
}

/// An Entity that is identified by a unique String id.
#[derive(Debug, Clone, PartialOrd, Ord, PartialEq, Eq)]
pub struct Identifier(String);

impl Identifier {
    /// Creates a new Arrow and returns a Result based on if it is valid or not.
    pub fn new(name: &str) -> Result<Self, LigatureError> {
        if validate_identifier(name) {
            Ok(Self(name.to_string()))
        } else {
            Err(LigatureError(format!("Invalid Entity id {}", name)))
        }
    }

    /// Returns the name of the given Arrow.
    pub fn id(&self) -> &str {
        &self.0
    }
}

/// Creates a new Entity with an optional prefix.
/// To assure this Entity is unique within a Dataset use the version located in WriteTx.
pub fn new_identifier(prefix: Option<String>) -> Result<Identifier, LigatureError> {
    let uuid = Uuid::new_v4().to_hyphenated().to_string();
    let p = match prefix {
        Some(s) => s + &uuid,
        None => uuid,
    };
    Identifier::new(p.as_str())
}

/// An enum that represents all the currently supported Value types.
#[derive(Debug, Clone, PartialOrd, Ord, PartialEq, Eq)]
pub enum Value {
    /// An Entity.
    Identifier(Identifier),
    /// A String used for a Ligature literal
    StringLiteral(String),
    /// An i64 used for a Ligature literal
    IntegerLiteral(i64),
    // /// An f64 used for a Ligature literal
    // FloatLiteral(f64),
    /// An array of bytes
    BytesLiteral(Bytes),
}

/// A set of enums used to express range queries when it makes sense for that type.
#[derive(Debug)]
pub enum Range {
    /// Represents a String range using basic String comparisons.
    StringLiteralRange {
        /// The starting String (inclusive)
        start: String,
        /// The end String (exclusive)
        end: String,
    },
    /// Represents a String range using basic i64 comparisons.
    IntegerLiteralRange {
        /// The starting i64 (inclusive)
        start: i64,
        /// The end i64 (exclusive)
        end: i64,
    },
    // /// Represents a String range using basic f64 comparisons.
    // FloatLiteralRange {
    //     /// The starting f64 (inclusive)
    //     start: f64,
    //     /// The end f64 (exclusive)
    //     end: f64,
    // },
    /// Represents a String range using basic Bytes comparisons.
    BytesLiteralRange {
        /// The starting Byte array (inclusive)
        start: Bytes,
        /// The end Byte array (exclusive)
        end: Bytes,
    },
}

/// A Statement is a grouping of an Entity, an Attribute, and a Value.
#[derive(Debug, Clone, PartialOrd, Ord, PartialEq, Eq)]
pub struct Statement {
    /// The Entity of a Statement
    pub entity: Identifier,
    /// The Attribute of a Statement
    pub attribute: Identifier,
    /// The Value of a Statement
    pub value: Value,
    /// The Context of this Statement
    pub context: Identifier,
}

/// A general struct for representing errors involving Ligature.
/// TODO should probably be an enum with a bunch of specific cases
#[derive(Debug, PartialEq, Eq)]
pub struct LigatureError(pub String);

/// A trait that all Ligature implementations implement.
pub trait Ligature {
    /// Returns all Datasets in a Ligature instance.
    fn all_datasets(&self) -> Box<dyn Iterator<Item = Result<Dataset, LigatureError>>>;

    /// Check if a given Dataset exists.
    fn dataset_exists(&self, dataset: &Dataset) -> Result<bool, LigatureError>;

    /// Returns all Datasets in a Ligature instance that start with the given prefix.
    fn match_datasets_prefix(
        &self,
        prefix: &str,
    ) -> Box<dyn Iterator<Item = Result<Dataset, LigatureError>>>;

    /// Returns all Datasets in a Ligature instance that are in a given range (inclusive, exclusive].
    fn match_datasets_range(
        &self,
        start: &str,
        end: &str,
    ) -> Box<dyn Iterator<Item = Result<Dataset, LigatureError>>>;

    /// Creates a dataset with the given name.
    /// TODO should probably return its own error type { InvalidDataset, DatasetExists, CouldNotCreateDataset }
    fn create_dataset(&self, dataset: &Dataset) -> Result<(), LigatureError>;

    /// Deletes a dataset with the given name.
    /// TODO should probably return its own error type { InvalidDataset, CouldNotDeleteDataset }
    fn delete_dataset(&self, dataset: &Dataset) -> Result<(), LigatureError>;

    /// Initializes a QueryTx
    /// TODO should probably return its own error type CouldNotInitializeQueryTx
    fn query<T>(&self, dataset: &Dataset, f: QueryFn<T>) -> Result<T, LigatureError>;

    /// Initializes a WriteTx
    /// TODO should probably return its own error type CouldNotInitializeWriteTx
    fn write<T>(&self, dataset: &Dataset, f: WriteFn<T>) -> Result<T, LigatureError>;
}

/// An Fn that is used when making a Query transaction.
pub type QueryFn<T> = Box<dyn Fn(Box<&dyn QueryTx>) -> Result<T, LigatureError>>;

/// An Fn that is used when making a Write transaction.
pub type WriteFn<T> = Box<dyn Fn(Box<&dyn WriteTx>) -> Result<T, LigatureError>>;

/// Represents a QueryTx within the context of a Ligature instance and a single Dataset
pub trait QueryTx {
    /// Returns all Statements in this Dataset.
    fn all_statements(&self) -> Box<dyn Iterator<Item = Result<Statement, LigatureError>> + '_>;

    /// Returns all Statements that match the given criteria.
    /// If a parameter is None then it matches all, so passing all Nones is the same as calling all_statements.
    fn match_statements(
        &self,
        source: Option<Identifier>,
        arrow: Option<Identifier>,
        target: Option<Value>,
    ) -> Box<dyn Iterator<Item = Result<Statement, LigatureError>> + '_>;

    /// Returns all Statements that match the given criteria.
    /// If a parameter is None then it matches all.
    fn match_statements_range(
        &self,
        source: Option<Identifier>,
        arrow: Option<Identifier>,
        target: Range,
    ) -> Box<dyn Iterator<Item = Result<Statement, LigatureError>> + '_>;

    /// Returns the Statement for the given context.
    fn statement_for_context(&self, context: &Identifier) -> Result<Option<Statement>, LigatureError>;
}

/// Represents a WriteTx within the context of a Ligature instance and a single Dataset
pub trait WriteTx {
    /// Creates a new, unique Entity within this Dataset with an optional prefix.
    /// This version of the function enforces that the new entity is unique in this Dataset.
    fn new_identifier(&self, prefix: Option<String>) -> Result<Identifier, LigatureError>;

    /// Adds a given Statement to this Dataset.
    /// If the Statement already exists nothing happens (TODO maybe add it with a new context?).
    /// Note: Potentially could trigger a ValidationError
    fn add_statement(&self, statement: &Statement) -> Result<Statement, LigatureError>;

    /// Removes a given Statement from this Dataset.
    /// If the Statement doesn't exist nothing happens and returns Ok(false).
    /// This function returns Ok(true) only if the given Statement was found and removed.
    /// Note: Potentially could trigger a ValidationError.
    fn remove_statement(&self, persisted_statement: &Statement) -> Result<bool, LigatureError>;

    /// Cancels this transaction so that none of the changes made so far will be stored.
    /// This also closes this transaction so no other methods can be called without returning a LigatureError.
    fn cancel(&self) -> Result<(), LigatureError>;
}
<|endoftext|>