<commit_before>// This example uses get_sock() to find out which file descriptors c-ares
// wants us to listen on, and uses epoll() to satisfy those requirements.
extern crate c_ares;
extern crate nix;

use c_ares::HostEntResults;
use nix::sys::epoll::{
    epoll_create,
    epoll_ctl,
    epoll_wait,
    EpollEvent,
    EpollEventKind,
    EpollOp,
    EPOLLIN,
    EPOLLOUT,
};
use std::collections::HashSet;
use std::error::Error;
use std::os::unix::io;

fn print_a_results(result: Result<c_ares::AResults, c_ares::AresError>) {
    match result {
        Err(e) => {
            println!("A lookup failed with error '{}'", e.description());
        }
        Ok(a_results) => {
            println!("Successful A lookup...");
            println!("Hostname: {}", a_results.hostname());
            for a_result in a_results.addresses() {
                println!("{:}", a_result.ip_address());
            }
        }
    }
}

fn print_aaaa_results(result: Result<c_ares::AAAAResults, c_ares::AresError>) {
    match result {
        Err(e) => {
            println!("AAAA lookup failed with error '{}'", e.description());
        }
        Ok(aaaa_results) => {
            println!("Successful AAAA lookup...");
            println!("Hostname: {}", aaaa_results.hostname());
            for aaaa_result in aaaa_results.addresses() {
                println!("{:}", aaaa_result.ip_address());
            }
        }
    }
}

fn print_srv_results(result: Result<c_ares::SRVResults, c_ares::AresError>) {
    match result {
        Err(e) => {
            println!("SRV lookup failed with error '{}'", e.description());
        }
        Ok(srv_results) => {
            println!("Successful SRV lookup...");
            for srv_result in &srv_results {
                println!("host: {} (port: {}), priority: {} weight: {}",
                         srv_result.host(),
                         srv_result.port(),
                         srv_result.priority(),
                         srv_result.weight());
            }
        }
    }
}

fn main() {
    // Create the c_ares::Channel.
    let mut options = c_ares::Options::new();
    options
        .set_flags(c_ares::flags::STAYOPEN)
        .set_timeout(500)
        .set_tries(3);
    let mut ares_channel = c_ares::Channel::new(options)
        .ok()
        .expect("Failed to create channel");

    // Set up some queries.
    ares_channel.query_a("apple.com", move |result| {
        println!("");
        print_a_results(result);
    });

    ares_channel.query_aaaa("google.com", move |result| {
        println!("");
        print_aaaa_results(result);
    });

    ares_channel.query_srv("_xmpp-server._tcp.gmail.com", move |result| {
        println!("");
        print_srv_results(result);
    });

    // Create an epoll file descriptor so that we can listen for events.
    let epoll = epoll_create().ok().expect("Failed to create epoll");
    let mut tracked_fds = HashSet::<io::RawFd>::new();
    loop {
        // Ask c-ares what file descriptors we should be listening on, and map
        // those requests onto the epoll file descriptor.
        let mut active = false;
        for (fd, readable, writable) in &ares_channel.get_sock() {
            let mut interest = EpollEventKind::empty();
            if readable { interest = interest | EPOLLIN; }
            if writable { interest = interest | EPOLLOUT; }
            let event = EpollEvent {
                events: interest,
                data: fd as u64,
            };
            let op = if tracked_fds.insert(fd) {
                EpollOp::EpollCtlAdd
            } else {
                EpollOp::EpollCtlMod
            };
            epoll_ctl(epoll, op, fd, &event).ok().expect("epoll_ctl failed");
            active = true;
        }
        if !active { break }

        // Wait for something to happen.
        let empty_event = EpollEvent {
            events: EpollEventKind::empty(),
            data: 0,
        };
        let mut events = [empty_event; 2];
        let results = epoll_wait(epoll, &mut events, 500)
            .ok()
            .expect("epoll_wait failed");

        // Process whatever happened.
        match results {
            0 => {
                // No events - must be a timeout.  Tell c-ares about it.
                ares_channel.process_fd(
                    c_ares::INVALID_FD,
                    c_ares::INVALID_FD);
            },
            n => {
                // Sockets became readable or writable.  Tell c-ares about it.
                for event in &events[0..n] {
                    let active_fd = event.data as io::RawFd;
                    let readable_fd = if (event.events & EPOLLIN).is_empty() {
                        c_ares::INVALID_FD
                    } else {
                        active_fd
                    };
                    let writable_fd = if (event.events & EPOLLOUT).is_empty() {
                        c_ares::INVALID_FD
                    } else {
                        active_fd
                    };
                    ares_channel.process_fd(readable_fd, writable_fd);
                }
            }
        }
    }
}
<commit_msg>Remove duplication in example<commit_after>// This example uses get_sock() to find out which file descriptors c-ares
// wants us to listen on, and uses epoll() to satisfy those requirements.
extern crate c_ares;
extern crate nix;

use c_ares::HostEntResults;
use nix::sys::epoll::{
    epoll_create,
    epoll_ctl,
    epoll_wait,
    EpollEvent,
    EpollEventKind,
    EpollOp,
    EPOLLIN,
    EPOLLOUT,
};
use std::collections::HashSet;
use std::error::Error;
use std::os::unix::io;

fn print_hostent_results<T>(result: Result<T, c_ares::AresError>)
    where T: c_ares::HostEntResults {
    match result {
        Err(e) => {
            println!("Lookup failed with error '{}'", e.description());
        }
        Ok(hostent_results) => {
            println!("Successful lookup...");
            println!("Hostname: {}", hostent_results.hostname());
            for alias in hostent_results.aliases() {
                println!("{:}", alias.alias());
            }
            for address in hostent_results.addresses() {
                println!("{:}", address.ip_address());
            }
        }
    }
}

fn print_srv_results(result: Result<c_ares::SRVResults, c_ares::AresError>) {
    match result {
        Err(e) => {
            println!("SRV lookup failed with error '{}'", e.description());
        }
        Ok(srv_results) => {
            println!("Successful SRV lookup...");
            for srv_result in &srv_results {
                println!("host: {} (port: {}), priority: {} weight: {}",
                         srv_result.host(),
                         srv_result.port(),
                         srv_result.priority(),
                         srv_result.weight());
            }
        }
    }
}

fn main() {
    // Create the c_ares::Channel.
    let mut options = c_ares::Options::new();
    options
        .set_flags(c_ares::flags::STAYOPEN)
        .set_timeout(500)
        .set_tries(3);
    let mut ares_channel = c_ares::Channel::new(options)
        .ok()
        .expect("Failed to create channel");

    // Set up some queries.
    ares_channel.query_a("apple.com", move |result| {
        println!("");
        print_hostent_results(result);
    });

    ares_channel.query_aaaa("google.com", move |result| {
        println!("");
        print_hostent_results(result);
    });

    ares_channel.query_srv("_xmpp-server._tcp.gmail.com", move |result| {
        println!("");
        print_srv_results(result);
    });

    // Create an epoll file descriptor so that we can listen for events.
    let epoll = epoll_create().ok().expect("Failed to create epoll");
    let mut tracked_fds = HashSet::<io::RawFd>::new();
    loop {
        // Ask c-ares what file descriptors we should be listening on, and map
        // those requests onto the epoll file descriptor.
        let mut active = false;
        for (fd, readable, writable) in &ares_channel.get_sock() {
            let mut interest = EpollEventKind::empty();
            if readable { interest = interest | EPOLLIN; }
            if writable { interest = interest | EPOLLOUT; }
            let event = EpollEvent {
                events: interest,
                data: fd as u64,
            };
            let op = if tracked_fds.insert(fd) {
                EpollOp::EpollCtlAdd
            } else {
                EpollOp::EpollCtlMod
            };
            epoll_ctl(epoll, op, fd, &event).ok().expect("epoll_ctl failed");
            active = true;
        }
        if !active { break }

        // Wait for something to happen.
        let empty_event = EpollEvent {
            events: EpollEventKind::empty(),
            data: 0,
        };
        let mut events = [empty_event; 2];
        let results = epoll_wait(epoll, &mut events, 500)
            .ok()
            .expect("epoll_wait failed");

        // Process whatever happened.
        match results {
            0 => {
                // No events - must be a timeout.  Tell c-ares about it.
                ares_channel.process_fd(
                    c_ares::INVALID_FD,
                    c_ares::INVALID_FD);
            },
            n => {
                // Sockets became readable or writable.  Tell c-ares about it.
                for event in &events[0..n] {
                    let active_fd = event.data as io::RawFd;
                    let readable_fd = if (event.events & EPOLLIN).is_empty() {
                        c_ares::INVALID_FD
                    } else {
                        active_fd
                    };
                    let writable_fd = if (event.events & EPOLLOUT).is_empty() {
                        c_ares::INVALID_FD
                    } else {
                        active_fd
                    };
                    ares_channel.process_fd(readable_fd, writable_fd);
                }
            }
        }
    }
}
<|endoftext|>


<commit_before>// Copyright 2013 The Rust Project Developers. See the COPYRIGHT
// file at the top-level directory of this distribution and at
// http://rust-lang.org/COPYRIGHT.
//
// 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.

//! Synchronous DNS Resolution
//!
//! Contains the functionality to perform DNS resolution or reverse lookup,
//! in a style related to `getaddrinfo()` and `getnameinfo()`, respectively.

#![allow(missing_docs)]

pub use self::SocketType::*;
pub use self::Flag::*;
pub use self::Protocol::*;

use iter::Iterator;
use old_io::IoResult;
use old_io::net::ip::{SocketAddr, IpAddr};
use option::Option;
use option::Option::{Some, None};
use string::String;
use sys;
use vec::Vec;

/// Hints to the types of sockets that are desired when looking up hosts
#[derive(Copy, Debug)]
pub enum SocketType {
    Stream, Datagram, Raw
}

