<commit_before>// Copyright © 2016, Canal TP and/or its affiliates. All rights reserved.
//
// This file is part of Navitia,
//     the software to build cool stuff with public transport.
//
// Hope you'll enjoy and contribute to this project,
//     powered by Canal TP (www.canaltp.fr).
// Help us simplify mobility and open public transport:
//     a non ending quest to the responsive locomotion way of traveling!
//
// LICENCE: This program is free software; you can redistribute it
// and/or modify it under the terms of the GNU Affero General Public
// License as published by the Free Software Foundation, either
// version 3 of the License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful, but
// WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public
// License along with this program. If not, see
// <http://www.gnu.org/licenses/>.
//
// Stay tuned using
// twitter @navitia
// IRC #navitia on freenode
// https://groups.google.com/d/forum/navitia
// www.navitia.io

use failure::ResultExt;
use mimir::rubber::IndexSettings;
use mimirsbrunn::stops::*;
use slog_scope::{info, warn};
use std::cmp::Ordering;
use std::collections::{hash_map::DefaultHasher, HashMap};
use std::hash::BuildHasherDefault;
use std::path::PathBuf;
use structopt::StructOpt;
use transit_model::objects as navitia;
use typed_index_collection::Idx;

#[derive(Debug, StructOpt)]
struct Args {
    /// NTFS directory.
    #[structopt(short = "i", long = "input", parse(from_os_str), default_value = ".")]
    input: PathBuf,
    /// Name of the dataset.
    #[structopt(short = "d", long = "dataset", default_value = "fr")]
    dataset: String,
    /// Elasticsearch parameters.
    #[structopt(
        short = "c",
        long = "connection-string",
        default_value = "http://localhost:9200/munin"
    )]
    connection_string: String,
    /// Deprecated option.
    #[structopt(short = "C", long = "city-level")]
    city_level: Option<String>,
    /// Number of shards for the es index
    #[structopt(short = "s", long = "nb-shards", default_value = "1")]
    nb_shards: usize,
    /// Number of replicas for the es index
    #[structopt(short = "r", long = "nb-replicas", default_value = "1")]
    nb_replicas: usize,
}

fn get_lines(idx: Idx<navitia::StopArea>, navitia: &transit_model::Model) -> Vec<mimir::Line> {
    use mimir::FromTransitModel;
    let mut lines: Vec<_> = navitia
        .get_corresponding_from_idx(idx)
        .into_iter()
        .map(|l_idx| mimir::Line::from_transit_model(l_idx, navitia))
        .collect();

    // we want the lines to be sorted in a way where
    // line-3 is before line-11, so be use a human_sort
    lines.sort_by(|lhs, rhs| {
        match (&lhs.sort_order, &rhs.sort_order) {
            (None, Some(_)) => Ordering::Greater,
            (Some(_), None) => Ordering::Less,
            (Some(s), Some(o)) => s.cmp(o),
            (None, None) => Ordering::Equal,
        }
        .then_with(|| match (&lhs.code, &rhs.code) {
            (Some(l), Some(r)) => human_sort::compare(l, r),
            _ => Ordering::Equal,
        })
        .then_with(|| human_sort::compare(&lhs.name, &rhs.name))
    });
    lines
}

fn to_mimir(
    idx: Idx<navitia::StopArea>,
    stop_area: &navitia::StopArea,
    navitia: &transit_model::Model,
) -> mimir::Stop {
    let commercial_modes = navitia
        .get_corresponding_from_idx(idx)
        .into_iter()
        .map(|cm_idx| mimir::CommercialMode {
            id: mimir::objects::normalize_id(
                "commercial_mode",
                &navitia.commercial_modes[cm_idx].id,
            ),
            name: navitia.commercial_modes[cm_idx].name.clone(),
        })
        .collect();
    let physical_modes = navitia
        .get_corresponding_from_idx(idx)
        .into_iter()
        .map(|pm_idx| mimir::PhysicalMode {
            id: mimir::objects::normalize_id("physical_mode", &navitia.physical_modes[pm_idx].id),
            name: navitia.physical_modes[pm_idx].name.clone(),
        })
        .collect();
    let comments = stop_area
        .comment_links
        .iter()
        .map(|com_id| mimir::Comment {
            name: navitia.comments.get(com_id).unwrap().name.clone(),
        })
        .collect();
    let feed_publishers = navitia
        .get_corresponding_from_idx(idx)
        .into_iter()
        .map(|contrib_idx| mimir::FeedPublisher {
            id: navitia.contributors[contrib_idx].id.clone(),
            name: navitia.contributors[contrib_idx].name.clone(),
            license: navitia.contributors[contrib_idx]
                .license
                .clone()
                .unwrap_or_else(|| "".into()),
            url: navitia.contributors[contrib_idx]
                .website
                .clone()
                .unwrap_or_else(|| "".into()),
        })
        .collect();
    let coord = mimir::Coord::new(stop_area.coord.lon, stop_area.coord.lat);

    let lines = get_lines(idx, navitia);

    mimir::Stop {
        id: mimir::objects::normalize_id("stop_area", &stop_area.id),
        label: stop_area.name.clone(),
        name: stop_area.name.clone(),
        coord,
        approx_coord: Some(coord.into()),
        commercial_modes,
        physical_modes,
        lines,
        comments,
        timezone: stop_area
            .timezone
            .clone()
            .map(chrono_tz::Tz::name)
            .map(str::to_owned)
            .unwrap_or_default(),
        codes: stop_area
            .codes
            .iter()
            .map(|&(ref t, ref v)| mimir::Code {
                name: t.clone(),
                value: v.clone(),
            })
            .collect(),
        properties: stop_area
            .object_properties
            .iter()
            .map(|&(ref k, ref v)| mimir::Property {
                key: k.clone(),
                value: v.clone(),
            })
            .collect(),
        feed_publishers,
        ..Default::default()
    }
}

fn main() {
    mimirsbrunn::utils::launch_run(run);
}

fn run(args: Args) -> Result<(), transit_model::Error> {
    info!("Launching ntfs2mimir...");

    if args.city_level.is_some() {
        warn!("city-level option is deprecated, it now has no effect.");
    }

    let navitia = transit_model::ntfs::read(&args.input)?;

    let nb_stop_points: HashMap<String, u32, BuildHasherDefault<DefaultHasher>> = navitia
        .stop_areas
        .iter()
        .map(|(idx, sa)| {
            let id = mimir::objects::normalize_id("stop_area", &sa.id);
            let nb_stop_points = navitia
                .get_corresponding_from_idx::<_, navitia::StopPoint>(idx)
                .len();
            (id, nb_stop_points as u32)
        })
        .collect();
    let mut stops: Vec<mimir::Stop> = navitia
        .stop_areas
        .iter()
        .map(|(idx, sa)| to_mimir(idx, sa, &navitia))
        .collect();
    initialize_weights(stops.iter_mut(), &nb_stop_points);

    let index_settings = IndexSettings {
        nb_shards: args.nb_shards,
        nb_replicas: args.nb_replicas,
    };

    import_stops(
        stops,
        &args.connection_string,
        &args.dataset,
        index_settings,
    )
    .with_context(|_| {
        format!(
            "Error occurred when importing stops into {} on {}",
            args.dataset, args.connection_string
        )
    })?;
    Ok(())
}

#[test]
fn test_bad_connection_string() {
    let args = Args {
        input: PathBuf::from("./tests/fixtures/ntfs"),
        connection_string: "http://localhost:1".to_string(),
        dataset: "bob".to_string(),
        city_level: None,
        nb_replicas: 1,
        nb_shards: 1,
    };
    let causes = run(args)
        .unwrap_err()
        .iter_chain()
        .map(|cause| format!("{}", cause))
        .collect::<Vec<String>>();
    assert_eq!(
        causes,
        [
            "Error occurred when importing stops into bob on http://localhost:1".to_string(),
            "Error: HTTP Error while creating template template_addr".to_string(),
        ]
    );
}

#[test]
fn test_bad_file() {
    let args = Args {
        input: PathBuf::from("./tests/fixtures/not_exist"),
        connection_string: "http://localhost:9200".to_string(),
        dataset: "bob".to_string(),
        city_level: None,
        nb_replicas: 1,
        nb_shards: 1,
    };
    let causes = run(args)
        .unwrap_err()
        .iter_chain()
        .map(|cause| format!("{}", cause))
        .collect::<Vec<String>>();
    assert_eq!(
        causes,
        [
            "Error reading \"./tests/fixtures/not_exist/contributors.txt\"",
            "No such file or directory (os error 2)",
        ]
    );
}
<commit_msg>[FIX]: Avoid unwrap<commit_after>// Copyright © 2016, Canal TP and/or its affiliates. All rights reserved.
//
// This file is part of Navitia,
//     the software to build cool stuff with public transport.
//
// Hope you'll enjoy and contribute to this project,
//     powered by Canal TP (www.canaltp.fr).
// Help us simplify mobility and open public transport:
//     a non ending quest to the responsive locomotion way of traveling!
//
// LICENCE: This program is free software; you can redistribute it
// and/or modify it under the terms of the GNU Affero General Public
// License as published by the Free Software Foundation, either
// version 3 of the License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful, but
// WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public
// License along with this program. If not, see
// <http://www.gnu.org/licenses/>.
//
// Stay tuned using
// twitter @navitia
// IRC #navitia on freenode
// https://groups.google.com/d/forum/navitia
// www.navitia.io

use failure::ResultExt;
use mimir::rubber::IndexSettings;
use mimirsbrunn::stops::*;
use slog_scope::{info, warn};
use std::cmp::Ordering;
use std::collections::{hash_map::DefaultHasher, HashMap};
use std::hash::BuildHasherDefault;
use std::path::PathBuf;
use structopt::StructOpt;
use transit_model::objects as navitia;
use typed_index_collection::Idx;

#[derive(Debug, StructOpt)]
struct Args {
    /// NTFS directory.
    #[structopt(short = "i", long = "input", parse(from_os_str), default_value = ".")]
    input: PathBuf,
    /// Name of the dataset.
    #[structopt(short = "d", long = "dataset", default_value = "fr")]
    dataset: String,
    /// Elasticsearch parameters.
    #[structopt(
        short = "c",
        long = "connection-string",
        default_value = "http://localhost:9200/munin"
    )]
    connection_string: String,
    /// Deprecated option.
    #[structopt(short = "C", long = "city-level")]
    city_level: Option<String>,
    /// Number of shards for the es index
    #[structopt(short = "s", long = "nb-shards", default_value = "1")]
    nb_shards: usize,
    /// Number of replicas for the es index
    #[structopt(short = "r", long = "nb-replicas", default_value = "1")]
    nb_replicas: usize,
}

