<commit_before>use std::str;
use std::ascii::AsciiExt;
use std::path::{Path, PathBuf, MAIN_SEPARATOR};

use kailua_diag::Stop;
use kailua_syntax::Chunk;

pub trait Options {
    // all of those methods return `Err(None)` if the error occurred and has not been reported,
    // or `Err(Some(Stop))` if the error occurred and the caller wants to bail out.
    // if the implementation has a handle to the reporter and wants to do its own reporting,
    // it can report and return `Ok` (recovery) or `Err(Some(Stop))` (error propagation).

    fn set_package_path(&mut self, _path: &[u8]) -> Result<(), Option<Stop>> { Ok(()) }
    fn set_package_cpath(&mut self, _path: &[u8]) -> Result<(), Option<Stop>> { Ok(()) }

    fn require_chunk(&mut self, _path: &[u8]) -> Result<Chunk, Option<Stop>> {
        Err(None)
    }
}

pub trait FsSource {
    // `Ok(Some(chunk))` normally; `Ok(None)` if path doesn't exist and search should continue;
    // `Err(None)` and `Err(Some(Stop))` follow the convention of `Options`.
    fn chunk_from_path(&self, resolved_path: &Path) -> Result<Option<Chunk>, Option<Stop>>;

    fn to_path_buf(&self, path: &[u8]) -> Result<PathBuf, Option<Stop>> {
        // by default we avoid parsing multibyte paths as it depends on the system encoding
        if path.is_ascii() {
            Ok(Path::new(str::from_utf8(path).unwrap()).to_owned())
        } else {
            Err(None)
        }
    }
}

pub struct FsOptions<S> {
    source: S,
    root: PathBuf,
    package_path: Vec<Vec<u8>>,
    package_cpath: Vec<Vec<u8>>,
}

impl<S: FsSource> FsOptions<S> {
    pub fn new(source: S, root: PathBuf) -> FsOptions<S> {
        FsOptions {
            source: source,
            root: root,

            // by default, local files only
            package_path: vec![b"?.lua".to_vec()],
            package_cpath: vec![],
        }
    }

    fn search_file(&self, path: &[u8], search_paths: &[Vec<u8>],
                   suffix: &[u8]) -> Result<Option<Chunk>, Option<Stop>> {
        for template in search_paths {
            let mut newpath = Vec::new();
            for (i, e) in template.split(|&b| b == b'?').enumerate() {
                if i > 0 {
                    newpath.extend(path.iter().map(|&b| {
                        if b == b'.' { MAIN_SEPARATOR as u8 } else { b }
                    }));
                }
                newpath.extend_from_slice(e);
            }
            newpath.extend_from_slice(suffix);

            let resolved_path = self.root.join(self.source.to_path_buf(&newpath)?);
            debug!("trying to load {:?}", path);

            if let Some(chunk) = self.source.chunk_from_path(&resolved_path)? {
                return Ok(Some(chunk));
            }
        }
        Ok(None)
    }
}

impl<S: FsSource> Options for FsOptions<S> {
    fn set_package_path(&mut self, path: &[u8]) -> Result<(), Option<Stop>> {
        self.package_path = path.split(|&b| b == b';').map(|s| s.to_owned()).collect();
        Ok(())
    }

    fn set_package_cpath(&mut self, path: &[u8]) -> Result<(), Option<Stop>> {
        self.package_cpath = path.split(|&b| b == b';').map(|s| s.to_owned()).collect();
        Ok(())
    }

    fn require_chunk(&mut self, path: &[u8]) -> Result<Chunk, Option<Stop>> {
        if let Some(chunk) = self.search_file(&path, &self.package_path, b".kailua")? {
            return Ok(chunk);
        }
        if let Some(chunk) = self.search_file(&path, &self.package_path, b"")? {
            return Ok(chunk);
        }
        if let Some(chunk) = self.search_file(&path, &self.package_cpath, b".kailua")? {
            return Ok(chunk);
        }
        // avoid loading the native libraries as is

        Err(None)
    }
}

<commit_msg>Made `require "a.b"` try both "a/b" and "a.b". (sigh)<commit_after>use std::str;
use std::ascii::AsciiExt;
use std::path::{Path, PathBuf, MAIN_SEPARATOR};

use kailua_diag::Stop;
use kailua_syntax::Chunk;

pub trait Options {
    // all of those methods return `Err(None)` if the error occurred and has not been reported,
    // or `Err(Some(Stop))` if the error occurred and the caller wants to bail out.
    // if the implementation has a handle to the reporter and wants to do its own reporting,
    // it can report and return `Ok` (recovery) or `Err(Some(Stop))` (error propagation).

    fn set_package_path(&mut self, _path: &[u8]) -> Result<(), Option<Stop>> { Ok(()) }
    fn set_package_cpath(&mut self, _path: &[u8]) -> Result<(), Option<Stop>> { Ok(()) }

    fn require_chunk(&mut self, _path: &[u8]) -> Result<Chunk, Option<Stop>> {
        Err(None)
    }
}

pub trait FsSource {
    // `Ok(Some(chunk))` normally; `Ok(None)` if path doesn't exist and search should continue;
    // `Err(None)` and `Err(Some(Stop))` follow the convention of `Options`.
    fn chunk_from_path(&self, resolved_path: &Path) -> Result<Option<Chunk>, Option<Stop>>;

    fn to_path_buf(&self, path: &[u8]) -> Result<PathBuf, Option<Stop>> {
        // by default we avoid parsing multibyte paths as it depends on the system encoding
        if path.is_ascii() {
            Ok(Path::new(str::from_utf8(path).unwrap()).to_owned())
        } else {
            Err(None)
        }
    }
}

pub struct FsOptions<S> {
    source: S,
    root: PathBuf,
    package_path: Vec<Vec<u8>>,
    package_cpath: Vec<Vec<u8>>,
}

impl<S: FsSource> FsOptions<S> {
    pub fn new(source: S, root: PathBuf) -> FsOptions<S> {
        FsOptions {
            source: source,
            root: root,

            // by default, local files only
            package_path: vec![b"?.lua".to_vec()],
            package_cpath: vec![],
        }
    }

    fn search_file(&self, path: &[u8], search_paths: &[Vec<u8>],
                   suffix: &[u8]) -> Result<Option<Chunk>, Option<Stop>> {
        for template in search_paths {
            let mut newpath = Vec::new();
            let mut newpathdot = Vec::new();
            for (i, e) in template.split(|&b| b == b'?').enumerate() {
                if i > 0 {
                    newpath.extend(path.iter().map(|&b| {
                        if b == b'.' { MAIN_SEPARATOR as u8 } else { b }
                    }));
                    newpathdot.extend(path.iter().cloned());
                }
                newpath.extend_from_slice(e);
                newpathdot.extend_from_slice(e);
            }
            newpath.extend_from_slice(suffix);
            newpathdot.extend_from_slice(suffix);

            let resolved_path = self.root.join(self.source.to_path_buf(&newpath)?);
            trace!("trying to load {:?}", resolved_path);
            if let Some(chunk) = self.source.chunk_from_path(&resolved_path)? {
                return Ok(Some(chunk));
            }

            // also try to load a dotted path
            let resolved_path = self.root.join(self.source.to_path_buf(&newpathdot)?);
            trace!("trying to load {:?}", resolved_path);
            if let Some(chunk) = self.source.chunk_from_path(&resolved_path)? {
                return Ok(Some(chunk));
            }
        }

        Ok(None)
    }
}

impl<S: FsSource> Options for FsOptions<S> {
    fn set_package_path(&mut self, path: &[u8]) -> Result<(), Option<Stop>> {
        self.package_path = path.split(|&b| b == b';').map(|s| s.to_owned()).collect();
        Ok(())
    }

    fn set_package_cpath(&mut self, path: &[u8]) -> Result<(), Option<Stop>> {
        self.package_cpath = path.split(|&b| b == b';').map(|s| s.to_owned()).collect();
        Ok(())
    }

    fn require_chunk(&mut self, path: &[u8]) -> Result<Chunk, Option<Stop>> {
        if let Some(chunk) = self.search_file(&path, &self.package_path, b".kailua")? {
            return Ok(chunk);
        }
        if let Some(chunk) = self.search_file(&path, &self.package_path, b"")? {
            return Ok(chunk);
        }
        if let Some(chunk) = self.search_file(&path, &self.package_cpath, b".kailua")? {
            return Ok(chunk);
        }
        // avoid loading the native libraries as is

        Err(None)
    }
}

<|endoftext|>


<commit_before>use std::fmt::{Debug, Formatter};
use std::fmt;
use std::ops::Deref;

use clap::ArgMatches;

use runtime::Runtime;
use module::Module;

use storage::parser::FileHeaderParser;
use storage::parser::Parser;
use storage::json::parser::JsonHeaderParser;
use module::helpers::cli::create_tag_filter;
use module::helpers::cli::create_hash_filter;
use module::helpers::cli::create_text_header_field_grep_filter;
use module::helpers::cli::CliFileFilter;

mod header;

use self::header::get_url_from_header;
use self::header::get_tags_from_header;

pub struct BM<'a> {
    rt: &'a Runtime<'a>,
}

impl<'a> BM<'a> {