/// Flags which can be or'd into the `flags` field of a `Hint`. These are used
/// to manipulate how a query is performed.
///
/// The meaning of each of these flags can be found with `man -s 3 getaddrinfo`
#[derive(Copy, Debug)]
pub enum Flag {
    AddrConfig,
    All,
    CanonName,
    NumericHost,
    NumericServ,
    Passive,
    V4Mapped,
}

/// A transport protocol associated with either a hint or a return value of
/// `lookup`
#[derive(Copy, Debug)]
pub enum Protocol {
    TCP, UDP
}

/// This structure is used to provide hints when fetching addresses for a
/// remote host to control how the lookup is performed.
///
/// For details on these fields, see their corresponding definitions via
/// `man -s 3 getaddrinfo`
#[derive(Copy, Debug)]
pub struct Hint {
    pub family: usize,
    pub socktype: Option<SocketType>,
    pub protocol: Option<Protocol>,
    pub flags: usize,
}

#[derive(Copy, Debug)]
pub struct Info {
    pub address: SocketAddr,
    pub family: usize,
    pub socktype: Option<SocketType>,
    pub protocol: Option<Protocol>,
    pub flags: usize,
}

/// Easy name resolution. Given a hostname, returns the list of IP addresses for
/// that hostname.
pub fn get_host_addresses(host: &str) -> IoResult<Vec<IpAddr>> {
    lookup(Some(host), None, None).map(|a| a.into_iter().map(|i| i.address.ip).collect())
}

/// Reverse name resolution. Given an address, returns the corresponding
/// hostname.
pub fn get_address_name(addr: IpAddr) -> IoResult<String> {
    sys::addrinfo::get_address_name(addr)
}

/// Full-fledged resolution. This function will perform a synchronous call to
/// getaddrinfo, controlled by the parameters
///
/// # Arguments
///
/// * hostname - an optional hostname to lookup against
/// * servname - an optional service name, listed in the system services
/// * hint - see the hint structure, and "man -s 3 getaddrinfo", for how this
///          controls lookup
///
/// FIXME: this is not public because the `Hint` structure is not ready for public
///      consumption just yet.
#[allow(unused_variables)]
fn lookup(hostname: Option<&str>, servname: Option<&str>, hint: Option<Hint>)
          -> IoResult<Vec<Info>> {
    sys::addrinfo::get_host_addresses(hostname, servname, hint)
}

// Ignored on android since we cannot give tcp/ip
// permission without help of apk
#[cfg(all(test, not(target_os = "android")))]
mod test {
    use prelude::v1::*;
    use super::*;
    use old_io::net::ip::*;

    #[test]
    fn dns_smoke_test() {
        let ipaddrs = get_host_addresses("localhost").unwrap();
        let mut found_local = false;
        let local_addr = &Ipv4Addr(127, 0, 0, 1);
        for addr in &ipaddrs {
            found_local = found_local || addr == local_addr;
        }
        assert!(found_local);
    }

    #[ignore]
    #[test]
    fn issue_10663() {
        // Something should happen here, but this certainly shouldn't cause
        // everything to die. The actual outcome we don't care too much about.
        get_host_addresses("example.com").unwrap();
    }
}
<commit_msg>std: Unignore a test for #10663<commit_after>// Copyright 2013 The Rust Project Developers. See the COPYRIGHT
// file at the top-level directory of this distribution and at
// http://rust-lang.org/COPYRIGHT.
//
// 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.

//! Synchronous DNS Resolution
//!
//! Contains the functionality to perform DNS resolution or reverse lookup,
//! in a style related to `getaddrinfo()` and `getnameinfo()`, respectively.

#![allow(missing_docs)]

pub use self::SocketType::*;
pub use self::Flag::*;
pub use self::Protocol::*;

use iter::Iterator;
use old_io::IoResult;
use old_io::net::ip::{SocketAddr, IpAddr};
use option::Option;
use option::Option::{Some, None};
use string::String;
use sys;
use vec::Vec;

/// Hints to the types of sockets that are desired when looking up hosts
#[derive(Copy, Debug)]
pub enum SocketType {
    Stream, Datagram, Raw
}

/// Flags which can be or'd into the `flags` field of a `Hint`. These are used
/// to manipulate how a query is performed.
///
/// The meaning of each of these flags can be found with `man -s 3 getaddrinfo`
#[derive(Copy, Debug)]
pub enum Flag {
    AddrConfig,
    All,
    CanonName,
    NumericHost,
    NumericServ,
    Passive,
    V4Mapped,
}

/// A transport protocol associated with either a hint or a return value of
/// `lookup`
#[derive(Copy, Debug)]
pub enum Protocol {
    TCP, UDP
}

/// This structure is used to provide hints when fetching addresses for a
/// remote host to control how the lookup is performed.
///
/// For details on these fields, see their corresponding definitions via
/// `man -s 3 getaddrinfo`
#[derive(Copy, Debug)]
pub struct Hint {
    pub family: usize,
    pub socktype: Option<SocketType>,
    pub protocol: Option<Protocol>,
    pub flags: usize,
}

#[derive(Copy, Debug)]
pub struct Info {
    pub address: SocketAddr,
    pub family: usize,
    pub socktype: Option<SocketType>,
    pub protocol: Option<Protocol>,
    pub flags: usize,
}

/// Easy name resolution. Given a hostname, returns the list of IP addresses for
/// that hostname.
pub fn get_host_addresses(host: &str) -> IoResult<Vec<IpAddr>> {
    lookup(Some(host), None, None).map(|a| a.into_iter().map(|i| i.address.ip).collect())
}

/// Reverse name resolution. Given an address, returns the corresponding
/// hostname.
pub fn get_address_name(addr: IpAddr) -> IoResult<String> {
    sys::addrinfo::get_address_name(addr)
}

/// Full-fledged resolution. This function will perform a synchronous call to
/// getaddrinfo, controlled by the parameters
///
/// # Arguments
///
/// * hostname - an optional hostname to lookup against
/// * servname - an optional service name, listed in the system services
/// * hint - see the hint structure, and "man -s 3 getaddrinfo", for how this
///          controls lookup
///
/// FIXME: this is not public because the `Hint` structure is not ready for public
///      consumption just yet.
#[allow(unused_variables)]
fn lookup(hostname: Option<&str>, servname: Option<&str>, hint: Option<Hint>)
          -> IoResult<Vec<Info>> {
    sys::addrinfo::get_host_addresses(hostname, servname, hint)
}

// Ignored on android since we cannot give tcp/ip
// permission without help of apk
#[cfg(all(test, not(target_os = "android")))]
mod test {
    use prelude::v1::*;
    use super::*;
    use old_io::net::ip::*;

    #[test]
    fn dns_smoke_test() {
        let ipaddrs = get_host_addresses("localhost").unwrap();
        let mut found_local = false;
        let local_addr = &Ipv4Addr(127, 0, 0, 1);
        for addr in &ipaddrs {
            found_local = found_local || addr == local_addr;
        }
        assert!(found_local);
    }

    #[test]
    fn issue_10663() {
        // Something should happen here, but this certainly shouldn't cause
        // everything to die. The actual outcome we don't care too much about.
        let _ = get_host_addresses("example.com");
    }
}
<|endoftext|>


<commit_before>//! Contains high-level interface for a pull-based XML parser.
//!
//! The most important type in this module is `EventReader`, which provides an iterator
//! view for events in XML document.

use std::io::prelude::*;

use self::parser::PullParser;
use self::events::XmlEvent;

pub use self::config::ParserConfig;

mod lexer;
mod parser;
pub mod config;
pub mod events;

/// Simple wrapper around an `std::io::BufReader` which provides pull-based XML parsing.
pub struct EventReader<B: Read> {
    source: B,
    parser: PullParser
}

impl<B: Read> EventReader<B> {
    /// Creates a new parser, consuming given stream.
    #[inline]
    pub fn new(source: B) -> EventReader<B> {
        EventReader::new_with_config(source, ParserConfig::new())
    }

    /// Creates a new parser with the provded configuration, consuming given `Buffer`.
    #[inline]
    pub fn new_with_config(source: B, config: ParserConfig) -> EventReader<B> {
        EventReader { source: source, parser: PullParser::new(config) }
    }

    /// Pulls and returns next XML event from the stream.
    ///
    /// If returned event is `xml::event::Error` or `xml::event::EndDocument`, then
    /// further calls to this method will return this event again.
    #[inline]
    pub fn next(&mut self) -> XmlEvent {
        self.parser.next(&mut self.source)
    }

    /// Returns an iterator over XML events.
    ///
    /// When the next event is `xml::event::Error` or `xml::event::EndDocument`, then
    /// it will be returned by the iterator once, and then it will stop producing events.
    #[inline]
    pub fn events<'a>(&'a mut self) -> Events<'a, B> {
        Events { reader: self, finished: false }
    }
}

/// XML events iterator, created by `events()` method on `Parser`.
pub struct Events<'a, B: Read+'a> {
    reader: &'a mut EventReader<B>,
    finished: bool
}

impl<'a, B: Read> Iterator for Events<'a, B> {
    type Item = XmlEvent;

    #[inline]
    fn next(&mut self) -> Option<XmlEvent> {
        if self.finished { None }
        else {
            let ev = self.reader.next();
            match ev {
                XmlEvent::EndDocument | XmlEvent::Error(_) => self.finished = true,
                _ => {}
            }
            Some(ev)
        }
    }
}

impl<'r> EventReader<&'r [u8]> {
    /// Convenience method to create a reader from a string slice.
    #[inline]
    pub fn new_from_str_slice(source: &'r str) -> EventReader<&'r [u8]> {
        EventReader::new(source.as_bytes())
    }
}

#[cfg(test)]
mod tests {
    use std::fs::File;
    use std::io::BufReader;
    use std::path::Path;

