<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/. */

//! Per-node data used in style calculation.

use context::SharedStyleContext;
use dom::TElement;
use invalidation::element::restyle_hints::RestyleHint;
use properties::ComputedValues;
use properties::longhands::display::computed_value as display;
use rule_tree::StrongRuleNode;
use selector_parser::{EAGER_PSEUDO_COUNT, PseudoElement, RestyleDamage};
use shared_lock::StylesheetGuards;
use std::ops::{Deref, DerefMut};
use stylearc::Arc;

bitflags! {
    flags RestyleFlags: u8 {
        /// Whether the styles changed for this restyle.
        const WAS_RESTYLED = 1 << 0,
        /// Whether we reframed/reconstructed any ancestor or self.
        const ANCESTOR_WAS_RECONSTRUCTED = 1 << 1,
    }
}

/// Transient data used by the restyle algorithm. This structure is instantiated
/// either before or during restyle traversal, and is cleared at the end of node
/// processing.
#[derive(Debug)]
pub struct RestyleData {
    /// The restyle hint, which indicates whether selectors need to be rematched
    /// for this element, its children, and its descendants.
    pub hint: RestyleHint,

    /// A few flags to have in mind.
    flags: RestyleFlags,

    /// The restyle damage, indicating what kind of layout changes are required
    /// afte restyling.
    pub damage: RestyleDamage,
}

impl Default for RestyleData {
    fn default() -> Self {
        Self::new()
    }
}

impl RestyleData {
    fn new() -> Self {
        Self {
            hint: RestyleHint::empty(),
            flags: RestyleFlags::empty(),
            damage: RestyleDamage::empty(),
        }
    }

    /// Clear all the restyle state associated with this element.
    fn clear(&mut self) {
        *self = Self::new();
    }

    /// Clear restyle flags and damage.
    fn clear_flags_and_damage(&mut self) {
        self.damage = RestyleDamage::empty();
        self.flags = RestyleFlags::empty();
    }

    /// Returns whether this element or any ancestor is going to be
    /// reconstructed.
    pub fn reconstructed_self_or_ancestor(&self) -> bool {
        self.reconstructed_ancestor() ||
        self.damage.contains(RestyleDamage::reconstruct())
    }

    /// Returns whether any ancestor of this element was restyled.
    fn reconstructed_ancestor(&self) -> bool {
        self.flags.contains(ANCESTOR_WAS_RECONSTRUCTED)
    }

    /// Sets the flag that tells us whether we've reconstructed an ancestor.
    pub fn set_reconstructed_ancestor(&mut self) {
        // If it weren't for animation-only traversals, we could assert
        // `!self.reconstructed_ancestor()` here.
        self.flags.insert(ANCESTOR_WAS_RECONSTRUCTED);
    }

    /// Mark this element as restyled, which is useful to know whether we need
    /// to do a post-traversal.
    pub fn set_restyled(&mut self) {
        self.flags.insert(WAS_RESTYLED);
    }

    /// Mark this element as restyled, which is useful to know whether we need
    /// to do a post-traversal.
    pub fn is_restyle(&self) -> bool {
        self.flags.contains(WAS_RESTYLED)
    }

    /// Returns whether this element has been part of a restyle.
    pub fn contains_restyle_data(&self) -> bool {
        self.is_restyle() || !self.hint.is_empty() || !self.damage.is_empty()
    }
}

/// A lazily-allocated list of styles for eagerly-cascaded pseudo-elements.
///
/// We use an Arc so that sharing these styles via the style sharing cache does
/// not require duplicate allocations. We leverage the copy-on-write semantics of
/// Arc::make_mut(), which is free (i.e. does not require atomic RMU operations)
/// in servo_arc.
#[derive(Clone, Debug, Default)]
pub struct EagerPseudoStyles(Option<Arc<EagerPseudoArray>>);

#[derive(Debug, Default)]
struct EagerPseudoArray(EagerPseudoArrayInner);
type EagerPseudoArrayInner = [Option<Arc<ComputedValues>>; EAGER_PSEUDO_COUNT];

impl Deref for EagerPseudoArray {
    type Target = EagerPseudoArrayInner;
    fn deref(&self) -> &Self::Target {
        &self.0
    }
}

impl DerefMut for EagerPseudoArray {
    fn deref_mut(&mut self) -> &mut Self::Target {
        &mut self.0
    }
}

// Manually implement `Clone` here because the derived impl of `Clone` for
// array types assumes the value inside is `Copy`.
impl Clone for EagerPseudoArray {
    fn clone(&self) -> Self {
        let mut clone = Self::default();
        for i in 0..EAGER_PSEUDO_COUNT {
            clone[i] = self.0[i].clone();
        }
        clone
    }
}

impl EagerPseudoStyles {
    /// Returns whether there are any pseudo styles.
    pub fn is_empty(&self) -> bool {
        self.0.is_none()
    }

    /// Grabs a reference to the list of styles, if they exist.
    pub fn as_array(&self) -> Option<&EagerPseudoArrayInner> {
        match self.0 {
            None => None,
            Some(ref x) => Some(&x.0),
        }
    }

    /// Returns a reference to the style for a given eager pseudo, if it exists.
    pub fn get(&self, pseudo: &PseudoElement) -> Option<&Arc<ComputedValues>> {
        debug_assert!(pseudo.is_eager());
        self.0.as_ref().and_then(|p| p[pseudo.eager_index()].as_ref())
    }

    /// Sets the style for the eager pseudo.
    pub fn set(&mut self, pseudo: &PseudoElement, value: Arc<ComputedValues>) {
        if self.0.is_none() {
            self.0 = Some(Arc::new(Default::default()));
        }
        let arr = Arc::make_mut(self.0.as_mut().unwrap());
        arr[pseudo.eager_index()] = Some(value);
    }
}

/// The styles associated with a node, including the styles for any
/// pseudo-elements.
#[derive(Clone, Debug, Default)]
pub struct ElementStyles {
    /// The element's style.
    pub primary: Option<Arc<ComputedValues>>,
    /// A list of the styles for the element's eagerly-cascaded pseudo-elements.
    pub pseudos: EagerPseudoStyles,
}

impl ElementStyles {
    /// Returns the primary style.
    pub fn get_primary(&self) -> Option<&Arc<ComputedValues>> {
        self.primary.as_ref()
    }

    /// Returns the primary style.  Panic if no style available.
    pub fn primary(&self) -> &Arc<ComputedValues> {
        self.primary.as_ref().unwrap()
    }

    /// Whether this element `display` value is `none`.
    pub fn is_display_none(&self) -> bool {
        self.primary().get_box().clone_display() == display::T::none
    }
}

/// Style system data associated with an Element.
///
/// In Gecko, this hangs directly off the Element. Servo, this is embedded
/// inside of layout data, which itself hangs directly off the Element. In
/// both cases, it is wrapped inside an AtomicRefCell to ensure thread safety.
#[derive(Debug, Default)]
pub struct ElementData {
    /// The styles for the element and its pseudo-elements.
    pub styles: ElementStyles,

    /// Restyle state.
    pub restyle: RestyleData,
}

/// The kind of restyle that a single element should do.
#[derive(Debug)]
pub enum RestyleKind {
    /// We need to run selector matching plus re-cascade, that is, a full
    /// restyle.
    MatchAndCascade,
    /// We need to recascade with some replacement rule, such as the style
    /// attribute, or animation rules.
    CascadeWithReplacements(RestyleHint),
    /// We only need to recascade, for example, because only inherited
    /// properties in the parent changed.
    CascadeOnly,
}

impl ElementData {
    /// Borrows both styles and restyle mutably at the same time.
    pub fn styles_and_restyle_mut(
        &mut self
    ) -> (&mut ElementStyles, &mut RestyleData) {
        (&mut self.styles,
         &mut self.restyle)
    }

    /// Invalidates style for this element, its descendants, and later siblings,
    /// based on the snapshot of the element that we took when attributes or
    /// state changed.
    pub fn invalidate_style_if_needed<'a, E: TElement>(
        &mut self,
        element: E,
        shared_context: &SharedStyleContext)
    {
        use invalidation::element::invalidator::TreeStyleInvalidator;

        debug!("invalidate_style_if_needed: {:?}, flags: {:?}, has_snapshot: {}, \
                handled_snapshot: {}, pseudo: {:?}",
                element,
                shared_context.traversal_flags,
                element.has_snapshot(),
                element.handled_snapshot(),
                element.implemented_pseudo_element());

        if element.has_snapshot() && !element.handled_snapshot() {
            let invalidator = TreeStyleInvalidator::new(
                element,
                Some(self),
                shared_context,
            );
            invalidator.invalidate();
            unsafe { element.set_handled_snapshot() }
            debug_assert!(element.handled_snapshot());
        }
    }

    /// Returns true if this element has styles.
    pub fn has_styles(&self) -> bool {
        self.styles.primary.is_some()
    }

    /// Returns whether we have any outstanding style invalidation.
    pub fn has_invalidations(&self) -> bool {
        self.restyle.hint.has_self_invalidations()
    }

    /// Returns the kind of restyling that we're going to need to do on this
    /// element, based of the stored restyle hint.
    pub fn restyle_kind(&self,
                        shared_context: &SharedStyleContext)
                        -> RestyleKind {
        debug_assert!(!self.has_styles() || self.has_invalidations(),
                      "Should've stopped earlier");
        if !self.has_styles() {
            debug_assert!(!shared_context.traversal_flags.for_animation_only(),
                          "Unstyled element shouldn't be traversed during \
                           animation-only traversal");
            return RestyleKind::MatchAndCascade;
        }

        let hint = self.restyle.hint;
        if shared_context.traversal_flags.for_animation_only() {
            // return either CascadeWithReplacements or CascadeOnly in case of
            // animation-only restyle.
            if hint.has_animation_hint() {
                return RestyleKind::CascadeWithReplacements(hint & RestyleHint::for_animations());
            }
            return RestyleKind::CascadeOnly;
        }

        if hint.match_self() {
            return RestyleKind::MatchAndCascade;
        }

        if hint.has_replacements() {
            debug_assert!(!hint.has_animation_hint(),
                          "Animation only restyle hint should have already processed");
            return RestyleKind::CascadeWithReplacements(hint & RestyleHint::replacements());
        }

        debug_assert!(hint.has_recascade_self(),
                      "We definitely need to do something!");
        return RestyleKind::CascadeOnly;
    }

    /// Return true if important rules are different.
    /// We use this to make sure the cascade of off-main thread animations is correct.
    /// Note: Ignore custom properties for now because we only support opacity and transform
    ///       properties for animations running on compositor. Actually, we only care about opacity
    ///       and transform for now, but it's fine to compare all properties and let the user
    ///       the check which properties do they want.
    ///       If it costs too much, get_properties_overriding_animations() should return a set
    ///       containing only opacity and transform properties.
    pub fn important_rules_are_different(&self,
                                         rules: &StrongRuleNode,
                                         guards: &StylesheetGuards) -> bool {
        debug_assert!(self.has_styles());
        let (important_rules, _custom) =
            self.styles.primary().rules().get_properties_overriding_animations(&guards);
        let (other_important_rules, _custom) = rules.get_properties_overriding_animations(&guards);
        important_rules != other_important_rules
    }

    /// Drops any restyle state from the element.
    pub fn clear_restyle_state(&mut self) {
        self.restyle.clear();
    }

    /// Drops restyle flags and damage from the element.
    pub fn clear_restyle_flags_and_damage(&mut self) {
        self.restyle.clear_flags_and_damage();
    }
}
<commit_msg>style: Remove unused ElementStyles::styles_and_restyle_mut.<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/. */