    pub fn new(rt: &'a Runtime<'a>) -> BM<'a> {
        BM {
            rt: rt,
        }
    }

    /**
     * Subcommand: add
     */
    fn command_add(&self, matches: &ArgMatches) -> bool {
        use ansi_term::Colour::{Green, Yellow, Red};
        use std::process::exit;
        use self::header::build_header;

        let parser = Parser::new(JsonHeaderParser::new(None));

        let url    = matches.value_of("url").map(String::from).unwrap(); // clap ensures this is present

        if !self.validate_url(&url, &parser) {
            error!("URL validation failed, exiting.");
            exit(1);
        } else {
            debug!("Verification succeeded");
        }

        let tags   = matches.value_of("tags").and_then(|s| {
            Some(s.split(",").map(String::from).collect())
        }).unwrap_or(vec![]);

        debug!("Building header with");
        debug!("    url  = '{:?}'", url);
        debug!("    tags = '{:?}'", tags);
        let header = build_header(url, tags);

        let fileid = self.rt
                         .store()
                         .new_file_with_header(self, header);

        let result = self.rt
            .store()
            .load(self, &parser, &fileid)
            .map(|file| {
                info!("{}", Yellow.paint(format!("Created file in memory: {}", fileid)));
                self.rt
                    .store()
                    .persist(&parser, file)
            })
            .unwrap_or(false);

        if result {
            info!("{}", Red.paint("Adding worked"));
        } else {
            info!("{}", Green.paint("Adding failed"));
        }

        result
    }

    fn validate_url<HP>(&self, url: &String, parser: &Parser<HP>) -> bool
        where HP: FileHeaderParser
    {
        use util::is_url;

        if !is_url(url) {
            error!("Url '{}' is not a valid URL. Will not store.", url);
            return false;
        }

        let is_in_store = self.rt
            .store()
            .load_for_module(self, parser)
            .iter()
            .any(|file| {
                let f = file.deref().borrow();
                get_url_from_header(f.header()).map(|url_in_store| {
                    &url_in_store == url
                }).unwrap_or(false)
            });

        if is_in_store {
            error!("URL '{}' seems to be in the store already", url);
            return false;
        }

        return true;
    }

    /**
     * Subcommand: list
     */
    fn command_list(&self, matches: &ArgMatches) -> bool {
        use ui::file::{FilePrinter, TablePrinter};
        use std::ops::Deref;

        let parser = Parser::new(JsonHeaderParser::new(None));
        let filter = {
            let hash_filter = create_hash_filter(matches, "id", true);
            let text_filter = create_text_header_field_grep_filter(matches, "match", "URL", true);
            let tags_filter = create_tag_filter(matches, "tags", true);
            hash_filter.and(Box::new(text_filter)).and(Box::new(tags_filter))
        };

        let files  = self.rt
            .store()
            .load_for_module(self, &parser)
            .into_iter()
            .filter(|file| filter.filter_file(file));
        let printer = TablePrinter::new(self.rt.is_verbose(), self.rt.is_debugging());

        printer.print_files_custom(files,
            &|file| {
                let fl = file.deref().borrow();
                let hdr = fl.header();
                let url = get_url_from_header(hdr).unwrap_or(String::from("Parser error"));
                let tags = get_tags_from_header(hdr);

                debug!("Custom printer field: url  = '{:?}'", url);
                debug!("Custom printer field: tags = '{:?}'", tags);

                vec![url, tags.join(", ")]
            }
        );
        true
    }

    /**
     * Subcommand: open
     */
    fn command_open(&self, matches: &ArgMatches) -> bool {
        use ansi_term::Colour::{Green, Red};
        use open;

        let parser = Parser::new(JsonHeaderParser::new(None));
        let filter : Box<CliFileFilter> = {
            let hash_filter = create_hash_filter(matches, "id", true);
            let text_filter = create_text_header_field_grep_filter(matches, "match", "URL", true);
            let tags_filter = create_tag_filter(matches, "tags", true);
            Box::new(hash_filter.and(Box::new(text_filter)).and(Box::new(tags_filter)))
        };
        let result = self.rt
            .store()
            .load_for_module(self, &parser)
            .iter()
            .filter(|file| filter.filter_file(file))
            .map(|file| {
                debug!("File loaded, can open now: {:?}", file);
                let f = file.deref().borrow();
                get_url_from_header(f.header()).map(|url| {
                    if open::that(&url[..]).is_ok() {
                        info!("{}", Green.paint(format!("open({})", url)));
                        true
                    } else {
                        info!("{}", Red.paint(format!("could not open({})", url)));
                        false
                    }
                })
                .unwrap_or(false)
            })
            .fold((0, 0), |acc, succeeded| {
                let (worked, failed) = acc;
                if succeeded {
                    (worked + 1, failed)
                } else {
                    (worked, failed + 1)
                }
            });

        let (succ, fail) = result;
        info!("{}", Green.paint(format!("open() succeeded for {} files", succ)));
        info!("{}", Red.paint(format!(  "open() failed    for {} files", fail)));
        return fail == 0;
    }

    /**
     * Subcommand: remove
     */
    fn command_remove(&self, matches: &ArgMatches) -> bool {
        use ansi_term::Colour::{Green, Red};

        let parser = Parser::new(JsonHeaderParser::new(None));

        let filter = {
            let hash_filter = create_hash_filter(matches, "id", false);
            let text_filter = create_text_header_field_grep_filter(matches, "match", "URL", false);
            let tags_filter = create_tag_filter(matches, "tags", false);
            hash_filter.or(Box::new(text_filter)).or(Box::new(tags_filter))
        };

        let result = self.rt
            .store()
            .load_for_module(self, &parser)
            .iter()
            .filter(|file| filter.filter_file(file))
            .map(|file| {
                debug!("File loaded, can remove now: {:?}", file);
                let f = file.deref().borrow();
                self.rt.store().remove(f.id().clone())
            })
            .fold((0, 0), |acc, succeeded| {
                let (worked, failed) = acc;
                if succeeded {
                    (worked + 1, failed)
                } else {
                    (worked, failed + 1)
                }
            });

        let (worked, failed) = result;

        info!("{}", Green.paint(format!("Removing succeeded for {} files", worked)));
        info!("{}", Red.paint(format!(  "Removing failed for {} files", failed)));

        return failed == 0;
    }

    /**
     * Subcommand: add_tags
     */
    fn command_add_tags(&self, matches: &ArgMatches) -> bool {
        use module::helpers::header::tags::data::alter_tags_in_files;
        use self::header::rebuild_header_with_tags;

        let parser = Parser::new(JsonHeaderParser::new(None));
        alter_tags_in_files(self, matches, &parser, |old_tags, cli_tags| {
            let mut new_tags = old_tags.clone();
            new_tags.append(&mut cli_tags.clone());
            new_tags
        }, rebuild_header_with_tags)
    }

    /**
     * Subcommand: rm_tags
     */
    fn command_rm_tags(&self, matches: &ArgMatches) -> bool {
        use module::helpers::header::tags::data::alter_tags_in_files;
        use self::header::rebuild_header_with_tags;

        let parser = Parser::new(JsonHeaderParser::new(None));
        alter_tags_in_files(self, matches, &parser, |old_tags, cli_tags| {
            old_tags.clone()
                .into_iter()
                .filter(|tag| !cli_tags.contains(tag))
                .collect()
        }, rebuild_header_with_tags)
    }

    /**
     * Subcommand: set_tags
     */
    fn command_set_tags(&self, matches: &ArgMatches) -> bool {
        use module::helpers::header::tags::data::alter_tags_in_files;
        use self::header::rebuild_header_with_tags;

        let parser = Parser::new(JsonHeaderParser::new(None));
        alter_tags_in_files(self, matches, &parser, |old_tags, cli_tags| {
            cli_tags.clone()
        }, rebuild_header_with_tags)
    }

}

/**
 * Trait implementation for BM module
 */
impl<'a> Module<'a> for BM<'a> {

    fn exec(&self, matches: &ArgMatches) -> bool {
        use ansi_term::Colour::{Yellow, Red};

        match matches.subcommand_name() {
            Some("add") => {
                self.command_add(matches.subcommand_matches("add").unwrap())
            },

            Some("list") => {
                self.command_list(matches.subcommand_matches("list").unwrap())
            },

            Some("open") => {
                self.command_open(matches.subcommand_matches("open").unwrap())
            },

            Some("remove") => {
                self.command_remove(matches.subcommand_matches("remove").unwrap())
            },

            Some("add_tags") => {
                self.command_add_tags(matches.subcommand_matches("add_tags").unwrap())
            },

            Some("rm_tags") => {
                self.command_rm_tags(matches.subcommand_matches("rm_tags").unwrap())
            },

            Some("set_tags") => {
                self.command_set_tags(matches.subcommand_matches("set_tags").unwrap())
            },

            Some(_) | None => {
                info!("{}", Red.bold().paint("No command given, doing nothing"));
                false
            },
        }
    }

    fn name(&self) -> &'static str {
        "bookmark"
    }

    fn runtime(&self) -> &Runtime {
        self.rt
    }
}

impl<'a> Debug for BM<'a> {

    fn fmt(&self, fmt: &mut Formatter) -> fmt::Result {
        try!(write!(fmt, "BM"));
        Ok(())
    }

}

<commit_msg>Remove unused variable<commit_after>use std::fmt::{Debug, Formatter};
use std::fmt;
use std::ops::Deref;

use clap::ArgMatches;

use runtime::Runtime;
use module::Module;

use storage::parser::FileHeaderParser;
use storage::parser::Parser;
use storage::json::parser::JsonHeaderParser;
use module::helpers::cli::create_tag_filter;
use module::helpers::cli::create_hash_filter;
use module::helpers::cli::create_text_header_field_grep_filter;
use module::helpers::cli::CliFileFilter;

mod header;

use self::header::get_url_from_header;
use self::header::get_tags_from_header;

pub struct BM<'a> {
    rt: &'a Runtime<'a>,
}

impl<'a> BM<'a> {

    pub fn new(rt: &'a Runtime<'a>) -> BM<'a> {
        BM {
            rt: rt,
        }
    }

    /**
     * Subcommand: add
     */
    fn command_add(&self, matches: &ArgMatches) -> bool {
        use ansi_term::Colour::{Green, Yellow, Red};
        use std::process::exit;
        use self::header::build_header;

        let parser = Parser::new(JsonHeaderParser::new(None));

        let url    = matches.value_of("url").map(String::from).unwrap(); // clap ensures this is present

        if !self.validate_url(&url, &parser) {
            error!("URL validation failed, exiting.");
            exit(1);
        } else {
            debug!("Verification succeeded");
        }

        let tags   = matches.value_of("tags").and_then(|s| {
            Some(s.split(",").map(String::from).collect())
        }).unwrap_or(vec![]);

        debug!("Building header with");
        debug!("    url  = '{:?}'", url);
        debug!("    tags = '{:?}'", tags);
        let header = build_header(url, tags);

        let fileid = self.rt
                         .store()
                         .new_file_with_header(self, header);

        let result = self.rt
            .store()
            .load(self, &parser, &fileid)
            .map(|file| {
                info!("{}", Yellow.paint(format!("Created file in memory: {}", fileid)));
                self.rt
                    .store()
                    .persist(&parser, file)
            })
            .unwrap_or(false);

        if result {
            info!("{}", Red.paint("Adding worked"));
        } else {
            info!("{}", Green.paint("Adding failed"));
        }

        result
    }

    fn validate_url<HP>(&self, url: &String, parser: &Parser<HP>) -> bool
        where HP: FileHeaderParser
    {
        use util::is_url;

        if !is_url(url) {
            error!("Url '{}' is not a valid URL. Will not store.", url);
            return false;
        }

        let is_in_store = self.rt
            .store()
            .load_for_module(self, parser)
            .iter()
            .any(|file| {
                let f = file.deref().borrow();
                get_url_from_header(f.header()).map(|url_in_store| {
                    &url_in_store == url
                }).unwrap_or(false)
            });

        if is_in_store {
            error!("URL '{}' seems to be in the store already", url);
            return false;
        }

        return true;
    }

    /**
     * Subcommand: list
     */
    fn command_list(&self, matches: &ArgMatches) -> bool {
        use ui::file::{FilePrinter, TablePrinter};
        use std::ops::Deref;

        let parser = Parser::new(JsonHeaderParser::new(None));
        let filter = {
            let hash_filter = create_hash_filter(matches, "id", true);
            let text_filter = create_text_header_field_grep_filter(matches, "match", "URL", true);
            let tags_filter = create_tag_filter(matches, "tags", true);
            hash_filter.and(Box::new(text_filter)).and(Box::new(tags_filter))
        };

        let files  = self.rt
            .store()
            .load_for_module(self, &parser)
            .into_iter()
            .filter(|file| filter.filter_file(file));
        let printer = TablePrinter::new(self.rt.is_verbose(), self.rt.is_debugging());

        printer.print_files_custom(files,
            &|file| {
                let fl = file.deref().borrow();
                let hdr = fl.header();
                let url = get_url_from_header(hdr).unwrap_or(String::from("Parser error"));
                let tags = get_tags_from_header(hdr);

                debug!("Custom printer field: url  = '{:?}'", url);
                debug!("Custom printer field: tags = '{:?}'", tags);

                vec![url, tags.join(", ")]
            }
        );
        true
    }

    /**
     * Subcommand: open
     */
    fn command_open(&self, matches: &ArgMatches) -> bool {
        use ansi_term::Colour::{Green, Red};
        use open;

        let parser = Parser::new(JsonHeaderParser::new(None));
        let filter : Box<CliFileFilter> = {
            let hash_filter = create_hash_filter(matches, "id", true);
            let text_filter = create_text_header_field_grep_filter(matches, "match", "URL", true);
            let tags_filter = create_tag_filter(matches, "tags", true);
            Box::new(hash_filter.and(Box::new(text_filter)).and(Box::new(tags_filter)))
        };
        let result = self.rt
            .store()
            .load_for_module(self, &parser)
            .iter()
            .filter(|file| filter.filter_file(file))
            .map(|file| {
                debug!("File loaded, can open now: {:?}", file);
                let f = file.deref().borrow();
                get_url_from_header(f.header()).map(|url| {
                    if open::that(&url[..]).is_ok() {
                        info!("{}", Green.paint(format!("open({})", url)));
                        true
                    } else {
                        info!("{}", Red.paint(format!("could not open({})", url)));
                        false
                    }
                })
                .unwrap_or(false)
            })
            .fold((0, 0), |acc, succeeded| {
                let (worked, failed) = acc;
                if succeeded {
                    (worked + 1, failed)
                } else {
                    (worked, failed + 1)
                }
            });

        let (succ, fail) = result;
        info!("{}", Green.paint(format!("open() succeeded for {} files", succ)));
        info!("{}", Red.paint(format!(  "open() failed    for {} files", fail)));
        return fail == 0;
    }

    /**
     * Subcommand: remove
     */
    fn command_remove(&self, matches: &ArgMatches) -> bool {
        use ansi_term::Colour::{Green, Red};

        let parser = Parser::new(JsonHeaderParser::new(None));

        let filter = {
            let hash_filter = create_hash_filter(matches, "id", false);
            let text_filter = create_text_header_field_grep_filter(matches, "match", "URL", false);
            let tags_filter = create_tag_filter(matches, "tags", false);
            hash_filter.or(Box::new(text_filter)).or(Box::new(tags_filter))
        };

        let result = self.rt
            .store()
            .load_for_module(self, &parser)
            .iter()
            .filter(|file| filter.filter_file(file))
            .map(|file| {
                debug!("File loaded, can remove now: {:?}", file);
                let f = file.deref().borrow();
                self.rt.store().remove(f.id().clone())
            })
            .fold((0, 0), |acc, succeeded| {
                let (worked, failed) = acc;
                if succeeded {
                    (worked + 1, failed)
                } else {
                    (worked, failed + 1)
                }
            });

        let (worked, failed) = result;

        info!("{}", Green.paint(format!("Removing succeeded for {} files", worked)));
        info!("{}", Red.paint(format!(  "Removing failed for {} files", failed)));

        return failed == 0;
    }

    /**
     * Subcommand: add_tags
     */
    fn command_add_tags(&self, matches: &ArgMatches) -> bool {
        use module::helpers::header::tags::data::alter_tags_in_files;
        use self::header::rebuild_header_with_tags;

        let parser = Parser::new(JsonHeaderParser::new(None));
        alter_tags_in_files(self, matches, &parser, |old_tags, cli_tags| {
            let mut new_tags = old_tags.clone();
            new_tags.append(&mut cli_tags.clone());
            new_tags
        }, rebuild_header_with_tags)
    }

    /**
     * Subcommand: rm_tags
     */
    fn command_rm_tags(&self, matches: &ArgMatches) -> bool {
        use module::helpers::header::tags::data::alter_tags_in_files;
        use self::header::rebuild_header_with_tags;

        let parser = Parser::new(JsonHeaderParser::new(None));
        alter_tags_in_files(self, matches, &parser, |old_tags, cli_tags| {
            old_tags.clone()
                .into_iter()
                .filter(|tag| !cli_tags.contains(tag))
                .collect()
        }, rebuild_header_with_tags)
    }

    /**
     * Subcommand: set_tags
     */
    fn command_set_tags(&self, matches: &ArgMatches) -> bool {
        use module::helpers::header::tags::data::alter_tags_in_files;
        use self::header::rebuild_header_with_tags;

        let parser = Parser::new(JsonHeaderParser::new(None));
        alter_tags_in_files(self, matches, &parser, |_, cli_tags| {
            cli_tags.clone()
        }, rebuild_header_with_tags)
    }

}