    use super::{EventReader, ParserConfig};

    fn test_sample(path: &str) {
        let file = File::open(Path::new(path));
        let reader = BufReader::new(file.unwrap());

        let mut eventreader = EventReader::new_with_config(
            reader,
            ParserConfig::new()
                .ignore_comments(true)
                .whitespace_to_characters(true)
                .cdata_to_characters(true)
                .trim_whitespace(true)
                .coalesce_characters(true)
        );

        for e in eventreader.events() {
            println!("{:?}", e);
        }
    }

    #[test]
    #[ignore]
    fn sample_1_test() {
        test_sample("data/sample_1.xml");
    }

    #[test]
    #[ignore]
    fn sample_2_test() {
        test_sample("data/sample_2.xml");
    }

    #[test]
    #[ignore]
    fn sample_3_test() {
        test_sample("data/sample_3.xml");
    }

    #[test]
    #[ignore]
    fn sample_4_test() {
        test_sample("data/sample_4.xml");
    }
}
<commit_msg>Follow standard naming convention for EventReader "constructors"<commit_after>//! Contains high-level interface for a pull-based XML parser.
//!
//! The most important type in this module is `EventReader`, which provides an iterator
//! view for events in XML document.

use std::io::prelude::*;

use self::parser::PullParser;
use self::events::XmlEvent;

pub use self::config::ParserConfig;

mod lexer;
mod parser;
pub mod config;
pub mod events;

/// Simple wrapper around an `std::io::BufReader` which provides pull-based XML parsing.
pub struct EventReader<B: Read> {
    source: B,
    parser: PullParser
}

impl<B: Read> EventReader<B> {
    /// Creates a new parser, consuming given stream.
    #[inline]
    pub fn new(source: B) -> EventReader<B> {
        EventReader::with_config(source, ParserConfig::new())
    }

    /// Creates a new parser with the provded configuration, consuming given `Buffer`.
    #[inline]
    pub fn with_config(source: B, config: ParserConfig) -> EventReader<B> {
        EventReader { source: source, parser: PullParser::new(config) }
    }

    /// Pulls and returns next XML event from the stream.
    ///
    /// If returned event is `xml::event::Error` or `xml::event::EndDocument`, then
    /// further calls to this method will return this event again.
    #[inline]
    pub fn next(&mut self) -> XmlEvent {
        self.parser.next(&mut self.source)
    }

    /// Returns an iterator over XML events.
    ///
    /// When the next event is `xml::event::Error` or `xml::event::EndDocument`, then
    /// it will be returned by the iterator once, and then it will stop producing events.
    #[inline]
    pub fn events<'a>(&'a mut self) -> Events<'a, B> {
        Events { reader: self, finished: false }
    }
}

/// XML events iterator, created by `events()` method on `Parser`.
pub struct Events<'a, B: Read+'a> {
    reader: &'a mut EventReader<B>,
    finished: bool
}

impl<'a, B: Read> Iterator for Events<'a, B> {
    type Item = XmlEvent;

    #[inline]
    fn next(&mut self) -> Option<XmlEvent> {
        if self.finished { None }
        else {
            let ev = self.reader.next();
            match ev {
                XmlEvent::EndDocument | XmlEvent::Error(_) => self.finished = true,
                _ => {}
            }
            Some(ev)
        }
    }
}

impl<'r> EventReader<&'r [u8]> {
    /// Convenience method to create a reader from a string slice.
    #[inline]
    pub fn from_str(source: &'r str) -> EventReader<&'r [u8]> {
        EventReader::new(source.as_bytes())
    }
}

#[cfg(test)]
mod tests {
    use std::fs::File;
    use std::io::BufReader;
    use std::path::Path;

    use super::{EventReader, ParserConfig};

    fn test_sample(path: &str) {
        let file = File::open(Path::new(path));
        let reader = BufReader::new(file.unwrap());

        let mut eventreader = EventReader::with_config(
            reader,
            ParserConfig::new()
                .ignore_comments(true)
                .whitespace_to_characters(true)
                .cdata_to_characters(true)
                .trim_whitespace(true)
                .coalesce_characters(true)
        );

        for e in eventreader.events() {
            println!("{:?}", e);
        }
    }

    #[test]
    #[ignore]
    fn sample_1_test() {
        test_sample("data/sample_1.xml");
    }

    #[test]
    #[ignore]
    fn sample_2_test() {
        test_sample("data/sample_2.xml");
    }

    #[test]
    #[ignore]
    fn sample_3_test() {
        test_sample("data/sample_3.xml");
    }

