<commit_before>// Copyright (c) 2016-2017 Chef Software Inc. and/or applicable contributors
//
// 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.

//! Formats user-visible output for the Supervisor.
//!
//! Most of this module is used via the `outputln!` and
//! `output_format!` macros. They create a `StructuredOutput` struct,
//! which includes the line number, file name, and column it was
//! called on. Additionally, it uses a standard constant called
//! `LOGKEY` as a short hint as to where the output was generated
//! within the Supervisor. Also supported is a `preamble`, which is
//! used to denote when output comes from a running service rather
//! than the Supervisor itself.
//!
//! The `StructuredOutput` struct supports three global options -
//! dealing with verbosity, coloring, and structured JSON output. If
//! verbose is turned on, then every line printed is annotated with
//! its preamble, logkey, and precise location; without verbose, it
//! prints simply the preamble and logkey. Coloring does what it says
//! on the tin :) JSON-formatted output emits this information as a
//! JSON object. It ignores the coloring option, and does _not_ ever log
//! with ANSI color codes, but does honor the verbose flag.

use std::sync::atomic::{AtomicBool, Ordering, ATOMIC_BOOL_INIT};
use std::fmt;
use std::result;

use ansi_term::Colour::{Cyan, Green, White};
use serde_json;
use serde::{Serialize, Serializer};
use serde::ser::SerializeMap;

use PROGRAM_NAME;

static mut VERBOSE: AtomicBool = ATOMIC_BOOL_INIT;
// I am sorry this isn't named the other way; I can't get an atomic initializer that defaults to
// true. Them's the breaks.
static mut NO_COLOR: AtomicBool = ATOMIC_BOOL_INIT;

static mut JSON: AtomicBool = ATOMIC_BOOL_INIT;

/// True if verbose output is on.
pub fn is_verbose() -> bool {
    unsafe { VERBOSE.load(Ordering::Relaxed) }
}

/// Turn verbose output on or off.
pub fn set_verbose(booly: bool) {
    unsafe {
        VERBOSE.store(booly, Ordering::Relaxed);
    }
}

/// True if color is enabled
pub fn is_color() -> bool {
    unsafe {
        if NO_COLOR.load(Ordering::Relaxed) {
            false
        } else {
            true
        }
    }
}

/// Set to true if you want color to turn off.
pub fn set_no_color(booly: bool) {
    unsafe {
        NO_COLOR.store(booly, Ordering::Relaxed);
    }
}

/// True if JSON formatting is enabled
pub fn is_json() -> bool {
    unsafe { JSON.load(Ordering::Relaxed) }
}

/// Set to true if you want JSON-formatted logs
pub fn set_json(booly: bool) {
    unsafe { JSON.store(booly, Ordering::Relaxed) }
}

/// Adds structure to printed output. Stores a preamble, a logkey, line, file, column, and content
/// to print.
pub struct StructuredOutput<'a> {
    preamble: &'a str,
    logkey: &'static str,
    line: u32,
    file: &'static str,
    column: u32,
    content: &'a str,
    /// Whether or not to render additional logging details such as
    /// file name and line numbers.
    pub verbose: Option<bool>,
    /// Whether or not to render output with embedded ANSI color
    /// codes. Ignored if serializing to JSON.
    pub color: Option<bool>,
    /// Whether or not to render as structured JSON logging output.
    pub json: Option<bool>,
}

impl<'a> StructuredOutput<'a> {
    /// Return a new StructuredOutput struct.
    pub fn new(
        preamble: &'a str,
        logkey: &'static str,
        line: u32,
        file: &'static str,
        column: u32,
        content: &'a str,
    ) -> StructuredOutput<'a> {
        StructuredOutput {
            preamble: preamble,
            logkey: logkey,
            line: line,
            file: file,
            column: column,
            content: content,
            verbose: None,
            color: None,
            json: None,
        }
    }
}

// Custom implementation of Serialize to ensure that we can
// appropriately represent both verbose and non-verbose output, a
// behavior which isn't otherwise possible to derive.
impl<'a> Serialize for StructuredOutput<'a> {
    fn serialize<S>(&self, serializer: S) -> result::Result<S::Ok, S::Error>
    where
        S: Serializer,
    {
        let verbose = self.verbose.unwrap_or(is_verbose());

        // Focused on JSON serialization right now, so the length hint
        // isn't needed; it might be later if we target other formats.
        let mut map = serializer.serialize_map(None)?;

        map.serialize_entry("preamble", &self.preamble)?;
        map.serialize_entry("logkey", &self.logkey)?;
        if verbose {
            map.serialize_entry("file", &self.file)?;
            map.serialize_entry("line", &self.line)?;
            map.serialize_entry("column", &self.column)?;
        }
        map.serialize_entry("content", &self.content)?;

        map.end()
    }
}

// If we ever want to create multiple output formats in the future, we would do it here -
// essentially create a flag we check to see what output you want, then call a different formatting
// function. Viola!
impl<'a> fmt::Display for StructuredOutput<'a> {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        if self.json.unwrap_or(is_json()) {
            // Our JSON serialization handles verbosity itself, and
            // color is ignored anyway, so there's no reason to check
            // those settings here.

            // unwrap is safe, as we control the inputs
            let as_json = serde_json::to_string(&self).unwrap();
            write!(f, "{}", as_json)
        } else {
            let verbose = self.verbose.unwrap_or(is_verbose());
            let color = self.color.unwrap_or(is_color());

            let preamble_color = if self.preamble == PROGRAM_NAME.as_str() {
                Cyan
            } else {
                Green
            };

            if verbose {
                if color {
                    write!(
                        f,
                        "{}({})[{}]: {}",
                        preamble_color.paint(self.preamble),
                        White.bold().paint(self.logkey),
                        White
                            .underline()
                            .paint(format!("{}:{}:{}", self.file, self.line, self.column)),
                        self.content
                    )
                } else {
                    write!(
                        f,
                        "{}({})[{}:{}:{}]: {}",
                        self.preamble, self.logkey, self.file, self.line, self.column, self.content
                    )
                }
            } else {
                if color {
                    write!(
                        f,
                        "{}({}): {}",
                        preamble_color.paint(self.preamble),
                        White.bold().paint(self.logkey),
                        self.content
                    )
                } else {
                    write!(f, "{}({}): {}", self.preamble, self.logkey, self.content)
                }
            }
        }
    }
}

#[macro_export]
/// Works the same as the print! macro, but uses our StructuredOutput formatter.
macro_rules! output {
    ($content: expr) => {
        {
            use $crate::output::StructuredOutput;
            use $crate::PROGRAM_NAME;
            let so = StructuredOutput::new(PROGRAM_NAME.as_str(),
                                           LOGKEY,
                                           line!(),
                                           file!(),
                                           column!(),
                                           $content);
            print!("{}", so);
        }
    };
    (preamble $preamble:expr, $content: expr) => {
        {
            use $crate::output::StructuredOutput;
            let so = StructuredOutput::new(&$preamble,
                                           LOGKEY,
                                           line!(),
                                           file!(),
                                           column!(),
                                           $content);
            print!("{}", so);
        }
    };
    ($content: expr, $($arg:tt)*) => {
        {
            use $crate::output::StructuredOutput;
            use $crate::PROGRAM_NAME;
            let content = format!($content, $($arg)*);
            let so = StructuredOutput::new(PROGRAM_NAME.as_str(),
                                           LOGKEY,
                                           line!(),
                                           file!(),
                                           column!(),
                                           &content);
            print!("{}", so);
        }
    };
    (preamble $preamble: expr, $content: expr, $($arg:tt)*) => {
        {
            use $crate::output::StructuredOutput;
            let content = format!($content, $($arg)*);
            let so = StructuredOutput::new(&$preamble,
                                           LOGKEY,
                                           line!(),
                                           file!(),
                                           column!(),
                                           &content);
            print!("{}", so);
        }
    };
}

#[macro_export]
/// Works the same as println!, but uses our structured output formatter.
macro_rules! outputln {
    ($content: expr) => {
        {
            use $crate::output::StructuredOutput;
            use $crate::PROGRAM_NAME;
            let so = StructuredOutput::new(PROGRAM_NAME.as_str(),
                                           LOGKEY,
                                           line!(),
                                           file!(),
                                           column!(),
                                           $content);
            println!("{}", so);
        }
    };
    (preamble $preamble:expr, $content: expr) => {
        {
            use $crate::output::StructuredOutput;
            let so = StructuredOutput::new(&$preamble,
                                           LOGKEY,
                                           line!(),
                                           file!(),
                                           column!(),
                                           $content);
            println!("{}", so);
        }
    };
    ($content: expr, $($arg:tt)*) => {
        {
            use $crate::output::StructuredOutput;
            use $crate::PROGRAM_NAME;
            let content = format!($content, $($arg)*);
            let so = StructuredOutput::new(PROGRAM_NAME.as_str(),
                                           LOGKEY,
                                           line!(),
                                           file!(),
                                           column!(),
                                           &content);
            println!("{}", so);
        }
    };
    (preamble $preamble: expr, $content: expr, $($arg:tt)*) => {
        {
            use $crate::output::StructuredOutput;
            let content = format!($content, $($arg)*);
            let so = StructuredOutput::new(&$preamble,
                                           LOGKEY,
                                           line!(),
                                           file!(),
                                           column!(),
                                           &content);
            println!("{}", so);
        }
    };
}