fn get_lines(idx: Idx<navitia::StopArea>, navitia: &transit_model::Model) -> Vec<mimir::Line> {
    use mimir::FromTransitModel;
    let mut lines: Vec<_> = navitia
        .get_corresponding_from_idx(idx)
        .into_iter()
        .map(|l_idx| mimir::Line::from_transit_model(l_idx, navitia))
        .collect();

    // we want the lines to be sorted in a way where
    // line-3 is before line-11, so be use a human_sort
    lines.sort_by(|lhs, rhs| {
        match (&lhs.sort_order, &rhs.sort_order) {
            (None, Some(_)) => Ordering::Greater,
            (Some(_), None) => Ordering::Less,
            (Some(s), Some(o)) => s.cmp(o),
            (None, None) => Ordering::Equal,
        }
        .then_with(|| match (&lhs.code, &rhs.code) {
            (Some(l), Some(r)) => human_sort::compare(l, r),
            _ => Ordering::Equal,
        })
        .then_with(|| human_sort::compare(&lhs.name, &rhs.name))
    });
    lines
}

fn to_mimir(
    idx: Idx<navitia::StopArea>,
    stop_area: &navitia::StopArea,
    navitia: &transit_model::Model,
) -> mimir::Stop {
    let commercial_modes = navitia
        .get_corresponding_from_idx(idx)
        .into_iter()
        .map(|cm_idx| mimir::CommercialMode {
            id: mimir::objects::normalize_id(
                "commercial_mode",
                &navitia.commercial_modes[cm_idx].id,
            ),
            name: navitia.commercial_modes[cm_idx].name.clone(),
        })
        .collect();
    let physical_modes = navitia
        .get_corresponding_from_idx(idx)
        .into_iter()
        .map(|pm_idx| mimir::PhysicalMode {
            id: mimir::objects::normalize_id("physical_mode", &navitia.physical_modes[pm_idx].id),
            name: navitia.physical_modes[pm_idx].name.clone(),
        })
        .collect();
    let comments = stop_area
        .comment_links
        .iter()
        .filter_map(|comment_id| {
            let res = navitia.comments.get(comment_id);
            if res.is_none() {
                warn!("Could not retrieve comments for id {}", comment_id);
            }
            res
        })
        .map(|comment| mimir::Comment {
            name: comment.name.clone(),
        })
        .collect();
    let feed_publishers = navitia
        .get_corresponding_from_idx(idx)
        .into_iter()
        .map(|contrib_idx| mimir::FeedPublisher {
            id: navitia.contributors[contrib_idx].id.clone(),
            name: navitia.contributors[contrib_idx].name.clone(),
            license: navitia.contributors[contrib_idx]
                .license
                .clone()
                .unwrap_or_else(|| "".into()),
            url: navitia.contributors[contrib_idx]
                .website
                .clone()
                .unwrap_or_else(|| "".into()),
        })
        .collect();
    let coord = mimir::Coord::new(stop_area.coord.lon, stop_area.coord.lat);

    let lines = get_lines(idx, navitia);

    mimir::Stop {
        id: mimir::objects::normalize_id("stop_area", &stop_area.id),
        label: stop_area.name.clone(),
        name: stop_area.name.clone(),
        coord,
        approx_coord: Some(coord.into()),
        commercial_modes,
        physical_modes,
        lines,
        comments,
        timezone: stop_area
            .timezone
            .clone()
            .map(chrono_tz::Tz::name)
            .map(str::to_owned)
            .unwrap_or_default(),
        codes: stop_area
            .codes
            .iter()
            .map(|&(ref t, ref v)| mimir::Code {
                name: t.clone(),
                value: v.clone(),
            })
            .collect(),
        properties: stop_area
            .object_properties
            .iter()
            .map(|&(ref k, ref v)| mimir::Property {
                key: k.clone(),
                value: v.clone(),
            })
            .collect(),
        feed_publishers,
        ..Default::default()
    }
}

fn main() {
    mimirsbrunn::utils::launch_run(run);
}

fn run(args: Args) -> Result<(), transit_model::Error> {
    info!("Launching ntfs2mimir...");

    if args.city_level.is_some() {
        warn!("city-level option is deprecated, it now has no effect.");
    }

    let navitia = transit_model::ntfs::read(&args.input)?;

    let nb_stop_points: HashMap<String, u32, BuildHasherDefault<DefaultHasher>> = navitia
        .stop_areas
        .iter()
        .map(|(idx, sa)| {
            let id = mimir::objects::normalize_id("stop_area", &sa.id);
            let nb_stop_points = navitia
                .get_corresponding_from_idx::<_, navitia::StopPoint>(idx)
                .len();
            (id, nb_stop_points as u32)
        })
        .collect();
    let mut stops: Vec<mimir::Stop> = navitia
        .stop_areas
        .iter()
        .map(|(idx, sa)| to_mimir(idx, sa, &navitia))
        .collect();
    initialize_weights(stops.iter_mut(), &nb_stop_points);

    let index_settings = IndexSettings {
        nb_shards: args.nb_shards,
        nb_replicas: args.nb_replicas,
    };

    import_stops(
        stops,
        &args.connection_string,
        &args.dataset,
        index_settings,
    )
    .with_context(|_| {
        format!(
            "Error occurred when importing stops into {} on {}",
            args.dataset, args.connection_string
        )
    })?;
    Ok(())
}

#[test]
fn test_bad_connection_string() {
    let args = Args {
        input: PathBuf::from("./tests/fixtures/ntfs"),
        connection_string: "http://localhost:1".to_string(),
        dataset: "bob".to_string(),
        city_level: None,
        nb_replicas: 1,
        nb_shards: 1,
    };
    let causes = run(args)
        .unwrap_err()
        .iter_chain()
        .map(|cause| format!("{}", cause))
        .collect::<Vec<String>>();
    assert_eq!(
        causes,
        [
            "Error occurred when importing stops into bob on http://localhost:1".to_string(),
            "Error: HTTP Error while creating template template_addr".to_string(),
        ]
    );
}

#[test]
fn test_bad_file() {
    let args = Args {
        input: PathBuf::from("./tests/fixtures/not_exist"),
        connection_string: "http://localhost:9200".to_string(),
        dataset: "bob".to_string(),
        city_level: None,
        nb_replicas: 1,
        nb_shards: 1,
    };
    let causes = run(args)
        .unwrap_err()
        .iter_chain()
        .map(|cause| format!("{}", cause))
        .collect::<Vec<String>>();
    assert_eq!(
        causes,
        [
            "Error reading \"./tests/fixtures/not_exist/contributors.txt\"",
            "No such file or directory (os error 2)",
        ]
    );
}
<|endoftext|>


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

use futures::prelude::*;
use futures::sync::mpsc::{self, Sender, Receiver};
use futures_cpupool::CpuPool;
use memmap::{Mmap, Protection};

use ::BATCH_FUTURE_BUFFER_SIZE;
use arc_slice::{self, ArcSlice};
use errors::*;
use marshal::{Hashed, Hasher, ObjectHash, SmallRecord, Tree};
use split::{self, Chunked};
use trace::BatchTrace;

/// A batch of files being marshalled.
pub struct Batch<T: BatchTrace = ()> {
    trace: T,

    marshal_tx: Sender<Hashed>,
    marshal_rx: Receiver<Hashed>,

    marshal_pool: CpuPool,

    len: usize,
}

impl<T: BatchTrace> Batch<T> {
    pub fn with_trace(marshal_pool: CpuPool, trace: T) -> Self {
        let (marshal_tx, marshal_rx) = mpsc::channel(BATCH_FUTURE_BUFFER_SIZE);

        Batch {
            trace,

            marshal_tx,
            marshal_rx,

            marshal_pool,

            len: 0,
        }
    }

    /// Read a file as a byte slice. This will memory-map the underlying file.
    ///
    /// * `file` - the file to read. *Must be opened with read permissions!*
    fn read(&mut self, file: &File) -> Result<ArcSlice> {
        Ok(
            arc_slice::mapped(Mmap::open(file, Protection::Read)?),
        )
    }

    /// Read a file as a byte slice. This will memory-map the underlying file.
    fn read_path<P: AsRef<Path>>(&mut self, path: P) -> Result<ArcSlice> {
        self.read(&File::open(path)?)
    }

    /// Chunk the file at the given path.
    pub fn chunk_file<P: AsRef<Path>>(&mut self, path: P) -> Result<Chunked> {
        let slice = self.read_path(path)?;
        let mut trace = self.trace.on_split(slice.len() as u64);
        let chunked = split::chunk_with_trace(slice, &mut trace);

        Ok(chunked)
    }

    /// Marshal a chunked file into a tree of objects, returning the marshalled objects along with
    /// the hash of the root object.
    pub fn marshal_file(
        &mut self,
        chunked: Chunked,
    ) -> Box<Future<Item = ObjectHash, Error = Error> + Send> {
        let tree = Tree::load(chunked.to_vec().into_iter().map(SmallRecord::from));
        let tree_len = tree.len();

        self.len += tree_len;

        let marshal = tree.marshal(Hasher::with_trace(
            self.marshal_tx.clone(),
            self.trace.on_marshal(tree_len),
        ));

        Box::new(self.marshal_pool.spawn(marshal))
    }

    /// The total size of the batch, in chunks.
    pub fn len(&self) -> usize {
        self.len
    }

    /// Convert this `Batch` into a stream of `Hashed` objects.
    pub fn into_stream(self) -> Box<Stream<Item = Hashed, Error = Error> + Send> {
        Box::new(self.marshal_rx.map_err(|()| "Upstream error!".into()))
    }
}
<commit_msg>Add module docs for batch.<commit_after>//! # `batch` - batches of files to marshal.
//!
//! A `Batch` represents a collection of files which are being marshalled and a stream of hashed
//! output objects. For a single `Batch`, there should correspond a set of valid marshalled files
//! and a stream of valid hashed objects produced from marshalling/hashing the batched files.
//!
//! Marshalling files in a `Batch` will spawn files to a specified threadpool provided by the
//! `Context`.

use std::fs::File;
use std::path::Path;

use futures::prelude::*;
use futures::sync::mpsc::{self, Sender, Receiver};
use futures_cpupool::CpuPool;
use memmap::{Mmap, Protection};

use ::BATCH_FUTURE_BUFFER_SIZE;
use arc_slice::{self, ArcSlice};
use errors::*;
use marshal::{Hashed, Hasher, ObjectHash, SmallRecord, Tree};
use split::{self, Chunked};
use trace::BatchTrace;

/// A batch of files being marshalled.
pub struct Batch<T: BatchTrace = ()> {
    trace: T,

    marshal_tx: Sender<Hashed>,
    marshal_rx: Receiver<Hashed>,

    marshal_pool: CpuPool,

    len: usize,
}