/**
 * Trait implementation for BM module
 */
impl<'a> Module<'a> for BM<'a> {

    fn exec(&self, matches: &ArgMatches) -> bool {
        use ansi_term::Colour::{Yellow, Red};

        match matches.subcommand_name() {
            Some("add") => {
                self.command_add(matches.subcommand_matches("add").unwrap())
            },

            Some("list") => {
                self.command_list(matches.subcommand_matches("list").unwrap())
            },

            Some("open") => {
                self.command_open(matches.subcommand_matches("open").unwrap())
            },

            Some("remove") => {
                self.command_remove(matches.subcommand_matches("remove").unwrap())
            },

            Some("add_tags") => {
                self.command_add_tags(matches.subcommand_matches("add_tags").unwrap())
            },

            Some("rm_tags") => {
                self.command_rm_tags(matches.subcommand_matches("rm_tags").unwrap())
            },

            Some("set_tags") => {
                self.command_set_tags(matches.subcommand_matches("set_tags").unwrap())
            },

            Some(_) | None => {
                info!("{}", Red.bold().paint("No command given, doing nothing"));
                false
            },
        }
    }

    fn name(&self) -> &'static str {
        "bookmark"
    }

    fn runtime(&self) -> &Runtime {
        self.rt
    }
}

impl<'a> Debug for BM<'a> {

    fn fmt(&self, fmt: &mut Formatter) -> fmt::Result {
        try!(write!(fmt, "BM"));
        Ok(())
    }

}

<|endoftext|>


<commit_before>use std::io::{Result, Write};
use std::path::Path;

use env::Env;
use file_saver::save_to_file;
use regex::Regex;

pub fn generate(env: &Env) {
    info!(
        "Generating sys build script for {}",
        env.config.library_name
    );

    let path = env.config.target_path.join("build.rs");

    info!("Generating file {:?}", path);
    save_to_file(
        &path,
        env.config.make_backup,
        |w| generate_build_script(w, env),
    );
}

fn generate_build_script(w: &mut Write, env: &Env) -> Result<()> {
    try!(write!(
        w,
        "{}",
        r##"extern crate pkg_config;

use pkg_config::{Config, Error};
use std::env;
use std::io::prelude::*;
use std::io;
use std::process;

fn main() {
    if let Err(s) = find() {
        let _ = writeln!(io::stderr(), "{}", s);
        process::exit(1);
    }
}

fn find() -> Result<(), Error> {
"##
    ));

    let ns = env.namespaces.main();
    let regex = Regex::new(r"^lib(.+)\.(so.*|dylib)$").expect("Regex failed");
    let shared_libs: Vec<_> = ns.shared_libs
        .iter()
        .map(|s| {
            let lib_path = Path::new(s);
            let lib_file_name = lib_path
                .file_name()
                .expect("A 'shared-library' in the GIR file has an invalid form")
                .to_str()
                .expect("Failed to convert OsStr to str");
            regex.replace(lib_file_name, "\"$1\"")
        })
        .collect();

    try!(writeln!(
        w,
        "\tlet package_name = \"{}\";",
        ns.package_name
            .as_ref()
            .expect("Package name doesn't exist")
    ));
    try!(writeln!(
        w,
        "\tlet shared_libs = [{}];",
        shared_libs.join(", ")
    ));
    try!(write!(w, "\tlet version = "));
    let versions = ns.versions
        .iter()
        .filter(|v| **v >= env.config.min_cfg_version)
        .skip(1)
        .collect::<Vec<_>>();
    for v in versions.iter().rev() {
        try!(write!(
            w,
            "if cfg!({}) {{\n\t\t\"{}\"\n\t}} else ",
            v.to_cfg(),
            v
        ));
    }
    try!(writeln!(
        w,
        "{{\n\t\t\"{}\"\n\t}};",
        env.config.min_cfg_version
    ));

    writeln!(
        w,
        "{}",
        r##"
    if let Ok(lib_dir) = env::var("GTK_LIB_DIR") {
        for lib_ in shared_libs.iter() {
            println!("cargo:rustc-link-lib=dylib={}", lib_);
        }
        println!("cargo:rustc-link-search=native={}", lib_dir);
        return Ok(())
    }

    let target = env::var("TARGET").expect("TARGET environment variable doesn't exist");
    let hardcode_shared_libs = target.contains("windows");

    let mut config = Config::new();
    config.atleast_version(version);
    config.print_system_libs(false);
    if hardcode_shared_libs {
        config.cargo_metadata(false);
    }
    match config.probe(package_name) {
        Ok(library) => {
            if hardcode_shared_libs {
                for lib_ in shared_libs.iter() {
                    println!("cargo:rustc-link-lib=dylib={}", lib_);
                }
                for path in library.link_paths.iter() {
                    println!("cargo:rustc-link-search=native={}",
                             path.to_str().expect("library path doesn't exist"));
                }
            }
            Ok(())
        }
        Err(Error::EnvNoPkgConfig(_)) | Err(Error::Command { .. }) => {
            for lib_ in shared_libs.iter() {
                println!("cargo:rustc-link-lib=dylib={}", lib_);
            }
            Ok(())
        }
        Err(err) => Err(err),
    }
}
"##
    )
}
<commit_msg>Expose include paths to other build scripts<commit_after>use std::io::{Result, Write};
use std::path::Path;

use env::Env;
use file_saver::save_to_file;
use regex::Regex;

pub fn generate(env: &Env) {
    info!(
        "Generating sys build script for {}",
        env.config.library_name
    );

    let path = env.config.target_path.join("build.rs");

    info!("Generating file {:?}", path);
    save_to_file(
        &path,
        env.config.make_backup,
        |w| generate_build_script(w, env),
    );
}

fn generate_build_script(w: &mut Write, env: &Env) -> Result<()> {
    try!(write!(
        w,
        "{}",
        r##"extern crate pkg_config;

use pkg_config::{Config, Error};
use std::env;
use std::io::prelude::*;
use std::io;
use std::process;

fn main() {
    if let Err(s) = find() {
        let _ = writeln!(io::stderr(), "{}", s);
        process::exit(1);
    }
}

fn find() -> Result<(), Error> {
"##
    ));

    let ns = env.namespaces.main();
    let regex = Regex::new(r"^lib(.+)\.(so.*|dylib)$").expect("Regex failed");
    let shared_libs: Vec<_> = ns.shared_libs
        .iter()
        .map(|s| {
            let lib_path = Path::new(s);
            let lib_file_name = lib_path
                .file_name()
                .expect("A 'shared-library' in the GIR file has an invalid form")
                .to_str()
                .expect("Failed to convert OsStr to str");
            regex.replace(lib_file_name, "\"$1\"")
        })
        .collect();

    try!(writeln!(
        w,
        "\tlet package_name = \"{}\";",
        ns.package_name
            .as_ref()
            .expect("Package name doesn't exist")
    ));
    try!(writeln!(
        w,
        "\tlet shared_libs = [{}];",
        shared_libs.join(", ")
    ));
    try!(write!(w, "\tlet version = "));
    let versions = ns.versions
        .iter()
        .filter(|v| **v >= env.config.min_cfg_version)
        .skip(1)
        .collect::<Vec<_>>();
    for v in versions.iter().rev() {
        try!(write!(
            w,
            "if cfg!({}) {{\n\t\t\"{}\"\n\t}} else ",
            v.to_cfg(),
            v
        ));
    }
    try!(writeln!(
        w,
        "{{\n\t\t\"{}\"\n\t}};",
        env.config.min_cfg_version
    ));

    writeln!(
        w,
        "{}",
        r##"
    if let Ok(inc_dir) = env::var("GTK_INCLUDE_DIR") {
        println!("cargo:include={}", inc_dir);
    }
    if let Ok(lib_dir) = env::var("GTK_LIB_DIR") {
        for lib_ in shared_libs.iter() {
            println!("cargo:rustc-link-lib=dylib={}", lib_);
        }
        println!("cargo:rustc-link-search=native={}", lib_dir);
        return Ok(())
    }

    let target = env::var("TARGET").expect("TARGET environment variable doesn't exist");
    let hardcode_shared_libs = target.contains("windows");

    let mut config = Config::new();
    config.atleast_version(version);
    config.print_system_libs(false);
    if hardcode_shared_libs {
        config.cargo_metadata(false);
    }
    match config.probe(package_name) {
        Ok(library) => {
            if let Ok(paths) = std::env::join_paths(library.include_paths) {
                println!("cargo:include={}", paths.to_string_lossy());
            }
            if hardcode_shared_libs {
                for lib_ in shared_libs.iter() {
                    println!("cargo:rustc-link-lib=dylib={}", lib_);
                }
                for path in library.link_paths.iter() {
                    println!("cargo:rustc-link-search=native={}",
                             path.to_str().expect("library path doesn't exist"));
                }
            }
            Ok(())
        }
        Err(Error::EnvNoPkgConfig(_)) | Err(Error::Command { .. }) => {
            for lib_ in shared_libs.iter() {
                println!("cargo:rustc-link-lib=dylib={}", lib_);
            }
            Ok(())
        }
        Err(err) => Err(err),
    }
}
"##
    )
}
<|endoftext|>