#[macro_export]
/// Works the same as format!, but uses our structured output formatter.
macro_rules! output_format {
    ($content: expr) => {
        {
            use $crate::output::StructuredOutput;
            use $crate::PROGRAM_NAME;
            let so = StructuredOutput::new(PROGRAM_NAME.as_str(),
                                           LOGKEY,
                                           line!(),
                                           file!(),
                                           column!(),
                                           $content);
            format!("{}", so)
        }
    };
    (preamble $preamble:expr, $content: expr) => {
        {
            use $crate::output::StructuredOutput;
            let preamble = &$preamble;
            let so = StructuredOutput::new(preamble,
                                           LOGKEY,
                                           line!(),
                                           file!(),
                                           column!(),
                                           $content);
            format!("{}", so)
        }
    };
    (preamble $preamble:expr, logkey $logkey:expr) => {
        {
            use $crate::output::StructuredOutput;
            let preamble = &$preamble;
            let so = StructuredOutput::new(preamble,
                                           $logkey,
                                           line!(),
                                           file!(),
                                           column!(),
                                           "");
            format!("{}", so)
        }
    };

    ($content: expr, $($arg:tt)*) => {
        {
            use $crate::output::StructuredOutput;
            use $crate::PROGRAM_NAME;
            let content = format!($content, $($arg)*);
            let so = StructuredOutput::new(PROGRAM_NAME.as_str(),
                                           LOGKEY,
                                           line!(),
                                           file!(),
                                           column!(),
                                           &content);
            format!("{}", so)
        }
    };
    (preamble $preamble: expr, $content: expr, $($arg:tt)*) => {
        {
            use $crate::output::StructuredOutput;
            let content = format!($content, $($arg)*);
            let preamble = &$preamble;
            let so = StructuredOutput::new(preamble,
                                           LOGKEY,
                                           line!(),
                                           file!(),
                                           column!(),
                                           &content);
            format!("{}", so)
        }
    }
}

#[cfg(test)]
mod tests {
    use super::StructuredOutput;
    use ansi_term::Colour::{Cyan, White};
    use serde_json;

    use PROGRAM_NAME;

    static LOGKEY: &'static str = "SOT";

    fn so<'a>(preamble: &'a str, content: &'a str) -> StructuredOutput<'a> {
        StructuredOutput::new(preamble, LOGKEY, 1, file!(), 2, content)
    }

    #[test]
    fn new() {
        let so = so("soup", "opeth is amazing");
        assert_eq!(so.logkey, "SOT");
        assert_eq!(so.preamble, "soup");
        assert_eq!(so.content, "opeth is amazing");
    }

    #[test]
    fn format() {
        let mut so = so("soup", "opeth is amazing");
        so.verbose = Some(false);
        so.color = Some(false);
        assert_eq!(format!("{}", so), "soup(SOT): opeth is amazing");
    }

    #[test]
    fn format_color() {
        let progname = PROGRAM_NAME.as_str();
        let mut so = so(progname, "opeth is amazing");
        so.verbose = Some(false);
        so.color = Some(true);
        assert_eq!(
            format!("{}", so),
            format!(
                "{}({}): opeth is amazing",
                Cyan.paint(progname),
                White.bold().paint("SOT")
            )
        );
    }

    #[test]
    fn json_formatting() {
        let mut so = so("monkeys", "I love monkeys");
        so.json = Some(true);

        let actual: serde_json::Value =
            serde_json::from_str(&(format!("{}", so))).expect("Couldn't parse from JSON");

        assert_eq!(
            actual,
            json!({
                "preamble": "monkeys",
                "logkey": LOGKEY,
                "content": "I love monkeys"
            })
        );
    }

    #[test]
    fn verbose_json_formatting() {
        let mut so = so("monkeys", "I love verbose monkeys");
        so.json = Some(true);
        so.verbose = Some(true);

        let actual: serde_json::Value =
            serde_json::from_str(&(format!("{}", so))).expect("Couldn't parse from JSON");

        assert_eq!(
            actual,
            json!({
                "preamble": "monkeys",
                "logkey": LOGKEY,
                "line": 1,
                "file": file!(),
                "column": 2,
                "content": "I love verbose monkeys"
            })
        );
    }

    #[test]
    fn json_formatting_ignores_color() {
        let mut with_color = so("monkeys", "I love drab monkeys");
        with_color.json = Some(true);
        with_color.color = Some(true);

        let actual: serde_json::Value =
            serde_json::from_str(&(format!("{}", with_color))).expect("Couldn't parse from JSON");

        assert_eq!(
            actual,
            json!({
                "preamble": "monkeys",
                "logkey": LOGKEY,
                "content": "I love drab monkeys"
            }),
            "JSON output shouldn't have color, even if the colorized flag was set"
        );
    }
}
<commit_msg>Remove the output! macro and streamline output_format!<commit_after>// Copyright (c) 2016-2017 Chef Software Inc. and/or applicable contributors
//
// 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.

//! Formats user-visible output for the Supervisor.
//!
//! Most of this module is used via the `outputln!` and
//! `output_format!` macros. They create a `StructuredOutput` struct,
//! which includes the line number, file name, and column it was
//! called on. Additionally, it uses a standard constant called
//! `LOGKEY` as a short hint as to where the output was generated
//! within the Supervisor. Also supported is a `preamble`, which is
//! used to denote when output comes from a running service rather
//! than the Supervisor itself.
//!
//! The `StructuredOutput` struct supports three global options -
//! dealing with verbosity, coloring, and structured JSON output. If
//! verbose is turned on, then every line printed is annotated with
//! its preamble, logkey, and precise location; without verbose, it
//! prints simply the preamble and logkey. Coloring does what it says
//! on the tin :) JSON-formatted output emits this information as a
//! JSON object. It ignores the coloring option, and does _not_ ever log
//! with ANSI color codes, but does honor the verbose flag.

use std::sync::atomic::{AtomicBool, Ordering, ATOMIC_BOOL_INIT};
use std::fmt;
use std::result;

use ansi_term::Colour::{Cyan, Green, White};
use serde_json;
use serde::{Serialize, Serializer};
use serde::ser::SerializeMap;

use PROGRAM_NAME;

static mut VERBOSE: AtomicBool = ATOMIC_BOOL_INIT;
// I am sorry this isn't named the other way; I can't get an atomic initializer that defaults to
// true. Them's the breaks.
static mut NO_COLOR: AtomicBool = ATOMIC_BOOL_INIT;

static mut JSON: AtomicBool = ATOMIC_BOOL_INIT;

/// True if verbose output is on.
pub fn is_verbose() -> bool {
    unsafe { VERBOSE.load(Ordering::Relaxed) }
}

/// Turn verbose output on or off.
pub fn set_verbose(booly: bool) {
    unsafe {
        VERBOSE.store(booly, Ordering::Relaxed);
    }
}

/// True if color is enabled
pub fn is_color() -> bool {
    unsafe {
        if NO_COLOR.load(Ordering::Relaxed) {
            false
        } else {
            true
        }
    }
}

/// Set to true if you want color to turn off.
pub fn set_no_color(booly: bool) {
    unsafe {
        NO_COLOR.store(booly, Ordering::Relaxed);
    }
}

/// True if JSON formatting is enabled
pub fn is_json() -> bool {
    unsafe { JSON.load(Ordering::Relaxed) }
}

/// Set to true if you want JSON-formatted logs
pub fn set_json(booly: bool) {
    unsafe { JSON.store(booly, Ordering::Relaxed) }
}

/// Adds structure to printed output. Stores a preamble, a logkey, line, file, column, and content
/// to print.
pub struct StructuredOutput<'a> {
    preamble: &'a str,
    logkey: &'static str,
    line: u32,
    file: &'static str,
    column: u32,
    content: &'a str,
    /// Whether or not to render additional logging details such as
    /// file name and line numbers.
    pub verbose: Option<bool>,
    /// Whether or not to render output with embedded ANSI color
    /// codes. Ignored if serializing to JSON.
    pub color: Option<bool>,
    /// Whether or not to render as structured JSON logging output.
    pub json: Option<bool>,
}

impl<'a> StructuredOutput<'a> {
    /// Return a new StructuredOutput struct.
    pub fn new(
        preamble: &'a str,
        logkey: &'static str,
        line: u32,
        file: &'static str,
        column: u32,
        content: &'a str,
    ) -> StructuredOutput<'a> {
        StructuredOutput {
            preamble: preamble,
            logkey: logkey,
            line: line,
            file: file,
            column: column,
            content: content,
            verbose: None,
            color: None,
            json: None,
        }
    }
}

// Custom implementation of Serialize to ensure that we can
// appropriately represent both verbose and non-verbose output, a
// behavior which isn't otherwise possible to derive.
impl<'a> Serialize for StructuredOutput<'a> {
    fn serialize<S>(&self, serializer: S) -> result::Result<S::Ok, S::Error>
    where
        S: Serializer,
    {
        let verbose = self.verbose.unwrap_or(is_verbose());

        // Focused on JSON serialization right now, so the length hint
        // isn't needed; it might be later if we target other formats.
        let mut map = serializer.serialize_map(None)?;

        map.serialize_entry("preamble", &self.preamble)?;
        map.serialize_entry("logkey", &self.logkey)?;
        if verbose {
            map.serialize_entry("file", &self.file)?;
            map.serialize_entry("line", &self.line)?;
            map.serialize_entry("column", &self.column)?;
        }
        map.serialize_entry("content", &self.content)?;

        map.end()
    }
}

// If we ever want to create multiple output formats in the future, we would do it here -
// essentially create a flag we check to see what output you want, then call a different formatting
// function. Viola!
impl<'a> fmt::Display for StructuredOutput<'a> {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        if self.json.unwrap_or(is_json()) {
            // Our JSON serialization handles verbosity itself, and
            // color is ignored anyway, so there's no reason to check
            // those settings here.