//! Per-node data used in style calculation.

use context::SharedStyleContext;
use dom::TElement;
use invalidation::element::restyle_hints::RestyleHint;
use properties::ComputedValues;
use properties::longhands::display::computed_value as display;
use rule_tree::StrongRuleNode;
use selector_parser::{EAGER_PSEUDO_COUNT, PseudoElement, RestyleDamage};
use shared_lock::StylesheetGuards;
use std::ops::{Deref, DerefMut};
use stylearc::Arc;

bitflags! {
    flags RestyleFlags: u8 {
        /// Whether the styles changed for this restyle.
        const WAS_RESTYLED = 1 << 0,
        /// Whether we reframed/reconstructed any ancestor or self.
        const ANCESTOR_WAS_RECONSTRUCTED = 1 << 1,
    }
}

/// Transient data used by the restyle algorithm. This structure is instantiated
/// either before or during restyle traversal, and is cleared at the end of node
/// processing.
#[derive(Debug)]
pub struct RestyleData {
    /// The restyle hint, which indicates whether selectors need to be rematched
    /// for this element, its children, and its descendants.
    pub hint: RestyleHint,

    /// A few flags to have in mind.
    flags: RestyleFlags,

    /// The restyle damage, indicating what kind of layout changes are required
    /// afte restyling.
    pub damage: RestyleDamage,
}

impl Default for RestyleData {
    fn default() -> Self {
        Self::new()
    }
}

impl RestyleData {
    fn new() -> Self {
        Self {
            hint: RestyleHint::empty(),
            flags: RestyleFlags::empty(),
            damage: RestyleDamage::empty(),
        }
    }

    /// Clear all the restyle state associated with this element.
    fn clear(&mut self) {
        *self = Self::new();
    }

    /// Clear restyle flags and damage.
    fn clear_flags_and_damage(&mut self) {
        self.damage = RestyleDamage::empty();
        self.flags = RestyleFlags::empty();
    }

    /// Returns whether this element or any ancestor is going to be
    /// reconstructed.
    pub fn reconstructed_self_or_ancestor(&self) -> bool {
        self.reconstructed_ancestor() ||
        self.damage.contains(RestyleDamage::reconstruct())
    }

    /// Returns whether any ancestor of this element was restyled.
    fn reconstructed_ancestor(&self) -> bool {
        self.flags.contains(ANCESTOR_WAS_RECONSTRUCTED)
    }

    /// Sets the flag that tells us whether we've reconstructed an ancestor.
    pub fn set_reconstructed_ancestor(&mut self) {
        // If it weren't for animation-only traversals, we could assert
        // `!self.reconstructed_ancestor()` here.
        self.flags.insert(ANCESTOR_WAS_RECONSTRUCTED);
    }

    /// Mark this element as restyled, which is useful to know whether we need
    /// to do a post-traversal.
    pub fn set_restyled(&mut self) {
        self.flags.insert(WAS_RESTYLED);
    }

    /// Mark this element as restyled, which is useful to know whether we need
    /// to do a post-traversal.
    pub fn is_restyle(&self) -> bool {
        self.flags.contains(WAS_RESTYLED)
    }

    /// Returns whether this element has been part of a restyle.
    pub fn contains_restyle_data(&self) -> bool {
        self.is_restyle() || !self.hint.is_empty() || !self.damage.is_empty()
    }
}

/// A lazily-allocated list of styles for eagerly-cascaded pseudo-elements.
///
/// We use an Arc so that sharing these styles via the style sharing cache does
/// not require duplicate allocations. We leverage the copy-on-write semantics of
/// Arc::make_mut(), which is free (i.e. does not require atomic RMU operations)
/// in servo_arc.
#[derive(Clone, Debug, Default)]
pub struct EagerPseudoStyles(Option<Arc<EagerPseudoArray>>);

#[derive(Debug, Default)]
struct EagerPseudoArray(EagerPseudoArrayInner);
type EagerPseudoArrayInner = [Option<Arc<ComputedValues>>; EAGER_PSEUDO_COUNT];

impl Deref for EagerPseudoArray {
    type Target = EagerPseudoArrayInner;
    fn deref(&self) -> &Self::Target {
        &self.0
    }
}

impl DerefMut for EagerPseudoArray {
    fn deref_mut(&mut self) -> &mut Self::Target {
        &mut self.0
    }
}

// Manually implement `Clone` here because the derived impl of `Clone` for
// array types assumes the value inside is `Copy`.
impl Clone for EagerPseudoArray {
    fn clone(&self) -> Self {
        let mut clone = Self::default();
        for i in 0..EAGER_PSEUDO_COUNT {
            clone[i] = self.0[i].clone();
        }
        clone
    }
}

impl EagerPseudoStyles {
    /// Returns whether there are any pseudo styles.
    pub fn is_empty(&self) -> bool {
        self.0.is_none()
    }

    /// Grabs a reference to the list of styles, if they exist.
    pub fn as_array(&self) -> Option<&EagerPseudoArrayInner> {
        match self.0 {
            None => None,
            Some(ref x) => Some(&x.0),
        }
    }

    /// Returns a reference to the style for a given eager pseudo, if it exists.
    pub fn get(&self, pseudo: &PseudoElement) -> Option<&Arc<ComputedValues>> {
        debug_assert!(pseudo.is_eager());
        self.0.as_ref().and_then(|p| p[pseudo.eager_index()].as_ref())
    }

    /// Sets the style for the eager pseudo.
    pub fn set(&mut self, pseudo: &PseudoElement, value: Arc<ComputedValues>) {
        if self.0.is_none() {
            self.0 = Some(Arc::new(Default::default()));
        }
        let arr = Arc::make_mut(self.0.as_mut().unwrap());
        arr[pseudo.eager_index()] = Some(value);
    }
}

/// The styles associated with a node, including the styles for any
/// pseudo-elements.
#[derive(Clone, Debug, Default)]
pub struct ElementStyles {
    /// The element's style.
    pub primary: Option<Arc<ComputedValues>>,
    /// A list of the styles for the element's eagerly-cascaded pseudo-elements.
    pub pseudos: EagerPseudoStyles,
}

impl ElementStyles {
    /// Returns the primary style.
    pub fn get_primary(&self) -> Option<&Arc<ComputedValues>> {
        self.primary.as_ref()
    }

    /// Returns the primary style.  Panic if no style available.
    pub fn primary(&self) -> &Arc<ComputedValues> {
        self.primary.as_ref().unwrap()
    }

    /// Whether this element `display` value is `none`.
    pub fn is_display_none(&self) -> bool {
        self.primary().get_box().clone_display() == display::T::none
    }
}

/// Style system data associated with an Element.
///
/// In Gecko, this hangs directly off the Element. Servo, this is embedded
/// inside of layout data, which itself hangs directly off the Element. In
/// both cases, it is wrapped inside an AtomicRefCell to ensure thread safety.
#[derive(Debug, Default)]
pub struct ElementData {
    /// The styles for the element and its pseudo-elements.
    pub styles: ElementStyles,

    /// Restyle state.
    pub restyle: RestyleData,
}

/// The kind of restyle that a single element should do.
#[derive(Debug)]
pub enum RestyleKind {
    /// We need to run selector matching plus re-cascade, that is, a full
    /// restyle.
    MatchAndCascade,
    /// We need to recascade with some replacement rule, such as the style
    /// attribute, or animation rules.
    CascadeWithReplacements(RestyleHint),
    /// We only need to recascade, for example, because only inherited
    /// properties in the parent changed.
    CascadeOnly,
}

impl ElementData {
    /// Invalidates style for this element, its descendants, and later siblings,
    /// based on the snapshot of the element that we took when attributes or
    /// state changed.
    pub fn invalidate_style_if_needed<'a, E: TElement>(
        &mut self,
        element: E,
        shared_context: &SharedStyleContext)
    {
        use invalidation::element::invalidator::TreeStyleInvalidator;

        debug!("invalidate_style_if_needed: {:?}, flags: {:?}, has_snapshot: {}, \
                handled_snapshot: {}, pseudo: {:?}",
                element,
                shared_context.traversal_flags,
                element.has_snapshot(),
                element.handled_snapshot(),
                element.implemented_pseudo_element());

        if element.has_snapshot() && !element.handled_snapshot() {
            let invalidator = TreeStyleInvalidator::new(
                element,
                Some(self),
                shared_context,
            );
            invalidator.invalidate();
            unsafe { element.set_handled_snapshot() }
            debug_assert!(element.handled_snapshot());
        }
    }

    /// Returns true if this element has styles.
    pub fn has_styles(&self) -> bool {
        self.styles.primary.is_some()
    }

    /// Returns whether we have any outstanding style invalidation.
    pub fn has_invalidations(&self) -> bool {
        self.restyle.hint.has_self_invalidations()
    }

    /// Returns the kind of restyling that we're going to need to do on this
    /// element, based of the stored restyle hint.
    pub fn restyle_kind(&self,
                        shared_context: &SharedStyleContext)
                        -> RestyleKind {
        debug_assert!(!self.has_styles() || self.has_invalidations(),
                      "Should've stopped earlier");
        if !self.has_styles() {
            debug_assert!(!shared_context.traversal_flags.for_animation_only(),
                          "Unstyled element shouldn't be traversed during \
                           animation-only traversal");
            return RestyleKind::MatchAndCascade;
        }

        let hint = self.restyle.hint;
        if shared_context.traversal_flags.for_animation_only() {
            // return either CascadeWithReplacements or CascadeOnly in case of
            // animation-only restyle.
            if hint.has_animation_hint() {
                return RestyleKind::CascadeWithReplacements(hint & RestyleHint::for_animations());
            }
            return RestyleKind::CascadeOnly;
        }

        if hint.match_self() {
            return RestyleKind::MatchAndCascade;
        }

        if hint.has_replacements() {
            debug_assert!(!hint.has_animation_hint(),
                          "Animation only restyle hint should have already processed");
            return RestyleKind::CascadeWithReplacements(hint & RestyleHint::replacements());
        }

        debug_assert!(hint.has_recascade_self(),
                      "We definitely need to do something!");
        return RestyleKind::CascadeOnly;
    }

    /// Return true if important rules are different.
    /// We use this to make sure the cascade of off-main thread animations is correct.
    /// Note: Ignore custom properties for now because we only support opacity and transform
    ///       properties for animations running on compositor. Actually, we only care about opacity
    ///       and transform for now, but it's fine to compare all properties and let the user
    ///       the check which properties do they want.
    ///       If it costs too much, get_properties_overriding_animations() should return a set
    ///       containing only opacity and transform properties.
    pub fn important_rules_are_different(&self,
                                         rules: &StrongRuleNode,
                                         guards: &StylesheetGuards) -> bool {
        debug_assert!(self.has_styles());
        let (important_rules, _custom) =
            self.styles.primary().rules().get_properties_overriding_animations(&guards);
        let (other_important_rules, _custom) = rules.get_properties_overriding_animations(&guards);
        important_rules != other_important_rules
    }

    /// Drops any restyle state from the element.
    pub fn clear_restyle_state(&mut self) {
        self.restyle.clear();
    }

    /// Drops restyle flags and damage from the element.
    pub fn clear_restyle_flags_and_damage(&mut self) {
        self.restyle.clear_flags_and_damage();
    }
}
<|endoftext|>