<commit_before>/* The 'fmt' extension is modeled on the posix printf system.
 *
 * A posix conversion ostensibly looks like this:
 *
 * %[parameter][flags][width][.precision][length]type
 *
 * Given the different numeric type bestiary we have, we omit the 'length'
 * parameter and support slightly different conversions for 'type':
 *
 * %[parameter][flags][width][.precision]type
 *
 * we also only support translating-to-rust a tiny subset of the possible
 * combinations at the moment.
 */

import front.parser;

import std._str;
import std._vec;
import std.option;
import std.option.none;
import std.option.some;

tag signedness {
    signed;
    unsigned;
}

tag caseness {
    case_upper;
    case_lower;
}

tag ty {
    ty_bool;
    ty_str;
    ty_char;
    ty_int(signedness);
    ty_bits;
    ty_hex(caseness);
    // FIXME: More types
}

tag flag {
    flag_left_justify;
    flag_left_zero_pad;
    flag_left_space_pad;
    flag_plus_if_positive;
    flag_alternate;
}

tag count {
    count_is(int);
    count_is_param(int);
    count_is_next_param;
    count_implied;
}

// A formatted conversion from an expression to a string
type conv = rec(option.t[int] param,
                vec[flag] flags,
                count width,
                count precision,
                ty typ);

// A fragment of the output sequence
tag piece {
    piece_string(str);
    piece_conv(conv);
}

fn bad_fmt_call() {
    log "malformed #fmt call";
    fail;
}

fn expand_syntax_ext(vec[@ast.expr] args,
                     option.t[@ast.expr] body) -> @ast.expr {

    if (_vec.len[@ast.expr](args) == 0u) {
        bad_fmt_call();
    }

    auto fmt = expr_to_str(args.(0));
    log fmt;
    auto pieces = parse_fmt_string(fmt);
    log "printing all pieces";
    for (piece p in pieces) {
        alt (p) {
            case (piece_string(?s)) {
                log s;
            }
            case (piece_conv(_)) {
                log "conv";
            }
        }
    }
    log "done printing all pieces";
    ret pieces_to_expr(pieces, args);
}

fn expr_to_str(@ast.expr expr) -> str {
    alt (expr.node) {
        case (ast.expr_lit(?l, _)) {
            alt (l.node) {
                case (ast.lit_str(?s)) {
                    ret s;
                }
            }
        }
    }
    bad_fmt_call();
    fail;
}

fn parse_fmt_string(str s) -> vec[piece] {
    let vec[piece] pieces = vec();
    // FIXME: Should be counting codepoints instead of bytes
    auto lim = _str.byte_len(s);
    auto buf = "";

    fn flush_buf(str buf, &vec[piece] pieces) -> str {
        if (_str.byte_len(buf) > 0u) {
            auto piece = piece_string(buf);
            pieces += piece;
        }
        ret "";
    }

    auto i = 0u;
    while (i < lim) {
        auto curr = _str.substr(s, i, 1u);
        if (_str.eq(curr, "%")) {
            i += 1u;
            if (i >= lim) {
                log "unterminated conversion at end of string";
                fail;
            }
            auto curr2 = _str.substr(s, i, 1u);
            if (_str.eq(curr2, "%")) {
                i += 1u;
            } else {
                buf = flush_buf(buf, pieces);
                auto res = parse_conversion(s, i, lim);
                pieces += res._0;
                i = res._1;
            }
        } else {
            buf += curr;
            i += 1u;
        }
    }
    buf = flush_buf(buf, pieces);
    ret pieces;
}

fn peek_num(str s, uint i, uint lim) -> option.t[tup(int, int)] {
    if (i >= lim) {
        ret none[tup(int, int)];
    } else {
        ret none[tup(int, int)];
        /*if ('0' <= c && c <= '9') {
            log c;
            fail;
        } else {
            ret option.none[tup(int, int)];
        }
        */
    }
}

fn parse_conversion(str s, uint i, uint lim) -> tup(piece, uint) {
    auto parm = parse_parameter(s, i, lim);
    auto flags = parse_flags(s, parm._1, lim);
    auto width = parse_width(s, flags._1, lim);
    auto prec = parse_precision(s, width._1, lim);
    auto ty = parse_type(s, prec._1, lim);
    ret tup(piece_conv(rec(param = parm._0,
                           flags = flags._0,
                           width = width._0,
                           precision = prec._0,
                           typ = ty._0)),
            ty._1);
}

fn parse_parameter(str s, uint i, uint lim) -> tup(option.t[int], uint) {
    if (i >= lim) {
        ret tup(none[int], i);
    }

    auto num = peek_num(s, i, lim);
    alt (num) {
        case (none[tup(int, int)]) {
            ret tup(none[int], i);
        }
        case (some[tup(int, int)](?t)) {
            fail;
        }
    }
}

fn parse_flags(str s, uint i, uint lim) -> tup(vec[flag], uint) {
    let vec[flag] flags = vec();
    ret tup(flags, i);
}

fn parse_width(str s, uint i, uint lim) -> tup(count, uint) {
    ret tup(count_implied, i);
}

fn parse_precision(str s, uint i, uint lim) -> tup(count, uint) {
    ret tup(count_implied, i);
}

fn parse_type(str s, uint i, uint lim) -> tup(ty, uint) {
    if (i >= lim) {
        log "missing type in conversion";
        fail;
    }

    auto t;
    auto tstr = _str.substr(s, i, 1u);
    if (_str.eq(tstr, "b")) {
        t = ty_bool;
    } else if (_str.eq(tstr, "s")) {
        t = ty_str;
    } else if (_str.eq(tstr, "c")) {
        t = ty_char;
    } else if (_str.eq(tstr, "d")
               || _str.eq(tstr, "i")) {
        // TODO: Do we really want two signed types here?
        // How important is it to be printf compatible?
        t = ty_int(signed);
    } else if (_str.eq(tstr, "u")) {
        t = ty_int(unsigned);
    } else if (_str.eq(tstr, "x")) {
        t = ty_hex(case_lower);
    } else if (_str.eq(tstr, "X")) {
        t = ty_hex(case_upper);
    } else if (_str.eq(tstr, "t")) {
        t = ty_bits;
    } else {
        // FIXME: This is a hack to avoid 'unsatisfied precondition
        // constraint' on uninitialized variable t below
        t = ty_bool;
        log "unknown type in conversion";
        fail;
    }

    ret tup(t, i + 1u);
}

fn pieces_to_expr(vec[piece] pieces, vec[@ast.expr] args) -> @ast.expr {
    auto lo = args.(0).span;
    auto hi = args.(0).span;
    auto strlit = ast.lit_str("TODO");
    auto spstrlit = @parser.spanned[ast.lit_](lo, hi, strlit);
    auto expr = ast.expr_lit(spstrlit, ast.ann_none);
    auto spexpr = @parser.spanned[ast.expr_](lo, hi, expr);
    ret spexpr;
}

//
// Local Variables:
// mode: rust
// fill-column: 78;
// indent-tabs-mode: nil
// c-basic-offset: 4
// buffer-file-coding-system: utf-8-unix
// compile-command: "make -k -C ../.. 2>&1 | sed -e 's/\\/x\\//x:\\//g'";
// End:
//
<commit_msg>Start generating AST nodes for #fmt<commit_after>/* The 'fmt' extension is modeled on the posix printf system.
 *
 * A posix conversion ostensibly looks like this:
 *
 * %[parameter][flags][width][.precision][length]type
 *
 * Given the different numeric type bestiary we have, we omit the 'length'
 * parameter and support slightly different conversions for 'type':
 *
 * %[parameter][flags][width][.precision]type
 *
 * we also only support translating-to-rust a tiny subset of the possible
 * combinations at the moment.
 */

import util.common;

import std._str;
import std._vec;
import std.option;
import std.option.none;
import std.option.some;

tag signedness {
    signed;
    unsigned;
}

tag caseness {
    case_upper;
    case_lower;
}

tag ty {
    ty_bool;
    ty_str;
    ty_char;
    ty_int(signedness);
    ty_bits;
    ty_hex(caseness);
    // FIXME: More types
}

tag flag {
    flag_left_justify;
    flag_left_zero_pad;
    flag_left_space_pad;
    flag_plus_if_positive;
    flag_alternate;
}

tag count {
    count_is(int);
    count_is_param(int);
    count_is_next_param;
    count_implied;
}

// A formatted conversion from an expression to a string
type conv = rec(option.t[int] param,
                vec[flag] flags,
                count width,
                count precision,
                ty typ);

// A fragment of the output sequence
tag piece {
    piece_string(str);
    piece_conv(conv);
}

fn bad_fmt_call() {
    log "malformed #fmt call";
    fail;
}

fn expand_syntax_ext(vec[@ast.expr] args,
                     option.t[@ast.expr] body) -> @ast.expr {

    if (_vec.len[@ast.expr](args) == 0u) {
        bad_fmt_call();
    }

    auto fmt = expr_to_str(args.(0));
    log fmt;
    auto pieces = parse_fmt_string(fmt);
    log "printing all pieces";
    for (piece p in pieces) {
        alt (p) {
            case (piece_string(?s)) {
                log s;
            }
            case (piece_conv(_)) {
                log "conv";
            }
        }
    }
    log "done printing all pieces";
    ret pieces_to_expr(pieces, args);
}

fn expr_to_str(@ast.expr expr) -> str {
    alt (expr.node) {
        case (ast.expr_lit(?l, _)) {
            alt (l.node) {
                case (ast.lit_str(?s)) {
                    ret s;
                }
            }
        }
    }
    bad_fmt_call();
    fail;
}

fn parse_fmt_string(str s) -> vec[piece] {
    let vec[piece] pieces = vec();
    // FIXME: Should be counting codepoints instead of bytes
    auto lim = _str.byte_len(s);
    auto buf = "";

    fn flush_buf(str buf, &vec[piece] pieces) -> str {
        if (_str.byte_len(buf) > 0u) {
            auto piece = piece_string(buf);
            pieces += piece;
        }
        ret "";
    }

    auto i = 0u;
    while (i < lim) {
        auto curr = _str.substr(s, i, 1u);
        if (_str.eq(curr, "%")) {
            i += 1u;
            if (i >= lim) {
                log "unterminated conversion at end of string";
                fail;
            }
            auto curr2 = _str.substr(s, i, 1u);
            if (_str.eq(curr2, "%")) {
                i += 1u;
            } else {
                buf = flush_buf(buf, pieces);
                auto res = parse_conversion(s, i, lim);
                pieces += res._0;
                i = res._1;
            }
        } else {
            buf += curr;
            i += 1u;
        }
    }
    buf = flush_buf(buf, pieces);
    ret pieces;
}