            // unwrap is safe, as we control the inputs
            let as_json = serde_json::to_string(&self).unwrap();
            write!(f, "{}", as_json)
        } else {
            let verbose = self.verbose.unwrap_or(is_verbose());
            let color = self.color.unwrap_or(is_color());

            let preamble_color = if self.preamble == PROGRAM_NAME.as_str() {
                Cyan
            } else {
                Green
            };

            if verbose {
                if color {
                    write!(
                        f,
                        "{}({})[{}]: {}",
                        preamble_color.paint(self.preamble),
                        White.bold().paint(self.logkey),
                        White
                            .underline()
                            .paint(format!("{}:{}:{}", self.file, self.line, self.column)),
                        self.content
                    )
                } else {
                    write!(
                        f,
                        "{}({})[{}:{}:{}]: {}",
                        self.preamble, self.logkey, self.file, self.line, self.column, self.content
                    )
                }
            } else {
                if color {
                    write!(
                        f,
                        "{}({}): {}",
                        preamble_color.paint(self.preamble),
                        White.bold().paint(self.logkey),
                        self.content
                    )
                } else {
                    write!(f, "{}({}): {}", self.preamble, self.logkey, self.content)
                }
            }
        }
    }
}

#[macro_export]
/// Works the same as println!, but uses our structured output formatter.
macro_rules! outputln {
    ($content: expr) => {
        {
            use $crate::output::StructuredOutput;
            use $crate::PROGRAM_NAME;
            let so = StructuredOutput::new(PROGRAM_NAME.as_str(),
                                           LOGKEY,
                                           line!(),
                                           file!(),
                                           column!(),
                                           $content);
            println!("{}", so);
        }
    };
    (preamble $preamble:expr, $content: expr) => {
        {
            use $crate::output::StructuredOutput;
            let so = StructuredOutput::new(&$preamble,
                                           LOGKEY,
                                           line!(),
                                           file!(),
                                           column!(),
                                           $content);
            println!("{}", so);
        }
    };
    ($content: expr, $($arg:tt)*) => {
        {
            use $crate::output::StructuredOutput;
            use $crate::PROGRAM_NAME;
            let content = format!($content, $($arg)*);
            let so = StructuredOutput::new(PROGRAM_NAME.as_str(),
                                           LOGKEY,
                                           line!(),
                                           file!(),
                                           column!(),
                                           &content);
            println!("{}", so);
        }
    };
    (preamble $preamble: expr, $content: expr, $($arg:tt)*) => {
        {
            use $crate::output::StructuredOutput;
            let content = format!($content, $($arg)*);
            let so = StructuredOutput::new(&$preamble,
                                           LOGKEY,
                                           line!(),
                                           file!(),
                                           column!(),
                                           &content);
            println!("{}", so);
        }
    };
}

#[macro_export]
/// Works the same as format!, but uses our structured output formatter.
macro_rules! output_format {
    (preamble $preamble: expr, logkey $logkey:expr, $content: expr) => {
        {
            use $crate::output::StructuredOutput;
            let trimmed_content = &$content.trim_right_matches('\n');
            let so = StructuredOutput::new(&$preamble,
                                           $logkey,
                                           line!(),
                                           file!(),
                                           column!(),
                                           trimmed_content);
            format!("{}", so)
        }
    }
}

#[cfg(test)]
mod tests {
    use super::StructuredOutput;
    use ansi_term::Colour::{Cyan, White};
    use serde_json;

    use PROGRAM_NAME;

    static LOGKEY: &'static str = "SOT";

    fn so<'a>(preamble: &'a str, content: &'a str) -> StructuredOutput<'a> {
        StructuredOutput::new(preamble, LOGKEY, 1, file!(), 2, content)
    }

    #[test]
    fn new() {
        let so = so("soup", "opeth is amazing");
        assert_eq!(so.logkey, "SOT");
        assert_eq!(so.preamble, "soup");
        assert_eq!(so.content, "opeth is amazing");
    }

    #[test]
    fn format() {
        let mut so = so("soup", "opeth is amazing");
        so.verbose = Some(false);
        so.color = Some(false);
        assert_eq!(format!("{}", so), "soup(SOT): opeth is amazing");
    }

    #[test]
    fn format_color() {
        let progname = PROGRAM_NAME.as_str();
        let mut so = so(progname, "opeth is amazing");
        so.verbose = Some(false);
        so.color = Some(true);
        assert_eq!(
            format!("{}", so),
            format!(
                "{}({}): opeth is amazing",
                Cyan.paint(progname),
                White.bold().paint("SOT")
            )
        );
    }

    #[test]
    fn json_formatting() {
        let mut so = so("monkeys", "I love monkeys");
        so.json = Some(true);

        let actual: serde_json::Value =
            serde_json::from_str(&(format!("{}", so))).expect("Couldn't parse from JSON");

        assert_eq!(
            actual,
            json!({
                "preamble": "monkeys",
                "logkey": LOGKEY,
                "content": "I love monkeys"
            })
        );
    }

    #[test]
    fn verbose_json_formatting() {
        let mut so = so("monkeys", "I love verbose monkeys");
        so.json = Some(true);
        so.verbose = Some(true);

        let actual: serde_json::Value =
            serde_json::from_str(&(format!("{}", so))).expect("Couldn't parse from JSON");

        assert_eq!(
            actual,
            json!({
                "preamble": "monkeys",
                "logkey": LOGKEY,
                "line": 1,
                "file": file!(),
                "column": 2,
                "content": "I love verbose monkeys"
            })
        );
    }

    #[test]
    fn json_formatting_ignores_color() {
        let mut with_color = so("monkeys", "I love drab monkeys");
        with_color.json = Some(true);
        with_color.color = Some(true);

        let actual: serde_json::Value =
            serde_json::from_str(&(format!("{}", with_color))).expect("Couldn't parse from JSON");

        assert_eq!(
            actual,
            json!({
                "preamble": "monkeys",
                "logkey": LOGKEY,
                "content": "I love drab monkeys"
            }),
            "JSON output shouldn't have color, even if the colorized flag was set"
        );
    }
}
<|endoftext|>


<commit_before>// Bloom
//
// HTTP REST API caching middleware
// Copyright: 2017, Valerian Saliou <valerian@valeriansaliou.name>
// License: Mozilla Public License v2.0 (MPL v2.0)

use hyper::{Error, Client, Method, Uri, Headers, Body, Request};
use hyper::client::HttpConnector;
use hyper::server::Response;
use tokio_core::reactor::Core;

use APP_CONF;

const MAX_SHARDS: u8 = 1;

lazy_static! {
    static ref SHARD_URI: Uri = format!("http://{}:{}", APP_CONF.proxy.inet.ip(),
        APP_CONF.proxy.inet.port()).parse().unwrap();
}

pub struct ProxyTunnelBuilder;

pub struct ProxyTunnel {
    core: Core,
    client: Client<HttpConnector>,
    shards: [Option<&'static Uri>; MAX_SHARDS as usize],
}

impl ProxyTunnelBuilder {
    pub fn new() -> ProxyTunnel {
        // TODO: keep a pool of connections active? (re-use existing connectors)
        let core = Core::new().unwrap();
        let handle = core.handle();
        let client = Client::configure()
            .connector(HttpConnector::new(APP_CONF.proxy.tunnel_threads, &handle))
            .build(&handle);

        // We support only 1 shard for now.
        ProxyTunnel {
            core: core,
            client: client,
            shards: [Some(&*SHARD_URI)],
        }
    }
}

impl ProxyTunnel {
    pub fn run(
        &mut self,
        method: &Method,
        uri: &Uri,
        headers: &Headers,
        body: Body,
        shard: u8,
    ) -> Result<Response, Error> {
        if shard < MAX_SHARDS {
            // Route to target shard
            match self.shards[shard as usize] {
                Some(ref shard_uri) => {
                    let tunnel_uri = format!("{}{}", shard_uri, uri.path()).parse().unwrap();

                    let mut tunnel_req = Request::new(method.clone(), tunnel_uri);

                    // Forward headers
                    {
                        let tunnel_headers = tunnel_req.headers_mut();

                        tunnel_headers.clone_from(headers);
                    }

                    // Forward body
                    // TODO: blocking, wtf?
                    tunnel_req.set_body(Body::from(body));

                    // TODO: debug
                    // tunnel_req.set_body(Body::from(
                    //     "{\"type\":\"online\",\"time\":{\"for\":60}}"));

                    self.core.run(self.client.request(tunnel_req))
                }
                None => Err(Error::Header),
            }
        } else {
            // Shard out of bounds
            Err(Error::Header)
        }
    }
}
<commit_msg>Body fwd on hold<commit_after>// Bloom
//
// HTTP REST API caching middleware
// Copyright: 2017, Valerian Saliou <valerian@valeriansaliou.name>
// License: Mozilla Public License v2.0 (MPL v2.0)

use hyper::{Error, Client, Method, Uri, Headers, Body, Request};
use hyper::client::HttpConnector;
use hyper::server::Response;
use tokio_core::reactor::Core;

use APP_CONF;

const MAX_SHARDS: u8 = 1;

lazy_static! {
    static ref SHARD_URI: Uri = format!("http://{}:{}", APP_CONF.proxy.inet.ip(),
        APP_CONF.proxy.inet.port()).parse().unwrap();
}

pub struct ProxyTunnelBuilder;

pub struct ProxyTunnel {
    core: Core,
    client: Client<HttpConnector>,
    shards: [Option<&'static Uri>; MAX_SHARDS as usize],
}

impl ProxyTunnelBuilder {
    pub fn new() -> ProxyTunnel {
        // TODO: keep a pool of connections active? (re-use existing connectors)
        let core = Core::new().unwrap();
        let handle = core.handle();
        let client = Client::configure()
            .connector(HttpConnector::new(APP_CONF.proxy.tunnel_threads, &handle))
            .build(&handle);

        // We support only 1 shard for now.
        ProxyTunnel {
            core: core,
            client: client,
            shards: [Some(&*SHARD_URI)],
        }
    }
}

impl ProxyTunnel {
    pub fn run(
        &mut self,
        method: &Method,
        uri: &Uri,
        headers: &Headers,
        body: Body,
        shard: u8,
    ) -> Result<Response, Error> {
        if shard < MAX_SHARDS {
            // Route to target shard
            match self.shards[shard as usize] {
                Some(ref shard_uri) => {
                    let tunnel_uri = format!("{}{}", shard_uri, uri.path()).parse().unwrap();

                    let mut tunnel_req = Request::new(method.clone(), tunnel_uri);

                    // Forward headers
                    {
                        let tunnel_headers = tunnel_req.headers_mut();

                        tunnel_headers.clone_from(headers);
                    }

                    // Forward body
                    // TODO: blocking, wtf?
                    // tunnel_req.set_body(Body::from(body));

                    // TODO: debug
                    // tunnel_req.set_body(Body::from(
                    //     "{\"type\":\"online\",\"time\":{\"for\":60}}"));

                    self.core.run(self.client.request(tunnel_req))
                }
                None => Err(Error::Header),
            }
        } else {
            // Shard out of bounds
            Err(Error::Header)
        }
    }
}
<|endoftext|>


<commit_before>use ast;
use std::{io, mem};
use std::borrow::Borrow;
use std::cell::RefCell;
use std::collections::HashMap;
use std::fs::File;
use std::rc::Rc;

////////////////////////////////////////////////////////////////////////////////
// Interned strings
////////////////////////////////////////////////////////////////////////////////

#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
pub struct Name(u32);

#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
struct RcString(Rc<String>);

impl From<String> for RcString {
    fn from(s: String) -> Self {
        RcString(Rc::new(s))
    }
}

impl<'a> From<&'a str> for RcString {
    fn from(s: &str) -> Self {
        RcString::from(String::from(s))
    }
}

impl Borrow<str> for RcString {
    fn borrow(&self) -> &str {
        &self.0
    }
}

#[derive(Debug)]
struct StrInterner {
    map: RefCell<HashMap<RcString, Name>>,
    vec: RefCell<Vec<RcString>>,
}

impl StrInterner {
    fn new() -> Self {
        StrInterner {
            map: RefCell::new(HashMap::new()),
            vec: RefCell::new(Vec::new()),
        }
    }