    #[test]
    #[ignore]
    fn sample_4_test() {
        test_sample("data/sample_4.xml");
    }
}
<|endoftext|>


<commit_before>// Copyright 2015 Gomez Guillaume
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//   http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

use std::fs::{OpenOptions, remove_file};
use std::io::{BufRead, BufReader, Write};
use std::collections::HashMap;
use std::ops::Deref;
use strip;
use utils::{join, loop_over_files};

use stripper_interface::{
    TypeStruct,
    EventType,
    Type,
    MOD_COMMENT,
    FILE_COMMENT,
    FILE,
};
use types::OUTPUT_COMMENT_FILE;

fn get_corresponding_type(elements: &[(Option<TypeStruct>, Vec<String>)],
                          to_find: &Option<TypeStruct>,
                          mut line: usize,
                          decal: &mut usize,
                          original_content: &mut Vec<String>) -> Option<usize> {
    let mut pos = 0;

    while pos < elements.len() {
        if match (&elements[pos].0, to_find) {
            (&Some(ref a), &Some(ref b)) => {
                let ret = a == b;

                // to detect variants
                if !ret && b.ty == Type::Unknown && b.parent.is_some() && a.parent.is_some() && a.parent == b.parent {
                    if match b.parent {
                        Some(ref p) => p.ty == Type::Struct || p.ty == Type::Enum || p.ty == Type::Use,
                        None => false,
                    } {
                        let mut tmp = b.clone();

                        tmp.ty = Type::Variant;
                        a == &tmp
                    } else {
                        false
                    }
                } else {
                    ret
                }
            },
            _ => false,
        } {
            let mut file_comment = false;

            if elements[pos].1.len() > 0 && elements[pos].1[0].starts_with(FILE_COMMENT) {
                line += 1;
                file_comment = true;
            } else {
                while line > 0 && (line + *decal) > 0 &&
                      original_content[line + *decal - 1].trim().starts_with("#") {
                    line -= 1;
                }
            }
            for comment in &elements[pos].1 {
                if file_comment {
                    original_content.insert(line + *decal, comment[FILE_COMMENT.len()..].to_owned());
                } else {
                    original_content.insert(line + *decal, comment.clone());
                }
                *decal += 1;
            }
            return Some(pos);
        }
        pos += 1;
    }
    None
}

pub fn regenerate_comments(path: &str, infos: &mut HashMap<String, Vec<(Option<TypeStruct>, Vec<String>)>>) {
    if !infos.contains_key(path) {
        return;
    }
    match strip::build_event_list(path) {
        Ok(mut parse_result) => {
            let mut elements = infos.get_mut(path).unwrap();
            let mut position = 0;
            let mut decal = 0;

            // first, we need to put back file comment
            for entry in elements.iter() {
                if entry.0.is_none() {
                    let mut it = 0;

                    while it < parse_result.original_content.len() {
                        if parse_result.original_content[it].starts_with("/") &&
                           it + 1 < parse_result.original_content.len() &&
                           parse_result.original_content[it + 1].len() < 1 {
                            it += 2;
                            break;
                        }
                        it += 1;
                    }
                    if it < parse_result.original_content.len() {
                        for line in &entry.1 {
                            parse_result.original_content.insert(it, line.clone());
                            decal += 1;
                            it += 1;
                        }
                    }
                    break;
                }
                position += 1;
            }
            if position < elements.len() {
                elements.remove(position);
            }
            let mut waiting_type = None;
            let mut current = None;
            let mut it = 0;

            while it < parse_result.event_list.len() {
                match parse_result.event_list[it].event {
                    EventType::Type(ref t) => {
                        if t.ty != Type::Unknown {
                            waiting_type = Some(t.clone());
                            let tmp = strip::add_to_type_scope(&current, &waiting_type);

                            match get_corresponding_type(&elements, &tmp,
                                                         parse_result.event_list[it].line,
                                                         &mut decal,
                                                         &mut parse_result.original_content) {
                                Some(l) => { elements.remove(l); },
                                None => {}
                            };
                        } else {
                            match current {
                                Some(ref c) => {
                                    if c.ty == Type::Struct || c.ty == Type::Enum ||
                                       c.ty == Type::Mod {
                                        let tmp = Some(t.clone());
                                        let cc = strip::add_to_type_scope(&current, &tmp);

                                        match get_corresponding_type(&elements, &cc,
                                                                     parse_result.event_list[it].line,
                                                                     &mut decal,
                                                                     &mut parse_result.original_content) {
                                            Some(l) => { elements.remove(l); },
                                            None => {}
                                        }
                                    }
                                }
                                None => {}
                            }
                        }
                    }
                    EventType::InScope => {
                        current = strip::add_to_type_scope(&current, &waiting_type);
                        waiting_type = None;
                        match get_corresponding_type(&elements, &current,
                                                     parse_result.event_list[it].line,
                                                     &mut decal,
                                                     &mut parse_result.original_content) {
                            Some(l) => { elements.remove(l); },
                            None => {}
                        };
                    }
                    EventType::OutScope => {
                        current = strip::type_out_scope(&current);
                        waiting_type = None;
                    }
                    _ => {}
                }
                it += 1;
            }
            rewrite_file(path, &parse_result.original_content);
        }
        Err(e) => {
            println!("Error on file '{}': {}", path, e);
        }
    }
}

fn rewrite_file(path: &str, o_content: &[String]) {
    match OpenOptions::new().write(true).create(true).truncate(true).open(path) {
        Ok(mut f) => {
            write!(f, "{}", o_content.join("\n")).unwrap();
        }
        Err(e) => {
            println!("Cannot open '{}': {}", path, e);
        }
    }
}

fn parse_mod_line(line: &str) -> Option<TypeStruct> {
    let line = line.replace(MOD_COMMENT, "");
    let parts : Vec<&str> = line.split("§").collect();
    let mut current = None;

    for part in parts {
        let elems : Vec<&str> = part.split(" ").filter(|x| x.len() > 0).collect();

        current = strip::add_to_type_scope(&current.clone(),
                                           &Some(TypeStruct::new(Type::from(elems[0]),
                                                                 elems[elems.len() - 1])));
    }
    current
}

fn save_remainings(infos: &HashMap<String, Vec<(Option<TypeStruct>, Vec<String>)>>) {
    let mut remainings = 0;

    for (_, content) in infos {
        if content.len() > 0 {
            remainings += 1;
        }
    }
    if remainings < 1 {
        let _ = remove_file(OUTPUT_COMMENT_FILE);
        return;
    }
    match OpenOptions::new().write(true).create(true).truncate(true).open(OUTPUT_COMMENT_FILE) {
        Ok(mut out_file) => {
            println!("Some comments couldn't have been regenerated to the files. Saving them back to '{}'.",
                     OUTPUT_COMMENT_FILE);
            for (key, content) in infos {
                if content.len() < 1 {
                    continue;
                }
                let _ = writeln!(out_file, "{}{}", FILE_COMMENT, key);
                for line in content {
                    match line.0 {
                        Some(ref d) => {
                            let _ = writeln!(out_file, "{}{}\n{}", MOD_COMMENT, d, join(&line.1, "\n"));
                        }
                        None => {}
                    }
                }
            }
            println!("Not implemented yet.");
        }
        Err(e) => {
            println!("An error occured while trying to open '{}': {}", OUTPUT_COMMENT_FILE, e);
            return;
        }
    }
}

pub fn regenerate_doc_comments(directory: &str, verbose: bool) {
    // we start by storing files info
    let f = match OpenOptions::new().read(true).open(OUTPUT_COMMENT_FILE) {
        Ok(f) => f,
        Err(e) => {
            println!("An error occured while trying to open '{}': {}", OUTPUT_COMMENT_FILE, e);
            return;
        }
    };
    let reader = BufReader::new(f);
    let lines = reader.lines().map(|line| line.unwrap());
    let mut infos = parse_cmts(lines);
    let ignores: &[&str] = &[];
    loop_over_files(directory, &mut infos, &regenerate_comments, &ignores, verbose);
    save_remainings(&infos);
    // TODO: rewrite comments.cmts with remaining infos in regenerate_comments
}

pub fn parse_cmts<S, I>(mut lines: I) -> HashMap<String, Vec<(Option<TypeStruct>, Vec<String>)>>
where S: Deref<Target = str>,
      I: Iterator<Item = S> {
    enum State {
        Initial,
        File {
            file: String,
            infos: Vec<(Option<TypeStruct>, Vec<String>)>,
            ty: Option<TypeStruct>,
            comments: Vec<String>,
        }
    }

    let mut ret = HashMap::new();
    let mut state = State::Initial;

    while let Some(line) = lines.next() {
        state = match state {
            State::Initial => {
                if line.starts_with(FILE) {
                    State::File {
                        file: line[FILE.len()..].to_owned(),
                        infos: vec![],
                        ty: None,
                        comments: vec![],
                    }
                } else {
                    panic!("Unrecognized format");
                }
            }
            State::File { mut file, mut infos, mut ty, mut comments } => {
                if line.starts_with(FILE) {
                    if !comments.is_empty() {
                        infos.push((ty.take(), comments));
                        comments = vec![];
                    }
                    if !infos.is_empty() {
                        ret.insert(file, infos);
                        file = line[FILE.len()..].to_owned();
                        infos = vec![];
                    }
                } else if line.starts_with(FILE_COMMENT) {
                    if let Some(ty) = ty.take() {
                        if !comments.is_empty() {
                            infos.push((Some(ty), comments));
                            comments = vec![];
                        }
                    } else {
                        comments.push(line[FILE_COMMENT.len()..].to_owned());
                    }
                } else if line.starts_with(MOD_COMMENT) {
                    if !comments.is_empty() {
                        infos.push((ty, comments));
                        comments = vec![];
                    }
                    ty = parse_mod_line(&line[..]);
                } else {
                    if ty.is_some() {
                        comments.push(line[..].to_owned());
                    } else {
                        panic!("Orphan comment");
                    }
                }
                State::File {
                    file: file,
                    infos: infos,
                    ty: ty,
                    comments: comments,
                }
            }
        }
    }

    if let State::File { file, mut infos, ty, comments } = state {
        if !comments.is_empty() {
            infos.push((ty, comments));
        }
        if !infos.is_empty() {
            ret.insert(file, infos);
        }
    }

    ret
}
<commit_msg>Add support for a blanket "*" file in `regenerate_comments`<commit_after>// Copyright 2015 Gomez Guillaume
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//   http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

use std::fs::{OpenOptions, remove_file};
use std::io::{BufRead, BufReader, Write};
use std::collections::HashMap;
use std::ops::Deref;
use strip;
use types::ParseResult;
use utils::{join, loop_over_files};

use stripper_interface::{
    TypeStruct,
    EventType,
    Type,
    MOD_COMMENT,
    FILE_COMMENT,
    FILE,
};
use types::OUTPUT_COMMENT_FILE;

fn get_corresponding_type(elements: &[(Option<TypeStruct>, Vec<String>)],
                          to_find: &Option<TypeStruct>,
                          mut line: usize,
                          decal: &mut usize,
                          original_content: &mut Vec<String>) -> Option<usize> {
    let mut pos = 0;

    while pos < elements.len() {
        if match (&elements[pos].0, to_find) {
            (&Some(ref a), &Some(ref b)) => {
                let ret = a == b;

                // to detect variants
                if !ret && b.ty == Type::Unknown && b.parent.is_some() && a.parent.is_some() && a.parent == b.parent {
                    if match b.parent {
                        Some(ref p) => p.ty == Type::Struct || p.ty == Type::Enum || p.ty == Type::Use,
                        None => false,
                    } {
                        let mut tmp = b.clone();

                        tmp.ty = Type::Variant;
                        a == &tmp
                    } else {
                        false
                    }
                } else {
                    ret
                }
            },
            _ => false,
        } {
            let mut file_comment = false;

            if elements[pos].1.len() > 0 && elements[pos].1[0].starts_with(FILE_COMMENT) {
                line += 1;
                file_comment = true;
            } else {
                while line > 0 && (line + *decal) > 0 &&
                      original_content[line + *decal - 1].trim().starts_with("#") {
                    line -= 1;
                }
            }
            for comment in &elements[pos].1 {
                if file_comment {
                    original_content.insert(line + *decal, comment[FILE_COMMENT.len()..].to_owned());
                } else {
                    original_content.insert(line + *decal, comment.clone());
                }
                *decal += 1;
            }
            return Some(pos);
        }
        pos += 1;
    }
    None
}

// The hashmap key is `Some(file name)` or `None` for entries that ignore file name
pub fn regenerate_comments(path: &str,
        infos: &mut HashMap<Option<String>, Vec<(Option<TypeStruct>, Vec<String>)>>) {
    if !infos.contains_key(&None) && !infos.contains_key(&Some(path.to_owned())) {
        return;
    }
    match strip::build_event_list(path) {
        Ok(ref mut parse_result) => {
            // exact path match
            if let Some(v) = infos.get_mut(&Some(path.to_owned())) {
                do_regenerate(path, parse_result, v);
            }
            // apply to all files
            if let Some(v) = infos.get_mut(&None) {
                do_regenerate(path, parse_result, v);
            }
        }
        Err(e) => {
            println!("Error in file '{}': {}", path, e);
        }
    }
}

fn do_regenerate(path: &str, parse_result: &mut ParseResult,
                 elements: &mut Vec<(Option<TypeStruct>, Vec<String>)>) {
    let mut position = 0;
    let mut decal = 0;

    // first, we need to put back file comment
    for entry in elements.iter() {
        if entry.0.is_none() {
            let mut it = 0;

            while it < parse_result.original_content.len() {
                if parse_result.original_content[it].starts_with("/") &&
                   it + 1 < parse_result.original_content.len() &&
                   parse_result.original_content[it + 1].len() < 1 {
                    it += 2;
                    break;
                }
                it += 1;
            }
            if it < parse_result.original_content.len() {
                for line in &entry.1 {
                    parse_result.original_content.insert(it, line.clone());
                    decal += 1;
                    it += 1;
                }
            }
            break;
        }
        position += 1;
    }
    if position < elements.len() {
        elements.remove(position);
    }
    let mut waiting_type = None;
    let mut current = None;
    let mut it = 0;

    while it < parse_result.event_list.len() {
        match parse_result.event_list[it].event {
            EventType::Type(ref t) => {
                if t.ty != Type::Unknown {
                    waiting_type = Some(t.clone());
                    let tmp = strip::add_to_type_scope(&current, &waiting_type);

                    match get_corresponding_type(&elements, &tmp,
                                                 parse_result.event_list[it].line,
                                                 &mut decal,
                                                 &mut parse_result.original_content) {
                        Some(l) => { elements.remove(l); },
                        None => {}
                    };
                } else {
                    match current {
                        Some(ref c) => {
                            if c.ty == Type::Struct || c.ty == Type::Enum ||
                               c.ty == Type::Mod {
                                let tmp = Some(t.clone());
                                let cc = strip::add_to_type_scope(&current, &tmp);

                                match get_corresponding_type(&elements, &cc,
                                                             parse_result.event_list[it].line,
                                                             &mut decal,
                                                             &mut parse_result.original_content) {
                                    Some(l) => { elements.remove(l); },
                                    None => {}
                                }
                            }
                        }
                        None => {}
                    }
                }
            }
            EventType::InScope => {
                current = strip::add_to_type_scope(&current, &waiting_type);
                waiting_type = None;
                match get_corresponding_type(&elements, &current,
                                             parse_result.event_list[it].line,
                                             &mut decal,
                                             &mut parse_result.original_content) {
                    Some(l) => { elements.remove(l); },
                    None => {}
                };
            }
            EventType::OutScope => {
                current = strip::type_out_scope(&current);
                waiting_type = None;
            }
            _ => {}
        }
        it += 1;
    }
    rewrite_file(path, &parse_result.original_content);
}

fn rewrite_file(path: &str, o_content: &[String]) {
    match OpenOptions::new().write(true).create(true).truncate(true).open(path) {
        Ok(mut f) => {
            write!(f, "{}", o_content.join("\n")).unwrap();
        }
        Err(e) => {
            println!("Cannot open '{}': {}", path, e);
        }
    }
}

fn parse_mod_line(line: &str) -> Option<TypeStruct> {
    let line = line.replace(MOD_COMMENT, "");
    let parts : Vec<&str> = line.split("§").collect();
    let mut current = None;

    for part in parts {
        let elems : Vec<&str> = part.split(" ").filter(|x| x.len() > 0).collect();

        current = strip::add_to_type_scope(&current.clone(),
                                           &Some(TypeStruct::new(Type::from(elems[0]),
                                                                 elems[elems.len() - 1])));
    }
    current
}

fn save_remainings(infos: &HashMap<Option<String>, Vec<(Option<TypeStruct>, Vec<String>)>>) {
    let mut remainings = 0;

    for (_, content) in infos {
        if content.len() > 0 {
            remainings += 1;
        }
    }
    if remainings < 1 {
        let _ = remove_file(OUTPUT_COMMENT_FILE);
        return;
    }
    match OpenOptions::new().write(true).create(true).truncate(true).open(OUTPUT_COMMENT_FILE) {
        Ok(mut out_file) => {
            println!("Some comments couldn't have been regenerated to the files. Saving them back to '{}'.",
                     OUTPUT_COMMENT_FILE);
            for (key, content) in infos {
                if content.len() < 1 {
                    continue;
                }
                // Set the name to "*" for entries that ignore file name
                let key = key.as_ref().map(|s| &s[..]).unwrap_or("*");
                let _ = writeln!(out_file, "{}{}", FILE, key);
                for line in content {
                    match line.0 {
                        Some(ref d) => {
                            let _ = writeln!(out_file, "{}{}\n{}", MOD_COMMENT, d, join(&line.1, "\n"));
                        }
                        None => {}
                    }
                }
            }
            println!("Not implemented yet.");
        }
        Err(e) => {
            println!("An error occured while trying to open '{}': {}", OUTPUT_COMMENT_FILE, e);
            return;
        }
    }
}

pub fn regenerate_doc_comments(directory: &str, verbose: bool) {
    // we start by storing files info
    let f = match OpenOptions::new().read(true).open(OUTPUT_COMMENT_FILE) {
        Ok(f) => f,
        Err(e) => {
            println!("An error occured while trying to open '{}': {}", OUTPUT_COMMENT_FILE, e);
            return;
        }
    };
    let reader = BufReader::new(f);
    let lines = reader.lines().map(|line| line.unwrap());
    let mut infos = parse_cmts(lines);
    let ignores: &[&str] = &[];
    loop_over_files(directory, &mut infos, &regenerate_comments, &ignores, verbose);
    save_remainings(&infos);
    // TODO: rewrite comments.cmts with remaining infos in regenerate_comments
}

pub fn parse_cmts<S, I>(mut lines: I)
    -> HashMap<Option<String>, Vec<(Option<TypeStruct>, Vec<String>)>>
where S: Deref<Target = str>,
      I: Iterator<Item = S> {
    enum State {
        Initial,
        File {
            file: Option<String>,
            infos: Vec<(Option<TypeStruct>, Vec<String>)>,
            ty: Option<TypeStruct>,
            comments: Vec<String>,
        }
    }

    // Returns `Some(name)` if the line matches FILE
    // where name is Some for an actual file name and None for "*"
    // The "*" entries are to be applied regardless of file name
    fn line_file(line: &str) -> Option<Option<String>> {
        if line.starts_with(FILE) {
            let name = &line[FILE.len()..];
            if name == "*" {
                Some(None)
            }
            else {
                Some(Some(name.to_owned()))
            }
        }
        else {
            None
        }
    }

    let mut ret = HashMap::new();
    let mut state = State::Initial;

    while let Some(line) = lines.next() {
        state = match state {
            State::Initial => {
                if let Some(file) = line_file(&line) {
                    State::File {
                        file: file,
                        infos: vec![],
                        ty: None,
                        comments: vec![],
                    }
                } else {
                    panic!("Unrecognized format");
                }
            }
            State::File { mut file, mut infos, mut ty, mut comments } => {
                if let Some(new_file) = line_file(&line) {
                    if !comments.is_empty() {
                        infos.push((ty.take(), comments));
                        comments = vec![];
                    }
                    if !infos.is_empty() {
                        ret.insert(file, infos);
                        file = new_file;
                        infos = vec![];
                    }
                } else if line.starts_with(FILE_COMMENT) {
                    if let Some(ty) = ty.take() {
                        if !comments.is_empty() {
                            infos.push((Some(ty), comments));
                            comments = vec![];
                        }
                    } else {
                        comments.push(line[FILE_COMMENT.len()..].to_owned());
                    }
                } else if line.starts_with(MOD_COMMENT) {
                    if !comments.is_empty() {
                        infos.push((ty, comments));
                        comments = vec![];
                    }
                    ty = parse_mod_line(&line[..]);
                } else {
                    if ty.is_some() {
                        comments.push(line[..].to_owned());
                    } else {
                        panic!("Orphan comment");
                    }
                }
                State::File {
                    file: file,
                    infos: infos,
                    ty: ty,
                    comments: comments,
                }
            }
        }
    }

    if let State::File { file, mut infos, ty, comments } = state {
        if !comments.is_empty() {
            infos.push((ty, comments));
        }
        if !infos.is_empty() {
            ret.insert(file, infos);
        }
    }

    ret
}
<|endoftext|>


<commit_before>//! Represents JavaScript exceptions as a Rust [`Result`](std::result) type.
//!
//! Most interactions with the JavaScript engine can throw a JavaScript exception. Neon APIs
//! that can throw an exception are called _throwing APIs_ and return the type
//! [`NeonResult`](NeonResult) (or its shorthand [`JsResult`](JsResult)).
//!
//! When a throwing API triggers a JavaScript exception, it returns an [Err](std::result::Result::Err)
//! result. This indicates that the thread is now throwing, and allows Rust code to perform any
//! cleanup—with an important restriction: **while the JavaScript thread is still throwing,
//! you cannot call additional throwing APIs**. All throwing APIs immediately panic if called
//! while the thread is already throwing.
//!
//! ## Example
//!
//! Neon functions typically use [`JsResult`](JsResult) for their return type. This
//! example defines a function that extracts a property called `"message"` from an object,
//! throwing an exception if the argument is not of the right type or extracting the property
//! fails:
//!
//! ```ignore
//! fn get_message(mut cx: FunctionContext) -> JsResult<JsValue> {
//!     let obj: Handle<JsObject> = cx.argument(0)?;
//!     let prop: Handle<JsValue> = obj.get(&mut cx, "message")?;
//!     Ok(prop)
//! }
//! ```

use context::Context;
use handle::Handle;
use std::fmt::{Display, Formatter, Result as FmtResult};
use types::Value;

/// A [unit type][unit] indicating that the JavaScript thread is throwing an exception.
///
/// `Throw` deliberately does not implement [`std::error::Error`](std::error::Error). It's
/// not recommended to chain JavaScript exceptions with other kinds of Rust errors,
/// since throwing means that the JavaScript thread is unavailable until the exception
/// is handled.
///
/// [unit]: https://doc.rust-lang.org/book/ch05-01-defining-structs.html#unit-like-structs-without-any-fields
#[derive(Debug)]
pub struct Throw;

impl Display for Throw {
    fn fmt(&self, fmt: &mut Formatter) -> FmtResult {
        fmt.write_str("JavaScript Error")
    }
}

/// The result type for throwing APIs.
pub type NeonResult<T> = Result<T, Throw>;

/// A result type for throwing APIs that produce JavaScript values.
pub type JsResult<'b, T> = NeonResult<Handle<'b, T>>;

/// Extension trait for converting Rust [`Result`](std::result::Result) values
/// into [`JsResult`](JsResult) values by throwing JavaScript exceptions.
pub trait JsResultExt<'a, V: Value> {
    fn or_throw<'b, C: Context<'b>>(self, cx: &mut C) -> JsResult<'a, V>;
}
<commit_msg>Mention idiomatic use of question mark operator.<commit_after>//! Represents JavaScript exceptions as a Rust [`Result`](std::result) type.
//!
//! Most interactions with the JavaScript engine can throw a JavaScript exception. Neon APIs
//! that can throw an exception are called _throwing APIs_ and return the type
//! [`NeonResult`](NeonResult) (or its shorthand [`JsResult`](JsResult)).
//!
//! When a throwing API triggers a JavaScript exception, it returns an [Err](std::result::Result::Err)
//! result. This indicates that the thread is now throwing, and allows Rust code to perform any
//! cleanup—with an important restriction: **while the JavaScript thread is still throwing,
//! you cannot call additional throwing APIs**. All throwing APIs immediately panic if called
//! while the thread is already throwing.
//!
//! Typically, Neon code can manage JavaScript exceptions correctly and conveniently by
//! using Rust's [question mark (`?`)][question-mark] operator. This ensures that Rust code
//! "short-circuits" when an exception is thrown and returns back to JavaScript without
//! calling any throwing APIs.
//!
//! ## Example
//!
//! Neon functions typically use [`JsResult`](JsResult) for their return type. This
//! example defines a function that extracts a property called `"message"` from an object,
//! throwing an exception if the argument is not of the right type or extracting the property
//! fails:
//!
//! ```ignore
//! fn get_message(mut cx: FunctionContext) -> JsResult<JsValue> {
//!     let obj: Handle<JsObject> = cx.argument(0)?;
//!     let prop: Handle<JsValue> = obj.get(&mut cx, "message")?;
//!     Ok(prop)
//! }
//! ```
//!
//! [question-mark]: https://doc.rust-lang.org/edition-guide/rust-2018/error-handling-and-panics/the-question-mark-operator-for-easier-error-handling.html