impl<T: BatchTrace> Batch<T> {
    pub fn with_trace(marshal_pool: CpuPool, trace: T) -> Self {
        let (marshal_tx, marshal_rx) = mpsc::channel(BATCH_FUTURE_BUFFER_SIZE);

        Batch {
            trace,

            marshal_tx,
            marshal_rx,

            marshal_pool,

            len: 0,
        }
    }

    /// Read a file as a byte slice. This will memory-map the underlying file.
    ///
    /// * `file` - the file to read. *Must be opened with read permissions!*
    fn read(&mut self, file: &File) -> Result<ArcSlice> {
        Ok(
            arc_slice::mapped(Mmap::open(file, Protection::Read)?),
        )
    }

    /// Read a file as a byte slice. This will memory-map the underlying file.
    fn read_path<P: AsRef<Path>>(&mut self, path: P) -> Result<ArcSlice> {
        self.read(&File::open(path)?)
    }

    /// Chunk the file at the given path.
    pub fn chunk_file<P: AsRef<Path>>(&mut self, path: P) -> Result<Chunked> {
        let slice = self.read_path(path)?;
        let mut trace = self.trace.on_split(slice.len() as u64);
        let chunked = split::chunk_with_trace(slice, &mut trace);

        Ok(chunked)
    }

    /// Marshal a chunked file into a tree of objects, returning the marshalled objects along with
    /// the hash of the root object.
    pub fn marshal_file(
        &mut self,
        chunked: Chunked,
    ) -> Box<Future<Item = ObjectHash, Error = Error> + Send> {
        let tree = Tree::load(chunked.to_vec().into_iter().map(SmallRecord::from));
        let tree_len = tree.len();

        self.len += tree_len;

        let marshal = tree.marshal(Hasher::with_trace(
            self.marshal_tx.clone(),
            self.trace.on_marshal(tree_len),
        ));

        Box::new(self.marshal_pool.spawn(marshal))
    }

    /// The total size of the batch, in chunks.
    pub fn len(&self) -> usize {
        self.len
    }

    /// Convert this `Batch` into a stream of `Hashed` objects.
    pub fn into_stream(self) -> Box<Stream<Item = Hashed, Error = Error> + Send> {
        Box::new(self.marshal_rx.map_err(|()| "Upstream error!".into()))
    }
}
<|endoftext|>


<commit_before>use super::{
    general::{cfg_deprecated, doc_alias, doc_hidden, version_condition},
    signal_body,
    trampoline::{self, func_string},
};
use crate::{
    analysis,
    chunk::Chunk,
    env::Env,
    writer::{primitives::tabs, ToCode},
};
use std::io::{Result, Write};

pub fn generate(
    w: &mut dyn Write,
    env: &Env,
    analysis: &analysis::signals::Info,
    in_trait: bool,
    only_declaration: bool,
    indent: usize,
) -> Result<()> {
    let commented = analysis.trampoline.is_err();
    let comment_prefix = if commented { "//" } else { "" };
    let pub_prefix = if in_trait { "" } else { "pub " };

    let function_type = function_type_string(env, analysis, true);
    let declaration = declaration(analysis, &function_type);
    let suffix = if only_declaration { ";" } else { " {" };

    writeln!(w)?;
    if !in_trait || only_declaration {
        cfg_deprecated(w, env, None, analysis.deprecated_version, commented, indent)?;
    }
    version_condition(w, env, None, analysis.version, commented, indent)?;
    doc_hidden(w, analysis.doc_hidden, comment_prefix, indent)?;
    // Strip the "prefix" from "prefix::prop-name", if any.
    // Ex.: "notify::is-locked".
    if !in_trait || only_declaration {
        doc_alias(
            w,
            analysis.signal_name.splitn(2, "::").last().unwrap(),
            comment_prefix,
            indent,
        )?;
    }
    writeln!(
        w,
        "{}{}{}{}{}",
        tabs(indent),
        comment_prefix,
        pub_prefix,
        declaration,
        suffix
    )?;

    if !only_declaration {
        if !commented {
            if let Ok(ref trampoline) = analysis.trampoline {
                trampoline::generate(w, env, trampoline, in_trait, 2)?;
            }
        }
        match function_type {
            Some(_) => {
                let body = body(analysis, in_trait).to_code(env);
                for s in body {
                    writeln!(w, "{}{}", tabs(indent), s)?;
                }
            }
            _ => {
                if let Err(ref errors) = analysis.trampoline {
                    for error in errors {
                        writeln!(w, "{}{}\t{}", tabs(indent), comment_prefix, error)?;
                    }
                    writeln!(w, "{}{}}}", tabs(indent), comment_prefix)?;
                } else {
                    writeln!(
                        w,
                        "{}{}\tTODO: connect to trampoline\n{0}{1}}}",
                        tabs(indent),
                        comment_prefix
                    )?;
                }
            }
        }
    }

    if function_type.is_none() {
        // Signal incomplete, can't generate emit
        return Ok(());
    }

    if let Some(ref emit_name) = analysis.action_emit_name {
        writeln!(w)?;
        if !in_trait || only_declaration {
            cfg_deprecated(w, env, None, analysis.deprecated_version, commented, indent)?;
        }
        version_condition(w, env, None, analysis.version, commented, indent)?;

        let function_type = function_type_string(env, analysis, false);

        writeln!(
            w,
            "{}{}{}fn {}{}{}",
            tabs(indent),
            comment_prefix,
            pub_prefix,
            emit_name,
            function_type.unwrap(),
            suffix
        )?;

        if !only_declaration {
            let trampoline = analysis.trampoline.as_ref().unwrap_or_else(|_| {
                panic!(
                    "Internal error: can't find trampoline for signal '{}'",
                    analysis.signal_name,
                )
            });
            let mut args = String::with_capacity(100);

            for (pos, par) in trampoline.parameters.rust_parameters.iter().enumerate() {
                // Skip the self parameter
                if pos == 0 {
                    continue;
                }

                if pos > 1 {
                    args.push_str(", ");
                }
                args.push('&');
                args.push_str(&par.name);
            }

            if trampoline.ret.typ != Default::default() {
                writeln!(
                    w,
                    "{}self.emit_by_name(\"{}\", &[{}])",
                    tabs(indent + 1),
                    analysis.signal_name,
                    args,
                )?;
            } else {
                writeln!(
                    w,
                    "{}self.emit_by_name::<()>(\"{}\", &[{}]);",
                    tabs(indent + 1),
                    analysis.signal_name,
                    args,
                )?;
            }
            writeln!(w, "{}}}", tabs(indent))?;
        }
    }

    Ok(())
}

fn function_type_string(
    env: &Env,
    analysis: &analysis::signals::Info,
    closure: bool,
) -> Option<String> {
    if analysis.trampoline.is_err() {
        return None;
    }

    let trampoline = analysis.trampoline.as_ref().unwrap_or_else(|_| {
        panic!(
            "Internal error: can't find trampoline for signal '{}'",
            analysis.signal_name
        )
    });

    let type_ = func_string(
        env,
        trampoline,
        Some(if closure { "Self" } else { "self" }),
        closure,
    );
    Some(type_)
}

fn declaration(analysis: &analysis::signals::Info, function_type: &Option<String>) -> String {
    let bounds = bounds(function_type);
    let param_str = if !analysis.is_detailed {
        "&self, f: F"
    } else {
        "&self, detail: Option<&str>, f: F"
    };
    let return_str = " -> SignalHandlerId";
    format!(
        "fn {}<{}>({}){}",
        analysis.connect_name, bounds, param_str, return_str
    )
}

fn bounds(function_type: &Option<String>) -> String {
    match function_type {
        Some(type_) => format!("F: {}", type_),
        _ => "Unsupported or ignored types".to_owned(),
    }
}

fn body(analysis: &analysis::signals::Info, in_trait: bool) -> Chunk {
    let mut builder = signal_body::Builder::new();

    builder
        .signal_name(&analysis.signal_name)
        .trampoline_name(&analysis.trampoline.as_ref().unwrap().name)
        .in_trait(in_trait)
        .is_detailed(analysis.is_detailed);

    builder.generate()
}
<commit_msg>codgen: make use of ? where possible<commit_after>use super::{
    general::{cfg_deprecated, doc_alias, doc_hidden, version_condition},
    signal_body,
    trampoline::{self, func_string},
};
use crate::{
    analysis,
    chunk::Chunk,
    env::Env,
    writer::{primitives::tabs, ToCode},
};
use std::io::{Result, Write};

pub fn generate(
    w: &mut dyn Write,
    env: &Env,
    analysis: &analysis::signals::Info,
    in_trait: bool,
    only_declaration: bool,
    indent: usize,
) -> Result<()> {
    let commented = analysis.trampoline.is_err();
    let comment_prefix = if commented { "//" } else { "" };
    let pub_prefix = if in_trait { "" } else { "pub " };

    let function_type = function_type_string(env, analysis, true);
    let declaration = declaration(analysis, &function_type);
    let suffix = if only_declaration { ";" } else { " {" };

    writeln!(w)?;
    if !in_trait || only_declaration {
        cfg_deprecated(w, env, None, analysis.deprecated_version, commented, indent)?;
    }
    version_condition(w, env, None, analysis.version, commented, indent)?;
    doc_hidden(w, analysis.doc_hidden, comment_prefix, indent)?;
    // Strip the "prefix" from "prefix::prop-name", if any.
    // Ex.: "notify::is-locked".
    if !in_trait || only_declaration {
        doc_alias(
            w,
            analysis.signal_name.splitn(2, "::").last().unwrap(),
            comment_prefix,
            indent,
        )?;
    }
    writeln!(
        w,
        "{}{}{}{}{}",
        tabs(indent),
        comment_prefix,
        pub_prefix,
        declaration,
        suffix
    )?;

    if !only_declaration {
        if !commented {
            if let Ok(ref trampoline) = analysis.trampoline {
                trampoline::generate(w, env, trampoline, in_trait, 2)?;
            }
        }
        match function_type {
            Some(_) => {
                let body = body(analysis, in_trait).to_code(env);
                for s in body {
                    writeln!(w, "{}{}", tabs(indent), s)?;
                }
            }
            _ => {
                if let Err(ref errors) = analysis.trampoline {
                    for error in errors {
                        writeln!(w, "{}{}\t{}", tabs(indent), comment_prefix, error)?;
                    }
                    writeln!(w, "{}{}}}", tabs(indent), comment_prefix)?;
                } else {
                    writeln!(
                        w,
                        "{}{}\tTODO: connect to trampoline\n{0}{1}}}",
                        tabs(indent),
                        comment_prefix
                    )?;
                }
            }
        }
    }

    if function_type.is_none() {
        // Signal incomplete, can't generate emit
        return Ok(());
    }

    if let Some(ref emit_name) = analysis.action_emit_name {
        writeln!(w)?;
        if !in_trait || only_declaration {
            cfg_deprecated(w, env, None, analysis.deprecated_version, commented, indent)?;
        }
        version_condition(w, env, None, analysis.version, commented, indent)?;

        let function_type = function_type_string(env, analysis, false);

        writeln!(
            w,
            "{}{}{}fn {}{}{}",
            tabs(indent),
            comment_prefix,
            pub_prefix,
            emit_name,
            function_type.unwrap(),
            suffix
        )?;

        if !only_declaration {
            let trampoline = analysis.trampoline.as_ref().unwrap_or_else(|_| {
                panic!(
                    "Internal error: can't find trampoline for signal '{}'",
                    analysis.signal_name,
                )
            });
            let mut args = String::with_capacity(100);

            for (pos, par) in trampoline.parameters.rust_parameters.iter().enumerate() {
                // Skip the self parameter
                if pos == 0 {
                    continue;
                }

                if pos > 1 {
                    args.push_str(", ");
                }
                args.push('&');
                args.push_str(&par.name);
            }

            if trampoline.ret.typ != Default::default() {
                writeln!(
                    w,
                    "{}self.emit_by_name(\"{}\", &[{}])",
                    tabs(indent + 1),
                    analysis.signal_name,
                    args,
                )?;
            } else {
                writeln!(
                    w,
                    "{}self.emit_by_name::<()>(\"{}\", &[{}]);",
                    tabs(indent + 1),
                    analysis.signal_name,
                    args,
                )?;
            }
            writeln!(w, "{}}}", tabs(indent))?;
        }
    }

    Ok(())
}