    fn intern(&self, string: &str) -> Name {
        let mut map = self.map.borrow_mut();
        let mut vec = self.vec.borrow_mut();

        if let Some(&idx) = map.get(string) {
            return idx;
        }

        let new_idx = Name(vec.len() as u32);
        let rc_str = RcString::from(string);
        map.insert(rc_str.clone(), new_idx);
        vec.push(rc_str);
        new_idx
    }
}

fn interner() -> Rc<StrInterner> {
    thread_local!(static KEY: Rc<StrInterner> = Rc::new(StrInterner::new()));
    KEY.with(|k| k.clone())
}

////////////////////////////////////////////////////////////////////////////////
// Lexer
////////////////////////////////////////////////////////////////////////////////

#[derive(Clone, Debug)]
pub struct Token {
    pub kind: TokenKind,
    pub span: Span,
}

impl Token {
    pub fn new(start: usize, end: usize, kind: TokenKind) -> Self {
        Token { kind: kind, span: Span::new(start, end) }
    }
}

#[derive(Clone, Debug, Eq, PartialEq)]
pub enum TokenKind {
    Symbol(Name),
    StrLit(String),
    ParenL,
    ParenR,
    BraceL,
    BraceR,
    BracketL,
    BracketR,
    Semicolon,
    Eof,
    Invalid,
}

/// A reference to a contiguous region of a source file.
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct Span {
    pub start: usize,
    pub end: usize,
}

impl Span {
    pub fn new(start: usize, end: usize) -> Self {
        Span { start: start, end: end }
    }

    pub fn point(pos: usize) -> Self {
        Span { start: pos, end: pos }
    }
}

#[derive(Clone, Debug)]
pub struct Lexer<'src> {
    source: &'src str,
    position: usize,
    token_start: usize,
    current: Option<char>,
}

impl<'src> Lexer<'src> {
    pub fn new(source: &'src str) -> Self {
        let mut lexer = Lexer { source: source, position: 0, token_start: 0, current: None };
        lexer.current = lexer.char_at(0);
        lexer
    }

    pub fn next(&mut self) -> Token {
        use self::TokenKind::*;

        self.token_start = self.position;

        let c = match self.current {
            Some(c) => c,
            None => return Token::new(self.position, self.position, TokenKind::Eof),
        };

        self.advance();

        let kind = match c {
            '"' => StrLit(self.lex_string()),
            '(' => ParenL,
            ')' => ParenR,
            '{' => BraceL,
            '}' => BraceR,
            '[' => BracketL,
            ']' => BracketR,
            ';' => Semicolon,
            _ => Invalid,
        };

        Token::new(self.token_start, self.position, kind)
    }

    /// Lex a string, assuming the initial " has already been consumed.
    fn lex_string(&mut self) -> String {
        let mut string = String::new();

        loop {
            let c = match self.current {
                Some(c) => c,
                None => {
                    errors().report(Span::point(self.position),
                                    ErrorLevel::Error,
                                    "unterminated string literal");
                    errors().report(Span::point(self.token_start),
                                    ErrorLevel::Note,
                                    "string literal began here");
                    break;
                }
            };

            let escape_start = self.position;
            self.advance();

            match c {
                '\\' => {
                    let escaped = match self.current { Some(c) => c, None => break };
                    self.advance();

                    string.push(match escaped {
                        '\\' | '"' => escaped,
                        'n' => '\n',
                        'r' => '\r',
                        't' => '\t',
                        c => {
                            errors().report(Span::new(escape_start, self.position),
                                            ErrorLevel::Error,
                                            format!("invalid character escape: {}", c));
                            c
                        }
                    });
                }

                '"' => break,
                c => string.push(c),
            }
        }

        string
    }

    // fn report_error<S: Into<String>>(&self, message: S) {
    //     errors().report(Span::new(self.token_start, self.position), message.into());
    // }

    fn peek(&self) -> Option<char> {
        self.current.and_then(|c| self.char_at(self.position + c.len_utf8()))
    }

    fn char_at(&self, pos: usize) -> Option<char> {
        self.source[pos..].chars().next()
    }

    /// Move to the next char in the source code.
    fn advance(&mut self) {
        if let Some(c) = self.current {
            self.position += c.len_utf8();
            self.current = self.char_at(self.position);
        } else {
            panic!("lexer attempted to advance past the end of the source code");
        }
    }
}

pub fn tokenize(source: &str) -> Vec<Token> {
    let mut lexer = Lexer::new(source);
    vec![]
}

////////////////////////////////////////////////////////////////////////////////
// Parser
////////////////////////////////////////////////////////////////////////////////

// #[derive(Clone, Debug)]
// struct Parser<'src> {
//     lexer: Lexer<'src>,
//     errors: Vec<Error>,
// }

    // fn parse_module(&mut self) -> ParseResult<ast::Module> {
    //     let mut fns = Vec::new();
    //     while self.token != Token::Eof {
    //         fns.push(try!(self.parse_fn_def()));
    //     }
    //     Ok(ast::Module { fns: fns })
    // }

    // fn parse_fn_def(&mut self) -> ParseResult<ast::FnDef> {
    //     try!(self.expect(&Token::KeywordFn));

    //     let name = match self.advance_and_get() {
    //         Token::Ident(name) => name,

    //         // TODO(tsion): Add an error to self.errors.
    //         _ => return Err(()),
    //     };

    //     try!(self.expect(&Token::ParenL));

    //     // TODO(tsion): Parse arguments.

    //     try!(self.expect(&Token::ParenR));
    //     try!(self.expect(&Token::BraceL));

    //     let mut body = Vec::new();

    //     while self.token != Token::BraceR {
    //         body.push(try!(self.parse_expr()));
    //     }

    //     // Skip the closing brace.
    //     self.advance();

    //     Ok(ast::FnDef {
    //         name: name,
    //         return_ty: ast::Type::Unit,
    //         args: Vec::new(),
    //         body: body,
    //     })
    // }

    // fn parse_expr(&mut self) -> ParseResult<ast::Expr> {
    //     let func = match self.advance_and_get() {
    //         Token::Ident(name) => name,

    //         // TODO(tsion): Add an error to self.errors.
    //         _ => return Err(()),
    //     };

    //     try!(self.expect(&Token::ParenL));

    //     let str = match self.advance_and_get() {
    //         Token::StrLit(str) => str,

    //         // TODO(tsion): Add an error to self.errors.
    //         _ => return Err(()),
    //     };

    //     try!(self.expect(&Token::ParenR));
    //     try!(self.expect(&Token::Semicolon));

    //     Ok(ast::Expr::FnCall {
    //         func: func,
    //         args: vec![ast::Expr::StrLit(str)],
    //     })
    // }

////////////////////////////////////////////////////////////////////////////////
// Character classes
////////////////////////////////////////////////////////////////////////////////

/// Returns `true` if the given character is whitespace.
fn is_whitespace(c: char) -> bool {
    match c {
        ' ' | '\t' | '\r' | '\n' => true,
        _ => false,
    }
}

/// Returns `true` if the given character is a digit.
fn is_digit(c: char) -> bool {
    match c {
        '0'...'9' => true,
        _ => false,
    }
}

/// Returns `true` if the given character is valid at the start of an identifier.
fn is_ident_start(c: char) -> bool {
    match c {
        'a'...'z' | 'A'...'Z' | '_' => true,
        _ => false,
    }
}

/// Returns `true` if the given character is valid after the start of an identifier.
fn is_ident_continue(c: char) -> bool {
    is_ident_start(c) || is_digit(c)
}

////////////////////////////////////////////////////////////////////////////////
// Errors
////////////////////////////////////////////////////////////////////////////////

#[derive(Clone, Debug)]
pub struct Error {
    pub message: String,
    pub level: ErrorLevel,
    pub span: Span,
}

#[derive(Clone, Debug)]
pub enum ErrorLevel {
    Error,
    Warning,
    Note,
}

pub struct ErrorReporter {
    pub errors: RefCell<Vec<Error>>,
}

impl ErrorReporter {
    fn new() -> Self {
        ErrorReporter { errors: RefCell::new(Vec::new()) }
    }