use context::Context;
use handle::Handle;
use std::fmt::{Display, Formatter, Result as FmtResult};
use types::Value;

/// A [unit type][unit] indicating that the JavaScript thread is throwing an exception.
///
/// `Throw` deliberately does not implement [`std::error::Error`](std::error::Error). It's
/// not recommended to chain JavaScript exceptions with other kinds of Rust errors,
/// since throwing means that the JavaScript thread is unavailable until the exception
/// is handled.
///
/// [unit]: https://doc.rust-lang.org/book/ch05-01-defining-structs.html#unit-like-structs-without-any-fields
#[derive(Debug)]
pub struct Throw;

impl Display for Throw {
    fn fmt(&self, fmt: &mut Formatter) -> FmtResult {
        fmt.write_str("JavaScript Error")
    }
}

/// The result type for throwing APIs.
pub type NeonResult<T> = Result<T, Throw>;

/// A result type for throwing APIs that produce JavaScript values.
pub type JsResult<'b, T> = NeonResult<Handle<'b, T>>;

/// Extension trait for converting Rust [`Result`](std::result::Result) values
/// into [`JsResult`](JsResult) values by throwing JavaScript exceptions.
pub trait JsResultExt<'a, V: Value> {
    fn or_throw<'b, C: Context<'b>>(self, cx: &mut C) -> JsResult<'a, V>;
}
<|endoftext|>