fn function_type_string(
    env: &Env,
    analysis: &analysis::signals::Info,
    closure: bool,
) -> Option<String> {
    analysis.trampoline.as_ref().ok()?;

    let trampoline = analysis.trampoline.as_ref().unwrap_or_else(|_| {
        panic!(
            "Internal error: can't find trampoline for signal '{}'",
            analysis.signal_name
        )
    });

    let type_ = func_string(
        env,
        trampoline,
        Some(if closure { "Self" } else { "self" }),
        closure,
    );
    Some(type_)
}

fn declaration(analysis: &analysis::signals::Info, function_type: &Option<String>) -> String {
    let bounds = bounds(function_type);
    let param_str = if !analysis.is_detailed {
        "&self, f: F"
    } else {
        "&self, detail: Option<&str>, f: F"
    };
    let return_str = " -> SignalHandlerId";
    format!(
        "fn {}<{}>({}){}",
        analysis.connect_name, bounds, param_str, return_str
    )
}

fn bounds(function_type: &Option<String>) -> String {
    match function_type {
        Some(type_) => format!("F: {}", type_),
        _ => "Unsupported or ignored types".to_owned(),
    }
}

fn body(analysis: &analysis::signals::Info, in_trait: bool) -> Chunk {
    let mut builder = signal_body::Builder::new();

    builder
        .signal_name(&analysis.signal_name)
        .trampoline_name(&analysis.trampoline.as_ref().unwrap().name)
        .in_trait(in_trait)
        .is_detailed(analysis.is_detailed);

    builder.generate()
}
<|endoftext|>


<commit_before>// Copyright (c) 2021-present, Gregory Szorc
// All rights reserved.
//
// This software may be modified and distributed under the terms
// of the BSD license. See the LICENSE file for details.

use {
    crate::{compression_dict::ZstdCompressionDict, exceptions::ZstdError},
    pyo3::{
        buffer::PyBuffer,
        exceptions::{PyMemoryError, PyNotImplementedError, PyValueError},
        prelude::*,
        types::PyBytes,
    },
    std::{marker::PhantomData, sync::Arc},
};

pub struct DCtx<'a>(*mut zstd_sys::ZSTD_DCtx, PhantomData<&'a ()>);

impl<'a> Drop for DCtx<'a> {
    fn drop(&mut self) {
        unsafe {
            zstd_sys::ZSTD_freeDCtx(self.0);
        }
    }
}

unsafe impl<'a> Send for DCtx<'a> {}
unsafe impl<'a> Sync for DCtx<'a> {}

impl<'a> DCtx<'a> {
    fn new() -> Result<Self, &'static str> {
        let dctx = unsafe { zstd_sys::ZSTD_createDCtx() };
        if dctx.is_null() {
            return Err("could not allocate ZSTD_DCtx instance");
        }

        Ok(Self(dctx, PhantomData))
    }
}

#[pyclass]
struct ZstdDecompressor {
    dict_data: Option<Py<ZstdCompressionDict>>,
    max_window_size: usize,
    format: zstd_sys::ZSTD_format_e,
    dctx: Arc<DCtx<'static>>,
}

impl ZstdDecompressor {
    fn setup_dctx(&self, py: Python, load_dict: bool) -> PyResult<()> {
        unsafe {
            zstd_sys::ZSTD_DCtx_reset(
                self.dctx.0,
                zstd_sys::ZSTD_ResetDirective::ZSTD_reset_session_only,
            );
        }

        if self.max_window_size != 0 {
            let zresult =
                unsafe { zstd_sys::ZSTD_DCtx_setMaxWindowSize(self.dctx.0, self.max_window_size) };
            if unsafe { zstd_sys::ZDICT_isError(zresult) } != 0 {
                return Err(ZstdError::new_err(format!(
                    "unable to set max window size: {}",
                    zstd_safe::get_error_name(zresult)
                )));
            }
        }

        let zresult = unsafe { zstd_sys::ZSTD_DCtx_setFormat(self.dctx.0, self.format) };
        if unsafe { zstd_sys::ZDICT_isError(zresult) } != 0 {
            return Err(ZstdError::new_err(format!(
                "unable to set decoding format: {}",
                zstd_safe::get_error_name(zresult)
            )));
        }

        if let Some(dict_data) = &self.dict_data {
            if load_dict {
                dict_data.try_borrow_mut(py)?.load_into_dctx(self.dctx.0)?;
            }
        }

        Ok(())
    }
}

#[pymethods]
impl ZstdDecompressor {
    #[new]
    #[args(dict_data = "None", max_window_size = "0", format = "0")]
    fn new(
        dict_data: Option<Py<ZstdCompressionDict>>,
        max_window_size: usize,
        format: u32,
    ) -> PyResult<Self> {
        let format = if format == zstd_sys::ZSTD_format_e::ZSTD_f_zstd1 as _ {
            zstd_sys::ZSTD_format_e::ZSTD_f_zstd1
        } else if format == zstd_sys::ZSTD_format_e::ZSTD_f_zstd1_magicless as _ {
            zstd_sys::ZSTD_format_e::ZSTD_f_zstd1_magicless
        } else {
            return Err(PyValueError::new_err(format!("invalid format value")));
        };

        let dctx = Arc::new(DCtx::new().map_err(|_| PyMemoryError::new_err(()))?);

        Ok(Self {
            dict_data,
            max_window_size,
            format,
            dctx,
        })
    }

    #[args(ifh, ofh, read_size = "None", write_size = "None")]
    fn copy_stream(
        &self,
        ifh: &PyAny,
        ofh: &PyAny,
        read_size: Option<usize>,
        write_size: Option<usize>,
    ) -> PyResult<()> {
        Err(PyNotImplementedError::new_err(()))
    }

    #[args(buffer, max_output_size = "0")]
    fn decompress<'p>(
        &mut self,
        py: Python<'p>,
        buffer: PyBuffer<u8>,
        max_output_size: usize,
    ) -> PyResult<&'p PyBytes> {
        self.setup_dctx(py, true)?;

        let output_size =
            unsafe { zstd_sys::ZSTD_getFrameContentSize(buffer.buf_ptr(), buffer.len_bytes()) };

        let output_buffer_size = if output_size == zstd_sys::ZSTD_CONTENTSIZE_ERROR as _ {
            return Err(ZstdError::new_err(
                "error determining content size from frame header",
            ));
        } else if output_size == 0 {
            return Ok(PyBytes::new(py, &[]));
        } else if output_size == zstd_sys::ZSTD_CONTENTSIZE_UNKNOWN as _ {
            if max_output_size == 0 {
                return Err(ZstdError::new_err(
                    "could not determine content size in frame header",
                ));
            }

            max_output_size
        } else {
            output_size as _
        };

        let mut dest_buffer: Vec<u8> = Vec::new();
        dest_buffer
            .try_reserve_exact(output_buffer_size)
            .map_err(|_| PyMemoryError::new_err(()))?;

        let mut out_buffer = zstd_sys::ZSTD_outBuffer {
            dst: dest_buffer.as_mut_ptr() as *mut _,
            size: dest_buffer.capacity(),
            pos: 0,
        };

        let mut in_buffer = zstd_sys::ZSTD_inBuffer {
            src: buffer.buf_ptr(),
            size: buffer.len_bytes(),
            pos: 0,
        };

        let zresult = unsafe {
            zstd_sys::ZSTD_decompressStream(
                self.dctx.0,
                &mut out_buffer as *mut _,
                &mut in_buffer as *mut _,
            )
        };
        if unsafe { zstd_sys::ZSTD_isError(zresult) } != 0 {
            Err(ZstdError::new_err(format!(
                "decompression error: {}",
                zstd_safe::get_error_name(zresult),
            )))
        } else if zresult != 0 {
            Err(ZstdError::new_err(
                "decompression error: did not decompress full frame",
            ))
        } else if output_size != 0 && out_buffer.pos != output_size as _ {
            Err(ZstdError::new_err(format!(
                "decompression error: decompressed {} bytes; expected {}",
                zresult, output_size
            )))
        } else {
            // TODO avoid memory copy
            unsafe { dest_buffer.set_len(out_buffer.pos) };
            Ok(PyBytes::new(py, &dest_buffer))
        }
    }

    fn decompress_content_dict_chain(&self, frames: &PyAny) -> PyResult<()> {
        Err(PyNotImplementedError::new_err(()))
    }

    #[args(write_size = "None")]
    fn decompressobj(&self, write_size: Option<usize>) -> PyResult<()> {
        Err(PyNotImplementedError::new_err(()))
    }

    fn memory_size(&self) -> PyResult<usize> {
        Ok(unsafe { zstd_sys::ZSTD_sizeof_DCtx(self.dctx.0) })
    }

    #[args(frames, decompressed_sizes = "None", threads = "0")]
    fn multi_decompress_to_buffer(
        &self,
        frames: &PyAny,
        decompressed_sizes: Option<&PyAny>,
        threads: usize,
    ) -> PyResult<()> {
        Err(PyNotImplementedError::new_err(()))
    }

    #[args(reader, read_size = "None", write_size = "None", skip_bytes = "None")]
    fn read_to_iter(
        &self,
        reader: &PyAny,
        read_size: Option<usize>,
        write_size: Option<usize>,
        skip_bytes: Option<usize>,
    ) -> PyResult<()> {
        Err(PyNotImplementedError::new_err(()))
    }

    #[args(
        source,
        read_size = "None",
        read_across_frames = "false",
        closefd = "true"
    )]
    fn stream_reader(
        &self,
        source: &PyAny,
        read_size: Option<usize>,
        read_across_frames: bool,
        closefd: bool,
    ) -> PyResult<()> {
        Err(PyNotImplementedError::new_err(()))
    }

    #[args(
        writer,
        write_size = "None",
        write_return_read = "true",
        closefd = "true"
    )]
    fn stream_writer(
        &self,
        writer: &PyAny,
        write_size: Option<usize>,
        write_return_read: bool,
        closefd: bool,
    ) -> PyResult<()> {
        Err(PyNotImplementedError::new_err(()))
    }
}