<commit_before>use crate::{
    logging, projects,
    pubsub::publish,
    telemetry,
    utils::{json, schemas},
};
use defaults::Defaults;
use eyre::{bail, Result};
use once_cell::sync::Lazy;
use schemars::{gen::SchemaGenerator, schema::Schema, JsonSchema};
use serde::{Deserialize, Serialize};
use std::{env, fs, io::Write, path::PathBuf};
use tokio::sync::Mutex;
use validator::Validate;

/// Get the directory where configuration data is stored
pub fn dir(ensure: bool) -> Result<PathBuf> {
    let config_base = dirs_next::config_dir().unwrap_or_else(|| env::current_dir().unwrap());
    let dir = match env::consts::OS {
        "macos" => config_base.join("Stencila"),
        "windows" => config_base.join("Stencila").join("Config"),
        _ => config_base.join("stencila"),
    };
    if ensure {
        fs::create_dir_all(&dir)?;
    }
    Ok(dir)
}

#[derive(Debug, JsonSchema, Serialize)]
#[serde(rename_all = "lowercase")]
enum ConfigEventType {
    Set,
    Reset,
}

/// An event associated with changes to the configuration
#[derive(Debug, JsonSchema, Serialize)]
#[schemars(deny_unknown_fields)]
struct ConfigEvent {
    /// The type of event
    #[serde(rename = "type")]
    type_: ConfigEventType,

    /// The configuration at the time of the event
    #[schemars(schema_with = "ConfigEvent::schema_config")]
    config: Config,
}

impl ConfigEvent {
    /// Generate the JSON Schema for the `config` property to avoid nesting
    fn schema_config(_generator: &mut SchemaGenerator) -> Schema {
        schemas::typescript("Config", true)
    }

    /// Publish an event.
    pub fn publish(type_: ConfigEventType, config: &Config) {
        let event = ConfigEvent {
            type_,
            config: config.clone(),
        };
        publish("config", &event)
    }
}

#[derive(Debug, Default, PartialEq, Clone, JsonSchema, Deserialize, Serialize, Validate)]
#[serde(default)]
#[schemars(deny_unknown_fields)]
pub struct Config {
    pub projects: projects::config::ProjectsConfig,

    pub logging: logging::config::LoggingConfig,

    pub telemetry: telemetry::config::TelemetryConfig,

    #[cfg(feature = "serve")]
    pub serve: crate::serve::config::ServeConfig,

    #[cfg(feature = "plugins")]
    pub plugins: crate::plugins::config::PluginsConfig,

    #[cfg(feature = "binaries")]
    pub binaries: crate::binaries::config::BinariesConfig,

    pub editors: EditorsConfig,

    #[cfg(feature = "upgrade")]
    pub upgrade: crate::upgrade::config::UpgradeConfig,
}

/// Editors
///
/// Configuration settings for document editors.
#[derive(Debug, Defaults, PartialEq, Clone, JsonSchema, Deserialize, Serialize)]
#[serde(default, rename_all = "camelCase")]
#[schemars(deny_unknown_fields)]
pub struct EditorsConfig {
    /// Default format for new documents
    #[def = "\"md\".to_string()"]
    pub default_format: String,

    /// Show line numbers
    #[def = "false"]
    pub line_numbers: bool,

    /// Enable wrapping of lines
    #[def = "true"]
    pub line_wrapping: bool,
}

impl Config {
    const CONFIG_FILE: &'static str = "config.toml";

    /// Get the path of the configuration file
    #[tracing::instrument]
    fn path() -> Result<PathBuf> {
        #[cfg(not(test))]
        return Ok(dir(true)?.join(Config::CONFIG_FILE));

        // When running tests, avoid messing with users existing config
        #[cfg(test)]
        {
            let path = std::env::temp_dir()
                .join("stencila")
                .join("test")
                .join(Config::CONFIG_FILE);
            fs::create_dir_all(path.parent().unwrap())?;
            Ok(path)
        }
    }

    /// Read a config from the configuration file
    #[tracing::instrument]
    pub fn load() -> Result<Config> {
        let config_file = Config::path()?;

        let content = if config_file.exists() {
            match fs::read_to_string(config_file.clone()) {
                Ok(content) => content,
                // If there was a problem reading the config file warn the user
                // but return an empty string.
                Err(error) => {
                    tracing::warn!("Error while reading config file: {}", error.to_string());
                    String::new()
                }
            }
        } else {
            // If there is no config file just return an empty string
            String::new()
        };

        let config = match toml::from_str(content.as_str()) {
            Ok(config) => config,
            // If there was a problem reading the config file. e.g syntax error, a change
            // in the enum variants for an option, then warn the user and return the
            // defaults instead. This avoids a "corrupt" config file from preventing the
            // user from doing anything.
            Err(error) => {
                tracing::warn!(
                "Error while parsing config file; use `stencila config reset all` or edit {} to fix; defaults will be used: {}",
                config_file.display(),
                error.to_string()
            );
                Config {
                    ..Default::default()
                }
            }
        };

        if let Err(errors) = config.validate() {
            tracing::error!(
                "Invalid config file; use `stencila config set`, `stencila config reset` or edit {} to fix.\n\n{}",
                config_file.display(),
                serde_json::to_string_pretty(&errors)?
            )
        }

        Ok(config)
    }

    /// Write a config to the configuration file
    #[tracing::instrument]
    pub fn write(&self) -> Result<()> {
        self.validate()?;

        let config_file = Config::path()?;
        let mut file = fs::File::create(config_file)?;
        file.write_all(toml::to_string(&self)?.as_bytes())?;
        Ok(())
    }

    /// Get a config property
    #[tracing::instrument]
    pub fn get(&self, pointer: Option<String>) -> Result<serde_json::Value> {
        match pointer {
            None => Ok(serde_json::to_value(self)?),
            Some(pointer) => {
                let config = serde_json::to_value(self)?;
                if let Some(part) = config.pointer(json::pointer(&pointer).as_str()) {
                    let json = serde_json::to_string(part)?;
                    let part: toml::Value = serde_json::from_str(&json)?;
                    Ok(serde_json::to_value(part)?)
                } else {
                    bail!("No configuration value at pointer: {}", pointer)
                }
            }
        }
    }

    /// Set a property and write to disk
    #[tracing::instrument]
    pub fn set(&mut self, pointer: &str, value: &str) -> Result<()> {
        // Serialize self to a JSON value and set property
        let mut config = serde_json::to_value(&self)?;
        if let Some(property) = config.pointer_mut(json::pointer(pointer).as_str()) {
            let value = match serde_json::from_str(value) {
                Ok(value) => value,
                Err(_) => serde_json::Value::String(value.into()),
            };
            *property = value;
        } else {
            bail!("No configuration value at pointer: {}", pointer)
        };

        // Now deserialize self from the JSON value
        *self = serde_json::from_value(config)?;

        ConfigEvent::publish(ConfigEventType::Set, self);
        self.write()
    }

    /// Reset one, or all, properties and write to disk
    #[tracing::instrument]
    pub fn reset(&mut self, property: &str) -> Result<()> {
        match property {
            "all" => *self = Config::default(),

            "logging" => self.logging = crate::logging::config::LoggingConfig::default(),

            #[cfg(feature = "plugins")]
            "plugins" => self.plugins = crate::plugins::config::PluginsConfig::default(),

            #[cfg(feature = "serve")]
            "serve" => self.serve = crate::serve::config::ServeConfig::default(),

            #[cfg(feature = "upgrade")]
            "upgrade" => self.upgrade = crate::upgrade::config::UpgradeConfig::default(),

            _ => bail!("No top level configuration property named: {}", property),
        }

        ConfigEvent::publish(ConfigEventType::Reset, self);
        self.write()
    }
}

/// A global config store
///
/// Previously, we loaded config on startup and then passed them to various
/// functions in other modules.
/// However, this proved complicated for things like `Project` and `Document`
/// file watching threads when they need access to the current config.
pub static CONFIG: Lazy<Mutex<Config>> =
    Lazy::new(|| Mutex::new(Config::load().expect("Unable to read config")));

/// Get the JSON Schema for the configuration
pub fn schemas() -> Result<serde_json::Value> {
    let schemas = serde_json::Value::Array(vec![
        schemas::generate::<Config>()?,
        schemas::generate::<ConfigEvent>()?,
    ]);
    Ok(schemas)
}

/// CLI options for the `config` command
#[cfg(feature = "cli")]
pub mod cli {
    use super::*;
    use crate::cli::display;
    use structopt::StructOpt;