<commit_before>use super::{Index, Range};
use small;
use std::cmp::Ordering;

fn stepped_range_numeric<'a>(
    start: isize,
    end: isize,
    step: isize,
    nb_digits: usize,
) -> Option<Box<Iterator<Item = small::String> + 'a>> {
    if step == 0 || (start < end && step < 0) || (start > end && step > 0) {
        None
    } else {
        let (x, y, ordering) = if start < end {
            (start, end, Ordering::Greater)
        } else {
            (end, start, Ordering::Less)
        };

        let iter = (x..y).scan(start, move |index, _| {
            if end.cmp(index) == ordering {
                let index_holder = *index;
                *index += step; // This step adds
                Some(format!("{:0width$}", index_holder, width = nb_digits).into())
            } else {
                None
            }
        });

        Some(Box::new(iter))
    }
}

fn stepped_range_chars<'a>(
    start: u8,
    end: u8,
    step: u8,
) -> Option<Box<Iterator<Item = small::String> + 'a>> {
    if step == 0 {
        None
    } else {
        let (x, y, ordering) = if start < end {
            (start, end, Ordering::Greater)
        } else {
            (end, start, Ordering::Less)
        };

        let iter = (x..y).scan(start, move |index, _| {
            if end.cmp(index) == ordering {
                let index_holder = *index;
                *index = match ordering {
                    Ordering::Greater => index.wrapping_add(step),
                    Ordering::Less => index.wrapping_sub(step),
                    _ => unreachable!(),
                };
                Some((index_holder as char).to_string().into())
            } else {
                None
            }
        });

        Some(Box::new(iter))
    }
}

fn numeric_range<'a>(
    start: isize,
    end: isize,
    step: isize,
    inclusive: bool,
    nb_digits: usize,
) -> Option<Box<Iterator<Item = small::String> + 'a>> {
    let end = if start < end && inclusive {
        end + 1
    } else if start > end && inclusive {
        end - 1
    } else {
        end
    };
    stepped_range_numeric(start, end, step, nb_digits)
}

#[inline]
fn byte_is_valid_range(b: u8) -> bool { (b >= b'a' && b <= b'z') || (b >= b'A' && b <= b'Z') }

use std::u8;
fn char_range<'a>(
    start: u8,
    end: u8,
    step: isize,
    inclusive: bool,
) -> Option<Box<Iterator<Item = small::String> + 'a>> {
    if !byte_is_valid_range(start) || !byte_is_valid_range(end) {
        return None;
    }

    let char_step = {
        let v = step.checked_abs()?;
        if v > u8::MAX as isize {
            return None;
        }
        v as u8
    };

    let end = if start < end && inclusive {
        end + 1
    } else if start > end && inclusive {
        end - 1
    } else {
        end
    };
    stepped_range_chars(start, end, char_step)
}

fn count_minimum_digits(a: &str) -> usize {
    for c in a.chars() {
        match c {
            '-' => (),
            '0' => return a.len(),
            '1'...'9' => break,
            _ => panic!("count_minimum_digits should only be called for a valid number."),
        }
    }
    0
}

fn strings_to_isizes(a: &str, b: &str) -> Option<(isize, isize, usize)> {
    let first: isize = a.parse().ok()?;
    let second: isize = b.parse().ok()?;
    let nb_digits = usize::max(count_minimum_digits(a), count_minimum_digits(b));
    Some((first, second, nb_digits))
}

// TODO: Make this an iterator structure.
// In a range we allow the following syntax:
//      Exclusive nonstepped: {start..end}
//      Inclusive nonstepped: {start...end}
//      Exclusive stepped: {start..step..end}
//      Inclusive stepped: {start..step...end}
pub fn parse_range(input: &str) -> Option<Box<Iterator<Item = small::String>>> {
    let mut read = 0;
    let mut bytes_iterator = input.bytes();
    while let Some(byte) = bytes_iterator.next() {
        match byte {
            // can only find these as the first byte, otherwise the syntax is bad
            b'a'...b'z' | b'-' | b'A'...b'Z' if read == 0 => read += 1,
            b'0'...b'9' => read += 1,
            b'.' => {
                let first = &input[..read];
                read += 1;
                // The next byte has to be a dot to be valid range
                // syntax
                match bytes_iterator.next() {
                    Some(b'.') => read += 1,
                    _ => return None,
                }

                macro_rules! finish_char {
                    ($inclusive:expr, $end_str:expr, $step:expr) => {
                        if first.len() == 1 && $end_str.len() == 1 {
                            let start = first.as_bytes()[0];
                            let end = $end_str.as_bytes()[0];
                            return char_range(start, end, $step, $inclusive);
                        } else {
                            return None;
                        }
                    };
                }

                macro_rules! finish {
                    ($inclusive:expr, $read:expr) => {
                        let end_str = &input[$read..];
                        if let Some((start, end, nb_digits)) = strings_to_isizes(first, end_str) {
                            return numeric_range(
                                start,
                                end,
                                if start < end { 1 } else { -1 },
                                $inclusive,
                                nb_digits,
                            );
                        } else {
                            finish_char!($inclusive, end_str, 1);
                        }
                    };
                    ($inclusive:expr, $read:expr, $step:expr) => {
                        let end_str = &input[$read..];
                        if let Some((start, end, nb_digits)) = strings_to_isizes(first, end_str) {
                            return numeric_range(start, end, $step, $inclusive, nb_digits);
                        } else {
                            finish_char!($inclusive, end_str, $step);
                        }
                    };
                }

                // if the next byte is a dot we're certain it is an inclusive
                // unstepped range otherwise it has to be [-0-9a-zA-Z]
                if let Some(b) = bytes_iterator.next() {
                    read += 1;
                    match b {
                        b'.' | b'=' => {
                            // this can only be an inclusive range
                            finish!(true, read);
                        }
                        b'0'...b'9' | b'-' | b'a'...b'z' | b'A'...b'Z' => {
                            // further processing needed to find out if we're reading a step or
                            // the end of an exclusive range. Step until we find another dot or
                            // the iterator ends
                            let start = read - 1;
                            while let Some(b) = bytes_iterator.next() {
                                read += 1;
                                match b {
                                    b'.' => {
                                        // stepped range input[start..read - 1] contains the step
                                        // size
                                        let step = match (&input[start..read - 1]).parse::<isize>()
                                        {
                                            Ok(v) => v,
                                            Err(_) => return None,
                                        };
                                        // count the dots to determine inclusive/exclusive
                                        let mut dots = 1;
                                        while let Some(b) = bytes_iterator.next() {
                                            read += 1;
                                            match b {
                                                b'.' => dots += 1,
                                                _ => break,
                                            }
                                        }
                                        finish!(dots == 3, read - 1, step);
                                    }
                                    // numeric values are OK but no letters anymore
                                    b'0'...b'9' => {}
                                    // unexpected
                                    _ => return None,
                                }
                            }
                            // exhausted the iterator without finding anything new means
                            // exclusive unstepped range
                            finish!(false, start);
                        }
                        // not a valid byte for ranges
                        _ => return None,
                    }
                }
            }
            _ => break,
        }
    }
    None
}