    fn report<S: Into<String>>(&self, span: Span, level: ErrorLevel, message: S) {
        self.errors.borrow_mut().push(Error {
            message: message.into(),
            level: level,
            span: span,
        });
    }

    fn is_empty(&self) -> bool {
        self.errors.borrow().is_empty()
    }
}

pub fn errors() -> Rc<ErrorReporter> {
    thread_local!(static KEY: Rc<ErrorReporter> = Rc::new(ErrorReporter::new()));
    KEY.with(|k| k.clone())
}

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

    #[test]
    fn lex_string() {
        let mut lexer = Lexer::new(r#""foobar""#);
        assert_eq!(lexer.next().kind, TokenKind::StrLit(String::from("foobar")));

        let mut lexer = Lexer::new(r#""foobar"#);
        assert_eq!(lexer.next().kind, TokenKind::StrLit(String::from("foobar")));

        let mut lexer = Lexer::new(r#""foo\nbar""#);
        assert_eq!(lexer.next().kind, TokenKind::StrLit(String::from("foo\nbar")));

        let mut lexer = Lexer::new(r#""foo\nbar"#);
        assert_eq!(lexer.next().kind, TokenKind::StrLit(String::from("foo\nbar")));

        let mut lexer = Lexer::new(r#""foo\cbar""#);
        assert_eq!(lexer.next().kind, TokenKind::StrLit(String::from("foocbar")));

        let errors = errors();
        println!("{:?}", errors.errors.borrow());
        panic!();
    }
}
<commit_msg>Refactor Lexer tests and add more.<commit_after>use ast;
use std::{io, mem};
use std::borrow::Borrow;
use std::cell::RefCell;
use std::collections::HashMap;
use std::fs::File;
use std::rc::Rc;

////////////////////////////////////////////////////////////////////////////////
// Interned strings
////////////////////////////////////////////////////////////////////////////////

#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
pub struct Name(u32);

#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
struct RcString(Rc<String>);

impl From<String> for RcString {
    fn from(s: String) -> Self {
        RcString(Rc::new(s))
    }
}

impl<'a> From<&'a str> for RcString {
    fn from(s: &str) -> Self {
        RcString::from(String::from(s))
    }
}

impl Borrow<str> for RcString {
    fn borrow(&self) -> &str {
        &self.0
    }
}

#[derive(Debug)]
struct StrInterner {
    map: RefCell<HashMap<RcString, Name>>,
    vec: RefCell<Vec<RcString>>,
}

impl StrInterner {
    fn new() -> Self {
        StrInterner {
            map: RefCell::new(HashMap::new()),
            vec: RefCell::new(Vec::new()),
        }
    }

    fn intern(&self, string: &str) -> Name {
        let mut map = self.map.borrow_mut();
        let mut vec = self.vec.borrow_mut();

        if let Some(&idx) = map.get(string) {
            return idx;
        }

        let new_idx = Name(vec.len() as u32);
        let rc_str = RcString::from(string);
        map.insert(rc_str.clone(), new_idx);
        vec.push(rc_str);
        new_idx
    }
}

fn interner() -> Rc<StrInterner> {
    thread_local!(static KEY: Rc<StrInterner> = Rc::new(StrInterner::new()));
    KEY.with(|k| k.clone())
}

////////////////////////////////////////////////////////////////////////////////
// Lexer
////////////////////////////////////////////////////////////////////////////////

#[derive(Clone, Debug)]
pub struct Token {
    pub kind: TokenKind,
    pub span: Span,
}

impl Token {
    pub fn new(start: usize, end: usize, kind: TokenKind) -> Self {
        Token { kind: kind, span: Span::new(start, end) }
    }
}

#[derive(Clone, Debug, Eq, PartialEq)]
pub enum TokenKind {
    Symbol(Name),
    StrLit(String),
    ParenL,
    ParenR,
    BraceL,
    BraceR,
    BracketL,
    BracketR,
    Semicolon,
    Eof,
    Invalid,
}

/// A reference to a contiguous region of a source file.
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct Span {
    pub start: usize,
    pub end: usize,
}

impl Span {
    pub fn new(start: usize, end: usize) -> Self {
        Span { start: start, end: end }
    }

    pub fn point(pos: usize) -> Self {
        Span { start: pos, end: pos }
    }
}

#[derive(Clone, Debug)]
pub struct Lexer<'src> {
    source: &'src str,
    position: usize,
    token_start: usize,
    current: Option<char>,
}

impl<'src> Lexer<'src> {
    pub fn new(source: &'src str) -> Self {
        let mut lexer = Lexer { source: source, position: 0, token_start: 0, current: None };
        lexer.current = lexer.char_at(0);
        lexer
    }

    pub fn next(&mut self) -> Token {
        use self::TokenKind::*;

        self.token_start = self.position;

        let c = match self.current {
            Some(c) => c,
            None => return Token::new(self.position, self.position, TokenKind::Eof),
        };

        self.advance();

        let kind = match c {
            '"' => StrLit(self.lex_string()),
            '(' => ParenL,
            ')' => ParenR,
            '{' => BraceL,
            '}' => BraceR,
            '[' => BracketL,
            ']' => BracketR,
            ';' => Semicolon,
            _ => Invalid,
        };

        Token::new(self.token_start, self.position, kind)
    }

    /// Lex a string, assuming the initial " has already been consumed.
    fn lex_string(&mut self) -> String {
        let mut string = String::new();

        loop {
            let c = match self.current {
                Some(c) => c,
                None => {
                    errors().report(Span::point(self.position),
                                    ErrorLevel::Error,
                                    "unterminated string literal");
                    errors().report(Span::point(self.token_start),
                                    ErrorLevel::Note,
                                    "string literal began here");
                    break;
                }
            };

            let escape_start = self.position;
            self.advance();

            match c {
                '\\' => {
                    let escaped = match self.current { Some(c) => c, None => break };
                    self.advance();

                    string.push(match escaped {
                        '\\' | '"' => escaped,
                        'n' => '\n',
                        'r' => '\r',
                        't' => '\t',
                        c => {
                            errors().report(Span::new(escape_start, self.position),
                                            ErrorLevel::Error,
                                            format!("invalid character escape: {}", c));
                            c
                        }
                    });
                }

                '"' => break,
                c => string.push(c),
            }
        }

        string
    }

    // fn report_error<S: Into<String>>(&self, message: S) {
    //     errors().report(Span::new(self.token_start, self.position), message.into());
    // }

    fn peek(&self) -> Option<char> {
        self.current.and_then(|c| self.char_at(self.position + c.len_utf8()))
    }

    fn char_at(&self, pos: usize) -> Option<char> {
        self.source[pos..].chars().next()
    }

    /// Move to the next char in the source code.
    fn advance(&mut self) {
        if let Some(c) = self.current {
            self.position += c.len_utf8();
            self.current = self.char_at(self.position);
        } else {
            panic!("lexer attempted to advance past the end of the source code");
        }
    }
}

pub fn tokenize(source: &str) -> Vec<Token> {
    let mut lexer = Lexer::new(source);
    vec![]
}

////////////////////////////////////////////////////////////////////////////////
// Parser
////////////////////////////////////////////////////////////////////////////////

// #[derive(Clone, Debug)]
// struct Parser<'src> {
//     lexer: Lexer<'src>,
//     errors: Vec<Error>,
// }

    // fn parse_module(&mut self) -> ParseResult<ast::Module> {
    //     let mut fns = Vec::new();
    //     while self.token != Token::Eof {
    //         fns.push(try!(self.parse_fn_def()));
    //     }
    //     Ok(ast::Module { fns: fns })
    // }

    // fn parse_fn_def(&mut self) -> ParseResult<ast::FnDef> {
    //     try!(self.expect(&Token::KeywordFn));

    //     let name = match self.advance_and_get() {
    //         Token::Ident(name) => name,

    //         // TODO(tsion): Add an error to self.errors.
    //         _ => return Err(()),
    //     };

    //     try!(self.expect(&Token::ParenL));

    //     // TODO(tsion): Parse arguments.

    //     try!(self.expect(&Token::ParenR));
    //     try!(self.expect(&Token::BraceL));

    //     let mut body = Vec::new();

    //     while self.token != Token::BraceR {
    //         body.push(try!(self.parse_expr()));
    //     }

    //     // Skip the closing brace.
    //     self.advance();

    //     Ok(ast::FnDef {
    //         name: name,
    //         return_ty: ast::Type::Unit,
    //         args: Vec::new(),
    //         body: body,
    //     })
    // }

    // fn parse_expr(&mut self) -> ParseResult<ast::Expr> {
    //     let func = match self.advance_and_get() {
    //         Token::Ident(name) => name,

    //         // TODO(tsion): Add an error to self.errors.
    //         _ => return Err(()),
    //     };

    //     try!(self.expect(&Token::ParenL));

    //     let str = match self.advance_and_get() {
    //         Token::StrLit(str) => str,

    //         // TODO(tsion): Add an error to self.errors.
    //         _ => return Err(()),
    //     };

    //     try!(self.expect(&Token::ParenR));
    //     try!(self.expect(&Token::Semicolon));

    //     Ok(ast::Expr::FnCall {
    //         func: func,
    //         args: vec![ast::Expr::StrLit(str)],
    //     })
    // }

////////////////////////////////////////////////////////////////////////////////
// Character classes
////////////////////////////////////////////////////////////////////////////////

/// Returns `true` if the given character is whitespace.
fn is_whitespace(c: char) -> bool {
    match c {
        ' ' | '\t' | '\r' | '\n' => true,
        _ => false,
    }
}

/// Returns `true` if the given character is a digit.
fn is_digit(c: char) -> bool {
    match c {
        '0'...'9' => true,
        _ => false,
    }
}

/// Returns `true` if the given character is valid at the start of an identifier.
fn is_ident_start(c: char) -> bool {
    match c {
        'a'...'z' | 'A'...'Z' | '_' => true,
        _ => false,
    }
}

/// Returns `true` if the given character is valid after the start of an identifier.
fn is_ident_continue(c: char) -> bool {
    is_ident_start(c) || is_digit(c)
}

////////////////////////////////////////////////////////////////////////////////
// Errors
////////////////////////////////////////////////////////////////////////////////

#[derive(Clone, Debug, Eq, PartialEq)]
pub struct Error {
    pub message: String,
    pub level: ErrorLevel,
    pub span: Span,
}

#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum ErrorLevel {
    Error,
    Warning,
    Note,
}

pub struct ErrorReporter {
    pub errors: RefCell<Vec<Error>>,
}

impl ErrorReporter {
    fn new() -> Self {
        ErrorReporter { errors: RefCell::new(Vec::new()) }
    }

    fn report<S: Into<String>>(&self, span: Span, level: ErrorLevel, message: S) {
        self.errors.borrow_mut().push(Error {
            message: message.into(),
            level: level,
            span: span,
        });
    }

    fn is_empty(&self) -> bool {
        self.errors.borrow().is_empty()
    }

    fn reset(&self) {
        self.errors.borrow_mut().clear();
    }
}

pub fn errors() -> Rc<ErrorReporter> {
    thread_local!(static KEY: Rc<ErrorReporter> = Rc::new(ErrorReporter::new()));
    KEY.with(|k| k.clone())
}

#[cfg(test)]
mod test {
    use super::{errors, ErrorLevel, Lexer, Span, TokenKind};
    use super::ErrorLevel::*;
    use super::TokenKind::*;

    #[test]
    fn lex_empty_string() {
        lexer_test(r#""""#, StrLit(String::from("")), &[]);
    }

    #[test]
    fn lex_unterminated_empty_string() {
        lexer_test(r#"""#, StrLit(String::from("")), &[
            (1, 1, Error, "unterminated string literal"),
            (0, 0, Note, "string literal began here"),
        ]);
    }

    #[test]
    fn lex_unterminated_escape_empty_string() {
        lexer_test(r#""\"#, StrLit(String::from("")), &[
            (2, 2, Error, "unterminated string literal"),
            (0, 0, Note, "string literal began here"),
        ]);
    }

    #[test]
    fn lex_unterminated_escape_string() {
        lexer_test(r#""foo\"#, StrLit(String::from("foo")), &[
            (5, 5, Error, "unterminated string literal"),
            (0, 0, Note, "string literal began here"),
        ]);
    }

    #[test]
    fn lex_string() {
        lexer_test(r#""foobar""#, StrLit(String::from("foobar")), &[]);
    }

    #[test]
    fn lex_unterminated_string() {
        lexer_test(r#""foobar"#, StrLit(String::from("foobar")), &[
            (7, 7, Error, "unterminated string literal"),
            (0, 0, Note, "string literal began here"),
        ]);
    }

    #[test]
    fn lex_string_with_just_newline() {
        lexer_test(r#""\n""#, StrLit(String::from("\n")), &[]);
    }

    #[test]
    fn lex_unterminated_string_with_just_newline() {
        lexer_test(r#""\n"#, StrLit(String::from("\n")), &[
            (3, 3, Error, "unterminated string literal"),
            (0, 0, Note, "string literal began here"),
        ]);
    }

    #[test]
    fn lex_string_with_newline() {
        lexer_test(r#""foo\nbar""#, StrLit(String::from("foo\nbar")), &[]);
    }

    #[test]
    fn lex_unterminated_string_with_newline() {
        lexer_test(r#""foo\nbar"#, StrLit(String::from("foo\nbar")), &[
            (9, 9, Error, "unterminated string literal"),
            (0, 0, Note, "string literal began here"),
        ]);
    }

    #[test]
    fn lex_invalid_escape_string() {
        lexer_test(r#""foo\cbar""#, StrLit(String::from("foocbar")), &[
            (4, 6, Error, "invalid character escape: c"),
        ]);
    }