fn peek_num(str s, uint i, uint lim) -> option.t[tup(int, int)] {
    if (i >= lim) {
        ret none[tup(int, int)];
    } else {
        ret none[tup(int, int)];
        /*if ('0' <= c && c <= '9') {
            log c;
            fail;
        } else {
            ret option.none[tup(int, int)];
        }
        */
    }
}

fn parse_conversion(str s, uint i, uint lim) -> tup(piece, uint) {
    auto parm = parse_parameter(s, i, lim);
    auto flags = parse_flags(s, parm._1, lim);
    auto width = parse_width(s, flags._1, lim);
    auto prec = parse_precision(s, width._1, lim);
    auto ty = parse_type(s, prec._1, lim);
    ret tup(piece_conv(rec(param = parm._0,
                           flags = flags._0,
                           width = width._0,
                           precision = prec._0,
                           typ = ty._0)),
            ty._1);
}

fn parse_parameter(str s, uint i, uint lim) -> tup(option.t[int], uint) {
    if (i >= lim) {
        ret tup(none[int], i);
    }

    auto num = peek_num(s, i, lim);
    alt (num) {
        case (none[tup(int, int)]) {
            ret tup(none[int], i);
        }
        case (some[tup(int, int)](?t)) {
            fail;
        }
    }
}

fn parse_flags(str s, uint i, uint lim) -> tup(vec[flag], uint) {
    let vec[flag] flags = vec();
    ret tup(flags, i);
}

fn parse_width(str s, uint i, uint lim) -> tup(count, uint) {
    ret tup(count_implied, i);
}

fn parse_precision(str s, uint i, uint lim) -> tup(count, uint) {
    ret tup(count_implied, i);
}

fn parse_type(str s, uint i, uint lim) -> tup(ty, uint) {
    if (i >= lim) {
        log "missing type in conversion";
        fail;
    }

    auto t;
    auto tstr = _str.substr(s, i, 1u);
    if (_str.eq(tstr, "b")) {
        t = ty_bool;
    } else if (_str.eq(tstr, "s")) {
        t = ty_str;
    } else if (_str.eq(tstr, "c")) {
        t = ty_char;
    } else if (_str.eq(tstr, "d")
               || _str.eq(tstr, "i")) {
        // TODO: Do we really want two signed types here?
        // How important is it to be printf compatible?
        t = ty_int(signed);
    } else if (_str.eq(tstr, "u")) {
        t = ty_int(unsigned);
    } else if (_str.eq(tstr, "x")) {
        t = ty_hex(case_lower);
    } else if (_str.eq(tstr, "X")) {
        t = ty_hex(case_upper);
    } else if (_str.eq(tstr, "t")) {
        t = ty_bits;
    } else {
        // FIXME: This is a hack to avoid 'unsatisfied precondition
        // constraint' on uninitialized variable t below
        t = ty_bool;
        log "unknown type in conversion";
        fail;
    }

    ret tup(t, i + 1u);
}

fn pieces_to_expr(vec[piece] pieces, vec[@ast.expr] args) -> @ast.expr {

    fn make_new_str(common.span sp, str s) -> @ast.expr {
        auto strlit = ast.lit_str(s);
        auto spstrlit = @parser.spanned[ast.lit_](sp, sp, strlit);
        auto expr = ast.expr_lit(spstrlit, ast.ann_none);
        ret @parser.spanned[ast.expr_](sp, sp, expr);
    }

    fn make_add_expr(common.span sp,
                     @ast.expr lhs, @ast.expr rhs) -> @ast.expr {
        auto binexpr = ast.expr_binary(ast.add, lhs, rhs, ast.ann_none);
        ret @parser.spanned[ast.expr_](sp, sp, binexpr);
    }

    auto sp = args.(0).span;
    auto n = 0;
    auto tmp_expr = make_new_str(sp, "whatever");

    for (piece p in pieces) {
        alt (p) {
            case (piece_string(?s)) {
                auto s_expr = make_new_str(sp, s);
                tmp_expr = make_add_expr(sp, tmp_expr, s_expr);
            }
            case (piece_conv(?conv)) {
            }
        }
    }

    // TODO: Remove this print and return the real expanded AST
    log "dumping expanded ast:";
    log pretty.print_expr(tmp_expr);
    ret make_new_str(sp, "TODO");
}

//
// Local Variables:
// mode: rust
// fill-column: 78;
// indent-tabs-mode: nil
// c-basic-offset: 4
// buffer-file-coding-system: utf-8-unix
// compile-command: "make -k -C ../.. 2>&1 | sed -e 's/\\/x\\//x:\\//g'";
// End:
//
<|endoftext|>


<commit_before>use std::collections::LinkedList;
use std::collections::BTreeMap;

use super::ast;
use super::errors::*;
use pest::prelude::*;

fn find_indent(input: &str) -> Option<usize> {
    let mut it = input.chars();

    let mut index = 0usize;

    while let Some(c) = it.next() {
        match c {
            ' ' | '\t' => {}
            _ => {
                return Some(index);
            }
        }

        index += 1;
    }

    None
}

fn strip_code_block(input: &str) -> Vec<String> {
    let mut indent: Option<usize> = None;

    let mut current_line = 0;
    let mut first_line = false;
    let mut empty_start = 0;
    let mut empty_end = 0;
    let mut it = input.lines();

    while let Some(line) = it.next() {
        current_line += 1;

        if let Some(current) = find_indent(line) {
            empty_end = current_line;

            if indent.map(|i| i > current).unwrap_or(true) {
                indent = Some(current);
            }

            first_line = true;
        } else {
            if !first_line {
                empty_start += 1;
            }
        }
    }

    if let Some(indent) = indent {
        let mut out: Vec<String> = Vec::new();
        let mut it = input.lines().skip(empty_start).take(empty_end - empty_start);

        while let Some(line) = it.next() {
            if line.len() < indent {
                out.push(line.to_owned());
            } else {
                let stripped = &line[indent..];
                out.push(stripped.to_owned());
            }
        }

        return out;
    }

    return input.lines().map(ToOwned::to_owned).collect();
}

/// Decode an escaped string.
fn decode_escaped_string(input: &str) -> Result<String> {
    let mut out = String::new();
    let mut it = input.chars().skip(1).peekable();

    loop {
        let c = match it.next() {
            None => break,
            Some(c) => c,
        };

        // strip end quote
        if it.peek().is_none() {
            break;
        }

        if c == '\\' {
            let escaped = match it.next().ok_or("expected character")? {
                'n' => '\n',
                'r' => '\r',
                't' => '\t',
                'u' => decode_unicode4(&mut it)?,
                _ => return Err(ErrorKind::InvalidEscape.into()),
            };

            out.push(escaped);
            continue;
        }

        out.push(c);
    }

    Ok(out)
}

/// Decode the next four characters as a unicode escape sequence.
fn decode_unicode4(it: &mut Iterator<Item = char>) -> Result<char> {
    let mut res = 0u32;

    for x in 0..4u32 {
        let c = it.next().ok_or("expected hex character")?.to_string();
        let c = u32::from_str_radix(&c, 16)?;
        res += c << (4 * (3 - x));
    }

    Ok(::std::char::from_u32(res).ok_or("expected valid character")?)
}