pub fn parse_index_range(input: &str) -> Option<Range> {
    let mut bytes_iterator = input.bytes().enumerate();
    while let Some((id, byte)) = bytes_iterator.next() {
        match byte {
            b'0'...b'9' | b'-' => continue,
            b'.' => {
                let first = &input[..id];

                let mut dots = 1;
                let mut last_byte = 0;

                for (_, byte) in bytes_iterator {
                    last_byte = byte;
                    if byte == b'.' {
                        dots += 1
                    } else {
                        break;
                    }
                }

                if dots < 2 || dots > 3 {
                    break;
                }

                let inclusive = dots == 3 || (dots == 2 && last_byte == b'=');

                let end = &input[id + if inclusive { 3 } else { 2 }..];

                if first.is_empty() {
                    return if end.is_empty() {
                        None
                    } else {
                        end.parse::<isize>().map(|end| Range::to(Index::new(end))).ok()
                    };
                } else if end.is_empty() {
                    return first.parse::<isize>().map(|start| Range::from(Index::new(start))).ok();
                }

                return first
                    .parse::<isize>()
                    .and_then(|start| end.parse::<isize>().map(|end| (start, end)))
                    .map(|(start, end)| {
                        if inclusive {
                            Range::inclusive(Index::new(start), Index::new(end))
                        } else {
                            Range::exclusive(Index::new(start), Index::new(end))
                        }
                    })
                    .ok();
            }
            _ => break,
        }
    }

    None
}
<commit_msg>:hammer: Refactor range functions [corrected]<commit_after>use super::{Index, Range};
use small;
use std::cmp::Ordering;

fn numeric_range<'a>(
    start: isize,
    end: isize,
    step: isize,
    inclusive: bool,
    nb_digits: usize,
) -> Option<Box<Iterator<Item = small::String> + 'a>> {
    let end = if start < end && inclusive {
        end + 1
    } else if start > end && inclusive {
        end - 1
    } else {
        end
    };

    if step == 0 || (start < end && step < 0) || (start > end && step > 0) {
        None
    } else {
        let (x, y, ordering) = if start < end {
            (start, end, Ordering::Greater)
        } else {
            (end, start, Ordering::Less)
        };

        let iter = (x..y).scan(start, move |index, _| {
            if end.cmp(index) == ordering {
                let index_holder = *index;
                *index += step; // This step adds
                Some(format!("{:0width$}", index_holder, width = nb_digits).into())
            } else {
                None
            }
        });

        Some(Box::new(iter))
    }
}

#[inline]
fn byte_is_valid_range(b: u8) -> bool { (b >= b'a' && b <= b'z') || (b >= b'A' && b <= b'Z') }

use std::u8;
fn char_range<'a>(
    start: u8,
    end: u8,
    step: isize,
    inclusive: bool,
) -> Option<Box<Iterator<Item = small::String> + 'a>> {
    let char_step = step.checked_abs()?;
    if !byte_is_valid_range(start) || !byte_is_valid_range(end) || step == 0 || char_step > u8::MAX as isize {
        return None;
    }

    let end = if start < end && inclusive {
        end + 1
    } else if start > end && inclusive {
        end - 1
    } else {
        end
    };
    let (x, y, ordering) = if start < end {
        (start, end, Ordering::Greater)
    } else {
        (end, start, Ordering::Less)
    };

    let iter = (x..y).scan(start, move |index, _| {
        if end.cmp(index) == ordering {
            let index_holder = *index;
            *index = match ordering {
                Ordering::Greater => index.wrapping_add(char_step as u8),
                Ordering::Less => index.wrapping_sub(char_step as u8),
                _ => unreachable!(),
            };
            Some((index_holder as char).to_string().into())
        } else {
            None
        }
    });

    Some(Box::new(iter))
}

fn count_minimum_digits(a: &str) -> usize {
    for c in a.chars() {
        match c {
            '-' => (),
            '0' => return a.len(),
            '1'...'9' => break,
            _ => panic!("count_minimum_digits should only be called for a valid number."),
        }
    }
    0
}

fn strings_to_isizes(a: &str, b: &str) -> Option<(isize, isize, usize)> {
    let first: isize = a.parse().ok()?;
    let second: isize = b.parse().ok()?;
    let nb_digits = usize::max(count_minimum_digits(a), count_minimum_digits(b));
    Some((first, second, nb_digits))
}

// TODO: Make this an iterator structure.
// In a range we allow the following syntax:
//      Exclusive nonstepped: {start..end}
//      Inclusive nonstepped: {start...end}
//      Exclusive stepped: {start..step..end}
//      Inclusive stepped: {start..step...end}
pub fn parse_range(input: &str) -> Option<Box<Iterator<Item = small::String>>> {
    let mut read = 0;
    let mut bytes_iterator = input.bytes();
    while let Some(byte) = bytes_iterator.next() {
        match byte {
            // can only find these as the first byte, otherwise the syntax is bad
            b'a'...b'z' | b'-' | b'A'...b'Z' if read == 0 => read += 1,
            b'0'...b'9' => read += 1,
            b'.' => {
                let first = &input[..read];
                read += 1;
                // The next byte has to be a dot to be valid range
                // syntax
                match bytes_iterator.next() {
                    Some(b'.') => read += 1,
                    _ => return None,
                }

                macro_rules! finish_char {
                    ($inclusive:expr, $end_str:expr, $step:expr) => {
                        if first.len() == 1 && $end_str.len() == 1 {
                            let start = first.as_bytes()[0];
                            let end = $end_str.as_bytes()[0];
                            return char_range(start, end, $step, $inclusive);
                        } else {
                            return None;
                        }
                    };
                }

                macro_rules! finish {
                    ($inclusive:expr, $read:expr) => {
                        let end_str = &input[$read..];
                        if let Some((start, end, nb_digits)) = strings_to_isizes(first, end_str) {
                            return numeric_range(
                                start,
                                end,
                                if start < end { 1 } else { -1 },
                                $inclusive,
                                nb_digits,
                            );
                        } else {
                            finish_char!($inclusive, end_str, 1);
                        }
                    };
                    ($inclusive:expr, $read:expr, $step:expr) => {
                        let end_str = &input[$read..];
                        if let Some((start, end, nb_digits)) = strings_to_isizes(first, end_str) {
                            return numeric_range(start, end, $step, $inclusive, nb_digits);
                        } else {
                            finish_char!($inclusive, end_str, $step);
                        }
                    };
                }

                // if the next byte is a dot we're certain it is an inclusive
                // unstepped range otherwise it has to be [-0-9a-zA-Z]
                if let Some(b) = bytes_iterator.next() {
                    read += 1;
                    match b {
                        b'.' | b'=' => {
                            // this can only be an inclusive range
                            finish!(true, read);
                        }
                        b'0'...b'9' | b'-' | b'a'...b'z' | b'A'...b'Z' => {
                            // further processing needed to find out if we're reading a step or
                            // the end of an exclusive range. Step until we find another dot or
                            // the iterator ends
                            let start = read - 1;
                            while let Some(b) = bytes_iterator.next() {
                                read += 1;
                                match b {
                                    b'.' => {
                                        // stepped range input[start..read - 1] contains the step
                                        // size
                                        let step = (&input[start..read - 1]).parse::<isize>().ok()?;
                                        // count the dots to determine inclusive/exclusive
                                        let mut dots = 1;
                                        while let Some(b) = bytes_iterator.next() {
                                            read += 1;
                                            match b {
                                                b'.' => dots += 1,
                                                _ => break,
                                            }
                                        }
                                        finish!(dots == 3, read - 1, step);
                                    }
                                    // numeric values are OK but no letters anymore
                                    b'0'...b'9' => {}
                                    // unexpected
                                    _ => return None,
                                }
                            }
                            // exhausted the iterator without finding anything new means
                            // exclusive unstepped range
                            finish!(false, start);
                        }
                        // not a valid byte for ranges
                        _ => return None,
                    }
                }
            }
            _ => break,
        }
    }
    None
}

pub fn parse_index_range(input: &str) -> Option<Range> {
    let mut bytes_iterator = input.bytes().enumerate();
    while let Some((id, byte)) = bytes_iterator.next() {
        match byte {
            b'0'...b'9' | b'-' => continue,
            b'.' => {
                let first = &input[..id];

                let mut dots = 1;
                let mut last_byte = 0;

                for (_, byte) in bytes_iterator {
                    last_byte = byte;
                    if byte == b'.' {
                        dots += 1
                    } else {
                        break;
                    }
                }

                if dots < 2 || dots > 3 {
                    break;
                }

                let inclusive = dots == 3 || (dots == 2 && last_byte == b'=');

                let end = &input[id + if inclusive { 3 } else { 2 }..];

                return if first.is_empty() && !end.is_empty() {
                    end.parse::<isize>().map(|end| Range::to(Index::new(end))).ok()
                } else if end.is_empty() {
                    first.parse::<isize>().map(|start| Range::from(Index::new(start))).ok()
                } else {
                    first
                        .parse::<isize>()
                        .and_then(|start| end.parse::<isize>().map(|end| (start, end)))
                        .map(|(start, end)| {
                            if inclusive {
                                Range::inclusive(Index::new(start), Index::new(end))
                            } else {
                                Range::exclusive(Index::new(start), Index::new(end))
                            }
                        })
                        .ok()
                };

            }
            _ => break,
        }
    }

    None
}
<|endoftext|>


<commit_before>// SPDX-License-Identifier: MIT
// Copyright (C) 2018-present iced project and contributors

use alloc::borrow::Cow;
use alloc::string::String;
use core::fmt;
#[cfg(feature = "std")]
use std::error::Error;

/// iced error
#[derive(Debug, Clone)]
pub struct IcedError {
	error: Cow<'static, str>,
}

impl IcedError {
	#[allow(dead_code)]
	pub(crate) fn new(error: &'static str) -> Self {
		Self { error: error.into() }
	}