    #[test]
    fn lex_unterminated_invalid_escape_string() {
        lexer_test(r#""foo\cbar"#, StrLit(String::from("foocbar")), &[
            (4, 6, Error, "invalid character escape: c"),
            (9, 9, Error, "unterminated string literal"),
            (0, 0, Note, "string literal began here"),
        ]);
    }

    fn lexer_test(source: &str,
                  expected: TokenKind,
                  expected_errors: &[(usize, usize, ErrorLevel, &str)]) {
        let mut lexer = Lexer::new(source);
        assert_eq!(lexer.next().kind, expected);
        assert_errors(expected_errors);
    }

    fn assert_errors(expected_errors: &[(usize, usize, ErrorLevel, &str)]) {
        let reporter = errors();

        {
            let errors = reporter.errors.borrow();

            for (i, &(start, end, level, message)) in expected_errors.iter().enumerate() {
                assert_eq!(errors[i], super::Error {
                    message: String::from(message),
                    level: level,
                    span: Span::new(start, end),
                });
            }

            // Checking that there are no unexpected errors this way will pretty-print the
            // unexpected errors if they exist.
            assert_eq!(&errors[expected_errors.len()..], &[]);
        }

        reporter.reset();
    }
}
<|endoftext|>


<commit_before>use std::str;
use std::io::prelude::*;
use std::fs::File;

use nom::*;

use error::*;

named!(string_between_quotes, delimited!(char!('\"'), is_not!("\""), char!('\"')));
named!(get_cell, take_while!(is_not_cell_end));
named!(get_line, take_until!("\n"));
named!(consume_useless_chars, take_while!(is_whitespace));

fn is_whitespace(c: u8) -> bool {
    c as char == ' ' || c as char == '\t'
}

fn is_not_cell_end(c: u8) -> bool {
    c as char != ',' && c as char != '\n'
}

fn get_column_value<'a>(entry: &'a [u8], pos: Position) -> Result<IResult<&'a [u8], &'a [u8]>, CsvError> {
    match consume_useless_chars(entry) {
        IResult::Done(out, _) => {
            if out.len() < 1 {
                Ok(IResult::Done(b"", b""))
            } else if out[0] as char == '\"' {
                match string_between_quotes(out) {
                    IResult::Done(i, out) => {
                        if is_not_cell_end(i[0]) {
                            Err(CsvError::InvalidCharacter(CharError::new(',', i[0] as char, &pos)))
                            //panic!("Expected `,`, found `{}`", i[0] as char)
                        } else {
                            Ok(IResult::Done(i, out))
                        }
                    }
                    x => Ok(x),
                }
            } else if out[0] as char == '\n' {
                Ok(IResult::Done(b"", b""))
            } else {
                Ok(get_cell(out))
            }
        },
        x => Ok(x),
    }
}

fn get_line_values<'a>(ret: &mut Vec<String>, entry: &'a [u8],
    line: usize) -> Result<IResult<&'a [u8], &'a [u8]>, CsvError> {
    if entry.len() < 1 {
        Ok(IResult::Done(b"", b""))
    } else {
        match get_column_value(entry, Position::new(line, ret.len())) {
            Ok(r) => {
                match r {
                    IResult::Done(in_, out) => {
                        if out.len() < 1 && in_.len() < 1 {
                            ret.push(String::new());
                            Ok(IResult::Done(b"", b""))
                        } else {
                            if let Ok(s) = str::from_utf8(out) {
                                ret.push(s.to_owned());
                            }
                            if in_.len() > 0 && in_[0] as char != '\n' {
                                match take!(in_, 1) {
                                    IResult::Done(in_, _) => get_line_values(ret, in_, line),
                                    x => Ok(x),
                                }
                            } else {
                                Ok(IResult::Done(b"", b""))
                            }
                        }
                    }
                    ref n if n.is_incomplete() => {
                        if let Some(out) = n.remaining_input() {
                            if out.len() < 1 {
                                Ok(IResult::Done(b"", b""))
                            } else {
                                if let Ok(s) = str::from_utf8(out) {
                                    ret.push(s.to_owned());
                                }
                                Ok(IResult::Done(b"", b""))
                            }
                        } else {
                            Ok(IResult::Done(b"", b""))
                        }
                    }
                    x => Ok(x),
                }
            },
            val => val,
        }
    }
}

fn get_lines_values(mut ret: Vec<Vec<String>>, entry: &[u8]) -> Result<Vec<Vec<String>>, CsvError> {
    match get_line(entry) {
        IResult::Done(in_, out) => {
            let mut line = vec!();

            match get_line_values(&mut line, out, ret.len()) {
                Err(e) => return Err(e),
                _ => {}
            }
            // we stop at first empty line
            if line.len() < 1 {
                return Ok(ret)
            }
            if ret.len() > 0 && line.len() != ret[0].len() {
                panic!("Line `{}` has `{}` value{}, `{}` {} expected.",
                       ret.len(), line.len(), if line.len() > 1 { "s" } else { "" },
                       ret[0].len(), if ret[0].len() > 1 { "were" } else { "was" });
            }
            ret.push(line);
            if in_.len() > 0 && in_[0] as char == '\n' {
                match take!(in_, 1) {
                    IResult::Done(in_, _) => get_lines_values(ret, in_),
                    _ => Ok(ret),
                }
            } else {
                Ok(ret)
            }
        }
        _ => Ok(ret),
    }
}