impl_rdp! {
    grammar! {
        file = _{ package_decl ~ use_decl* ~ decl* ~ eoi }
        decl = { type_decl | interface_decl | tuple_decl }

        use_decl = { ["use"] ~ package_ident ~ use_as? ~ [";"] }
        use_as = { ["as"] ~ ident }
        package_decl = { ["package"] ~ package_ident ~ [";"] }
        type_decl = { ["type"] ~ ident ~ ["{"] ~ option_decl* ~ member* ~ ["}"] }
        tuple_decl = { ["tuple"] ~ ident ~ ["{"] ~ option_decl* ~ member* ~ ["}"] }
        interface_decl = { ["interface"] ~ ident ~ ["{"] ~ option_decl* ~ member* ~ sub_type_decl* ~ ["}"] }
        sub_type_decl = { sub_type }
        sub_type = { ident ~ ["{"] ~ option_decl* ~ member* ~ ["}"] }

        member = { field | code_block }

        field = { ident ~ modifier? ~ [":"] ~ type_spec ~ [";"] }
        code_block = @{ ident ~ whitespace* ~ ["{{"] ~ code_body ~ ["}}"] }
        code_body = { (!(["}}"]) ~ any)* }
        // body of a code block, either another balanced block, or anything but brackets
        modifier = { ["?"] }

        type_spec = { map | array | used_type | type_literal }
        type_literal = { ident }
        used_type = { ident ~ ["."] ~ ident }
        map = { ["{"] ~ type_spec ~ [":"] ~ type_spec ~ ["}"] }
        array = { ["["] ~ array_argument ~ ["]"] }
        array_argument = { type_spec }

        option_decl = { ident ~ (option_value ~ ([","] ~ option_value)*) ~ [";"] }

        option_value = { string | number }

        package_ident = @{ ident ~ (["."] ~ ident)* }
        ident =  @{ (['a'..'z'] | ['A'..'Z'] | ["_"]) ~ (['0'..'9'] | ['a'..'z'] | ['A'..'Z'] | ["_"])* }

        string  = @{ ["\""] ~ (escape | !(["\""] | ["\\"]) ~ any)* ~ ["\""] }
        escape  =  _{ ["\\"] ~ (["\""] | ["\\"] | ["/"] | ["n"] | ["r"] | ["t"] | unicode) }
        unicode =  _{ ["u"] ~ hex ~ hex ~ hex ~ hex }
        hex     =  _{ ['0'..'9'] | ['a'..'f'] }

        number = @{ ["-"]? ~ int ~ (["."] ~ ['0'..'9']+ ~ exp? | exp)? }
        int    =  _{ ["0"] | ['1'..'9'] ~ ['0'..'9']* }
        exp    =  _{ (["E"] | ["e"]) ~ (["+"] | ["-"])? ~ int }

        whitespace = _{ [" "] | ["\t"] | ["\r"] | ["\n"] }

        comment = _{
            // line comment
            ( ["//"] ~ (!(["\r"] | ["\n"]) ~ any)* ~ (["\n"] | ["\r\n"] | ["\r"] | eoi) ) |
            // block comment
            ( ["/*"] ~ (!(["*/"]) ~ any)* ~ ["*/"] )
        }
    }

    process! {
        _file(&self) -> Result<ast::File> {
            (_: package_decl, package: _package(), imports: _use_list(), decls: _decl_list()) => {
                let imports = imports.into_iter().collect();
                let decls = decls.into_iter().collect();
                Ok(ast::File::new(package, imports, decls))
            },
        }

        _use_list(&self) -> LinkedList<ast::UseDecl> {
            (_: use_decl, package: _package(), alias: _use_as(), mut tail: _use_list()) => {
                tail.push_front(ast::UseDecl::new(package, alias));
                tail
            },

            () => LinkedList::new(),
        }

        _use_as(&self) -> Option<String> {
            (_: use_as, &alias: ident) => {
                Some(alias.to_owned())
            },

            () => None,
        }

        _package(&self) -> ast::Package {
            (_: package_ident, idents: _ident_list()) => {
                ast::Package::new(idents.into_iter().collect())
            },
        }

        _decl_list(&self) -> LinkedList<ast::Decl> {
            (_: decl, first: _decl(), mut tail: _decl_list()) => {
                tail.push_front(first);
                tail
            },

            () => LinkedList::new(),
        }

        _type_body(&self) -> ast::TypeBody {
            (&name: ident, options: _option_list(), members: _member_list()) => {
                let options = ast::Options::new(options.into_iter().collect());
                let members = members.into_iter().collect();
                ast::TypeBody::new(name.to_owned(), options, members, BTreeMap::new())
            },
        }

        _decl(&self) -> ast::Decl {
            (token: type_decl, ty: _type_body()) => {
                let pos = (token.start, token.end);
                ast::Decl::Type(ty, pos)
            },

            (token: tuple_decl, ty: _type_body()) => {
                let pos = (token.start, token.end);
                ast::Decl::Tuple(ty, pos)
            },

            (
                token: interface_decl,
                &name: ident,
                options: _option_list(),
                members: _member_list(),
                sub_types: _sub_type_list()
            ) => {
                let options = ast::Options::new(options.into_iter().collect());
                let members = members.into_iter().collect();
                let pos = (token.start, token.end);
                let body = ast::TypeBody::new(name.to_owned(), options, members, sub_types);
                ast::Decl::Interface(body, pos)
            },
        }

        _option_list(&self) -> LinkedList<ast::OptionDecl> {
            (_: option_decl, first: _option_decl(), mut tail: _option_list()) => {
                tail.push_front(first);
                tail
            },

            () => LinkedList::new(),
        }

        _option_decl(&self) -> ast::OptionDecl {
            (&name: ident, values: _option_value_list()) => {
                let values = values.into_iter().collect();
                ast::OptionDecl::new(name.to_owned(), values)
            },
        }

        _option_value_list(&self) -> LinkedList<ast::OptionValue> {
            (_: option_value, first: _option_value(), mut tail: _option_value_list()) => {
                tail.push_front(first);
                tail
            },

            () => {
                LinkedList::new()
            },
        }

        _option_value(&self) -> ast::OptionValue {
            (&string: string) => {
                ast::OptionValue::String(decode_escaped_string(string).unwrap())
            },
        }

        _member_list(&self) -> LinkedList<ast::Member> {
            (_: member, first: _member(), mut tail: _member_list()) => {
                tail.push_front(first);
                tail
            },

            () => LinkedList::new(),
        }

        _member(&self) -> ast::Member {
            (token: field, field: _field()) => {
                let pos = (token.start, token.end);
                ast::Member::Field(field, pos)
            },

            (token: code_block, &context: ident, &content: code_body) => {
                let pos = (token.start, token.end);
                ast::Member::Code(context.to_owned(), strip_code_block(content), pos)
            },
        }

        _sub_type_list(&self) -> BTreeMap<String, ast::SubType> {
            (_: sub_type_decl, first: _sub_type(), mut tail: _sub_type_list()) => {
                tail.insert(first.name.clone(), first);
                tail
            },

            () => {
                BTreeMap::new()
            },
        }

        _sub_type(&self) -> ast::SubType {
            (token: sub_type, &name: ident, options: _option_list(), members: _member_list()) => {
                let name = name.to_owned();
                let options = ast::Options::new(options.into_iter().collect());
                let members = members.into_iter().collect();
                let pos = (token.start, token.end);
                ast::SubType::new(name, options, members, pos)
            },
        }

        _field(&self) -> ast::Field {
            (&name: ident, modifier: _modifier(), type_spec: _type_spec()) => {
                ast::Field::new(modifier, name.to_owned(), type_spec, 0)
            },
        }

        _type_spec(&self) -> ast::Type {
            (_: type_spec, _: type_literal, &value: ident) => {
                match value {
                    "double" => ast::Type::Double,
                    "float" => ast::Type::Float,
                    "i32" => ast::Type::I32,
                    "i64" => ast::Type::I64,
                    "u32" => ast::Type::U32,
                    "u64" => ast::Type::U64,
                    "bool" => ast::Type::Bool,
                    "string" => ast::Type::String,
                    "bytes" => ast::Type::Bytes,
                    "any" => ast::Type::Any,
                    name => ast::Type::Custom(name.to_owned()),
                }
            },

            (_: type_spec, _: used_type, &used: ident, &value: ident) => {
                ast::Type::UsedType(used.to_owned(), value.to_owned())
            },

            (_: type_spec, _: array, _: array_argument, argument: _type_spec()) => {
                ast::Type::Array(Box::new(argument))
            },

            (_: type_spec, _: map, key: _type_spec(), value: _type_spec()) => {
                ast::Type::Map(Box::new(key), Box::new(value))
            },
        }

        _modifier(&self) -> ast::Modifier {
            (_: modifier) => ast::Modifier::Optional,
            () => ast::Modifier::Required,
        }

        _ident_list(&self) -> LinkedList<String> {
            (&first: ident, mut tail: _ident_list()) => {
                tail.push_front(first.to_owned());
                tail
            },

            () => LinkedList::new(),
        }
    }
}

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

    const FILE1: &[u8] = include_bytes!("tests/file1.reproto");

    #[test]
    fn test_file1() {
        let input = ::std::str::from_utf8(FILE1).unwrap();
        let mut parser = Rdp::new(StringInput::new(input));

        assert!(parser.file());
        assert!(parser.end());

        let file = parser._file().unwrap();

        let package = ast::Package::new(vec!["foo".to_owned(), "bar".to_owned(), "baz".to_owned()]);

        assert_eq!(package, file.package);
        assert_eq!(3, file.decls.len());
    }

    #[test]
    fn test_array() {
        let mut parser = Rdp::new(StringInput::new("[string]"));

        assert!(parser.type_spec());
        assert!(parser.end());

        parser._type_spec();
    }

    #[test]
    fn test_block_comment() {
        let mut parser = Rdp::new(StringInput::new("/* hello \n world */"));

        assert!(parser.comment());
    }

    #[test]
    fn test_line_comment() {
        let mut parser = Rdp::new(StringInput::new("// hello world\n"));

        assert!(parser.comment());
    }

    #[test]
    fn test_code_block() {
        let mut parser = Rdp::new(StringInput::new("a { b { c } d } e"));

        assert!(parser.code_body());
        assert!(parser.end());
    }

    #[test]
    fn test_code() {
        let mut parser = Rdp::new(StringInput::new("java{{\na { b { c } d } e\n}}"));

        assert!(parser.code_block());
        assert!(parser.end());
    }

    #[test]
    fn test_strip_code_block() {
        let result = strip_code_block("\n   hello\n  world\n\n\n again\n\n\n");
        assert_eq!(vec!["  hello", " world", "", "", "again"], result);
    }
}
<commit_msg>parser: cleaner find_indent<commit_after>use pest::prelude::*;
use std::collections::BTreeMap;
use std::collections::LinkedList;

use super::ast;
use super::errors::*;

/// Check if character is an indentation character.
fn is_indent(c: char) -> bool {
    match c {
        ' ' | '\t' => true,
        _ => false,
    }
}

/// Find the number of whitespace characters that the given string is indented.
fn find_indent(input: &str) -> Option<usize> {
    input.chars().enumerate().find(|&(_, c)| !is_indent(c)).map(|(i, _)| i)
}

fn code_block_indent(input: &str) -> Option<(usize, usize, usize)> {
    let mut indent: Option<usize> = None;

    let mut start = 0;
    let mut end = 0;

    let mut first_line = false;

    for (line_no, line) in input.lines().enumerate() {
        if let Some(current) = find_indent(line) {
            end = line_no + 1;

            if indent.map(|i| i > current).unwrap_or(true) {
                indent = Some(current);
            }

            first_line = true;
        } else {
            if !first_line {
                start += 1;
            }
        }
    }

    indent.map(|indent| (indent, start, end - start))
}

/// Strip common indent from all input lines.
fn strip_code_block(input: &str) -> Vec<String> {
    if let Some((indent, empty_start, len)) = code_block_indent(input) {
        input.lines()
            .skip(empty_start)
            .take(len)
            .map(|line| {
                if line.len() < indent {
                    line.to_owned()
                } else {
                    (&line[indent..]).to_owned()
                }
            })
            .collect()
    } else {
        input.lines().map(ToOwned::to_owned).collect()
    }
}

/// Decode an escaped string.
fn decode_escaped_string(input: &str) -> Result<String> {
    let mut out = String::new();
    let mut it = input.chars().skip(1).peekable();

    loop {
        let c = match it.next() {
            None => break,
            Some(c) => c,
        };

        // strip end quote
        if it.peek().is_none() {
            break;
        }

        if c == '\\' {
            let escaped = match it.next().ok_or("expected character")? {
                'n' => '\n',
                'r' => '\r',
                't' => '\t',
                'u' => decode_unicode4(&mut it)?,
                _ => return Err(ErrorKind::InvalidEscape.into()),
            };

            out.push(escaped);
            continue;
        }

        out.push(c);
    }

    Ok(out)
}

/// Decode the next four characters as a unicode escape sequence.
fn decode_unicode4(it: &mut Iterator<Item = char>) -> Result<char> {
    let mut res = 0u32;

    for x in 0..4u32 {
        let c = it.next().ok_or("expected hex character")?.to_string();
        let c = u32::from_str_radix(&c, 16)?;
        res += c << (4 * (3 - x));
    }

    Ok(::std::char::from_u32(res).ok_or("expected valid character")?)
}