	#[allow(dead_code)]
	pub(crate) fn with_string(error: String) -> Self {
		Self { error: error.into() }
	}
}

#[cfg(feature = "std")]
impl Error for IcedError {
	// Required since MSRV < 1.42.0
	#[allow(clippy::missing_inline_in_public_items)]
	fn description(&self) -> &str {
		&self.error
	}
}

impl fmt::Display for IcedError {
	#[allow(clippy::missing_inline_in_public_items)]
	fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
		write!(f, "{}", &self.error)
	}
}
<commit_msg>Verify that IcedError is Send + Sync<commit_after>// SPDX-License-Identifier: MIT
// Copyright (C) 2018-present iced project and contributors

use alloc::borrow::Cow;
use alloc::string::String;
use core::fmt;
#[cfg(feature = "std")]
use std::error::Error;

/// iced error
#[derive(Debug, Clone)]
pub struct IcedError {
	error: Cow<'static, str>,
}

struct _TraitsCheck
where
	IcedError: fmt::Debug + Clone + fmt::Display + Send + Sync;
#[cfg(feature = "std")]
struct _TraitsCheckStd
where
	IcedError: Error;

impl IcedError {
	#[allow(dead_code)]
	pub(crate) fn new(error: &'static str) -> Self {
		Self { error: error.into() }
	}

	#[allow(dead_code)]
	pub(crate) fn with_string(error: String) -> Self {
		Self { error: error.into() }
	}
}

#[cfg(feature = "std")]
impl Error for IcedError {
	// Required since MSRV < 1.42.0
	#[allow(clippy::missing_inline_in_public_items)]
	fn description(&self) -> &str {
		&self.error
	}
}

impl fmt::Display for IcedError {
	#[allow(clippy::missing_inline_in_public_items)]
	fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
		write!(f, "{}", &self.error)
	}
}
<|endoftext|>


<commit_before>// See LICENSE file for copyright and license details.

use std::str::{Words, Split, FromStr};
use std::io::{BufferedReader, File};
use cgmath::{Vector3, Vector2};
use core_types::{ZInt};
use visualizer_types::{VertexCoord, TextureCoord, Normal};

struct Face {
    vertex: [ZInt; 3],
    texture: [ZInt; 3],
    normal: [ZInt; 3],
}

pub struct Model {
    coords: Vec<VertexCoord>,
    normals: Vec<Normal>,
    texture_coords: Vec<TextureCoord>,
    faces: Vec<Face>,
}

fn parse_word<T: FromStr>(words: &mut Words) -> T {
    let str = words.next().expect("Can not read next word");
    str.parse().expect("Can not convert from string")
}

fn parse_charsplit<T: FromStr>(words: &mut Split<char>) -> T {
    let str = words.next().expect("Can not read next word");
    str.parse().expect("Can not convert from string")
}

impl Model {
    pub fn new(path: &Path) -> Model {
        let mut obj = Model {
            coords: Vec::new(),
            normals: Vec::new(),
            texture_coords: Vec::new(),
            faces: Vec::new(),
        };
        obj.read(path);
        obj
    }

    fn read_v(words: &mut Words) -> VertexCoord {
        VertexCoord{v: Vector3 {
            x: parse_word(words),
            y: parse_word(words),
            z: parse_word(words),
        }}
    }

    fn read_vn(words: &mut Words) -> Normal {
        Normal{v: Vector3 {
            x: parse_word(words),
            y: parse_word(words),
            z: parse_word(words),
        }}
    }

    fn read_vt(words: &mut Words) -> TextureCoord {
        TextureCoord{v: Vector2 {
            x: parse_word(words),
            y: 1.0 - parse_word(words), // flip
        }}
    }

    fn read_f(words: &mut Words) -> Face {
        let mut face = Face {
            vertex: [0, 0, 0],
            texture: [0, 0, 0],
            normal: [0, 0, 0],
        };
        let mut i = 0;
        for group in *words {
            let mut w = group.split('/');
            face.vertex[i] = parse_charsplit(&mut w);
            face.texture[i] = parse_charsplit(&mut w);
            face.normal[i] = parse_charsplit(&mut w);
            i += 1;
        }
        face
    }

    fn read_line(&mut self, line: &str) {
        let mut words = line.words();
        fn is_correct_tag(tag: &str) -> bool {
            tag.len() != 0 && tag.char_at(0) != '#'
        }
        match words.next() {
            Some(tag) if is_correct_tag(tag) => {
                let w = &mut words;
                match tag {
                    "v" => self.coords.push(Model::read_v(w)),
                    "vn" => self.normals.push(Model::read_vn(w)),
                    "vt" => self.texture_coords.push(Model::read_vt(w)),
                    "f" => self.faces.push(Model::read_f(w)),
                    _ => {},
                }
            }
            _ => {},
        };
    }

    fn read(&mut self, path: &Path) {
        let mut file = BufferedReader::new(File::open(path));
        for line in file.lines() {
            match line {
                Ok(line) => self.read_line(line.as_slice()),
                Err(msg) => panic!("Obj: read error: {}", msg),
            }
        }
    }

    pub fn build(&self) -> Vec<VertexCoord> {
        let mut mesh = Vec::new();
        for face in self.faces.iter() {
            for i in range(0, 3) {
                let vertex_id = face.vertex[i] as usize - 1;
                mesh.push(self.coords[vertex_id].clone());
            }
        }
        mesh
    }

    pub fn build_tex_coord(&self) -> Vec<TextureCoord> {
        let mut tex_coords = Vec::new();
        for face in self.faces.iter() {
            for i in range(0, 3) {
                let texture_coord_id = face.texture[i] as usize - 1;
                tex_coords.push(self.texture_coords[texture_coord_id].clone());
            }
        }
        tex_coords
    }
}

// vim: set tabstop=4 shiftwidth=4 softtabstop=4 expandtab:
<commit_msg>src/obj.rs: File::open -> fs::load<commit_after>// See LICENSE file for copyright and license details.

use std::str::{Words, Split, FromStr};
use cgmath::{Vector3, Vector2};
use core_types::{ZInt};
use visualizer_types::{VertexCoord, TextureCoord, Normal};
use fs;

struct Face {
    vertex: [ZInt; 3],
    texture: [ZInt; 3],
    normal: [ZInt; 3],
}

pub struct Model {
    coords: Vec<VertexCoord>,
    normals: Vec<Normal>,
    texture_coords: Vec<TextureCoord>,
    faces: Vec<Face>,
}

fn parse_word<T: FromStr>(words: &mut Words) -> T {
    let str = words.next().expect("Can not read next word");
    str.parse().expect("Can not convert from string")
}

fn parse_charsplit<T: FromStr>(words: &mut Split<char>) -> T {
    let str = words.next().expect("Can not read next word");
    str.parse().expect("Can not convert from string")
}

impl Model {
    pub fn new(path: &Path) -> Model {
        let mut obj = Model {
            coords: Vec::new(),
            normals: Vec::new(),
            texture_coords: Vec::new(),
            faces: Vec::new(),
        };
        obj.read(path);
        obj
    }

    fn read_v(words: &mut Words) -> VertexCoord {
        VertexCoord{v: Vector3 {
            x: parse_word(words),
            y: parse_word(words),
            z: parse_word(words),
        }}
    }

    fn read_vn(words: &mut Words) -> Normal {
        Normal{v: Vector3 {
            x: parse_word(words),
            y: parse_word(words),
            z: parse_word(words),
        }}
    }

    fn read_vt(words: &mut Words) -> TextureCoord {
        TextureCoord{v: Vector2 {
            x: parse_word(words),
            y: 1.0 - parse_word(words), // flip
        }}
    }

    fn read_f(words: &mut Words) -> Face {
        let mut face = Face {
            vertex: [0, 0, 0],
            texture: [0, 0, 0],
            normal: [0, 0, 0],
        };
        let mut i = 0;
        for group in *words {
            let mut w = group.split('/');
            face.vertex[i] = parse_charsplit(&mut w);
            face.texture[i] = parse_charsplit(&mut w);
            face.normal[i] = parse_charsplit(&mut w);
            i += 1;
        }
        face
    }

    fn read_line(&mut self, line: &str) {
        let mut words = line.words();
        fn is_correct_tag(tag: &str) -> bool {
            tag.len() != 0 && tag.char_at(0) != '#'
        }
        match words.next() {
            Some(tag) if is_correct_tag(tag) => {
                let w = &mut words;
                match tag {
                    "v" => self.coords.push(Model::read_v(w)),
                    "vn" => self.normals.push(Model::read_vn(w)),
                    "vt" => self.texture_coords.push(Model::read_vt(w)),
                    "f" => self.faces.push(Model::read_f(w)),
                    _ => {},
                }
            }
            _ => {},
        };
    }

    fn read(&mut self, path: &Path) {
        for line in fs::load(path).lines() {
            match line {
                Ok(line) => self.read_line(line.as_slice()),
                Err(msg) => panic!("Obj: read error: {}", msg),
            }
        }
    }

    pub fn build(&self) -> Vec<VertexCoord> {
        let mut mesh = Vec::new();
        for face in self.faces.iter() {
            for i in range(0, 3) {
                let vertex_id = face.vertex[i] as usize - 1;
                mesh.push(self.coords[vertex_id].clone());
            }
        }
        mesh
    }

    pub fn build_tex_coord(&self) -> Vec<TextureCoord> {
        let mut tex_coords = Vec::new();
        for face in self.faces.iter() {
            for i in range(0, 3) {
                let texture_coord_id = face.texture[i] as usize - 1;
                tex_coords.push(self.texture_coords[texture_coord_id].clone());
            }
        }
        tex_coords
    }
}

// vim: set tabstop=4 shiftwidth=4 softtabstop=4 expandtab:
<|endoftext|>