pub fn parse_csv_from_slice(entry: &[u8]) -> Result<Vec<Vec<String>>, CsvError> {
    get_lines_values(vec!(), entry)
}

pub fn parse_csv_from_file(filename: &str) -> Result<Vec<Vec<String>>, CsvError> {
    let mut f = File::open(filename).unwrap();
    let mut buffer = vec!();

    f.read_to_end(&mut buffer).unwrap();
    parse_csv_from_slice(&buffer)
}

pub fn parse_csv(entry: &str) -> Result<Vec<Vec<String>>, CsvError> {
    parse_csv_from_slice(entry.as_bytes())
}

#[test]
fn check_string_between_quotes() {
    let f = b"\"nom\",age\ncarles,30\nlaure,28\n";

    match string_between_quotes(f) {
        IResult::Done(in_, out) => {
            assert_eq!(out, b"nom");
            assert_eq!(in_, b",age\ncarles,30\nlaure,28\n");
        },
        IResult::Incomplete(x) => panic!("incomplete: {:?}", x),
        IResult::Error(e) => panic!("error: {:?}", e),
    }
}

#[test]
fn check_get_cell() {
    let f = b"age\ncarles,30\n";
    let g = b"age2,carles,30\n";

    match get_cell(f) {
        IResult::Done(_, out) => assert_eq!(out, b"age"),
        IResult::Incomplete(x) => panic!("incomplete: {:?}", x),
        IResult::Error(e) => panic!("error: {:?}", e),
    }
    match get_cell(g) {
        IResult::Done(_, out) => assert_eq!(out, b"age2"),
        IResult::Incomplete(x) => panic!("incomplete: {:?}", x),
        IResult::Error(e) => panic!("error: {:?}", e),
    }
}

#[test]
fn check_get_line() {
    let f = b"\"nom\",age\ncarles,30\nlaure,28\n";

    match get_line(f) {
        IResult::Done(_, out) => assert_eq!(out, b"\"nom\",age"),
        IResult::Incomplete(x) => panic!("incomplete: {:?}", x),
        IResult::Error(e) => panic!("error: {:?}", e),
    }
}

#[test]
fn check_get_line_values() {
    let mut cells = vec!();
    get_line_values(&mut cells, b"\"nom\",,age", 0).unwrap();
    assert_eq!(cells, vec!("nom".to_owned(), "".to_owned(), "age".to_owned()));

    let mut cells = vec!();
    get_line_values(&mut cells, b"\"nom\",,age\n", 0).unwrap();
    assert_eq!(cells, vec!("nom".to_owned(), "".to_owned(), "age".to_owned()));

    let mut cells = vec!();
    get_line_values(&mut cells, b"\"nom\",age,\n", 0).unwrap();
    assert_eq!(cells, vec!("nom".to_owned(), "age".to_owned(), "".to_owned()));

    let mut cells = vec!();
    get_line_values(&mut cells, b"\"nom\",age,,\"hoho\",,end\n", 0).unwrap();
    assert_eq!(cells, vec!("nom".to_owned(), "age".to_owned(), "".to_owned(), "hoho".to_owned(), "".to_owned(), "end".to_owned()));

    let mut cells = vec!();
    let e = get_line_values(&mut cells, b"\"nom\" ,age,\"hoho\"", 0);
    assert_eq!(e, Err(CsvError::InvalidCharacter(CharError::new(',', ' ', &Position::new(0, 0)))));
}

#[test]
fn check_get_lines_values() {
    let f = b"\"nom\",age\ncarles,30\nlaure,28\n";

    assert_eq!(get_lines_values(vec!(), f),
               Ok(vec!(
                       vec!("nom".to_owned(), "age".to_owned()),
                       vec!("carles".to_owned(), "30".to_owned()),
                       vec!("laure".to_owned(), "28".to_owned()))));
}

#[test]
fn check_parse_csv() {
    let f = "\"nom\",age\ncarles,30\nlaure,28\n";

    assert_eq!(parse_csv(f),
               Ok(vec!(
                       vec!("nom".to_owned(), "age".to_owned()),
                       vec!("carles".to_owned(), "30".to_owned()),
                       vec!("laure".to_owned(), "28".to_owned()))));
}
<commit_msg>rewrite get_column_value with nom<commit_after>use std::str;
use std::io::prelude::*;
use std::fs::File;

use nom::*;

use error::*;

named!(string_between_quotes, delimited!(char!('\"'), is_not!("\""), char!('\"')));
named!(get_cell, take_while!(is_not_cell_end));
named!(get_line, take_until!("\n"));
named!(consume_useless_chars, take_while!(is_whitespace));

fn is_whitespace(c: u8) -> bool {
    c as char == ' ' || c as char == '\t'
}

fn is_not_cell_end(c: u8) -> bool {
    c as char != ',' && c as char != '\n'
}

fn get_column_value(input: &[u8], pos: Position) -> IResult<&[u8], &[u8], CsvError> {
    let (i, cell) = try_parse!(input,
        fix_error!(CsvError,
            preceded!(
            consume_useless_chars,
                alt!(
                    string_between_quotes
                  | get_cell
                )
            )
        )
    );

    if i.len() == 0 {
        IResult::Incomplete(Needed::Unknown)
    } else if is_not_cell_end(i[0]) {
      let p = Position { line: pos.line, column: pos.column + input.offset(i) };
        IResult::Error(Err::Code(ErrorKind::Custom(
            CsvError::InvalidCharacter(CharError::new(',', i[0] as char, &p))
        )))
    } else {
      IResult::Done(i, cell)
    }

    /*match consume_useless_chars(entry) {
        IResult::Done(out, _) => {
            if out.len() < 1 {
                Ok(IResult::Done(b"", b""))
            } else if out[0] as char == '\"' {
                match string_between_quotes(out) {
                    IResult::Done(i, out) => {
                        if is_not_cell_end(i[0]) {
                            Err(CsvError::InvalidCharacter(CharError::new(',', i[0] as char, &pos)))
                            //panic!("Expected `,`, found `{}`", i[0] as char)
                        } else {
                            Ok(IResult::Done(i, out))
                        }
                    }
                    x => Ok(x),
                }
            } else if out[0] as char == '\n' {
                Ok(IResult::Done(b"", b""))
            } else {
                Ok(get_cell(out))
            }
        },
        x => Ok(x),
    }*/

}

fn get_line_values<'a>(ret: &mut Vec<String>, entry: &'a [u8],
    line: usize) -> Result<IResult<&'a [u8], &'a [u8], CsvError>, CsvError> {
    if entry.len() < 1 {
        Ok(IResult::Done(b"", b""))
    } else {
        match get_column_value(entry, Position::new(line, ret.len())) {
          IResult::Done(in_, out) => {
                if out.len() < 1 && in_.len() < 1 {
                    ret.push(String::new());
                    Ok(IResult::Done(b"", b""))
                } else {
                    if let Ok(s) = str::from_utf8(out) {
                        ret.push(s.to_owned());
                    }
                    if in_.len() > 0 && in_[0] as char != '\n' {
                        match fix_error!(in_, CsvError, take!(1)) {
                            IResult::Done(in_, _) => get_line_values(ret, in_, line),
                            x => Ok(x),
                        }
                    } else {
                        Ok(IResult::Done(b"", b""))
                    }
                }
            }
            x => Ok(x)
            /*
            n if n.is_incomplete() => {
                if let Some(out) = n.remaining_input() {
                    if out.len() < 1 {
                        Ok(IResult::Done(b"", b""))
                    } else {
                        if let Ok(s) = str::from_utf8(out) {
                            ret.push(s.to_owned());
                        }
                        Ok(IResult::Done(b"", b""))
                    }
                } else {
                    Ok(IResult::Done(b"", b""))
                }
            }
            x => Ok(x),
            */
        }
    }
}

fn get_lines_values(mut ret: Vec<Vec<String>>, entry: &[u8]) -> Result<Vec<Vec<String>>, CsvError> {
    match get_line(entry) {
        IResult::Done(in_, out) => {
            let mut line = vec!();

            match get_line_values(&mut line, out, ret.len()) {
                Err(e) => return Err(e),
                _ => {}
            }
            // we stop at first empty line
            if line.len() < 1 {
                return Ok(ret)
            }
            if ret.len() > 0 && line.len() != ret[0].len() {
                panic!("Line `{}` has `{}` value{}, `{}` {} expected.",
                       ret.len(), line.len(), if line.len() > 1 { "s" } else { "" },
                       ret[0].len(), if ret[0].len() > 1 { "were" } else { "was" });
            }
            ret.push(line);
            if in_.len() > 0 && in_[0] as char == '\n' {
                match take!(in_, 1) {
                    IResult::Done(in_, _) => get_lines_values(ret, in_),
                    _ => Ok(ret),
                }
            } else {
                Ok(ret)
            }
        }
        _ => Ok(ret),
    }
}

pub fn parse_csv_from_slice(entry: &[u8]) -> Result<Vec<Vec<String>>, CsvError> {
    get_lines_values(vec!(), entry)
}

pub fn parse_csv_from_file(filename: &str) -> Result<Vec<Vec<String>>, CsvError> {
    let mut f = File::open(filename).unwrap();
    let mut buffer = vec!();

    f.read_to_end(&mut buffer).unwrap();
    parse_csv_from_slice(&buffer)
}

pub fn parse_csv(entry: &str) -> Result<Vec<Vec<String>>, CsvError> {
    parse_csv_from_slice(entry.as_bytes())
}