pub(crate) fn init_module(module: &PyModule) -> PyResult<()> {
    module.add_class::<ZstdDecompressor>()?;

    Ok(())
}
<commit_msg>rust: implement ZstdDecompressor.copy_stream()<commit_after>// Copyright (c) 2021-present, Gregory Szorc
// All rights reserved.
//
// This software may be modified and distributed under the terms
// of the BSD license. See the LICENSE file for details.

use {
    crate::{compression_dict::ZstdCompressionDict, exceptions::ZstdError},
    pyo3::{
        buffer::PyBuffer,
        exceptions::{PyMemoryError, PyNotImplementedError, PyValueError},
        prelude::*,
        types::PyBytes,
    },
    std::{marker::PhantomData, sync::Arc},
};

pub struct DCtx<'a>(*mut zstd_sys::ZSTD_DCtx, PhantomData<&'a ()>);

impl<'a> Drop for DCtx<'a> {
    fn drop(&mut self) {
        unsafe {
            zstd_sys::ZSTD_freeDCtx(self.0);
        }
    }
}

unsafe impl<'a> Send for DCtx<'a> {}
unsafe impl<'a> Sync for DCtx<'a> {}

impl<'a> DCtx<'a> {
    fn new() -> Result<Self, &'static str> {
        let dctx = unsafe { zstd_sys::ZSTD_createDCtx() };
        if dctx.is_null() {
            return Err("could not allocate ZSTD_DCtx instance");
        }

        Ok(Self(dctx, PhantomData))
    }
}

#[pyclass]
struct ZstdDecompressor {
    dict_data: Option<Py<ZstdCompressionDict>>,
    max_window_size: usize,
    format: zstd_sys::ZSTD_format_e,
    dctx: Arc<DCtx<'static>>,
}

impl ZstdDecompressor {
    fn setup_dctx(&self, py: Python, load_dict: bool) -> PyResult<()> {
        unsafe {
            zstd_sys::ZSTD_DCtx_reset(
                self.dctx.0,
                zstd_sys::ZSTD_ResetDirective::ZSTD_reset_session_only,
            );
        }

        if self.max_window_size != 0 {
            let zresult =
                unsafe { zstd_sys::ZSTD_DCtx_setMaxWindowSize(self.dctx.0, self.max_window_size) };
            if unsafe { zstd_sys::ZDICT_isError(zresult) } != 0 {
                return Err(ZstdError::new_err(format!(
                    "unable to set max window size: {}",
                    zstd_safe::get_error_name(zresult)
                )));
            }
        }

        let zresult = unsafe { zstd_sys::ZSTD_DCtx_setFormat(self.dctx.0, self.format) };
        if unsafe { zstd_sys::ZDICT_isError(zresult) } != 0 {
            return Err(ZstdError::new_err(format!(
                "unable to set decoding format: {}",
                zstd_safe::get_error_name(zresult)
            )));
        }

        if let Some(dict_data) = &self.dict_data {
            if load_dict {
                dict_data.try_borrow_mut(py)?.load_into_dctx(self.dctx.0)?;
            }
        }

        Ok(())
    }
}

#[pymethods]
impl ZstdDecompressor {
    #[new]
    #[args(dict_data = "None", max_window_size = "0", format = "0")]
    fn new(
        dict_data: Option<Py<ZstdCompressionDict>>,
        max_window_size: usize,
        format: u32,
    ) -> PyResult<Self> {
        let format = if format == zstd_sys::ZSTD_format_e::ZSTD_f_zstd1 as _ {
            zstd_sys::ZSTD_format_e::ZSTD_f_zstd1
        } else if format == zstd_sys::ZSTD_format_e::ZSTD_f_zstd1_magicless as _ {
            zstd_sys::ZSTD_format_e::ZSTD_f_zstd1_magicless
        } else {
            return Err(PyValueError::new_err(format!("invalid format value")));
        };

        let dctx = Arc::new(DCtx::new().map_err(|_| PyMemoryError::new_err(()))?);

        Ok(Self {
            dict_data,
            max_window_size,
            format,
            dctx,
        })
    }

    #[args(ifh, ofh, read_size = "None", write_size = "None")]
    fn copy_stream(
        &self,
        py: Python,
        ifh: &PyAny,
        ofh: &PyAny,
        read_size: Option<usize>,
        write_size: Option<usize>,
    ) -> PyResult<(usize, usize)> {
        let read_size = read_size.unwrap_or_else(|| zstd_safe::dstream_in_size());
        let write_size = write_size.unwrap_or_else(|| zstd_safe::dstream_out_size());

        if !ifh.hasattr("read")? {
            return Err(PyValueError::new_err(
                "first argument must have a read() method",
            ));
        }

        if !ofh.hasattr("write")? {
            return Err(PyValueError::new_err(
                "second argument must have a write() method",
            ));
        }

        self.setup_dctx(py, true)?;

        let mut dest_buffer: Vec<u8> = Vec::with_capacity(write_size);

        let mut in_buffer = zstd_sys::ZSTD_inBuffer {
            src: std::ptr::null(),
            size: 0,
            pos: 0,
        };

        let mut out_buffer = zstd_sys::ZSTD_outBuffer {
            dst: dest_buffer.as_mut_ptr() as *mut _,
            size: dest_buffer.capacity(),
            pos: 0,
        };

        let mut total_read = 0;
        let mut total_write = 0;

        // Read all available input.
        loop {
            let read_object = ifh.call_method1("read", (read_size,))?;
            let read_bytes: &PyBytes = read_object.downcast()?;
            let read_data = read_bytes.as_bytes();

            if read_data.len() == 0 {
                break;
            }

            total_read += read_data.len();

            in_buffer.src = read_data.as_ptr() as *const _;
            in_buffer.size = read_data.len();
            in_buffer.pos = 0;

            // Flush all read data to output.
            while in_buffer.pos < in_buffer.size {
                let zresult = unsafe {
                    zstd_sys::ZSTD_decompressStream(
                        self.dctx.0,
                        &mut out_buffer as *mut _,
                        &mut in_buffer as *mut _,
                    )
                };
                if unsafe { zstd_sys::ZSTD_isError(zresult) } != 0 {
                    return Err(ZstdError::new_err(format!(
                        "zstd decompressor error: {}",
                        zstd_safe::get_error_name(zresult)
                    )));
                }

                if out_buffer.pos != 0 {
                    unsafe {
                        dest_buffer.set_len(out_buffer.pos);
                    }

                    // TODO avoid buffer copy.
                    let data = PyBytes::new(py, &dest_buffer);

                    ofh.call_method1("write", (data,))?;
                    total_write += out_buffer.pos;
                    out_buffer.pos = 0;
                }
            }
            // Continue loop to keep reading.
        }

        Ok((total_read, total_write))
    }

    #[args(buffer, max_output_size = "0")]
    fn decompress<'p>(
        &mut self,
        py: Python<'p>,
        buffer: PyBuffer<u8>,
        max_output_size: usize,
    ) -> PyResult<&'p PyBytes> {
        self.setup_dctx(py, true)?;

        let output_size =
            unsafe { zstd_sys::ZSTD_getFrameContentSize(buffer.buf_ptr(), buffer.len_bytes()) };

        let output_buffer_size = if output_size == zstd_sys::ZSTD_CONTENTSIZE_ERROR as _ {
            return Err(ZstdError::new_err(
                "error determining content size from frame header",
            ));
        } else if output_size == 0 {
            return Ok(PyBytes::new(py, &[]));
        } else if output_size == zstd_sys::ZSTD_CONTENTSIZE_UNKNOWN as _ {
            if max_output_size == 0 {
                return Err(ZstdError::new_err(
                    "could not determine content size in frame header",
                ));
            }

            max_output_size
        } else {
            output_size as _
        };

        let mut dest_buffer: Vec<u8> = Vec::new();
        dest_buffer
            .try_reserve_exact(output_buffer_size)
            .map_err(|_| PyMemoryError::new_err(()))?;

        let mut out_buffer = zstd_sys::ZSTD_outBuffer {
            dst: dest_buffer.as_mut_ptr() as *mut _,
            size: dest_buffer.capacity(),
            pos: 0,
        };

        let mut in_buffer = zstd_sys::ZSTD_inBuffer {
            src: buffer.buf_ptr(),
            size: buffer.len_bytes(),
            pos: 0,
        };

        let zresult = unsafe {
            zstd_sys::ZSTD_decompressStream(
                self.dctx.0,
                &mut out_buffer as *mut _,
                &mut in_buffer as *mut _,
            )
        };
        if unsafe { zstd_sys::ZSTD_isError(zresult) } != 0 {
            Err(ZstdError::new_err(format!(
                "decompression error: {}",
                zstd_safe::get_error_name(zresult),
            )))
        } else if zresult != 0 {
            Err(ZstdError::new_err(
                "decompression error: did not decompress full frame",
            ))
        } else if output_size != 0 && out_buffer.pos != output_size as _ {
            Err(ZstdError::new_err(format!(
                "decompression error: decompressed {} bytes; expected {}",
                zresult, output_size
            )))
        } else {
            // TODO avoid memory copy
            unsafe { dest_buffer.set_len(out_buffer.pos) };
            Ok(PyBytes::new(py, &dest_buffer))
        }
    }

    fn decompress_content_dict_chain(&self, frames: &PyAny) -> PyResult<()> {
        Err(PyNotImplementedError::new_err(()))
    }

    #[args(write_size = "None")]
    fn decompressobj(&self, write_size: Option<usize>) -> PyResult<()> {
        Err(PyNotImplementedError::new_err(()))
    }

    fn memory_size(&self) -> PyResult<usize> {
        Ok(unsafe { zstd_sys::ZSTD_sizeof_DCtx(self.dctx.0) })
    }

    #[args(frames, decompressed_sizes = "None", threads = "0")]
    fn multi_decompress_to_buffer(
        &self,
        frames: &PyAny,
        decompressed_sizes: Option<&PyAny>,
        threads: usize,
    ) -> PyResult<()> {
        Err(PyNotImplementedError::new_err(()))
    }

    #[args(reader, read_size = "None", write_size = "None", skip_bytes = "None")]
    fn read_to_iter(
        &self,
        reader: &PyAny,
        read_size: Option<usize>,
        write_size: Option<usize>,
        skip_bytes: Option<usize>,
    ) -> PyResult<()> {
        Err(PyNotImplementedError::new_err(()))
    }

    #[args(
        source,
        read_size = "None",
        read_across_frames = "false",
        closefd = "true"
    )]
    fn stream_reader(
        &self,
        source: &PyAny,
        read_size: Option<usize>,
        read_across_frames: bool,
        closefd: bool,
    ) -> PyResult<()> {
        Err(PyNotImplementedError::new_err(()))
    }

    #[args(
        writer,
        write_size = "None",
        write_return_read = "true",
        closefd = "true"
    )]
    fn stream_writer(
        &self,
        writer: &PyAny,
        write_size: Option<usize>,
        write_return_read: bool,
        closefd: bool,
    ) -> PyResult<()> {
        Err(PyNotImplementedError::new_err(()))
    }
}