    #[derive(Debug, StructOpt)]
    #[structopt(
        about = "Manage configuration settings",
        setting = structopt::clap::AppSettings::ColoredHelp,
        setting = structopt::clap::AppSettings::VersionlessSubcommands
    )]
    pub struct Command {
        #[structopt(subcommand)]
        pub action: Action,
    }

    #[derive(Debug, StructOpt)]
    #[structopt(
        setting = structopt::clap::AppSettings::DeriveDisplayOrder
    )]
    pub enum Action {
        Get(Get),
        Set(Set),
        Reset(Reset),

        #[structopt(
            about = "Get the directories used for config, cache etc",
            setting = structopt::clap::AppSettings::ColoredHelp
        )]
        Dirs,

        #[structopt(
            about = "Get JSON Schemas for configuration and associated types",
            setting = structopt::clap::AppSettings::ColoredHelp
        )]
        Schemas,
    }

    #[derive(Debug, StructOpt)]
    #[structopt(
        about = "Get configuration properties",
        setting = structopt::clap::AppSettings::ColoredHelp
    )]
    pub struct Get {
        /// A pointer to a config property e.g. `upgrade.auto`
        pub pointer: Option<String>,
    }

    #[derive(Debug, StructOpt)]
    #[structopt(
        about = "Set configuration properties",
        setting = structopt::clap::AppSettings::ColoredHelp
    )]
    pub struct Set {
        /// A pointer to a config property e.g. `upgrade.auto`
        pub pointer: String,

        /// The value to set the property to
        pub value: String,
    }

    #[derive(Debug, StructOpt)]
    #[structopt(
        about = "Reset configuration properties to their defaults",
        setting = structopt::clap::AppSettings::ColoredHelp
    )]
    pub struct Reset {
        /// The config property to reset. Use 'all' to reset the entire config.
        pub property: String,
    }

    pub async fn run(args: Command) -> display::Result {
        let Command { action } = args;

        let config = &mut *CONFIG.lock().await;

        match action {
            Action::Get(action) => {
                let Get { pointer } = action;
                let value = config.get(pointer)?;
                display::value(value)
            }
            Action::Set(action) => {
                let Set { pointer, value } = action;
                config.set(&pointer, &value)?;
                display::nothing()
            }
            Action::Reset(action) => {
                let Reset { property } = action;
                config.reset(&property)?;
                display::nothing()
            }
            Action::Dirs => {
                let config_dir = dir(false)?.display().to_string();
                let logs_dir = crate::logging::config::dir(false)?.display().to_string();
                let plugins_dir = crate::plugins::plugins_dir(false)?.display().to_string();
                let binaries_dir = crate::binaries::binaries_dir().display().to_string();
                let value = serde_json::json!({
                    "config": config_dir,
                    "logs": logs_dir,
                    "plugins": plugins_dir,
                    "binaries": binaries_dir
                });
                display::value(value)
            }
            Action::Schemas => {
                let value = schemas()?;
                display::value(value)
            }
        }
    }
}

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

    #[test]
    fn test_path() -> Result<()> {
        let path = Config::path()?;
        assert!(path.starts_with(std::env::temp_dir()));
        Ok(())
    }

    #[test]
    fn test_display() -> Result<()> {
        let config = Config {
            ..Default::default()
        };

        serde_json::from_value::<Config>(config.get(None)?)?;

        assert_eq!(
            config
                .get(Some("foo.bar".to_string()))
                .unwrap_err()
                .to_string(),
            "No configuration value at pointer: foo.bar"
        );

        Ok(())
    }

    #[cfg(feature = "upgrade")]
    #[test]
    fn test_set() -> Result<()> {
        let mut config = Config {
            ..Default::default()
        };

        config.set("upgrade.auto", "off")?;
        assert_eq!(config.upgrade.auto, "off");

        config.set("upgrade.verbose", "true")?;
        assert_eq!(config.upgrade.verbose, true);

        assert_eq!(
            config.set("foo.bar", "baz").unwrap_err().to_string(),
            "No configuration value at pointer: foo.bar"
        );

        Ok(())
    }

    #[cfg(feature = "upgrade")]
    #[test]
    fn test_reset() -> Result<()> {
        let mut config: Config = serde_json::from_value(serde_json::json!({
            "upgrade": {
                "auto": "off",
                "verbose": true,
                "confirm": true,
            }
        }))?;

        let default = Config {
            ..Default::default()
        };

        config.reset("upgrade")?;
        assert_eq!(config.upgrade.auto, default.upgrade.auto);
        assert_eq!(config.upgrade.verbose, default.upgrade.verbose);

        config.reset("all")?;
        assert_eq!(config, default);

        assert_eq!(
            config.reset("foo").unwrap_err().to_string(),
            "No top level configuration property named: foo"
        );

        Ok(())
    }

    #[cfg(feature = "cli")]
    #[tokio::test]
    async fn test_cli() -> Result<()> {
        use super::cli::*;

        run(Command {
            action: Action::Get(Get { pointer: None }),
        })
        .await?;

        run(Command {
            action: Action::Set(Set {
                pointer: "upgrade.confirm".to_string(),
                value: "true".to_string(),
            }),
        })
        .await?;

        run(Command {
            action: Action::Reset(Reset {
                property: "upgrade".to_string(),
            }),
        })
        .await?;

        run(Command {
            action: Action::Dirs,
        })
        .await?;

        Ok(())
    }
}
<commit_msg>shore(Config): Add show alias for CLI get command<commit_after>use crate::{
    logging, projects,
    pubsub::publish,
    telemetry,
    utils::{json, schemas},
};
use defaults::Defaults;
use eyre::{bail, Result};
use once_cell::sync::Lazy;
use schemars::{gen::SchemaGenerator, schema::Schema, JsonSchema};
use serde::{Deserialize, Serialize};
use std::{env, fs, io::Write, path::PathBuf};
use tokio::sync::Mutex;
use validator::Validate;

/// Get the directory where configuration data is stored
pub fn dir(ensure: bool) -> Result<PathBuf> {
    let config_base = dirs_next::config_dir().unwrap_or_else(|| env::current_dir().unwrap());
    let dir = match env::consts::OS {
        "macos" => config_base.join("Stencila"),
        "windows" => config_base.join("Stencila").join("Config"),
        _ => config_base.join("stencila"),
    };
    if ensure {
        fs::create_dir_all(&dir)?;
    }
    Ok(dir)
}

#[derive(Debug, JsonSchema, Serialize)]
#[serde(rename_all = "lowercase")]
enum ConfigEventType {
    Set,
    Reset,
}

/// An event associated with changes to the configuration
#[derive(Debug, JsonSchema, Serialize)]
#[schemars(deny_unknown_fields)]
struct ConfigEvent {
    /// The type of event
    #[serde(rename = "type")]
    type_: ConfigEventType,

    /// The configuration at the time of the event
    #[schemars(schema_with = "ConfigEvent::schema_config")]
    config: Config,
}

impl ConfigEvent {
    /// Generate the JSON Schema for the `config` property to avoid nesting
    fn schema_config(_generator: &mut SchemaGenerator) -> Schema {
        schemas::typescript("Config", true)
    }

    /// Publish an event.
    pub fn publish(type_: ConfigEventType, config: &Config) {
        let event = ConfigEvent {
            type_,
            config: config.clone(),
        };
        publish("config", &event)
    }
}

#[derive(Debug, Default, PartialEq, Clone, JsonSchema, Deserialize, Serialize, Validate)]
#[serde(default)]
#[schemars(deny_unknown_fields)]
pub struct Config {
    pub projects: projects::config::ProjectsConfig,

    pub logging: logging::config::LoggingConfig,

    pub telemetry: telemetry::config::TelemetryConfig,

    #[cfg(feature = "serve")]
    pub serve: crate::serve::config::ServeConfig,

    #[cfg(feature = "plugins")]
    pub plugins: crate::plugins::config::PluginsConfig,

    #[cfg(feature = "binaries")]
    pub binaries: crate::binaries::config::BinariesConfig,

    pub editors: EditorsConfig,

    #[cfg(feature = "upgrade")]
    pub upgrade: crate::upgrade::config::UpgradeConfig,
}

/// Editors
///
/// Configuration settings for document editors.
#[derive(Debug, Defaults, PartialEq, Clone, JsonSchema, Deserialize, Serialize)]
#[serde(default, rename_all = "camelCase")]
#[schemars(deny_unknown_fields)]
pub struct EditorsConfig {
    /// Default format for new documents
    #[def = "\"md\".to_string()"]
    pub default_format: String,

    /// Show line numbers
    #[def = "false"]
    pub line_numbers: bool,

    /// Enable wrapping of lines
    #[def = "true"]
    pub line_wrapping: bool,
}

impl Config {
    const CONFIG_FILE: &'static str = "config.toml";

    /// Get the path of the configuration file
    #[tracing::instrument]
    fn path() -> Result<PathBuf> {
        #[cfg(not(test))]
        return Ok(dir(true)?.join(Config::CONFIG_FILE));

        // When running tests, avoid messing with users existing config
        #[cfg(test)]
        {
            let path = std::env::temp_dir()
                .join("stencila")
                .join("test")
                .join(Config::CONFIG_FILE);
            fs::create_dir_all(path.parent().unwrap())?;
            Ok(path)
        }
    }

    /// Read a config from the configuration file
    #[tracing::instrument]
    pub fn load() -> Result<Config> {
        let config_file = Config::path()?;

        let content = if config_file.exists() {
            match fs::read_to_string(config_file.clone()) {
                Ok(content) => content,
                // If there was a problem reading the config file warn the user
                // but return an empty string.
                Err(error) => {
                    tracing::warn!("Error while reading config file: {}", error.to_string());
                    String::new()
                }
            }
        } else {
            // If there is no config file just return an empty string
            String::new()
        };

        let config = match toml::from_str(content.as_str()) {
            Ok(config) => config,
            // If there was a problem reading the config file. e.g syntax error, a change
            // in the enum variants for an option, then warn the user and return the
            // defaults instead. This avoids a "corrupt" config file from preventing the
            // user from doing anything.
            Err(error) => {
                tracing::warn!(
                "Error while parsing config file; use `stencila config reset all` or edit {} to fix; defaults will be used: {}",
                config_file.display(),
                error.to_string()
            );
                Config {
                    ..Default::default()
                }
            }
        };

        if let Err(errors) = config.validate() {
            tracing::error!(
                "Invalid config file; use `stencila config set`, `stencila config reset` or edit {} to fix.\n\n{}",
                config_file.display(),
                serde_json::to_string_pretty(&errors)?
            )
        }

        Ok(config)
    }

    /// Write a config to the configuration file
    #[tracing::instrument]
    pub fn write(&self) -> Result<()> {
        self.validate()?;

        let config_file = Config::path()?;
        let mut file = fs::File::create(config_file)?;
        file.write_all(toml::to_string(&self)?.as_bytes())?;
        Ok(())
    }

    /// Get a config property
    #[tracing::instrument]
    pub fn get(&self, pointer: Option<String>) -> Result<serde_json::Value> {
        match pointer {
            None => Ok(serde_json::to_value(self)?),
            Some(pointer) => {
                let config = serde_json::to_value(self)?;
                if let Some(part) = config.pointer(json::pointer(&pointer).as_str()) {
                    let json = serde_json::to_string(part)?;
                    let part: toml::Value = serde_json::from_str(&json)?;
                    Ok(serde_json::to_value(part)?)
                } else {
                    bail!("No configuration value at pointer: {}", pointer)
                }
            }
        }
    }

    /// Set a property and write to disk
    #[tracing::instrument]
    pub fn set(&mut self, pointer: &str, value: &str) -> Result<()> {
        // Serialize self to a JSON value and set property
        let mut config = serde_json::to_value(&self)?;
        if let Some(property) = config.pointer_mut(json::pointer(pointer).as_str()) {
            let value = match serde_json::from_str(value) {
                Ok(value) => value,
                Err(_) => serde_json::Value::String(value.into()),
            };
            *property = value;
        } else {
            bail!("No configuration value at pointer: {}", pointer)
        };

        // Now deserialize self from the JSON value
        *self = serde_json::from_value(config)?;

        ConfigEvent::publish(ConfigEventType::Set, self);
        self.write()
    }