impl_rdp! {
    grammar! {
        file = _{ package_decl ~ use_decl* ~ decl* ~ eoi }
        decl = { type_decl | interface_decl | tuple_decl }

        use_decl = { ["use"] ~ package_ident ~ use_as? ~ [";"] }
        use_as = { ["as"] ~ ident }
        package_decl = { ["package"] ~ package_ident ~ [";"] }
        type_decl = { ["type"] ~ ident ~ ["{"] ~ option_decl* ~ member* ~ ["}"] }
        tuple_decl = { ["tuple"] ~ ident ~ ["{"] ~ option_decl* ~ member* ~ ["}"] }
        interface_decl = { ["interface"] ~ ident ~ ["{"] ~ option_decl* ~ member* ~ sub_type_decl* ~ ["}"] }
        sub_type_decl = { sub_type }
        sub_type = { ident ~ ["{"] ~ option_decl* ~ member* ~ ["}"] }

        member = { field | code_block }

        field = { ident ~ modifier? ~ [":"] ~ type_spec ~ [";"] }
        code_block = @{ ident ~ whitespace* ~ ["{{"] ~ code_body ~ ["}}"] }
        code_body = { (!(["}}"]) ~ any)* }
        // body of a code block, either another balanced block, or anything but brackets
        modifier = { ["?"] }

        type_spec = { map | array | used_type | type_literal }
        type_literal = { ident }
        used_type = { ident ~ ["."] ~ ident }
        map = { ["{"] ~ type_spec ~ [":"] ~ type_spec ~ ["}"] }
        array = { ["["] ~ array_argument ~ ["]"] }
        array_argument = { type_spec }

        option_decl = { ident ~ (option_value ~ ([","] ~ option_value)*) ~ [";"] }

        option_value = { string | number }

        package_ident = @{ ident ~ (["."] ~ ident)* }
        ident =  @{ (['a'..'z'] | ['A'..'Z'] | ["_"]) ~ (['0'..'9'] | ['a'..'z'] | ['A'..'Z'] | ["_"])* }

        string  = @{ ["\""] ~ (escape | !(["\""] | ["\\"]) ~ any)* ~ ["\""] }
        escape  =  _{ ["\\"] ~ (["\""] | ["\\"] | ["/"] | ["n"] | ["r"] | ["t"] | unicode) }
        unicode =  _{ ["u"] ~ hex ~ hex ~ hex ~ hex }
        hex     =  _{ ['0'..'9'] | ['a'..'f'] }

        number = @{ ["-"]? ~ int ~ (["."] ~ ['0'..'9']+ ~ exp? | exp)? }
        int    =  _{ ["0"] | ['1'..'9'] ~ ['0'..'9']* }
        exp    =  _{ (["E"] | ["e"]) ~ (["+"] | ["-"])? ~ int }

        whitespace = _{ [" "] | ["\t"] | ["\r"] | ["\n"] }

        comment = _{
            // line comment
            ( ["//"] ~ (!(["\r"] | ["\n"]) ~ any)* ~ (["\n"] | ["\r\n"] | ["\r"] | eoi) ) |
            // block comment
            ( ["/*"] ~ (!(["*/"]) ~ any)* ~ ["*/"] )
        }
    }

    process! {
        _file(&self) -> Result<ast::File> {
            (_: package_decl, package: _package(), imports: _use_list(), decls: _decl_list()) => {
                let imports = imports.into_iter().collect();
                let decls = decls.into_iter().collect();
                Ok(ast::File::new(package, imports, decls))
            },
        }

        _use_list(&self) -> LinkedList<ast::UseDecl> {
            (_: use_decl, package: _package(), alias: _use_as(), mut tail: _use_list()) => {
                tail.push_front(ast::UseDecl::new(package, alias));
                tail
            },

            () => LinkedList::new(),
        }

        _use_as(&self) -> Option<String> {
            (_: use_as, &alias: ident) => {
                Some(alias.to_owned())
            },

            () => None,
        }

        _package(&self) -> ast::Package {
            (_: package_ident, idents: _ident_list()) => {
                ast::Package::new(idents.into_iter().collect())
            },
        }

        _decl_list(&self) -> LinkedList<ast::Decl> {
            (_: decl, first: _decl(), mut tail: _decl_list()) => {
                tail.push_front(first);
                tail
            },

            () => LinkedList::new(),
        }

        _type_body(&self) -> ast::TypeBody {
            (&name: ident, options: _option_list(), members: _member_list()) => {
                let options = ast::Options::new(options.into_iter().collect());
                let members = members.into_iter().collect();
                ast::TypeBody::new(name.to_owned(), options, members, BTreeMap::new())
            },
        }

        _decl(&self) -> ast::Decl {
            (token: type_decl, ty: _type_body()) => {
                let pos = (token.start, token.end);
                ast::Decl::Type(ty, pos)
            },

            (token: tuple_decl, ty: _type_body()) => {
                let pos = (token.start, token.end);
                ast::Decl::Tuple(ty, pos)
            },

            (
                token: interface_decl,
                &name: ident,
                options: _option_list(),
                members: _member_list(),
                sub_types: _sub_type_list()
            ) => {
                let options = ast::Options::new(options.into_iter().collect());
                let members = members.into_iter().collect();
                let pos = (token.start, token.end);
                let body = ast::TypeBody::new(name.to_owned(), options, members, sub_types);
                ast::Decl::Interface(body, pos)
            },
        }

        _option_list(&self) -> LinkedList<ast::OptionDecl> {
            (_: option_decl, first: _option_decl(), mut tail: _option_list()) => {
                tail.push_front(first);
                tail
            },

            () => LinkedList::new(),
        }

        _option_decl(&self) -> ast::OptionDecl {
            (&name: ident, values: _option_value_list()) => {
                let values = values.into_iter().collect();
                ast::OptionDecl::new(name.to_owned(), values)
            },
        }

        _option_value_list(&self) -> LinkedList<ast::OptionValue> {
            (_: option_value, first: _option_value(), mut tail: _option_value_list()) => {
                tail.push_front(first);
                tail
            },

            () => {
                LinkedList::new()
            },
        }

        _option_value(&self) -> ast::OptionValue {
            (&string: string) => {
                ast::OptionValue::String(decode_escaped_string(string).unwrap())
            },
        }

        _member_list(&self) -> LinkedList<ast::Member> {
            (_: member, first: _member(), mut tail: _member_list()) => {
                tail.push_front(first);
                tail
            },

            () => LinkedList::new(),
        }

        _member(&self) -> ast::Member {
            (token: field, field: _field()) => {
                let pos = (token.start, token.end);
                ast::Member::Field(field, pos)
            },

            (token: code_block, &context: ident, &content: code_body) => {
                let pos = (token.start, token.end);
                let block = strip_code_block(content);
                ast::Member::Code(context.to_owned(), block, pos)
            },
        }

        _sub_type_list(&self) -> BTreeMap<String, ast::SubType> {
            (_: sub_type_decl, first: _sub_type(), mut tail: _sub_type_list()) => {
                tail.insert(first.name.clone(), first);
                tail
            },

            () => {
                BTreeMap::new()
            },
        }

        _sub_type(&self) -> ast::SubType {
            (token: sub_type, &name: ident, options: _option_list(), members: _member_list()) => {
                let name = name.to_owned();
                let options = ast::Options::new(options.into_iter().collect());
                let members = members.into_iter().collect();
                let pos = (token.start, token.end);
                ast::SubType::new(name, options, members, pos)
            },
        }

        _field(&self) -> ast::Field {
            (&name: ident, modifier: _modifier(), type_spec: _type_spec()) => {
                ast::Field::new(modifier, name.to_owned(), type_spec, 0)
            },
        }

        _type_spec(&self) -> ast::Type {
            (_: type_spec, _: type_literal, &value: ident) => {
                match value {
                    "double" => ast::Type::Double,
                    "float" => ast::Type::Float,
                    "i32" => ast::Type::I32,
                    "i64" => ast::Type::I64,
                    "u32" => ast::Type::U32,
                    "u64" => ast::Type::U64,
                    "bool" => ast::Type::Bool,
                    "string" => ast::Type::String,
                    "bytes" => ast::Type::Bytes,
                    "any" => ast::Type::Any,
                    name => ast::Type::Custom(name.to_owned()),
                }
            },

            (_: type_spec, _: used_type, &used: ident, &value: ident) => {
                ast::Type::UsedType(used.to_owned(), value.to_owned())
            },

            (_: type_spec, _: array, _: array_argument, argument: _type_spec()) => {
                ast::Type::Array(Box::new(argument))
            },

            (_: type_spec, _: map, key: _type_spec(), value: _type_spec()) => {
                ast::Type::Map(Box::new(key), Box::new(value))
            },
        }

        _modifier(&self) -> ast::Modifier {
            (_: modifier) => ast::Modifier::Optional,
            () => ast::Modifier::Required,
        }

        _ident_list(&self) -> LinkedList<String> {
            (&first: ident, mut tail: _ident_list()) => {
                tail.push_front(first.to_owned());
                tail
            },

            () => LinkedList::new(),
        }
    }
}

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

    const FILE1: &[u8] = include_bytes!("tests/file1.reproto");

    #[test]
    fn test_file1() {
        let input = ::std::str::from_utf8(FILE1).unwrap();
        let mut parser = Rdp::new(StringInput::new(input));

        assert!(parser.file());
        assert!(parser.end());

        let file = parser._file().unwrap();

        let package = ast::Package::new(vec!["foo".to_owned(), "bar".to_owned(), "baz".to_owned()]);

        assert_eq!(package, file.package);
        assert_eq!(3, file.decls.len());
    }

    #[test]
    fn test_array() {
        let mut parser = Rdp::new(StringInput::new("[string]"));

        assert!(parser.type_spec());
        assert!(parser.end());

        parser._type_spec();
    }

    #[test]
    fn test_block_comment() {
        let mut parser = Rdp::new(StringInput::new("/* hello \n world */"));

        assert!(parser.comment());
    }

    #[test]
    fn test_line_comment() {
        let mut parser = Rdp::new(StringInput::new("// hello world\n"));

        assert!(parser.comment());
    }

    #[test]
    fn test_code_block() {
        let mut parser = Rdp::new(StringInput::new("a { b { c } d } e"));

        assert!(parser.code_body());
        assert!(parser.end());
    }

    #[test]
    fn test_code() {
        let mut parser = Rdp::new(StringInput::new("java{{\na { b { c } d } e\n}}"));

        assert!(parser.code_block());
        assert!(parser.end());
    }

    #[test]
    fn test_find_indent() {
        assert_eq!(Some(4), find_indent("   \thello"));
        assert_eq!(Some(0), find_indent("nope"));
        assert_eq!(None, find_indent(""));
        assert_eq!(None, find_indent("    "));
    }

    #[test]
    fn test_strip_code_block() {
        let result = strip_code_block("\n   hello\n  world\n\n\n again\n\n\n");
        assert_eq!(vec!["  hello", " world", "", "", "again"], result);
    }
}
<|endoftext|>


<commit_before>// This module implements a number of http errors.
// Copyright (c) 2014 by Shipeng Feng.
// Licensed under the BSD License, see LICENSE for more details.

use std::error::Error;

use httputils::get_name_by_http_code;

use types::PenString;
use wrappers::Response;
use helpers::make_response;

pub use self::HTTPError::{
    BadRequest,
    Unauthorized,
    Forbidden,
    NotFound,
    MethodNotAllowed,
    NotAcceptable,
    RequestTimeout,
    Conflict,
    Gone,
    LengthRequired,
    PreconditionFailed,
    RequestEntityTooLarge,
    RequestURITooLarge,
    UnsupportedMediaType,
    RequestedRangeNotSatisfiable,
    ExpectationFailed,
    ImATeapot,
    UnprocessableEntity,
    PreconditionRequired,
    TooManyRequests,
    RequestHeaderFieldsTooLarge,
    InternalServerError,
    NotImplemented,
    BadGateway,
    ServiceUnavailable,
};

/// The HTTP Error type you can return from within your views to trigger a
/// non-200 response.  Here is one usage example:
///
/// ```rust,no_run
/// use pencil::{Request, Params, PencilResult};
/// use pencil::{NotFound, PenHTTPError};
///
///
/// fn view(_: Request, _: Params) -> PencilResult {
///     return Err(PenHTTPError(NotFound))
/// }
/// ```
///
/// Pencil comes with a shortcut that can be used to return non-200 HTTP error easily:
///
/// ```rust,no_run
/// use pencil::{Request, Params, PencilResult};
/// use pencil::abort;
///
///
/// fn view(_: Request, _: Params) -> PencilResult {
///     return abort(404)
/// }
/// ```
#[deriving(Clone)]
pub enum HTTPError {
    BadRequest,
    Unauthorized,
    Forbidden,
    NotFound,
    MethodNotAllowed,
    NotAcceptable,
    RequestTimeout,
    Conflict,
    Gone,
    LengthRequired,
    PreconditionFailed,
    RequestEntityTooLarge,
    RequestURITooLarge,
    UnsupportedMediaType,
    RequestedRangeNotSatisfiable,
    ExpectationFailed,
    ImATeapot,
    UnprocessableEntity,
    PreconditionRequired,
    TooManyRequests,
    RequestHeaderFieldsTooLarge,
    InternalServerError,
    NotImplemented,
    BadGateway,
    ServiceUnavailable,
}

impl HTTPError {
    /// Create a new `HTTPError`.
    pub fn new(code: int) -> HTTPError {
        match code {
            400 => BadRequest,
            401 => Unauthorized,
            403 => Forbidden,
            404 => NotFound,
            405 => MethodNotAllowed,
            406 => NotAcceptable,
            408 => RequestTimeout,
            409 => Conflict,
            410 => Gone,
            411 => LengthRequired,
            412 => PreconditionFailed,
            413 => RequestEntityTooLarge,
            414 => RequestURITooLarge,
            415 => UnsupportedMediaType,
            416 => RequestedRangeNotSatisfiable,
            417 => ExpectationFailed,
            418 => ImATeapot,
            422 => UnprocessableEntity,
            428 => PreconditionRequired,
            429 => TooManyRequests,
            431 => RequestHeaderFieldsTooLarge,
            500 => InternalServerError,
            501 => NotImplemented,
            502 => BadGateway,
            503 => ServiceUnavailable,
            _ => InternalServerError,
        }
    }