pub(crate) fn init_module(module: &PyModule) -> PyResult<()> {
    module.add_class::<ZstdDecompressor>()?;

    Ok(())
}
<|endoftext|>


<commit_before>//  * This file is part of the uutils coreutils package.
//  *
//  * (c) Maciej Dziardziel <fiedzia@gmail.com>
//  *
//  * For the full copyright and license information, please view the LICENSE file
//  * that was distributed with this source code.

// spell-checker:ignore (ToDO) signalname pids

#[macro_use]
extern crate uucore;

use clap::{crate_version, Arg, Command};
use libc::{c_int, pid_t};
use std::io::Error;
use uucore::display::Quotable;
use uucore::error::{UResult, USimpleError};
use uucore::signals::{signal_by_name_or_value, ALL_SIGNALS};
use uucore::{format_usage, InvalidEncodingHandling};

static ABOUT: &str = "Send signal to processes or list information about signals.";
const USAGE: &str = "{} [OPTIONS]... PID...";

pub mod options {
    pub static PIDS_OR_SIGNALS: &str = "pids_of_signals";
    pub static LIST: &str = "list";
    pub static TABLE: &str = "table";
    pub static TABLE_OLD: &str = "table_old";
    pub static SIGNAL: &str = "signal";
}

#[derive(Clone, Copy)]
pub enum Mode {
    Kill,
    Table,
    List,
}

#[uucore::main]
pub fn uumain(args: impl uucore::Args) -> UResult<()> {
    let mut args = args
        .collect_str(InvalidEncodingHandling::Ignore)
        .accept_any();
    let obs_signal = handle_obsolete(&mut args);

    let matches = uu_app().get_matches_from(args);

    let mode = if matches.is_present(options::TABLE) || matches.is_present(options::TABLE_OLD) {
        Mode::Table
    } else if matches.is_present(options::LIST) {
        Mode::List
    } else {
        Mode::Kill
    };

    let pids_or_signals: Vec<String> = matches
        .values_of(options::PIDS_OR_SIGNALS)
        .map(|v| v.map(ToString::to_string).collect())
        .unwrap_or_default();

    match mode {
        Mode::Kill => {
            let sig = if let Some(signal) = obs_signal {
                signal
            } else if let Some(signal) = matches.value_of(options::SIGNAL) {
                parse_signal_value(signal)?
            } else {
                15_usize //SIGTERM
            };
            let pids = parse_pids(&pids_or_signals)?;
            kill(sig, &pids)
        }
        Mode::Table => {
            table();
            Ok(())
        }
        Mode::List => list(pids_or_signals.get(0)),
    }
}

pub fn uu_app<'a>() -> Command<'a> {
    Command::new(uucore::util_name())
        .version(crate_version!())
        .about(ABOUT)
        .override_usage(format_usage(USAGE))
        .infer_long_args(true)
        .arg(
            Arg::new(options::LIST)
                .short('l')
                .long(options::LIST)
                .help("Lists signals")
                .conflicts_with(options::TABLE)
                .conflicts_with(options::TABLE_OLD),
        )
        .arg(
            Arg::new(options::TABLE)
                .short('t')
                .long(options::TABLE)
                .help("Lists table of signals"),
        )
        .arg(Arg::new(options::TABLE_OLD).short('L').hide(true))
        .arg(
            Arg::new(options::SIGNAL)
                .short('s')
                .long(options::SIGNAL)
                .help("Sends given signal")
                .takes_value(true),
        )
        .arg(
            Arg::new(options::PIDS_OR_SIGNALS)
                .hide(true)
                .multiple_occurrences(true),
        )
}

fn handle_obsolete(args: &mut Vec<String>) -> Option<usize> {
    // Sanity check
    if args.len() > 2 {
        // Old signal can only be in the first argument position
        let slice = args[1].as_str();
        if let Some(signal) = slice.strip_prefix('-') {
            // Check if it is a valid signal
            let opt_signal = signal_by_name_or_value(signal);
            if opt_signal.is_some() {
                // remove the signal before return
                args.remove(1);
                return opt_signal;
            }
        }
    }
    None
}

fn table() {
    let name_width = ALL_SIGNALS.iter().map(|n| n.len()).max().unwrap();

    for (idx, signal) in ALL_SIGNALS.iter().enumerate() {
        print!("{0: >#2} {1: <#2$}", idx, signal, name_width + 2);
        if (idx + 1) % 7 == 0 {
            println!();
        }
    }
    println!();
}

fn print_signal(signal_name_or_value: &str) -> UResult<()> {
    for (value, &signal) in ALL_SIGNALS.iter().enumerate() {
        if signal == signal_name_or_value || (format!("SIG{}", signal)) == signal_name_or_value {
            println!("{}", value);
            return Ok(());
        } else if signal_name_or_value == value.to_string() {
            println!("{}", signal);
            return Ok(());
        }
    }
    Err(USimpleError::new(
        1,
        format!("unknown signal name {}", signal_name_or_value.quote()),
    ))
}

fn print_signals() {
    for (idx, signal) in ALL_SIGNALS.iter().enumerate() {
        if idx > 0 {
            print!(" ");
        }
        print!("{}", signal);
    }
    println!();
}

fn list(arg: Option<&String>) -> UResult<()> {
    match arg {
        Some(x) => print_signal(x),
        None => {
            print_signals();
            Ok(())
        }
    }
}

fn parse_signal_value(signal_name: &str) -> UResult<usize> {
    let optional_signal_value = signal_by_name_or_value(signal_name);
    match optional_signal_value {
        Some(x) => Ok(x),
        None => Err(USimpleError::new(
            1,
            format!("unknown signal name {}", signal_name.quote()),
        )),
    }
}

fn parse_pids(pids: &[String]) -> UResult<Vec<usize>> {
    pids.iter()
        .map(|x| {
            x.parse::<usize>().map_err(|e| {
                USimpleError::new(1, format!("failed to parse argument {}: {}", x.quote(), e))
            })
        })
        .collect()
}

fn kill(signal_value: usize, pids: &[usize]) -> UResult<()> {
    for &pid in pids {
        if unsafe { libc::kill(pid as pid_t, signal_value as c_int) } != 0 {
            show!(USimpleError::new(1, format!("{}", Error::last_os_error())));
        }
    }
    Ok(())
}
<commit_msg>kill: fix typo<commit_after>//  * This file is part of the uutils coreutils package.
//  *
//  * (c) Maciej Dziardziel <fiedzia@gmail.com>
//  *
//  * For the full copyright and license information, please view the LICENSE file
//  * that was distributed with this source code.

// spell-checker:ignore (ToDO) signalname pids

#[macro_use]
extern crate uucore;

use clap::{crate_version, Arg, Command};
use libc::{c_int, pid_t};
use std::io::Error;
use uucore::display::Quotable;
use uucore::error::{UResult, USimpleError};
use uucore::signals::{signal_by_name_or_value, ALL_SIGNALS};
use uucore::{format_usage, InvalidEncodingHandling};

static ABOUT: &str = "Send signal to processes or list information about signals.";
const USAGE: &str = "{} [OPTIONS]... PID...";

pub mod options {
    pub static PIDS_OR_SIGNALS: &str = "pids_or_signals";
    pub static LIST: &str = "list";
    pub static TABLE: &str = "table";
    pub static TABLE_OLD: &str = "table_old";
    pub static SIGNAL: &str = "signal";
}

#[derive(Clone, Copy)]
pub enum Mode {
    Kill,
    Table,
    List,
}

#[uucore::main]
pub fn uumain(args: impl uucore::Args) -> UResult<()> {
    let mut args = args
        .collect_str(InvalidEncodingHandling::Ignore)
        .accept_any();
    let obs_signal = handle_obsolete(&mut args);

    let matches = uu_app().get_matches_from(args);

    let mode = if matches.is_present(options::TABLE) || matches.is_present(options::TABLE_OLD) {
        Mode::Table
    } else if matches.is_present(options::LIST) {
        Mode::List
    } else {
        Mode::Kill
    };

    let pids_or_signals: Vec<String> = matches
        .values_of(options::PIDS_OR_SIGNALS)
        .map(|v| v.map(ToString::to_string).collect())
        .unwrap_or_default();

    match mode {
        Mode::Kill => {
            let sig = if let Some(signal) = obs_signal {
                signal
            } else if let Some(signal) = matches.value_of(options::SIGNAL) {
                parse_signal_value(signal)?
            } else {
                15_usize //SIGTERM
            };
            let pids = parse_pids(&pids_or_signals)?;
            kill(sig, &pids)
        }
        Mode::Table => {
            table();
            Ok(())
        }
        Mode::List => list(pids_or_signals.get(0)),
    }
}

pub fn uu_app<'a>() -> Command<'a> {
    Command::new(uucore::util_name())
        .version(crate_version!())
        .about(ABOUT)
        .override_usage(format_usage(USAGE))
        .infer_long_args(true)
        .arg(
            Arg::new(options::LIST)
                .short('l')
                .long(options::LIST)
                .help("Lists signals")
                .conflicts_with(options::TABLE)
                .conflicts_with(options::TABLE_OLD),
        )
        .arg(
            Arg::new(options::TABLE)
                .short('t')
                .long(options::TABLE)
                .help("Lists table of signals"),
        )
        .arg(Arg::new(options::TABLE_OLD).short('L').hide(true))
        .arg(
            Arg::new(options::SIGNAL)
                .short('s')
                .long(options::SIGNAL)
                .help("Sends given signal")
                .takes_value(true),
        )
        .arg(
            Arg::new(options::PIDS_OR_SIGNALS)
                .hide(true)
                .multiple_occurrences(true)
                .allow_hyphen_values(true)
        )
}