    /// Reset one, or all, properties and write to disk
    #[tracing::instrument]
    pub fn reset(&mut self, property: &str) -> Result<()> {
        match property {
            "all" => *self = Config::default(),

            "logging" => self.logging = crate::logging::config::LoggingConfig::default(),

            #[cfg(feature = "plugins")]
            "plugins" => self.plugins = crate::plugins::config::PluginsConfig::default(),

            #[cfg(feature = "serve")]
            "serve" => self.serve = crate::serve::config::ServeConfig::default(),

            #[cfg(feature = "upgrade")]
            "upgrade" => self.upgrade = crate::upgrade::config::UpgradeConfig::default(),

            _ => bail!("No top level configuration property named: {}", property),
        }

        ConfigEvent::publish(ConfigEventType::Reset, self);
        self.write()
    }
}

/// A global config store
///
/// Previously, we loaded config on startup and then passed them to various
/// functions in other modules.
/// However, this proved complicated for things like `Project` and `Document`
/// file watching threads when they need access to the current config.
pub static CONFIG: Lazy<Mutex<Config>> =
    Lazy::new(|| Mutex::new(Config::load().expect("Unable to read config")));

/// Get the JSON Schema for the configuration
pub fn schemas() -> Result<serde_json::Value> {
    let schemas = serde_json::Value::Array(vec![
        schemas::generate::<Config>()?,
        schemas::generate::<ConfigEvent>()?,
    ]);
    Ok(schemas)
}

/// CLI options for the `config` command
#[cfg(feature = "cli")]
pub mod cli {
    use super::*;
    use crate::cli::display;
    use structopt::StructOpt;

    #[derive(Debug, StructOpt)]
    #[structopt(
        about = "Manage configuration settings",
        setting = structopt::clap::AppSettings::ColoredHelp,
        setting = structopt::clap::AppSettings::VersionlessSubcommands
    )]
    pub struct Command {
        #[structopt(subcommand)]
        pub action: Action,
    }

    #[derive(Debug, StructOpt)]
    #[structopt(
        setting = structopt::clap::AppSettings::DeriveDisplayOrder
    )]
    pub enum Action {
        Get(Get),
        Set(Set),
        Reset(Reset),

        #[structopt(
            about = "Get the directories used for config, cache etc",
            setting = structopt::clap::AppSettings::ColoredHelp
        )]
        Dirs,

        #[structopt(
            about = "Get JSON Schemas for configuration and associated types",
            setting = structopt::clap::AppSettings::ColoredHelp
        )]
        Schemas,
    }

    #[derive(Debug, StructOpt)]
    #[structopt(
        about = "Get configuration properties",
        // Provide `show` alias for consistency with `documents` and `projects`
        aliases = &["show"],
        setting = structopt::clap::AppSettings::ColoredHelp
    )]
    pub struct Get {
        /// A pointer to a config property e.g. `upgrade.auto`
        pub pointer: Option<String>,
    }

    #[derive(Debug, StructOpt)]
    #[structopt(
        about = "Set configuration properties",
        setting = structopt::clap::AppSettings::ColoredHelp
    )]
    pub struct Set {
        /// A pointer to a config property e.g. `upgrade.auto`
        pub pointer: String,

        /// The value to set the property to
        pub value: String,
    }

    #[derive(Debug, StructOpt)]
    #[structopt(
        about = "Reset configuration properties to their defaults",
        setting = structopt::clap::AppSettings::ColoredHelp
    )]
    pub struct Reset {
        /// The config property to reset. Use 'all' to reset the entire config.
        pub property: String,
    }

    pub async fn run(args: Command) -> display::Result {
        let Command { action } = args;

        let config = &mut *CONFIG.lock().await;

        match action {
            Action::Get(action) => {
                let Get { pointer } = action;
                let value = config.get(pointer)?;
                display::value(value)
            }
            Action::Set(action) => {
                let Set { pointer, value } = action;
                config.set(&pointer, &value)?;
                display::nothing()
            }
            Action::Reset(action) => {
                let Reset { property } = action;
                config.reset(&property)?;
                display::nothing()
            }
            Action::Dirs => {
                let config_dir = dir(false)?.display().to_string();
                let logs_dir = crate::logging::config::dir(false)?.display().to_string();
                let plugins_dir = crate::plugins::plugins_dir(false)?.display().to_string();
                let binaries_dir = crate::binaries::binaries_dir().display().to_string();
                let value = serde_json::json!({
                    "config": config_dir,
                    "logs": logs_dir,
                    "plugins": plugins_dir,
                    "binaries": binaries_dir
                });
                display::value(value)
            }
            Action::Schemas => {
                let value = schemas()?;
                display::value(value)
            }
        }
    }
}

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

    #[test]
    fn test_path() -> Result<()> {
        let path = Config::path()?;
        assert!(path.starts_with(std::env::temp_dir()));
        Ok(())
    }

    #[test]
    fn test_display() -> Result<()> {
        let config = Config {
            ..Default::default()
        };

        serde_json::from_value::<Config>(config.get(None)?)?;

        assert_eq!(
            config
                .get(Some("foo.bar".to_string()))
                .unwrap_err()
                .to_string(),
            "No configuration value at pointer: foo.bar"
        );

        Ok(())
    }

    #[cfg(feature = "upgrade")]
    #[test]
    fn test_set() -> Result<()> {
        let mut config = Config {
            ..Default::default()
        };

        config.set("upgrade.auto", "off")?;
        assert_eq!(config.upgrade.auto, "off");

        config.set("upgrade.verbose", "true")?;
        assert_eq!(config.upgrade.verbose, true);

        assert_eq!(
            config.set("foo.bar", "baz").unwrap_err().to_string(),
            "No configuration value at pointer: foo.bar"
        );

        Ok(())
    }

    #[cfg(feature = "upgrade")]
    #[test]
    fn test_reset() -> Result<()> {
        let mut config: Config = serde_json::from_value(serde_json::json!({
            "upgrade": {
                "auto": "off",
                "verbose": true,
                "confirm": true,
            }
        }))?;

        let default = Config {
            ..Default::default()
        };

        config.reset("upgrade")?;
        assert_eq!(config.upgrade.auto, default.upgrade.auto);
        assert_eq!(config.upgrade.verbose, default.upgrade.verbose);

        config.reset("all")?;
        assert_eq!(config, default);

        assert_eq!(
            config.reset("foo").unwrap_err().to_string(),
            "No top level configuration property named: foo"
        );

        Ok(())
    }

    #[cfg(feature = "cli")]
    #[tokio::test]
    async fn test_cli() -> Result<()> {
        use super::cli::*;

        run(Command {
            action: Action::Get(Get { pointer: None }),
        })
        .await?;

        run(Command {
            action: Action::Set(Set {
                pointer: "upgrade.confirm".to_string(),
                value: "true".to_string(),
            }),
        })
        .await?;

        run(Command {
            action: Action::Reset(Reset {
                property: "upgrade".to_string(),
            }),
        })
        .await?;

        run(Command {
            action: Action::Dirs,
        })
        .await?;

        Ok(())
    }
}
<|endoftext|>


<commit_before>use super::*;

/// Builder for a [`GlyphBrush`](struct.GlyphBrush.html).
///
/// # Example
///
/// ```no_run
/// # extern crate gfx;
/// # extern crate gfx_window_glutin;
/// # extern crate glutin;
/// extern crate gfx_glyph;
/// use gfx_glyph::GlyphBrushBuilder;
/// # fn main() {
/// # let events_loop = glutin::EventsLoop::new();
/// # let (_window, _device, gfx_factory, _gfx_target, _main_depth) =
/// #     gfx_window_glutin::init::<gfx::format::Srgba8, gfx::format::Depth>(
/// #         glutin::WindowBuilder::new(),
/// #         glutin::ContextBuilder::new(),
/// #         &events_loop);
///
/// let dejavu: &[u8] = include_bytes!("../../fonts/DejaVuSans.ttf");
/// let mut glyph_brush = GlyphBrushBuilder::using_font_bytes(dejavu).build(gfx_factory.clone());
/// # let _ = glyph_brush;
/// # }
/// ```
pub struct GlyphBrushBuilder<'a, H = DefaultSectionHasher> {
    inner: glyph_brush::GlyphBrushBuilder<'a, H>,
    depth_test: gfx::state::Depth,
    texture_filter_method: texture::FilterMethod,
}

impl<'a> GlyphBrushBuilder<'a> {
    /// Specifies the default font data used to render glyphs.
    /// Referenced with `FontId(0)`, which is default.
    #[inline]
    pub fn using_font_bytes<B: Into<SharedBytes<'a>>>(font_0_data: B) -> Self {
        Self::using_font(Font::from_bytes(font_0_data).unwrap())
    }

    #[inline]
    pub fn using_fonts_bytes<B, V>(font_data: V) -> Self
    where
        B: Into<SharedBytes<'a>>,
        V: Into<Vec<B>>,
    {
        Self::using_fonts(
            font_data
                .into()
                .into_iter()
                .map(|data| Font::from_bytes(data).unwrap())
                .collect::<Vec<_>>(),
        )
    }

    /// Specifies the default font used to render glyphs.
    /// Referenced with `FontId(0)`, which is default.
    #[inline]
    pub fn using_font(font_0: Font<'a>) -> Self {
        Self::using_fonts(vec![font_0])
    }

    pub fn using_fonts<V: Into<Vec<Font<'a>>>>(fonts: V) -> Self {
        GlyphBrushBuilder {
            inner: glyph_brush::GlyphBrushBuilder::using_fonts(fonts),
            depth_test: gfx::preset::depth::PASS_TEST,
            texture_filter_method: texture::FilterMethod::Bilinear,
        }
    }
}

impl<'a, H: BuildHasher> GlyphBrushBuilder<'a, H> {
    delegate_glyph_brush_builder_fns!(inner);

    /// Sets the depth test to use on the text section **z** values.
    ///
    /// Defaults to: *Always pass the depth test, never write to the depth buffer write*
    ///
    /// # Example
    ///
    /// ```no_run
    /// # extern crate gfx;
    /// # extern crate gfx_glyph;
    /// # use gfx_glyph::GlyphBrushBuilder;
    /// # fn main() {
    /// # let some_font: &[u8] = include_bytes!("../../fonts/DejaVuSans.ttf");
    /// GlyphBrushBuilder::using_font_bytes(some_font)
    ///     .depth_test(gfx::preset::depth::LESS_EQUAL_WRITE)
    ///     // ...
    /// # ;
    /// # }
    /// ```
    pub fn depth_test(mut self, depth_test: gfx::state::Depth) -> Self {
        self.depth_test = depth_test;
        self
    }

    /// Sets the texture filtering method.
    ///
    /// Defaults to `Bilinear`
    ///
    /// # Example
    /// ```no_run
    /// # extern crate gfx;
    /// # extern crate gfx_glyph;
    /// # use gfx_glyph::GlyphBrushBuilder;
    /// # fn main() {
    /// # let some_font: &[u8] = include_bytes!("../../fonts/DejaVuSans.ttf");
    /// GlyphBrushBuilder::using_font_bytes(some_font)
    ///     .texture_filter_method(gfx::texture::FilterMethod::Scale)
    ///     // ...
    /// # ;
    /// # }
    /// ```
    pub fn texture_filter_method(mut self, filter_method: texture::FilterMethod) -> Self {
        self.texture_filter_method = filter_method;
        self
    }