    /// The status code.
    pub fn code(&self) -> int {
        match *self {
            BadRequest => 400,
            Unauthorized => 401,
            Forbidden => 403,
            NotFound => 404,
            MethodNotAllowed => 405,
            NotAcceptable => 406,
            RequestTimeout => 408,
            Conflict => 409,
            Gone => 410,
            LengthRequired => 411,
            PreconditionFailed => 412,
            RequestEntityTooLarge => 413,
            RequestURITooLarge => 414,
            UnsupportedMediaType => 415,
            RequestedRangeNotSatisfiable => 416,
            ExpectationFailed => 417,
            ImATeapot => 418,
            UnprocessableEntity => 422,
            PreconditionRequired => 428,
            TooManyRequests => 429,
            RequestHeaderFieldsTooLarge => 431,
            InternalServerError => 500,
            NotImplemented => 501,
            BadGateway => 502,
            ServiceUnavailable => 503,
        }
    }

    /// The status name.
    pub fn name(&self) -> &str {
        match get_name_by_http_code(self.code()) {
            Some(name) => name,
            None => "Unknown Error"
        }
    }

    /// Get the HTML body.
    pub fn get_body(&self) -> String {
        format!(
"<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 3.2 Final//EN\">
<title>{} {}</title>

{}

{}

", self.code().to_string(), self.name(), self.name(), self.description())
    }

    /// Get a response object.
    pub fn to_response(&self) -> Response {
        let mut response = make_response(PenString(self.get_body()));
        response.set_status_code(self.code() as u16);
        response.set_content_type("text/html");
        return response;
    }
}

impl Error for HTTPError {
    fn description(&self) -> &str {
        match *self {
            BadRequest => "The browser (or proxy) sent a request that this server could not understand.",
            Unauthorized => "The server could not verify that you are authorized to access the URL requested.  You either supplied the wrong credentials (e.g. a bad password), or your browser doesn't understand how to supply the credentials required.",
            Forbidden => "You don't have the permission to access the requested resource.  It is either read-protected or not readable by the server.",
            NotFound => "The requested URL was not found on the server.  If you entered the URL manually please check your spelling and try again.",
            MethodNotAllowed => "The method is not allowed for the requested URL.",
            NotAcceptable => "The resource identified by the request is only capable of generating response entities which have content characteristics not acceptable according to the accept headers sent in the request.",
            RequestTimeout => "The server closed the network connection because the browser didn't finish the request within the specified time.",
            Conflict => "A conflict happened while processing the request.  The resource might have been modified while the request was being processed.",
            Gone => "The requested URL is no longer available on this server and there is no forwarding address.  If you followed a link from a foreign page, please contact the author of this page.",
            LengthRequired => "A request with this method requires a valid Content-Length header.",
            PreconditionFailed => "The precondition on the request for the URL failed positive evaluation.",
            RequestEntityTooLarge => "The data value transmitted exceeds the capacity limit.",
            RequestURITooLarge => "The length of the requested URL exceeds the capacity limit for this server.  The request cannot be processed.",
            UnsupportedMediaType => "The server does not support the media type transmitted in the request.",
            RequestedRangeNotSatisfiable => "The server cannot provide the requested range.",
            ExpectationFailed => "The server could not meet the requirements of the Expect header",
            ImATeapot => "This server is a teapot, not a coffee machine",
            UnprocessableEntity => "The request was well-formed but was unable to be followed due to semantic errors.",
            PreconditionRequired => "This request is required to be conditional; try using \"If-Match\" or \"If-Unmodified-Since\".",
            TooManyRequests => "This user has exceeded an allotted request count. Try again later.",
            RequestHeaderFieldsTooLarge => "One or more header fields exceeds the maximum size.",
            InternalServerError => "The server encountered an internal error and was unable to complete your request.  Either the server is overloaded or there is an error in the application.",
            NotImplemented => "The server does not support the action requested by the browser.",
            BadGateway => "The proxy server received an invalid response from an upstream server.",
            ServiceUnavailable => "The server is temporarily unable to service your request due to maintenance downtime or capacity problems.  Please try again later.",
        }
    }
}
<commit_msg>Fixed too long line length<commit_after>// This module implements a number of http errors.
// Copyright (c) 2014 by Shipeng Feng.
// Licensed under the BSD License, see LICENSE for more details.

use std::error::Error;

use httputils::get_name_by_http_code;

use types::PenString;
use wrappers::Response;
use helpers::make_response;

pub use self::HTTPError::{
    BadRequest,
    Unauthorized,
    Forbidden,
    NotFound,
    MethodNotAllowed,
    NotAcceptable,
    RequestTimeout,
    Conflict,
    Gone,
    LengthRequired,
    PreconditionFailed,
    RequestEntityTooLarge,
    RequestURITooLarge,
    UnsupportedMediaType,
    RequestedRangeNotSatisfiable,
    ExpectationFailed,
    ImATeapot,
    UnprocessableEntity,
    PreconditionRequired,
    TooManyRequests,
    RequestHeaderFieldsTooLarge,
    InternalServerError,
    NotImplemented,
    BadGateway,
    ServiceUnavailable,
};

/// The HTTP Error type you can return from within your views to trigger a
/// non-200 response.  Here is one usage example:
///
/// ```rust,no_run
/// use pencil::{Request, Params, PencilResult};
/// use pencil::{NotFound, PenHTTPError};
///
///
/// fn view(_: Request, _: Params) -> PencilResult {
///     return Err(PenHTTPError(NotFound))
/// }
/// ```
///
/// Pencil comes with a shortcut that can be used to return non-200 HTTP error easily:
///
/// ```rust,no_run
/// use pencil::{Request, Params, PencilResult};
/// use pencil::abort;
///
///
/// fn view(_: Request, _: Params) -> PencilResult {
///     return abort(404)
/// }
/// ```
#[deriving(Clone)]
pub enum HTTPError {
    BadRequest,
    Unauthorized,
    Forbidden,
    NotFound,
    MethodNotAllowed,
    NotAcceptable,
    RequestTimeout,
    Conflict,
    Gone,
    LengthRequired,
    PreconditionFailed,
    RequestEntityTooLarge,
    RequestURITooLarge,
    UnsupportedMediaType,
    RequestedRangeNotSatisfiable,
    ExpectationFailed,
    ImATeapot,
    UnprocessableEntity,
    PreconditionRequired,
    TooManyRequests,
    RequestHeaderFieldsTooLarge,
    InternalServerError,
    NotImplemented,
    BadGateway,
    ServiceUnavailable,
}

impl HTTPError {
    /// Create a new `HTTPError`.
    pub fn new(code: int) -> HTTPError {
        match code {
            400 => BadRequest,
            401 => Unauthorized,
            403 => Forbidden,
            404 => NotFound,
            405 => MethodNotAllowed,
            406 => NotAcceptable,
            408 => RequestTimeout,
            409 => Conflict,
            410 => Gone,
            411 => LengthRequired,
            412 => PreconditionFailed,
            413 => RequestEntityTooLarge,
            414 => RequestURITooLarge,
            415 => UnsupportedMediaType,
            416 => RequestedRangeNotSatisfiable,
            417 => ExpectationFailed,
            418 => ImATeapot,
            422 => UnprocessableEntity,
            428 => PreconditionRequired,
            429 => TooManyRequests,
            431 => RequestHeaderFieldsTooLarge,
            500 => InternalServerError,
            501 => NotImplemented,
            502 => BadGateway,
            503 => ServiceUnavailable,
            _ => InternalServerError,
        }
    }

    /// The status code.
    pub fn code(&self) -> int {
        match *self {
            BadRequest => 400,
            Unauthorized => 401,
            Forbidden => 403,
            NotFound => 404,
            MethodNotAllowed => 405,
            NotAcceptable => 406,
            RequestTimeout => 408,
            Conflict => 409,
            Gone => 410,
            LengthRequired => 411,
            PreconditionFailed => 412,
            RequestEntityTooLarge => 413,
            RequestURITooLarge => 414,
            UnsupportedMediaType => 415,
            RequestedRangeNotSatisfiable => 416,
            ExpectationFailed => 417,
            ImATeapot => 418,
            UnprocessableEntity => 422,
            PreconditionRequired => 428,
            TooManyRequests => 429,
            RequestHeaderFieldsTooLarge => 431,
            InternalServerError => 500,
            NotImplemented => 501,
            BadGateway => 502,
            ServiceUnavailable => 503,
        }
    }

    /// The status name.
    pub fn name(&self) -> &str {
        match get_name_by_http_code(self.code()) {
            Some(name) => name,
            None => "Unknown Error"
        }
    }

    /// Get the HTML body.
    pub fn get_body(&self) -> String {
        format!(
"<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 3.2 Final//EN\">
<title>{} {}</title>

{}

{}

", self.code().to_string(), self.name(), self.name(), self.description())
    }

    /// Get a response object.
    pub fn to_response(&self) -> Response {
        let mut response = make_response(PenString(self.get_body()));
        response.set_status_code(self.code() as u16);
        response.set_content_type("text/html");
        return response;
    }
}

impl Error for HTTPError {
    fn description(&self) -> &str {
        match *self {
            BadRequest => "The browser (or proxy) sent a request that this server \
                           could not understand.",
            Unauthorized => "The server could not verify that you are authorized \
                             to access the URL requested.  You either supplied the \
                             wrong credentials (e.g. a bad password), or your \
                             browser doesn't understand how to supply the \
                             credentials required.",
            Forbidden => "You don't have the permission to access the requested \
                          resource.  It is either read-protected or not readable \
                          by the server.",
            NotFound => "The requested URL was not found on the server.  If you \
                         entered the URL manually please check your spelling and try again.",
            MethodNotAllowed => "The method is not allowed for the requested URL.",
            NotAcceptable => "The resource identified by the request is only capable \
                              of generating response entities which have content \
                              characteristics not acceptable according to the accept \
                              headers sent in the request.",
            RequestTimeout => "The server closed the network connection because the \
                               browser didn't finish the request within the specified time.",
            Conflict => "A conflict happened while processing the request.  The resource \
                         might have been modified while the request was being processed.",
            Gone => "The requested URL is no longer available on this server and there is \
                     no forwarding address.  If you followed a link from a foreign page, \
                     please contact the author of this page.",
            LengthRequired => "A request with this method requires a valid Content-Length header.",
            PreconditionFailed => "The precondition on the request for the URL failed \
                                   positive evaluation.",
            RequestEntityTooLarge => "The data value transmitted exceeds the capacity limit.",
            RequestURITooLarge => "The length of the requested URL exceeds the capacity \
                                   limit for this server.  The request cannot be processed.",
            UnsupportedMediaType => "The server does not support the media type transmitted \
                                     in the request.",
            RequestedRangeNotSatisfiable => "The server cannot provide the requested range.",
            ExpectationFailed => "The server could not meet the requirements of the Expect header",
            ImATeapot => "This server is a teapot, not a coffee machine",
            UnprocessableEntity => "The request was well-formed but was unable to be \
                                    followed due to semantic errors.",
            PreconditionRequired => "This request is required to be conditional; try \
                                     using \"If-Match\" or \"If-Unmodified-Since\".",
            TooManyRequests => "This user has exceeded an allotted request count. Try again later.",
            RequestHeaderFieldsTooLarge => "One or more header fields exceeds the maximum size.",
            InternalServerError => "The server encountered an internal error and was unable \
                                    to complete your request.  Either the server is overloaded \
                                    or there is an error in the application.",
            NotImplemented => "The server does not support the action requested by the browser.",
            BadGateway => "The proxy server received an invalid response from an upstream server.",
            ServiceUnavailable => "The server is temporarily unable to service your request \
                                   due to maintenance downtime or capacity problems.  Please \
                                   try again later.",
        }
    }
}
<|endoftext|>