fn handle_obsolete(args: &mut Vec<String>) -> Option<usize> {
    // Sanity check
    if args.len() > 2 {
        // Old signal can only be in the first argument position
        let slice = args[1].as_str();
        if let Some(signal) = slice.strip_prefix('-') {
            // Check if it is a valid signal
            let opt_signal = signal_by_name_or_value(signal);
            if opt_signal.is_some() {
                // remove the signal before return
                args.remove(1);
                return opt_signal;
            }
        }
    }
    None
}

fn table() {
    let name_width = ALL_SIGNALS.iter().map(|n| n.len()).max().unwrap();

    for (idx, signal) in ALL_SIGNALS.iter().enumerate() {
        print!("{0: >#2} {1: <#2$}", idx, signal, name_width + 2);
        if (idx + 1) % 7 == 0 {
            println!();
        }
    }
    println!();
}

fn print_signal(signal_name_or_value: &str) -> UResult<()> {
    for (value, &signal) in ALL_SIGNALS.iter().enumerate() {
        if signal == signal_name_or_value || (format!("SIG{}", signal)) == signal_name_or_value {
            println!("{}", value);
            return Ok(());
        } else if signal_name_or_value == value.to_string() {
            println!("{}", signal);
            return Ok(());
        }
    }
    Err(USimpleError::new(
        1,
        format!("unknown signal name {}", signal_name_or_value.quote()),
    ))
}

fn print_signals() {
    for (idx, signal) in ALL_SIGNALS.iter().enumerate() {
        if idx > 0 {
            print!(" ");
        }
        print!("{}", signal);
    }
    println!();
}

fn list(arg: Option<&String>) -> UResult<()> {
    match arg {
        Some(x) => print_signal(x),
        None => {
            print_signals();
            Ok(())
        }
    }
}

fn parse_signal_value(signal_name: &str) -> UResult<usize> {
    let optional_signal_value = signal_by_name_or_value(signal_name);
    match optional_signal_value {
        Some(x) => Ok(x),
        None => Err(USimpleError::new(
            1,
            format!("unknown signal name {}", signal_name.quote()),
        )),
    }
}

fn parse_pids(pids: &[String]) -> UResult<Vec<usize>> {
    pids.iter()
        .map(|x| {
            x.parse::<usize>().map_err(|e| {
                USimpleError::new(1, format!("failed to parse argument {}: {}", x.quote(), e))
            })
        })
        .collect()
}

fn kill(signal_value: usize, pids: &[usize]) -> UResult<()> {
    for &pid in pids {
        if unsafe { libc::kill(pid as pid_t, signal_value as c_int) } != 0 {
            show!(USimpleError::new(1, format!("{}", Error::last_os_error())));
        }
    }
    Ok(())
}
<|endoftext|>


<commit_before>
// Copyright 2017 The gltf Library Developers
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.

use v2::json::{texture, Extras, Index};

/// The material appearance of a primitive.
#[derive(Clone, Debug, Deserialize, Serialize, Validate)]
#[serde(deny_unknown_fields)]
pub struct Material {
    /// The alpha cutoff value of the material.
    #[serde(default = "material_alpha_cutoff_default", rename = "alphaCutoff")]
    pub alpha_cutoff: f32,

    /// The alpha rendering mode of the material.
    ///
    /// The material's alpha rendering mode enumeration specifying the
    /// interpretation of the alpha value of the main factor and texture.
    ///
    /// * In `Opaque` mode (default) the alpha value is ignored and the rendered
    ///   output is fully opaque.
    ///
    /// * In `Mask` mode, the rendered output is either fully opaque or fully
    ///   transparent depending on the alpha value and the specified alpha cutoff
    ///   value.
    ///
    /// * In `Blend` mode, the alpha value is used to composite the source and
    ///   destination areas and the rendered output is combined with the
    ///   background using the normal painting operation (i.e. the Porter and
    ///   Duff over operator).
    #[serde(default = "material_alpha_mode_default", rename = "alphaMode")]
    pub alpha_mode: String,

    /// Specifies whether the material is double-sided.
    ///
    /// * When this value is false, back-face culling is enabled.
    ///
    /// * When this value is true, back-face culling is disabled and double sided
    ///   lighting is enabled.
    ///
    /// The back-face must have its normals reversed before the lighting
    /// equation is evaluated.
    #[serde(default, rename = "doubleSided")]
    pub double_sided: bool,

    /// Optional user-defined name for this object.
    pub name: Option<String>,

    /// A set of parameter values that are used to define the metallic-roughness material model from Physically-Based Rendering (PBR) methodology. When not specified, all the default values of `pbrMetallicRoughness` apply.
    #[serde(rename = "pbrMetallicRoughness")]
    pub pbr_metallic_roughness: PbrMetallicRoughness,

    /// A tangent space normal map. The texture contains RGB components in linear space. Each texel represents the XYZ components of a normal vector in tangent space. Red [0 to 255] maps to X [-1 to 1]. Green [0 to 255] maps to Y [-1 to 1]. Blue [128 to 255] maps to Z [1/255 to 1]. The normal vectors use OpenGL conventions where +X is right and +Y is up. +Z points toward the viewer.
    #[serde(rename = "normalTexture")]
    pub normal_texture: Option<NormalTexture>,

    /// The occlusion map texture. The occlusion values are sampled from the R channel. Higher values indicate areas that should receive full indirect lighting and lower values indicate no indirect lighting. These values are linear. If other channels are present (GBA), they are ignored for occlusion calculations.
    #[serde(rename = "occlusionTexture")]
    pub occlusion_texture: Option<OcclusionTexture>,

    /// The emissive map controls the color and intensity of the light being emitted by the material. This texture contains RGB components in sRGB color space. If a fourth component (A) is present, it is ignored.
    #[serde(rename = "emissiveTexture")]
    pub emissive_texture: Option<texture::Info>,

    /// The emissive color of the material.
    #[serde(default, rename = "emissiveFactor")]
    pub emissive_factor: [f32; 3],

    /// Extension specific data.
    #[serde(default)]
    pub extensions: MaterialExtensions,

    /// Optional application specific data.
    #[serde(default)]
    pub extras: Extras,
}

fn material_alpha_cutoff_default() -> f32 {
    0.5
}

fn material_alpha_mode_default() -> String {
    "OPAQUE".to_string()
}

/// Extension specific data for `Material`.
#[derive(Clone, Debug, Default, Deserialize, Serialize, Validate)]
pub struct MaterialExtensions {
    #[serde(default)]
    _allow_unknown_fields: (),
}

/// A set of parameter values that are used to define the metallic-roughness
/// material model from Physically-Based Rendering (PBR) methodology.
#[derive(Clone, Debug, Deserialize, Serialize, Validate)]
#[serde(deny_unknown_fields)]
pub struct PbrMetallicRoughness {
    /// The material's base color factor.
    #[serde(default = "material_pbr_metallic_roughness_base_color_factor_default")]
    #[serde(rename = "baseColorFactor")]
    pub base_color_factor: [f32; 4],

    /// The base color texture.
    #[serde(rename = "baseColorTexture")]
    pub base_color_texture: Option<texture::Info>,

    /// The metalness of the material.
    #[serde(default = "material_pbr_metallic_roughness_metallic_factor_default")]
    #[serde(rename = "metallicFactor")]
    pub metallic_factor: f32,

    /// The roughness of the material.
    ///
    /// * A value of 1.0 means the material is completely rough.
    /// * A value of 0.0 means the material is completely smooth.
    #[serde(default = "material_pbr_metallic_roughness_roughness_factor_default")]
    #[serde(rename = "roughnessFactor")]
    pub roughness_factor: f32,

    /// The metallic-roughness texture.
    ///
    /// This texture has two components:
    ///
    /// * The first component (R) contains the metallic-ness of the material.
    /// * The second component (G) contains the roughness of the material.
    /// * If the third component (B) and/or the fourth component (A) are present
    ///   then they are ignored.
    #[serde(rename = "metallicRoughnessTexture")]
    pub metallic_roughness_texture: Option<texture::Info>,

    /// Extension specific data.
    #[serde(default)]
    pub extensions: PbrMetallicRoughnessExtensions,

    /// Optional application specific data.
    #[serde(default)]
    pub extras: Extras,
}

/// Extension specific data for `PbrMetallicRoughness`.
#[derive(Clone, Debug, Default, Deserialize, Serialize, Validate)]
pub struct PbrMetallicRoughnessExtensions {
    #[serde(default)]
    _allow_unknown_fields: (),
}

fn material_pbr_metallic_roughness_base_color_factor_default() -> [f32; 4] {
    [1.0, 1.0, 1.0, 1.0]
}

fn material_pbr_metallic_roughness_metallic_factor_default() -> f32 {
    1.0
}

fn material_pbr_metallic_roughness_roughness_factor_default() -> f32 {
    1.0
}

/// Defines the normal texture of a material.
#[derive(Clone, Debug, Deserialize, Serialize, Validate)]
#[serde(deny_unknown_fields)]
pub struct NormalTexture {
    /// The index of the texture.
    pub index: Index<texture::Texture>,

    /// The scalar multiplier applied to each normal vector of the texture.
    ///
    /// This value is ignored if normalTexture is not specified.
    #[serde(default = "material_normal_texture_scale_default")]
    pub scale: f32,

    /// The set index of the texture's `TEXCOORD` attribute.
    #[serde(default, rename = "texCoord")]
    pub tex_coord: u32,

    /// Extension specific data.
    #[serde(default)]
    pub extensions: NormalTextureExtensions,

    /// Optional application specific data.
    #[serde(default)]
    pub extras: Extras,
}

/// Extension specific data for `NormalTexture`.
#[derive(Clone, Debug, Default, Deserialize, Serialize, Validate)]
pub struct NormalTextureExtensions {
    #[serde(default)]
    _allow_unknown_fields: (),
}

fn material_normal_texture_scale_default() -> f32 {
    1.0
}

/// Defines the occlusion texture of a material.
#[derive(Clone, Debug, Deserialize, Serialize, Validate)]
#[serde(deny_unknown_fields)]
pub struct OcclusionTexture {
    /// The index of the texture.
    pub index: Index<texture::Texture>,

    /// The scalar multiplier controlling the amount of occlusion applied.
    #[serde(default = "material_occlusion_texture_strength_default")]
    pub strength: f32,

    /// The set index of the texture's `TEXCOORD` attribute.
    #[serde(default, rename = "texCoord")]
    pub tex_coord: u32,

    /// Extension specific data.
    #[serde(default)]
    pub extensions: OcclusionTextureExtensions,

    /// Optional application specific data.
    #[serde(default)]
    pub extras: Extras,
}

/// Extension specific data for `OcclusionTexture`.
#[derive(Clone, Debug, Default, Deserialize, Serialize, Validate)]
pub struct OcclusionTextureExtensions {
    #[serde(default)]
    _allow_unknown_fields: (),
}