    /// Sets the section hasher. `GlyphBrush` cannot handle absolute section hash collisions
    /// so use a good hash algorithm.
    ///
    /// This hasher is used to distinguish sections, rather than for hashmap internal use.
    ///
    /// Defaults to [seahash](https://docs.rs/seahash).
    ///
    /// # Example
    /// ```no_run
    /// # extern crate gfx;
    /// # extern crate gfx_glyph;
    /// # extern crate seahash;
    /// # use gfx_glyph::GlyphBrushBuilder;
    /// # fn main() {
    /// # let some_font: &[u8] = include_bytes!("../../fonts/DejaVuSans.ttf");
    /// # type SomeOtherBuildHasher = ::std::hash::BuildHasherDefault<seahash::SeaHasher>;
    /// GlyphBrushBuilder::using_font_bytes(some_font)
    ///     .section_hasher(SomeOtherBuildHasher::default())
    ///     // ...
    /// # ;
    /// # }
    /// ```
    pub fn section_hasher<T: BuildHasher>(self, section_hasher: T) -> GlyphBrushBuilder<'a, T> {
        GlyphBrushBuilder {
            inner: self.inner.section_hasher(section_hasher),
            depth_test: self.depth_test,
            texture_filter_method: self.texture_filter_method,
        }
    }

    /// Builds a `GlyphBrush` using the input gfx factory
    pub fn build<R, F>(self, mut factory: F) -> GlyphBrush<'a, R, F, H>
    where
        R: gfx::Resources,
        F: gfx::Factory<R>,
    {
        let (cache_width, cache_height) = self.inner.initial_cache_size;
        let font_cache_tex = create_texture(&mut factory, cache_width, cache_height).unwrap();
        let program = factory
            .link_program(
                include_bytes!("shader/vert.glsl"),
                include_bytes!("shader/frag.glsl"),
            ).unwrap();

        GlyphBrush {
            font_cache_tex,
            texture_filter_method: self.texture_filter_method,
            glyph_brush: self.inner.build(),

            factory,
            program,
            draw_cache: None,

            depth_test: self.depth_test,
        }
    }
}
<commit_msg>Fix doc test compile<commit_after>use super::*;

/// Builder for a [`GlyphBrush`](struct.GlyphBrush.html).
///
/// # Example
///
/// ```no_run
/// # extern crate gfx;
/// # extern crate gfx_window_glutin;
/// # extern crate glutin;
/// extern crate gfx_glyph;
/// use gfx_glyph::GlyphBrushBuilder;
/// # fn main() {
/// # let events_loop = glutin::EventsLoop::new();
/// # let (_window, _device, gfx_factory, _gfx_target, _main_depth) =
/// #     gfx_window_glutin::init::<gfx::format::Srgba8, gfx::format::Depth>(
/// #         glutin::WindowBuilder::new(),
/// #         glutin::ContextBuilder::new(),
/// #         &events_loop);
///
/// let dejavu: &[u8] = include_bytes!("../../fonts/DejaVuSans.ttf");
/// let mut glyph_brush = GlyphBrushBuilder::using_font_bytes(dejavu).build(gfx_factory.clone());
/// # let _ = glyph_brush;
/// # }
/// ```
pub struct GlyphBrushBuilder<'a, H = DefaultSectionHasher> {
    inner: glyph_brush::GlyphBrushBuilder<'a, H>,
    depth_test: gfx::state::Depth,
    texture_filter_method: texture::FilterMethod,
}

impl<'a> GlyphBrushBuilder<'a> {
    /// Specifies the default font data used to render glyphs.
    /// Referenced with `FontId(0)`, which is default.
    #[inline]
    pub fn using_font_bytes<B: Into<SharedBytes<'a>>>(font_0_data: B) -> Self {
        Self::using_font(Font::from_bytes(font_0_data).unwrap())
    }

    #[inline]
    pub fn using_fonts_bytes<B, V>(font_data: V) -> Self
    where
        B: Into<SharedBytes<'a>>,
        V: Into<Vec<B>>,
    {
        Self::using_fonts(
            font_data
                .into()
                .into_iter()
                .map(|data| Font::from_bytes(data).unwrap())
                .collect::<Vec<_>>(),
        )
    }

    /// Specifies the default font used to render glyphs.
    /// Referenced with `FontId(0)`, which is default.
    #[inline]
    pub fn using_font(font_0: Font<'a>) -> Self {
        Self::using_fonts(vec![font_0])
    }

    pub fn using_fonts<V: Into<Vec<Font<'a>>>>(fonts: V) -> Self {
        GlyphBrushBuilder {
            inner: glyph_brush::GlyphBrushBuilder::using_fonts(fonts),
            depth_test: gfx::preset::depth::PASS_TEST,
            texture_filter_method: texture::FilterMethod::Bilinear,
        }
    }
}

impl<'a, H: BuildHasher> GlyphBrushBuilder<'a, H> {
    delegate_glyph_brush_builder_fns!(inner);

    /// Sets the depth test to use on the text section **z** values.
    ///
    /// Defaults to: *Always pass the depth test, never write to the depth buffer write*
    ///
    /// # Example
    ///
    /// ```no_run
    /// # extern crate gfx;
    /// # extern crate gfx_glyph;
    /// # use gfx_glyph::GlyphBrushBuilder;
    /// # fn main() {
    /// # let some_font: &[u8] = include_bytes!("../../fonts/DejaVuSans.ttf");
    /// GlyphBrushBuilder::using_font_bytes(some_font)
    ///     .depth_test(gfx::preset::depth::LESS_EQUAL_WRITE)
    ///     // ...
    /// # ;
    /// # }
    /// ```
    pub fn depth_test(mut self, depth_test: gfx::state::Depth) -> Self {
        self.depth_test = depth_test;
        self
    }

    /// Sets the texture filtering method.
    ///
    /// Defaults to `Bilinear`
    ///
    /// # Example
    /// ```no_run
    /// # extern crate gfx;
    /// # extern crate gfx_glyph;
    /// # use gfx_glyph::GlyphBrushBuilder;
    /// # fn main() {
    /// # let some_font: &[u8] = include_bytes!("../../fonts/DejaVuSans.ttf");
    /// GlyphBrushBuilder::using_font_bytes(some_font)
    ///     .texture_filter_method(gfx::texture::FilterMethod::Scale)
    ///     // ...
    /// # ;
    /// # }
    /// ```
    pub fn texture_filter_method(mut self, filter_method: texture::FilterMethod) -> Self {
        self.texture_filter_method = filter_method;
        self
    }

    /// Sets the section hasher. `GlyphBrush` cannot handle absolute section hash collisions
    /// so use a good hash algorithm.
    ///
    /// This hasher is used to distinguish sections, rather than for hashmap internal use.
    ///
    /// Defaults to [seahash](https://docs.rs/seahash).
    ///
    /// # Example
    /// ```no_run
    /// # extern crate gfx;
    /// # extern crate gfx_glyph;
    /// # use gfx_glyph::GlyphBrushBuilder;
    /// # fn main() {
    /// # let some_font: &[u8] = include_bytes!("../../fonts/DejaVuSans.ttf");
    /// # type SomeOtherBuildHasher = std::collections::hash_map::RandomState;
    /// GlyphBrushBuilder::using_font_bytes(some_font)
    ///     .section_hasher(SomeOtherBuildHasher::default())
    ///     // ...
    /// # ;
    /// # }
    /// ```
    pub fn section_hasher<T: BuildHasher>(self, section_hasher: T) -> GlyphBrushBuilder<'a, T> {
        GlyphBrushBuilder {
            inner: self.inner.section_hasher(section_hasher),
            depth_test: self.depth_test,
            texture_filter_method: self.texture_filter_method,
        }
    }

    /// Builds a `GlyphBrush` using the input gfx factory
    pub fn build<R, F>(self, mut factory: F) -> GlyphBrush<'a, R, F, H>
    where
        R: gfx::Resources,
        F: gfx::Factory<R>,
    {
        let (cache_width, cache_height) = self.inner.initial_cache_size;
        let font_cache_tex = create_texture(&mut factory, cache_width, cache_height).unwrap();
        let program = factory
            .link_program(
                include_bytes!("shader/vert.glsl"),
                include_bytes!("shader/frag.glsl"),
            ).unwrap();

        GlyphBrush {
            font_cache_tex,
            texture_filter_method: self.texture_filter_method,
            glyph_brush: self.inner.build(),

            factory,
            program,
            draw_cache: None,

            depth_test: self.depth_test,
        }
    }
}
<|endoftext|>


<commit_before>fn main() {
    let mut s = String::new();

    let data = "random stuff";

    let s_to_string = data.to_string();

    let s_from = String::from("Initial content");

    s.push_str("pushed");

    s.push('l');
}
<commit_msg>Example of concatenating strings with + operator<commit_after>fn main() {
    let mut s = String::new();

    let data = "random stuff";

    let s_to_string = data.to_string();

    let s_from = String::from("Initial content");

    s.push_str("pushed");

    s.push('l');

    let s1 = String::from("First");
    let s2 = String::from("Second");

    let s3 = s1 + &s2;  // s1 is no longer valid

}
<|endoftext|>


<commit_before>#[cfg(feature = "model")]
use reqwest::Client as ReqwestClient;

#[cfg(feature = "model")]
use crate::internal::prelude::*;
use crate::model::id::AttachmentId;

/// A file uploaded with a message. Not to be confused with [`Embed`]s.
///
/// [`Embed`]: super::Embed
#[derive(Clone, Debug, Deserialize, Serialize)]
#[non_exhaustive]
pub struct Attachment {
    /// The unique ID given to this attachment.
    pub id: AttachmentId,
    /// The filename of the file that was uploaded. This is equivalent to what
    /// the uploader had their file named.
    pub filename: String,
    /// If the attachment is an image, then the height of the image is provided.
    pub height: Option<u64>,
    /// The proxy URL.
    pub proxy_url: String,
    /// The size of the file in bytes.
    pub size: u64,
    /// The URL of the uploaded attachment.
    pub url: String,
    /// If the attachment is an image, then the width of the image is provided.
    pub width: Option<u64>,
    /// The attachment's [media type].
    ///
    /// [media type]: https://en.wikipedia.org/wiki/Media_type
    pub content_type: Option<String>,
}

#[cfg(feature = "model")]
impl Attachment {
    /// If this attachment is an image, then a tuple of the width and height
    /// in pixels is returned.
    pub fn dimensions(&self) -> Option<(u64, u64)> {
        self.width.and_then(|width| self.height.map(|height| (width, height)))
    }