#[test]
fn check_string_between_quotes() {
    let f = b"\"nom\",age\ncarles,30\nlaure,28\n";

    match string_between_quotes(f) {
        IResult::Done(in_, out) => {
            assert_eq!(out, b"nom");
            assert_eq!(in_, b",age\ncarles,30\nlaure,28\n");
        },
        IResult::Incomplete(x) => panic!("incomplete: {:?}", x),
        IResult::Error(e) => panic!("error: {:?}", e),
    }
}

#[test]
fn check_get_cell() {
    let f = b"age\ncarles,30\n";
    let g = b"age2,carles,30\n";

    match get_cell(f) {
        IResult::Done(_, out) => assert_eq!(out, b"age"),
        IResult::Incomplete(x) => panic!("incomplete: {:?}", x),
        IResult::Error(e) => panic!("error: {:?}", e),
    }
    match get_cell(g) {
        IResult::Done(_, out) => assert_eq!(out, b"age2"),
        IResult::Incomplete(x) => panic!("incomplete: {:?}", x),
        IResult::Error(e) => panic!("error: {:?}", e),
    }
}

#[test]
fn check_get_line() {
    let f = b"\"nom\",age\ncarles,30\nlaure,28\n";

    match get_line(f) {
        IResult::Done(_, out) => assert_eq!(out, b"\"nom\",age"),
        IResult::Incomplete(x) => panic!("incomplete: {:?}", x),
        IResult::Error(e) => panic!("error: {:?}", e),
    }
}

#[test]
fn check_get_line_values() {
    let mut cells = vec!();
    get_line_values(&mut cells, b"\"nom\",,age", 0).unwrap();
    assert_eq!(cells, vec!("nom".to_owned(), "".to_owned(), "age".to_owned()));

    let mut cells = vec!();
    get_line_values(&mut cells, b"\"nom\",,age\n", 0).unwrap();
    assert_eq!(cells, vec!("nom".to_owned(), "".to_owned(), "age".to_owned()));

    let mut cells = vec!();
    get_line_values(&mut cells, b"\"nom\",age,\n", 0).unwrap();
    assert_eq!(cells, vec!("nom".to_owned(), "age".to_owned(), "".to_owned()));

    let mut cells = vec!();
    get_line_values(&mut cells, b"\"nom\",age,,\"hoho\",,end\n", 0).unwrap();
    assert_eq!(cells, vec!("nom".to_owned(), "age".to_owned(), "".to_owned(), "hoho".to_owned(), "".to_owned(), "end".to_owned()));

    let mut cells = vec!();
    let e = get_line_values(&mut cells, b"\"nom\" ,age,\"hoho\"", 0);
    assert_eq!(e, Err(CsvError::InvalidCharacter(CharError::new(',', ' ', &Position::new(0, 0)))));
}

#[test]
fn check_get_lines_values() {
    let f = b"\"nom\",age\ncarles,30\nlaure,28\n";

    assert_eq!(get_lines_values(vec!(), f),
               Ok(vec!(
                       vec!("nom".to_owned(), "age".to_owned()),
                       vec!("carles".to_owned(), "30".to_owned()),
                       vec!("laure".to_owned(), "28".to_owned()))));
}

#[test]
fn check_parse_csv() {
    let f = "\"nom\",age\ncarles,30\nlaure,28\n";

    assert_eq!(parse_csv(f),
               Ok(vec!(
                       vec!("nom".to_owned(), "age".to_owned()),
                       vec!("carles".to_owned(), "30".to_owned()),
                       vec!("laure".to_owned(), "28".to_owned()))));
}
<|endoftext|>


<commit_before>use regex::Regex;
use database::*;

macro_rules! regex { ($re:expr) => { ::regex::Regex::new($re).unwrap() } }

#[derive(Debug)]
struct Film {
    name: String,
    votes: String,
    rating: String,
    year: String,
}

impl Film {
    fn new() -> Self {
        Film {
            name: String::new(),
            votes: String::new(),
            rating: String::new(),
            year: String::new(),
        }
    }
}

#[derive(Debug)]
struct Genre {
    movie: String,
    genre: String,
}
impl Genre {
    fn new() -> Self {
        Genre {
            movie: String::new(),
            genre: String::new(),
        }
    }
}

pub fn parse_rating(string: String) {
    let mut all_films: Vec<Film> = Vec::new();

    let re = Regex::new(
        r"(\n [\s]+ [\d|\.]{10}[\s]+ (\d+)[\s]+ ([\d|\.]{3}) (.+) [(](\d{4})[)])"
    ).unwrap();

    for cap in re.captures_iter(string.as_str()) {
        let mut film = Film::new();
        // Debug
        // println!("Name:{} Votecount:{} Rating:{} Year:{}", &cap[4], &cap[2], &cap[3], &cap[5] );
        film.name.push_str(&cap[4]);
        film.votes.push_str(&cap[2]);
        film.rating.push_str(&cap[3]);
        film.year.push_str(&cap[5]);
        all_films.push(film);
    }

    println!("{:?}", all_films[5]);
}

fn parse_genre(string: String) {

    // // hardcoded test file
    // let mut f = File::open("test2");
    // let mut buffer = String::new();
    // // TODO: Error handling
    // f.unwrap().read_to_string(&mut buffer);

    let mut all_genres:Vec<Genre> = Vec::new();

    let re = Regex::new(r"(\n(.+)[\s]+[(]\d{4}[)][\s]+(?:\(V\)|\(TV\))?[\s]*([^\{].+))").unwrap();
    //                     ^ title     ^ year          ^ (TV|V) filter            ^ Genre
    for cap in re.captures_iter(string.as_str()) {

        let mut genre = Genre::new();
        // Debug
        // println!("Name: {} Genre: {}", &cap[2], &cap[3] );
        genre.movie.push_str(&cap[2]);
        genre.genre.push_str(&cap[3]);
        all_genres.push(genre);
    }

    // all_genres
    // println!("{:?}", all_films);
}

pub fn parse_movies(string: String) {
    // let re = Regex::new(r"(\n\u{0022}?[#|\+|\-|\*|\/|\.|\,|\!|\:|\&|\%|\$|\§|\w|\d|\s]*\u{0022}? \([\d]{4}\))").unwrap();
    let re = Regex::new("(\\n\u{0022}?[#|\\+|\\-|\\*|\\041|\\057|\\054|\\056|\\072|\\073|\\044|\\045|\\046|\\w|\\d|\\s]*\u{0022}? \\([\\d]{4}\\))")
        .unwrap();
    println!("{:?}", re);
    for cap in re.captures_iter(string.as_str()) {
        let mut movie = &cap[0];
        movie = movie.trim();
        let tmp: Vec<&str> = movie.rsplitn(2, ' ').collect();
        let mut year = tmp[0];
        year = year.trim();
        import_movie(tmp[1], &year[1..5]);
    }
}
<commit_msg>Modify parser functions for genre/rankings<commit_after>use regex::Regex;
use database::*;

macro_rules! regex { ($re:expr) => { ::regex::Regex::new($re).unwrap() } }

/// Represents a movie
#[derive(Debug)]
// Brauchen wir das struct überhaupt?

// struct Film {
//     name: String,
//     votes: String,
//     rating: String,
//     year: String,
// }

// impl Film {
//     fn new() -> Self {
//         Film {
//             name: String::new(),
//             votes: String::new(),
//             rating: String::new(),
//             year: String::new(),
//         }
//     }
// }

// #[derive(Debug)]
// struct Genre {
//     movie: String,
//     genre: String,
// }
// impl Genre {
//     fn new() -> Self {
//         Genre {
//             movie: String::new(),
//             genre: String::new(),
//         }
//     }
// }

pub fn parse_rating(string: String) {
    let re = Regex::new(
        r"(\n [\s]+ [\d|\.]{10}[\s]+ (\d+)[\s]+ ([\d|\.]{3}) (.+) [(](\d{4})[)])"
    ).unwrap(); //                    ^number     ^rating      ^title  ^year

    for cap in re.captures_iter(string.as_str()) {
        add_rating(&cap[4], &cap[5], &cap[3], &cap[2]);
    }
}

fn parse_genre(string: String) {
    let re = Regex::new(
        r"(\n(.+)[\s]+[(](\d{4})[)][\s]+(?:\(V\)|\(TV\))?[\s]*([^\{].+))"
    ).unwrap(); //        ^year           ^(TV|V) filter             ^genre
    // Series with form "NAME (YEAR)  {EPISODE INFO}   GENRE" are filtered out

    for cap in re.captures_iter(string.as_str()) {
        add_rating(&cap[4], &cap[5], &cap[3], &cap[2]);
    }
}

// TODO: Filter series with form "NAME (YEAR)   {EPISODE INFO}"
pub fn parse_movies(string: String) {
    // let re = Regex::new(r"(\n\u{0022}?[#|\+|\-|\*|\/|\.|\,|\!|\:|\&|\%|\$|\§|\w|\d|\s]*\u{0022}? \([\d]{4}\))").unwrap();
    let re = Regex::new("(\\n\u{0022}?[#|\\+|\\-|\\*|\\041|\\057|\\054|\\056|\\072|\\073|\\044|\\045|\\046|\\w|\\d|\\s]*\u{0022}? \\([\\d]{4}\\))")
        .unwrap();
    println!("{:?}", re);
    for cap in re.captures_iter(string.as_str()) {
        let mut movie = &cap[0];
        movie = movie.trim();
        let tmp: Vec<&str> = movie.rsplitn(2, ' ').collect();
        let mut year = tmp[0];
        year = year.trim();
        import_movie(tmp[1], &year[1..5]);
    }
}

pub fn parse_actors() {
    // TODO: implement it!
}

pub fn parse_directors() {
    // TODO: implement it!
}
<|endoftext|>