fn material_occlusion_texture_strength_default() -> f32 {
    1.0
}
<commit_msg>Added validation for Material<commit_after>
// Copyright 2017 The gltf Library Developers
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.

use v2::json::{texture, Extras, Index, Root};
use v2::validation::{Error, JsonPath, Validate};

/// All valid alpha modes.
pub const VALID_ALPHA_MODES: &'static [&'static str] = &[
    "OPAQUE",
    "MASK",
    "BLEND",
];

/// The material appearance of a primitive.
#[derive(Clone, Debug, Deserialize, Serialize, Validate)]
#[serde(deny_unknown_fields)]
pub struct Material {
    /// The alpha cutoff value of the material.
    #[serde(default, rename = "alphaCutoff")]
    pub alpha_cutoff: AlphaCutoff,

    /// The alpha rendering mode of the material.
    ///
    /// The material's alpha rendering mode enumeration specifying the
    /// interpretation of the alpha value of the main factor and texture.
    ///
    /// * In `Opaque` mode (default) the alpha value is ignored and the rendered
    ///   output is fully opaque.
    ///
    /// * In `Mask` mode, the rendered output is either fully opaque or fully
    ///   transparent depending on the alpha value and the specified alpha cutoff
    ///   value.
    ///
    /// * In `Blend` mode, the alpha value is used to composite the source and
    ///   destination areas and the rendered output is combined with the
    ///   background using the normal painting operation (i.e. the Porter and
    ///   Duff over operator).
    #[serde(default, rename = "alphaMode")]
    pub alpha_mode: AlphaMode,

    /// Specifies whether the material is double-sided.
    ///
    /// * When this value is false, back-face culling is enabled.
    ///
    /// * When this value is true, back-face culling is disabled and double sided
    ///   lighting is enabled.
    ///
    /// The back-face must have its normals reversed before the lighting
    /// equation is evaluated.
    #[serde(default, rename = "doubleSided")]
    pub double_sided: bool,

    /// Optional user-defined name for this object.
    pub name: Option<String>,

    /// A set of parameter values that are used to define the metallic-roughness material model from Physically-Based Rendering (PBR) methodology. When not specified, all the default values of `pbrMetallicRoughness` apply.
    #[serde(rename = "pbrMetallicRoughness")]
    pub pbr_metallic_roughness: Option<PbrMetallicRoughness>,

    /// A tangent space normal map. The texture contains RGB components in linear space. Each texel represents the XYZ components of a normal vector in tangent space. Red [0 to 255] maps to X [-1 to 1]. Green [0 to 255] maps to Y [-1 to 1]. Blue [128 to 255] maps to Z [1/255 to 1]. The normal vectors use OpenGL conventions where +X is right and +Y is up. +Z points toward the viewer.
    #[serde(rename = "normalTexture")]
    pub normal_texture: Option<NormalTexture>,

    /// The occlusion map texture. The occlusion values are sampled from the R channel. Higher values indicate areas that should receive full indirect lighting and lower values indicate no indirect lighting. These values are linear. If other channels are present (GBA), they are ignored for occlusion calculations.
    #[serde(rename = "occlusionTexture")]
    pub occlusion_texture: Option<OcclusionTexture>,

    /// The emissive map controls the color and intensity of the light being emitted by the material. This texture contains RGB components in sRGB color space. If a fourth component (A) is present, it is ignored.
    #[serde(rename = "emissiveTexture")]
    pub emissive_texture: Option<texture::Info>,

    /// The emissive color of the material.
    #[serde(default, rename = "emissiveFactor")]
    pub emissive_factor: EmissiveFactor,

    /// Extension specific data.
    #[serde(default)]
    pub extensions: MaterialExtensions,

    /// Optional application specific data.
    #[serde(default)]
    pub extras: Extras,
}

/// Extension specific data for `Material`.
#[derive(Clone, Debug, Default, Deserialize, Serialize, Validate)]
pub struct MaterialExtensions {
    #[serde(default)]
    _allow_unknown_fields: (),
}

/// A set of parameter values that are used to define the metallic-roughness
/// material model from Physically-Based Rendering (PBR) methodology.
#[derive(Clone, Debug, Deserialize, Serialize, Validate)]
#[serde(deny_unknown_fields)]
pub struct PbrMetallicRoughness {
    /// The material's base color factor.
    #[serde(default, rename = "baseColorFactor")]
    pub base_color_factor: PbrBaseColorFactor,

    /// The base color texture.
    #[serde(rename = "baseColorTexture")]
    pub base_color_texture: Option<texture::Info>,

    /// The metalness of the material.
    #[serde(default, rename = "metallicFactor")]
    pub metallic_factor: StrengthFactor,

    /// The roughness of the material.
    ///
    /// * A value of 1.0 means the material is completely rough.
    /// * A value of 0.0 means the material is completely smooth.
    #[serde(default, rename = "roughnessFactor")]
    pub roughness_factor: StrengthFactor,

    /// The metallic-roughness texture.
    ///
    /// This texture has two components:
    ///
    /// * The first component (R) contains the metallic-ness of the material.
    /// * The second component (G) contains the roughness of the material.
    /// * If the third component (B) and/or the fourth component (A) are present
    ///   then they are ignored.
    #[serde(rename = "metallicRoughnessTexture")]
    pub metallic_roughness_texture: Option<texture::Info>,

    /// Extension specific data.
    #[serde(default)]
    pub extensions: PbrMetallicRoughnessExtensions,

    /// Optional application specific data.
    #[serde(default)]
    pub extras: Extras,
}

/// Extension specific data for `PbrMetallicRoughness`.
#[derive(Clone, Debug, Default, Deserialize, Serialize, Validate)]
pub struct PbrMetallicRoughnessExtensions {
    #[serde(default)]
    _allow_unknown_fields: (),
}

/// Defines the normal texture of a material.
#[derive(Clone, Debug, Deserialize, Serialize, Validate)]
#[serde(deny_unknown_fields)]
pub struct NormalTexture {
    /// The index of the texture.
    pub index: Index<texture::Texture>,

    /// The scalar multiplier applied to each normal vector of the texture.
    ///
    /// This value is ignored if normalTexture is not specified.
    #[serde(default = "material_normal_texture_scale_default")]
    pub scale: f32,

    /// The set index of the texture's `TEXCOORD` attribute.
    #[serde(default, rename = "texCoord")]
    pub tex_coord: u32,

    /// Extension specific data.
    #[serde(default)]
    pub extensions: NormalTextureExtensions,

    /// Optional application specific data.
    #[serde(default)]
    pub extras: Extras,
}

/// Extension specific data for `NormalTexture`.
#[derive(Clone, Debug, Default, Deserialize, Serialize, Validate)]
pub struct NormalTextureExtensions {
    #[serde(default)]
    _allow_unknown_fields: (),
}

fn material_normal_texture_scale_default() -> f32 {
    1.0
}

/// Defines the occlusion texture of a material.
#[derive(Clone, Debug, Deserialize, Serialize, Validate)]
#[serde(deny_unknown_fields)]
pub struct OcclusionTexture {
    /// The index of the texture.
    pub index: Index<texture::Texture>,

    /// The scalar multiplier controlling the amount of occlusion applied.
    #[serde(default)]
    pub strength: StrengthFactor,

    /// The set index of the texture's `TEXCOORD` attribute.
    #[serde(default, rename = "texCoord")]
    pub tex_coord: u32,

    /// Extension specific data.
    #[serde(default)]
    pub extensions: OcclusionTextureExtensions,

    /// Optional application specific data.
    #[serde(default)]
    pub extras: Extras,
}

/// Extension specific data for `OcclusionTexture`.
#[derive(Clone, Debug, Default, Deserialize, Serialize, Validate)]
pub struct OcclusionTextureExtensions {
    #[serde(default)]
    _allow_unknown_fields: (),
}

/// The alpha cutoff value of a material.
#[derive(Clone, Copy, Debug, Deserialize, Serialize)]
pub struct AlphaCutoff(pub f32);

/// The alpha rendering mode of a material.
#[derive(Clone, Debug, Deserialize, Serialize)]
pub struct AlphaMode(pub String);

/// The emissive color of a material.
#[derive(Clone, Copy, Debug, Default, Deserialize, Serialize)]
pub struct EmissiveFactor(pub [f32; 3]);

/// The base color factor of a material.
#[derive(Clone, Copy, Debug, Deserialize, Serialize)]
pub struct PbrBaseColorFactor(pub [f32; 4]);

/// A number in the inclusive range [0.0, 1.0] with a default value of 1.0.
#[derive(Clone, Copy, Debug, Deserialize, Serialize)]
pub struct StrengthFactor(pub f32);

impl Default for AlphaCutoff {
    fn default() -> Self {
        AlphaCutoff(0.5)
    }
}

impl Default for AlphaMode {
    fn default() -> Self {
        AlphaMode("OPAQUE".to_string())
    }
}

impl Validate for AlphaCutoff {
    fn validate<F>(&self, _: &Root, path: JsonPath, report: &mut F)
        where F: FnMut(Error)
    {
        if self.0 < 0.0 {
            report(Error::invalid_value(path, self.0));
        }
    }
}

impl Validate for AlphaMode {
    fn validate<F>(&self, _: &Root, path: JsonPath, report: &mut F)
        where F: FnMut(Error)
    {
        if !VALID_ALPHA_MODES.contains(&self.0.as_str()) {
            report(Error::invalid_enum(path, self.0.clone()));
        }
    }
}

impl Validate for EmissiveFactor {
    fn validate<F>(&self, _: &Root, path: JsonPath, report: &mut F)
        where F: FnMut(Error)
    {
        for x in &self.0 {
            if *x < 0.0 || *x > 1.0 {
                report(Error::invalid_value(path, self.0.to_vec()));
                // Only report once
                break;
            }
        }
    }
}

impl Default for PbrBaseColorFactor {
    fn default() -> Self {
        PbrBaseColorFactor([1.0, 1.0, 1.0, 1.0])
    }
}

impl Validate for PbrBaseColorFactor {
    fn validate<F>(&self, _: &Root, path: JsonPath, report: &mut F)
        where F: FnMut(Error)
    {
        for x in &self.0 {
            if *x < 0.0 || *x > 1.0 {
                report(Error::invalid_value(path, self.0.to_vec()));
                // Only report once
                break;
            }
        }
    }
}

impl Default for StrengthFactor {
    fn default() -> Self {
        StrengthFactor(1.0)
    }
}

impl Validate for StrengthFactor {
    fn validate<F>(&self, _: &Root, path: JsonPath, report: &mut F)
        where F: FnMut(Error)
    {
        if self.0 < 0.0 || self.0 > 1.0 {
            report(Error::invalid_value(path, self.0));
        }
    }
}

<|endoftext|>