    /// Downloads the attachment, returning back a vector of bytes.
    ///
    /// # Examples
    ///
    /// Download all of the attachments associated with a [`Message`]:
    ///
    /// ```rust,no_run
    /// # #[cfg(feature = "client")]
    /// # async fn run() -> Result<(), Box<dyn std::error::Error>> {
    /// use std::io::Write;
    /// use std::path::Path;
    ///
    /// use serenity::model::prelude::*;
    /// use serenity::prelude::*;
    /// use tokio::fs::File;
    /// use tokio::io::AsyncWriteExt;
    ///
    /// struct Handler;
    ///
    /// #[serenity::async_trait]
    /// impl EventHandler for Handler {
    ///     async fn message(&self, context: Context, mut message: Message) {
    ///         for attachment in message.attachments {
    ///             let content = match attachment.download().await {
    ///                 Ok(content) => content,
    ///                 Err(why) => {
    ///                     println!("Error downloading attachment: {:?}", why);
    ///                     let _ =
    ///                         message.channel_id.say(&context, "Error downloading attachment").await;
    ///
    ///                     return;
    ///                 },
    ///             };
    ///
    ///             let mut file = match File::create(&attachment.filename).await {
    ///                 Ok(file) => file,
    ///                 Err(why) => {
    ///                     println!("Error creating file: {:?}", why);
    ///                     let _ = message.channel_id.say(&context, "Error creating file").await;
    ///
    ///                     return;
    ///                 },
    ///             };
    ///
    ///             if let Err(why) = file.write_all(&content).await {
    ///                 println!("Error writing to file: {:?}", why);
    ///
    ///                 return;
    ///             }
    ///
    ///             let _ = message
    ///                 .channel_id
    ///                 .say(&context, &format!("Saved {:?}", attachment.filename))
    ///                 .await;
    ///         }
    ///     }
    ///
    ///     async fn ready(&self, _: Context, ready: Ready) {
    ///         println!("{} is connected!", ready.user.name);
    ///     }
    /// }
    /// let token = std::env::var("DISCORD_TOKEN")?;
    /// let mut client = Client::builder(&token).event_handler(Handler).await?;
    ///
    /// client.start().await?;
    /// #     Ok(())
    /// # }
    /// ```
    ///
    /// # Errors
    ///
    /// Returns an [`Error::Io`] when there is a problem reading the contents
    /// of the HTTP response.
    ///
    /// Returns an [`Error::Http`] when there is a problem retrieving the
    /// attachment.
    ///
    /// [`Error::Http`]: crate::Error::Http
    /// [`Error::Io`]: crate::Error::Io
    /// [`Message`]: super::Message
    pub async fn download(&self) -> Result<Vec<u8>> {
        let reqwest = ReqwestClient::new();

        Ok(reqwest.get(&self.url).send().await?.bytes().await?.into_iter().collect::<Vec<u8>>())
    }
}
<commit_msg>Improve the download of `Attachment`s by calling `Bytes::to_vec` (#1670)<commit_after>#[cfg(feature = "model")]
use reqwest::Client as ReqwestClient;

#[cfg(feature = "model")]
use crate::internal::prelude::*;
use crate::model::id::AttachmentId;

/// A file uploaded with a message. Not to be confused with [`Embed`]s.
///
/// [`Embed`]: super::Embed
#[derive(Clone, Debug, Deserialize, Serialize)]
#[non_exhaustive]
pub struct Attachment {
    /// The unique ID given to this attachment.
    pub id: AttachmentId,
    /// The filename of the file that was uploaded. This is equivalent to what
    /// the uploader had their file named.
    pub filename: String,
    /// If the attachment is an image, then the height of the image is provided.
    pub height: Option<u64>,
    /// The proxy URL.
    pub proxy_url: String,
    /// The size of the file in bytes.
    pub size: u64,
    /// The URL of the uploaded attachment.
    pub url: String,
    /// If the attachment is an image, then the width of the image is provided.
    pub width: Option<u64>,
    /// The attachment's [media type].
    ///
    /// [media type]: https://en.wikipedia.org/wiki/Media_type
    pub content_type: Option<String>,
}

#[cfg(feature = "model")]
impl Attachment {
    /// If this attachment is an image, then a tuple of the width and height
    /// in pixels is returned.
    pub fn dimensions(&self) -> Option<(u64, u64)> {
        self.width.and_then(|width| self.height.map(|height| (width, height)))
    }

    /// Downloads the attachment, returning back a vector of bytes.
    ///
    /// # Examples
    ///
    /// Download all of the attachments associated with a [`Message`]:
    ///
    /// ```rust,no_run
    /// # #[cfg(feature = "client")]
    /// # async fn run() -> Result<(), Box<dyn std::error::Error>> {
    /// use std::io::Write;
    /// use std::path::Path;
    ///
    /// use serenity::model::prelude::*;
    /// use serenity::prelude::*;
    /// use tokio::fs::File;
    /// use tokio::io::AsyncWriteExt;
    ///
    /// struct Handler;
    ///
    /// #[serenity::async_trait]
    /// impl EventHandler for Handler {
    ///     async fn message(&self, context: Context, mut message: Message) {
    ///         for attachment in message.attachments {
    ///             let content = match attachment.download().await {
    ///                 Ok(content) => content,
    ///                 Err(why) => {
    ///                     println!("Error downloading attachment: {:?}", why);
    ///                     let _ =
    ///                         message.channel_id.say(&context, "Error downloading attachment").await;
    ///
    ///                     return;
    ///                 },
    ///             };
    ///
    ///             let mut file = match File::create(&attachment.filename).await {
    ///                 Ok(file) => file,
    ///                 Err(why) => {
    ///                     println!("Error creating file: {:?}", why);
    ///                     let _ = message.channel_id.say(&context, "Error creating file").await;
    ///
    ///                     return;
    ///                 },
    ///             };
    ///
    ///             if let Err(why) = file.write_all(&content).await {
    ///                 println!("Error writing to file: {:?}", why);
    ///
    ///                 return;
    ///             }
    ///
    ///             let _ = message
    ///                 .channel_id
    ///                 .say(&context, &format!("Saved {:?}", attachment.filename))
    ///                 .await;
    ///         }
    ///     }
    ///
    ///     async fn ready(&self, _: Context, ready: Ready) {
    ///         println!("{} is connected!", ready.user.name);
    ///     }
    /// }
    /// let token = std::env::var("DISCORD_TOKEN")?;
    /// let mut client = Client::builder(&token).event_handler(Handler).await?;
    ///
    /// client.start().await?;
    /// #     Ok(())
    /// # }
    /// ```
    ///
    /// # Errors
    ///
    /// Returns an [`Error::Io`] when there is a problem reading the contents
    /// of the HTTP response.
    ///
    /// Returns an [`Error::Http`] when there is a problem retrieving the
    /// attachment.
    ///
    /// [`Error::Http`]: crate::Error::Http
    /// [`Error::Io`]: crate::Error::Io
    /// [`Message`]: super::Message
    pub async fn download(&self) -> Result<Vec<u8>> {
        let reqwest = ReqwestClient::new();
        let bytes = reqwest.get(&self.url).send().await?.bytes().await?;
        Ok(bytes.to_vec())
    }
}
<|endoftext|>


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

#[macro_use] extern crate clap;

extern crate image;

extern crate onmf;

// `name` and `long` have the same lifetime
fn named_usize_arg<'n, 'h, 'g, 'p, 'r>(name: &'n str, help: &'h str) -> clap::Arg<'n, 'n, 'h, 'g, 'p, 'r> {
    // we don't own `name` but we now own `error_message`
    // which contains `name`.
    let error_message = format!(
        "value provided for argument `{}` is not a valid unsigned integer number",
        name);
    clap::Arg::with_name(name)
         .long(name)
         .help(help)
         // `move` makes it such that the  closure owns `error_message`
         .validator(move |val| {
             match val.parse::<usize>() {
                 Ok(_) => Ok(()),
                 // the closure owns `error_message`.
                 // we can't move `error_message` out of the closure.
                 // we have to clone it instead.
                 Err(_) => Err(error_message.clone())
             }
         })
         .takes_value(true)
}

fn main() {
    let default_nhidden: usize = 10;
    let help_nhidden = format!("number of hidden (latent) variables to find (defaults to `{}`)", default_nhidden);

    let help_input_image_path = "path to image on the the grayscale amounts in the columns of which the NMF should be applied";

    let matches =
        clap::App::new("image-columns-nmf")
            .version(&crate_version!()[..])
            .about("applies NMF on the grayscale amounts of the columns of an image")
            .arg(clap::Arg::with_name("input-image-path")
                 .index(1)
                 .help(&help_input_image_path)
                 .required(true))
            .arg(named_usize_arg("nhidden", &help_nhidden))
            .get_matches();

    let nhidden: usize = value_t!(matches.value_of("nhidden"), usize).unwrap_or(default_nhidden);
    let input_image_path_string = matches.value_of("input-image-path").unwrap();
    let input_image_path = Path::new(input_image_path_string);

    println!("nhidden = {}", nhidden);
    println!("input_image_path = {:?}", input_image_path);
    println!("input_image_path.parent() = {:?}", input_image_path.parent());
    println!("input_image_path.file_name() = {:?}", input_image_path.file_name());
    println!("input_image_path.file_stem() = {:?}", input_image_path.file_stem());
    println!("input_image_path.extension() = {:?}", input_image_path.extension());

    // let mut input_image_file = std::fs::File::open(&input_image_path).unwrap();

    let image = image::open(&input_image_path).unwrap();

    // to grayscale
    let image_luma = image.to_luma();

    // TODO read the file into an array you can pass into nmf

    // TODO
    // let mut ortho_nmf = onmf::OrthogonalNMFBlas::new_random01(
    //     nhidden, nobserved, nsamples);

    // TODO iterate nmf
}
<commit_msg>image-columns-nmf: load image data into array to pass to NMF<commit_after>use std::path::Path;

#[macro_use] extern crate clap;

extern crate image;

extern crate onmf;
use onmf::helpers::Array2D;

// `name` and `long` have the same lifetime
fn named_usize_arg<'n, 'h, 'g, 'p, 'r>(name: &'n str, help: &'h str) -> clap::Arg<'n, 'n, 'h, 'g, 'p, 'r> {
    // we don't own `name` but we now own `error_message`
    // which contains `name`.
    let error_message = format!(
        "value provided for argument `{}` is not a valid unsigned integer number",
        name);
    clap::Arg::with_name(name)
         .long(name)
         .help(help)
         // `move` makes it such that the  closure owns `error_message`
         .validator(move |val| {
             match val.parse::<usize>() {
                 Ok(_) => Ok(()),
                 // the closure owns `error_message`.
                 // we can't move `error_message` out of the closure.
                 // we have to clone it instead.
                 Err(_) => Err(error_message.clone())
             }
         })
         .takes_value(true)
}

fn main() {
    let default_nhidden: usize = 10;
    let help_nhidden = format!("number of hidden (latent) variables to find (defaults to `{}`)", default_nhidden);

    let help_input_image_path = "path to image on the the grayscale amounts in the columns of which the NMF should be applied";

    let matches =
        clap::App::new("image-columns-nmf")
            .version(&crate_version!()[..])
            .about("applies NMF on the grayscale amounts of the columns of an image")
            .arg(clap::Arg::with_name("input-image-path")
                 .index(1)
                 .help(&help_input_image_path)
                 .required(true))
            .arg(named_usize_arg("nhidden", &help_nhidden))
            .get_matches();

    let nhidden: usize = value_t!(matches.value_of("nhidden"), usize).unwrap_or(default_nhidden);
    let input_image_path_string = matches.value_of("input-image-path").unwrap();
    let input_image_path = Path::new(input_image_path_string);

    println!("nhidden = {}", nhidden);
    println!("input_image_path = {:?}", input_image_path);
    println!("input_image_path.parent() = {:?}", input_image_path.parent());
    println!("input_image_path.file_name() = {:?}", input_image_path.file_name());
    println!("input_image_path.file_stem() = {:?}", input_image_path.file_stem());
    println!("input_image_path.extension() = {:?}", input_image_path.extension());

    // let mut input_image_file = std::fs::File::open(&input_image_path).unwrap();

    let image = image::open(&input_image_path).unwrap();

    // to grayscale
    let image_luma = image.to_luma();

    println!("image_luma.dimensions() = {:?}", image_luma.dimensions());
    let (width, height) = image_luma.dimensions();

    let nobserved = height as usize;
    let nsamples = width as usize;
    println!("nobserved = {}", nobserved);
    println!("nsamples = {}", nsamples);

    // the indexing of image_array is reversed in respect to image_luma
    let mut samples = Array2D::<f32>::zeros((nsamples, nobserved));

    for (x, y, pixel) in image_luma.enumerate_pixels() {
        let index = (x as usize, y as usize);

        // convert from 0-255 to 0.-1.
        samples[index] = (pixel[0] as f32) / 255.;
    }

    // TODO
    // let mut ortho_nmf = onmf::OrthogonalNMFBlas::new_random01(
    //     nhidden, nobserved, nsamples);

    // TODO iterate nmf
}
<|endoftext|>


<commit_before>use super::StockPrice;
use utils::console::*;

use hyper::Client;
use hyper::header::Connection;
use hyper::net::HttpsConnector;
use hyper_native_tls::NativeTlsClient;
use select::document::Document;
use select::predicate::{Predicate, Attr, Class, Name};
use serde_urlencoded;
use std::io::Read;

static BASE_URL: &'static str = "https://www.comdirect.de/inf/search/all.html";

error_chain! {
    errors {
        ComdirectScrapingFailed(info: String) {
            description("Did not find information in HTML document")
            display("Did not find {} in HTML document", info)
        }
    }
}

pub fn scrape_stock_price(search: String) -> Result<StockPrice> {
    let parameters = &[("SEARCH_VALUE", search) ];
    let parameters_enc = serde_urlencoded::to_string(&parameters)
        .chain_err(|| "Could not encode URL parameters")?;
    let url = format!("{}?{}", BASE_URL, parameters_enc);

    let body = get_stock_page(&url).unwrap();
    let stock_price = parse_stock_price(&body)?;

    Ok(stock_price)
}

fn get_stock_page(url: &str) -> Result<Vec<u8>> {
    let ssl = NativeTlsClient::new().chain_err(|| "Could not create TLS client")?;
    let connector = HttpsConnector::new(ssl);
    let client = Client::with_connector(connector);
    info("Sending search request ...");
    let mut response = client.get(url).header(Connection::close()).send()
        .chain_err(|| "Could not send request")?;

    let mut body = Vec::new();
    let size = response.read_to_end(&mut body)
        .chain_err(|| "Could not read response body")?;
    info(format!("Received {} bytes body.", size));

    Ok(body)
}

fn parse_stock_price(body: &[u8]) -> Result<StockPrice> {
    let document = Document::from_read(body).chain_err(|| "Could not parse HTML in response body")?;

    let no_exact_match = document.find(Class("Informer")).nth(0).is_some();
    if no_exact_match {
        bail!("Could not find exact match for your search");
    }

    let name = document.find(Name("h1")).nth(0)
        .ok_or_else(|| ErrorKind::ComdirectScrapingFailed("name".to_string()))?.text();
    let price_currency = document.find(Class("price")).nth(0)
        .ok_or_else(|| ErrorKind::ComdirectScrapingFailed("stock price".to_string()))?.text();
    let date = document.find(Attr("id", "keyelement_kurs_update").descendant(Class("date"))).last()
        .ok_or_else(|| ErrorKind::ComdirectScrapingFailed("stock price date".to_string()))?.text();
    let wkn = document.find(Class("clearfix").descendant(Name("table")).descendant(Name("tr")).descendant(Class("right"))).last()
        .ok_or_else(|| ErrorKind::ComdirectScrapingFailed("stock WKN".to_string()))?.text();

    let mut pc_split = price_currency.split_whitespace();
    let price = pc_split.nth(0)
        .ok_or_else(|| ErrorKind::ComdirectScrapingFailed("price".to_string()))?
        .trim()
        .replace(",", ".") // decimal separation
        .parse()
        .chain_err(|| ErrorKind::ComdirectScrapingFailed("price".to_string()))?;
    let currency = pc_split.nth(0).ok_or_else(|| ErrorKind::ComdirectScrapingFailed("currency".to_string()))?;

    let stock_price = StockPrice {
        name: name.trim().to_string(),
        wkn: wkn.trim().to_string(),
        date: date.to_string(),
        price: price,
        currency: currency.trim().to_string(),
    };

    Ok(stock_price)
}

#[cfg(test)]
mod test {
    use super::*;
    use std::io::Read;
    use std::fs::File;

    fn get_stock_page(file: &str) -> Result<Vec<u8>, std::io::Error> {
        let mut f = File::open(file)?;
        let mut buffer = Vec::new();
        f.read_to_end(&mut buffer)?;

        Ok(buffer)
    }

    #[test]
    fn test_parse_ok() {
        let body = get_stock_page("test/data/stocks/deutsche_bank.html").unwrap();
        let db = parse_stock_price(&body).unwrap();

        assert_eq!(db.name, "Deutsche Bank AG Namens-Aktien o.N.");
        assert_eq!(db.wkn, "514000");
        assert_eq!(db.date, "10.03.17\u{a0}\u{a0}17:35:07\u{a0}Uhr");
        assert_eq!(db.price, "18,26 EUR");
    }

    #[test]
    fn test_parse_no_exact_match() {
        let body = get_stock_page("test/data/stocks/no_exact_match.html").unwrap();
        let db = parse_stock_price(&body);

        assert!(db.is_err());
    }
}
<commit_msg>Fix tests for comdirect scraping<commit_after>use super::StockPrice;
use utils::console::*;

use hyper::Client;
use hyper::header::Connection;
use hyper::net::HttpsConnector;
use hyper_native_tls::NativeTlsClient;
use select::document::Document;
use select::predicate::{Predicate, Attr, Class, Name};
use serde_urlencoded;
use std::io::Read;

static BASE_URL: &'static str = "https://www.comdirect.de/inf/search/all.html";

error_chain! {
    errors {
        ComdirectScrapingFailed(info: String) {
            description("Did not find information in HTML document")
            display("Did not find {} in HTML document", info)
        }
    }
}

pub fn scrape_stock_price(search: String) -> Result<StockPrice> {
    let parameters = &[("SEARCH_VALUE", search) ];
    let parameters_enc = serde_urlencoded::to_string(&parameters)
        .chain_err(|| "Could not encode URL parameters")?;
    let url = format!("{}?{}", BASE_URL, parameters_enc);

    let body = get_stock_page(&url).unwrap();
    let stock_price = parse_stock_price(&body)?;

    Ok(stock_price)
}

fn get_stock_page(url: &str) -> Result<Vec<u8>> {
    let ssl = NativeTlsClient::new().chain_err(|| "Could not create TLS client")?;
    let connector = HttpsConnector::new(ssl);
    let client = Client::with_connector(connector);
    info("Sending search request ...");
    let mut response = client.get(url).header(Connection::close()).send()
        .chain_err(|| "Could not send request")?;

    let mut body = Vec::new();
    let size = response.read_to_end(&mut body)
        .chain_err(|| "Could not read response body")?;
    info(format!("Received {} bytes body.", size));

    Ok(body)
}

fn parse_stock_price(body: &[u8]) -> Result<StockPrice> {
    let document = Document::from_read(body).chain_err(|| "Could not parse HTML in response body")?;

    let no_exact_match = document.find(Class("Informer")).nth(0).is_some();
    if no_exact_match {
        bail!("Could not find exact match for your search");
    }

    let name = document.find(Name("h1")).nth(0)
        .ok_or_else(|| ErrorKind::ComdirectScrapingFailed("name".to_string()))?.text();
    let price_currency = document.find(Class("price")).nth(0)
        .ok_or_else(|| ErrorKind::ComdirectScrapingFailed("stock price".to_string()))?.text();
    let date = document.find(Attr("id", "keyelement_kurs_update").descendant(Class("date"))).last()
        .ok_or_else(|| ErrorKind::ComdirectScrapingFailed("stock price date".to_string()))?.text();
    let wkn = document.find(Class("clearfix").descendant(Name("table")).descendant(Name("tr")).descendant(Class("right"))).last()
        .ok_or_else(|| ErrorKind::ComdirectScrapingFailed("stock WKN".to_string()))?.text();

    let mut pc_split = price_currency.split_whitespace();
    let price = pc_split.nth(0)
        .ok_or_else(|| ErrorKind::ComdirectScrapingFailed("price".to_string()))?
        .trim()
        .replace(",", ".") // decimal separation
        .parse()
        .chain_err(|| ErrorKind::ComdirectScrapingFailed("price".to_string()))?;
    let currency = pc_split.nth(0).ok_or_else(|| ErrorKind::ComdirectScrapingFailed("currency".to_string()))?;

    let stock_price = StockPrice {
        name: name.trim().to_string(),
        wkn: wkn.trim().to_string(),
        date: date.to_string(),
        price: price,
        currency: currency.trim().to_string(),
    };

    Ok(stock_price)
}

#[cfg(test)]
mod test {
    use super::*;
    use std::io::Read;
    use std::fs::File;

    fn get_stock_page(file: &str) -> Result<Vec<u8>> {
        let mut f = File::open(file).unwrap();
        let mut buffer = Vec::new();
        f.read_to_end(&mut buffer).unwrap();

        Ok(buffer)
    }

    #[test]
    fn test_parse_ok() {
        let body = get_stock_page("test/data/stocks/deutsche_bank.html").unwrap();
        let db = parse_stock_price(&body).unwrap();

        assert_eq!(db.name, "Deutsche Bank AG Namens-Aktien o.N.");
        assert_eq!(db.wkn, "514000");
        assert_eq!(db.date, "10.03.17\u{a0}\u{a0}17:35:07\u{a0}Uhr");
        assert_eq!(db.price, 18.26f32);
        assert_eq!(db.currency, "EUR");
    }

    #[test]
    fn test_parse_no_exact_match() {
        let body = get_stock_page("test/data/stocks/no_exact_match.html").unwrap();
        let db = parse_stock_price(&body);

        assert!(db.is_err());
    }
}
<|endoftext|>