<commit_before>
//! DocBuilder

// TODO:
// * Need to get proper version of crate when dealing with local dependencies
//   Some crates are using * version for their local dependencies. DocBuilder
//   or Crate must get proper version information. To do this, I am planning
//   to find correct crate file in crates.io-index and generate a crate
//   from it. And add a function like find_version to return correct version
//   of local dependency.
// * Build doc with chroot.
// * Move docs into destination. Need to write a copy_doc function or module
//   for this and this function must handle URL's for css and src files.
// * Check paths if they are exist before doing any operation.

pub mod crte;

use std::io::prelude::*;
use std::io;
use std::fmt;
use std::env;
use std::path::PathBuf;
use std::fs;
use std::process::{Command, Output};
use std::collections;

use toml;

/// Alright
pub struct DocBuilder {
    keep_build_directory: bool,
    destination: PathBuf,
    chroot_path: PathBuf,
    chroot_user: String,
    build_dir: PathBuf,
    crates_io_index_path: PathBuf,
    logs_path: PathBuf,
    skip_if_exist: bool,
    skip_if_log_exists: bool,
    debug: bool,
}

#[derive(Debug)]
pub enum DocBuilderError {
    DownloadCrateError(String),
    ExtractCrateError(String),
    HandleLocalDependenciesError,
    LocalDependencyDownloadError(String),
    LocalDependencyExtractCrateError(String),
    LocalDependencyDownloadDirNotExist,
    LocalDependencyIoError(io::Error)
}

impl Default for DocBuilder {
    fn default() -> DocBuilder {

        let cwd = env::current_dir().unwrap();

        let (destination, chroot_path, build_dir, crates_io_index_path, logs_path) =
            generate_paths(cwd);

        DocBuilder {
            destination: destination,
            chroot_path: chroot_path,
            build_dir: build_dir,
            crates_io_index_path: crates_io_index_path,
            logs_path: logs_path,

            chroot_user: "onur".to_string(),

            keep_build_directory: false,
            skip_if_exist: false,
            skip_if_log_exists: false,
            debug: false,
        }
    }
}

impl fmt::Debug for DocBuilder {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f,
               "DocBuilder {{ destination: {:?}, chroot_path: {:?}, chroot_user_home_dir: {:?}, \
               crates_io_index_path: {:?}, logs_path: {:?}, chroot_user: {:?}, \
                keep_build_directory: {:?}, skip_if_exist: {:?}, skip_if_log_exists: {:?}, debug: \
                {:?} }}",
                self.destination,
                self.chroot_path,
                self.build_dir,
                self.crates_io_index_path,
                self.logs_path,
                self.chroot_user,
                self.keep_build_directory,
                self.skip_if_exist,
                self.skip_if_log_exists,
                self.debug)
    }
}

impl DocBuilder {

    /// Creates new DocBuilder from prefix
    pub fn from_prefix(prefix: PathBuf) -> DocBuilder {

        let (destination, chroot_path, build_dir, crates_io_index_path, logs_path) =
            generate_paths(prefix);

        DocBuilder {
            destination: destination,
            chroot_path: chroot_path,
            build_dir: build_dir,
            crates_io_index_path: crates_io_index_path,
            logs_path: logs_path,

            .. Default::default()
        }

    }

    /// This functions reads files in crates.io-index and tries to build
    /// documentation for crates.
    pub fn build_doc_for_every_crate(&self) {
        self.build_doc_for_crate_path(&self.crates_io_index_path);
    }

    fn build_doc_for_crate_path(&self, path: &PathBuf) {
        for dir in path.read_dir().unwrap() {

            let path = dir.unwrap().path();

            // skip files under .git and config.json
            if path.to_str().unwrap().contains(".git") ||
                path.file_name().unwrap() == "config.json" {
                    continue;
                }

            if path.is_dir() {
                self.build_doc_for_crate_path(&path);
                continue
            }

            // FIXME: check errors here
            let crte = crte::Crate::from_cargo_index_file(path);
            self.build_doc_for_crate(&crte.unwrap());
        }
    }

    /// Builds documentation for crate
    ///
    /// This function will try to build documentation for every version of crate
    pub fn build_doc_for_crate(&self, crte: &crte::Crate) {
        for i in 0..crte.versions.len() {
            if let Err(e) = self.build_doc_for_crate_version(crte, i) {
                println!("Failed to build docs for crate {}-{}: {:#?}",
                         &crte.name, &crte.versions[i], e)
            }
        }
    }

    fn open_log_for_crate(&self, crte: &crte::Crate, version_index: usize) -> Option<fs::File> {

        // Create a directory in logs folder
        let mut log_file_path = PathBuf::from(&self.logs_path);
        log_file_path.push(&crte.name);
        fs::create_dir(&log_file_path).unwrap();

        log_file_path.push(format!("{}-{}.log",
                                   crte.name, crte.versions[version_index]));

        // FIXME: We are getting panic if file is already exist
        match fs::File::create(log_file_path) {
            Ok(f) => Some(f),
            Err(e) => panic!(e)
        }
    }

    /// Download local dependencies from crate root and place them into right place
    ///
    /// Some packages have local dependencies defined in Cargo.toml
    ///
    /// This function is intentionall written verbose
    fn download_dependencies(&self, root_dir: &PathBuf) -> Result<(), DocBuilderError> {

        let mut cargo_toml_path = PathBuf::from(&root_dir);
        cargo_toml_path.push("Cargo.toml");

        let mut cargo_toml_fh = try!(fs::File::open(cargo_toml_path)
                                     .map_err(DocBuilderError::LocalDependencyIoError));
        let mut cargo_toml_content = String::new();
        try!(cargo_toml_fh.read_to_string(&mut cargo_toml_content)
             .map_err(DocBuilderError::LocalDependencyIoError));

        toml::Parser::new(&cargo_toml_content[..]).parse().as_ref()
            .and_then(|cargo_toml| cargo_toml.get("dependencies"))
            .and_then(|dependencies| dependencies.as_table())
            .and_then(|dependencies_table| get_local_dependencies(dependencies_table))
            .map(|local_dependencies| self.handle_local_dependencies(local_dependencies, &root_dir))
            .unwrap_or(Err(DocBuilderError::HandleLocalDependenciesError))
    }

    /// Handles local dependencies
    fn handle_local_dependencies(&self,
                                 local_dependencies: Vec<(crte::Crate, String)>,
                                 root_dir: &PathBuf) -> Result<(), DocBuilderError> {
        for local_dependency in local_dependencies {
            let crte = local_dependency.0;

            let mut path = PathBuf::from(&root_dir);
            path.push(local_dependency.1);

            // make sure path exists
            if !path.exists() {
                try!(fs::create_dir_all(&path).map_err(DocBuilderError::LocalDependencyIoError));
            }

            try!(self.download_latest_version_of_crate(&crte)
                 .map_err(DocBuilderError::LocalDependencyDownloadError));
            try!(self.extract_crate(&crte, 0)
                 .map_err(DocBuilderError::LocalDependencyExtractCrateError));

            let crte_download_dir = PathBuf::from(format!("{}/{}-{}",
                                                          self.build_dir.to_str().unwrap(),
                                                          crte.name, crte.versions[0]));

            if !crte_download_dir.exists() {
                return Err(DocBuilderError::LocalDependencyDownloadDirNotExist);
            }

            // self.extract_crate will extract crate into build_dir
            // Copy files to proper location
            try!(copy_files(&crte_download_dir, &path));

            // Remove download_dir
            try!(fs::remove_dir_all(&crte_download_dir)
                 .map_err(DocBuilderError::LocalDependencyIoError));
        }

        Ok(())
    }

    /// Returns package folder inside build directory
    fn crate_root_dir(&self, crte: &crte::Crate, version_index: usize) -> PathBuf {
        let mut package_root = PathBuf::from(&self.build_dir);
        package_root.push(format!("{}-{}", &crte.name, &crte.versions[version_index]));
        package_root
    }

    /// Builds documentation for crate
    ///
    /// This operation involves following process:
    ///
    /// * Cleaning up build directory
    /// * Downloading crate
    /// * Extracting it into build directory (chroot dir home directory)
    /// * Building crate documentation with chroot
    /// * Checking build directory for if crate actually has any documentation
    /// * Copying crate documentation into destination path
    /// * Cleaning up build directory
    /// * Removing downloaded crate file
    pub fn build_doc_for_crate_version(&self, crte: &crte::Crate, version_index: usize) ->
        Result<(), DocBuilderError> {

            let package_root = self.crate_root_dir(&crte, version_index);

            // TODO try to replace noob style logging
            let mut log_file = self.open_log_for_crate(crte, version_index).unwrap();

            println!("Building documentation for {}-{}", crte.name, crte.versions[version_index]);
            write!(&mut log_file,
                   "Building documentation for {}-{}\n",
                   crte.name, crte.versions[version_index]).unwrap();

            // Download crate
            write!(&mut log_file, "Downloading crate\n").unwrap();;
            // FIXME: Need to capture failed command outputs
            try!(self.download_crate(&crte, version_index)
                 .map_err(DocBuilderError::DownloadCrateError));

            // Extract crate
            write!(&mut log_file, "Extracting crate\n").unwrap();
            try!(self.extract_crate(&crte, version_index)
                 .map_err(DocBuilderError::ExtractCrateError));

            try!(self.download_dependencies(&package_root));

            Ok(())
        }

    /// Generates download url
    ///
    /// By default crates.io is using:
    /// https://crates.io/api/v1/crates/$crate/$version/download
    /// But I believe this url is increasing download count and this program is
    /// downloading alot during development. I am using redirected url.
    fn generate_download_url(&self, crte: &crte::Crate, version_index: usize) -> String {
        format!("https://crates-io.s3-us-west-1.amazonaws.com/crates/{}/{}-{}.crate",
                crte.name,
                crte.name,
                crte.versions[version_index])
    }

    /// Downloads crate
    fn download_crate(&self, crte: &crte::Crate, version_index: usize) -> Result<String, String> {
        let url = self.generate_download_url(&crte, version_index);
        // Use wget for now
        command_result(Command::new("wget")
                       .arg("-c")
                       .arg("--content-disposition")
                       .arg(url)
                       .output()
                       .unwrap())
    }

    /// Download latest version of crate
    fn download_latest_version_of_crate(&self, crte: &crte::Crate) -> Result<String, String> {
        // TODO: it might be better to check crte.versions len here
        self.download_crate(&crte, 0)
    }

    /// Extracts crate into build directory
    fn extract_crate(&self, crte: &crte::Crate, version_index: usize) -> Result<String, String> {

        let crate_name = format!("{}-{}.crate", &crte.name, &crte.versions[version_index]);
        command_result(Command::new("tar")
                       .arg("-C")
                       .arg(&self.build_dir)
                       .arg("-xzvf")
                       .arg(crate_name)
                       .output()
                       .unwrap())
    }

}

/// a simple function to capture command output
fn command_result(output: Output) -> Result<String, String> {
    let mut command_out = String::from_utf8_lossy(&output.stdout).into_owned();
    command_out.push_str(&String::from_utf8_lossy(&output.stderr).into_owned()[..]);
    match output.status.success() {
        true => Ok(command_out),
        false => Err(command_out)
    }
}

/// Get's local_dependencies from dependencies_table
fn get_local_dependencies(dependencies_table: &collections::BTreeMap<String, toml::Value>) ->
Option<Vec<(crte::Crate, String)>>  {

    let mut local_dependencies: Vec<(crte::Crate, String)> = Vec::new();

    for key in dependencies_table.keys() {

        dependencies_table.get(key)
            .and_then(|key_val| key_val.as_table())
            .map(|key_table| {
                key_table.get("path").and_then(|p| p.as_str()).map(|path| {
                    key_table.get("version").and_then(|p| p.as_str())
                        .map(|version| {
                            let dep_crate = crte::Crate::new(key.clone(),
                            vec![version.to_string()]);
                            local_dependencies.push((dep_crate, path.to_string()));
                        });
                });
            });

    }
    Some(local_dependencies)
}

/// A simple function to copy files from source to destination
fn copy_files(source: &PathBuf, destination: &PathBuf) -> Result<(), DocBuilderError> {

    for file in try!(source.read_dir().map_err(DocBuilderError::LocalDependencyIoError)) {

        let file = try!(file.map_err(DocBuilderError::LocalDependencyIoError));
        let mut destination_full_path = PathBuf::from(&destination);
        destination_full_path.push(file.file_name());

        let metadata = try!(file.metadata().map_err(DocBuilderError::LocalDependencyIoError));

        if metadata.is_dir() {
            try!(fs::create_dir(&destination_full_path)
                 .map_err(DocBuilderError::LocalDependencyIoError));
            try!(copy_files(&file.path(), &destination_full_path));
        } else {
            try!(fs::copy(&file.path(), &destination_full_path)
                 .map_err(DocBuilderError::LocalDependencyIoError));
        }

    }
    Ok(())
}

fn generate_paths(prefix: PathBuf) -> (PathBuf, PathBuf, PathBuf, PathBuf, PathBuf) {

    let mut destination = PathBuf::from(&prefix);
    destination.push("public_html/crates");

    let mut chroot_path = PathBuf::from(&prefix);
    chroot_path.push("chroot");

    let mut build_dir = PathBuf::from(&prefix);
    build_dir.push(&chroot_path);
    build_dir.push("home/onur");

    let mut crates_io_index_path = PathBuf::from(&prefix);
    crates_io_index_path.push("crates.io-index");

    let mut logs_path = PathBuf::from(&prefix);
    logs_path.push("logs");

    (destination, chroot_path, build_dir, crates_io_index_path, logs_path)
}
<commit_msg>Add a naieve build_doc_in_chroot function<commit_after>
//! DocBuilder

// TODO:
// * Need to get proper version of crate when dealing with local dependencies
//   Some crates are using * version for their local dependencies. DocBuilder
//   or Crate must get proper version information. To do this, I am planning
//   to find correct crate file in crates.io-index and generate a crate
//   from it. And add a function like find_version to return correct version
//   of local dependency.
// * Build doc with chroot.
// * Move docs into destination. Need to write a copy_doc function or module
//   for this and this function must handle URL's for css and src files.
// * Check paths if they are exist before doing any operation.

pub mod crte;

use std::io::prelude::*;
use std::io;
use std::fmt;
use std::env;
use std::path::PathBuf;
use std::fs;
use std::process::{Command, Output};
use std::collections;

use toml;

/// Alright
pub struct DocBuilder {
    keep_build_directory: bool,
    destination: PathBuf,
    chroot_path: PathBuf,
    chroot_user: String,
    build_dir: PathBuf,
    crates_io_index_path: PathBuf,
    logs_path: PathBuf,
    skip_if_exist: bool,
    skip_if_log_exists: bool,
    debug: bool,
}

#[derive(Debug)]
pub enum DocBuilderError {
    DownloadCrateError(String),
    ExtractCrateError(String),
    HandleLocalDependenciesError,
    LocalDependencyDownloadError(String),
    LocalDependencyExtractCrateError(String),
    LocalDependencyDownloadDirNotExist,
    LocalDependencyIoError(io::Error),
    FailedToBuildCrate(String),
}

impl Default for DocBuilder {
    fn default() -> DocBuilder {

        let cwd = env::current_dir().unwrap();

        let (destination, chroot_path, build_dir, crates_io_index_path, logs_path) =
            generate_paths(cwd);

        DocBuilder {
            destination: destination,
            chroot_path: chroot_path,
            build_dir: build_dir,
            crates_io_index_path: crates_io_index_path,
            logs_path: logs_path,

            chroot_user: "onur".to_string(),

            keep_build_directory: false,
            skip_if_exist: false,
            skip_if_log_exists: false,
            debug: false,
        }
    }
}

impl fmt::Debug for DocBuilder {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f,
               "DocBuilder {{ destination: {:?}, chroot_path: {:?}, chroot_user_home_dir: {:?}, \
               crates_io_index_path: {:?}, logs_path: {:?}, chroot_user: {:?}, \
                keep_build_directory: {:?}, skip_if_exist: {:?}, skip_if_log_exists: {:?}, debug: \
                {:?} }}",
                self.destination,
                self.chroot_path,
                self.build_dir,
                self.crates_io_index_path,
                self.logs_path,
                self.chroot_user,
                self.keep_build_directory,
                self.skip_if_exist,
                self.skip_if_log_exists,
                self.debug)
    }
}

impl DocBuilder {

    /// Creates new DocBuilder from prefix
    pub fn from_prefix(prefix: PathBuf) -> DocBuilder {

        let (destination, chroot_path, build_dir, crates_io_index_path, logs_path) =
            generate_paths(prefix);

        DocBuilder {
            destination: destination,
            chroot_path: chroot_path,
            build_dir: build_dir,
            crates_io_index_path: crates_io_index_path,
            logs_path: logs_path,

            .. Default::default()
        }

    }

    /// This functions reads files in crates.io-index and tries to build
    /// documentation for crates.
    pub fn build_doc_for_every_crate(&self) {
        self.build_doc_for_crate_path(&self.crates_io_index_path);
    }

    fn build_doc_for_crate_path(&self, path: &PathBuf) {
        for dir in path.read_dir().unwrap() {

            let path = dir.unwrap().path();

            // skip files under .git and config.json
            if path.to_str().unwrap().contains(".git") ||
                path.file_name().unwrap() == "config.json" {
                    continue;
                }

            if path.is_dir() {
                self.build_doc_for_crate_path(&path);
                continue
            }

            // FIXME: check errors here
            let crte = crte::Crate::from_cargo_index_file(path);
            self.build_doc_for_crate(&crte.unwrap());
        }
    }

    /// Builds documentation for crate
    ///
    /// This function will try to build documentation for every version of crate
    pub fn build_doc_for_crate(&self, crte: &crte::Crate) {
        for i in 0..crte.versions.len() {
            if let Err(e) = self.build_doc_for_crate_version(crte, i) {
                println!("Failed to build docs for crate {}-{}: {:#?}",
                         &crte.name, &crte.versions[i], e)
            }
        }
    }

    fn open_log_for_crate(&self, crte: &crte::Crate, version_index: usize) -> Option<fs::File> {

        // Create a directory in logs folder
        let mut log_file_path = PathBuf::from(&self.logs_path);
        log_file_path.push(&crte.name);
        fs::create_dir(&log_file_path).unwrap();

        log_file_path.push(format!("{}-{}.log",
                                   crte.name, crte.versions[version_index]));

        // FIXME: We are getting panic if file is already exist
        match fs::File::create(log_file_path) {
            Ok(f) => Some(f),
            Err(e) => panic!(e)
        }
    }

    /// Download local dependencies from crate root and place them into right place
    ///
    /// Some packages have local dependencies defined in Cargo.toml
    ///
    /// This function is intentionall written verbose
    fn download_dependencies(&self, root_dir: &PathBuf) -> Result<(), DocBuilderError> {

        let mut cargo_toml_path = PathBuf::from(&root_dir);
        cargo_toml_path.push("Cargo.toml");

        let mut cargo_toml_fh = try!(fs::File::open(cargo_toml_path)
                                     .map_err(DocBuilderError::LocalDependencyIoError));
        let mut cargo_toml_content = String::new();
        try!(cargo_toml_fh.read_to_string(&mut cargo_toml_content)
             .map_err(DocBuilderError::LocalDependencyIoError));

        toml::Parser::new(&cargo_toml_content[..]).parse().as_ref()
            .and_then(|cargo_toml| cargo_toml.get("dependencies"))
            .and_then(|dependencies| dependencies.as_table())
            .and_then(|dependencies_table| get_local_dependencies(dependencies_table))
            .map(|local_dependencies| self.handle_local_dependencies(local_dependencies, &root_dir))
            .unwrap_or(Err(DocBuilderError::HandleLocalDependenciesError))
    }

    /// Handles local dependencies
    fn handle_local_dependencies(&self,
                                 local_dependencies: Vec<(crte::Crate, String)>,
                                 root_dir: &PathBuf) -> Result<(), DocBuilderError> {
        for local_dependency in local_dependencies {
            let crte = local_dependency.0;

            let mut path = PathBuf::from(&root_dir);
            path.push(local_dependency.1);

            // make sure path exists
            if !path.exists() {
                try!(fs::create_dir_all(&path).map_err(DocBuilderError::LocalDependencyIoError));
            }

            try!(self.download_latest_version_of_crate(&crte)
                 .map_err(DocBuilderError::LocalDependencyDownloadError));
            try!(self.extract_crate(&crte, 0)
                 .map_err(DocBuilderError::LocalDependencyExtractCrateError));

            let crte_download_dir = PathBuf::from(format!("{}/{}-{}",
                                                          self.build_dir.to_str().unwrap(),
                                                          crte.name, crte.versions[0]));

            if !crte_download_dir.exists() {
                return Err(DocBuilderError::LocalDependencyDownloadDirNotExist);
            }

            // self.extract_crate will extract crate into build_dir
            // Copy files to proper location
            try!(copy_files(&crte_download_dir, &path));

            // Remove download_dir
            try!(fs::remove_dir_all(&crte_download_dir)
                 .map_err(DocBuilderError::LocalDependencyIoError));
        }

        Ok(())
    }

    /// Returns package folder inside build directory
    fn crate_root_dir(&self, crte: &crte::Crate, version_index: usize) -> PathBuf {
        let mut package_root = PathBuf::from(&self.build_dir);
        package_root.push(format!("{}-{}", &crte.name, &crte.versions[version_index]));
        package_root
    }

    /// Builds documentation for crate
    ///
    /// This operation involves following process:
    ///
    /// * Cleaning up build directory
    /// * Downloading crate
    /// * Extracting it into build directory (chroot dir home directory)
    /// * Building crate documentation with chroot
    /// * Checking build directory for if crate actually has any documentation
    /// * Copying crate documentation into destination path
    /// * Cleaning up build directory
    /// * Removing downloaded crate file
    pub fn build_doc_for_crate_version(&self, crte: &crte::Crate, version_index: usize) ->
        Result<(), DocBuilderError> {

            let package_root = self.crate_root_dir(&crte, version_index);

            // TODO try to replace noob style logging
            let mut log_file = self.open_log_for_crate(crte, version_index).unwrap();

            println!("Building documentation for {}-{}", crte.name, crte.versions[version_index]);
            write!(&mut log_file,
                   "Building documentation for {}-{}\n",
                   crte.name, crte.versions[version_index]).unwrap();

            // Download crate
            write!(&mut log_file, "Downloading crate\n").unwrap();;
            // FIXME: Need to capture failed command outputs
            try!(self.download_crate(&crte, version_index)
                 .map_err(DocBuilderError::DownloadCrateError));

            // Extract crate
            write!(&mut log_file, "Extracting crate\n").unwrap();
            try!(self.extract_crate(&crte, version_index)
                 .map_err(DocBuilderError::ExtractCrateError));

            try!(self.download_dependencies(&package_root));

            // build docs
            try!(self.build_doc_in_chroot(&crte, version_index)
                 .map_err(DocBuilderError::FailedToBuildCrate));

            Ok(())
        }

    /// Generates download url
    ///
    /// By default crates.io is using:
    /// https://crates.io/api/v1/crates/$crate/$version/download
    /// But I believe this url is increasing download count and this program is
    /// downloading alot during development. I am using redirected url.
    fn generate_download_url(&self, crte: &crte::Crate, version_index: usize) -> String {
        format!("https://crates-io.s3-us-west-1.amazonaws.com/crates/{}/{}-{}.crate",
                crte.name,
                crte.name,
                crte.versions[version_index])
    }

    /// Downloads crate
    fn download_crate(&self, crte: &crte::Crate, version_index: usize) -> Result<String, String> {
        let url = self.generate_download_url(&crte, version_index);
        // Use wget for now
        command_result(Command::new("wget")
                       .arg("-c")
                       .arg("--content-disposition")
                       .arg(url)
                       .output()
                       .unwrap())
    }

    /// Download latest version of crate
    fn download_latest_version_of_crate(&self, crte: &crte::Crate) -> Result<String, String> {
        // TODO: it might be better to check crte.versions len here
        self.download_crate(&crte, 0)
    }

    /// Extracts crate into build directory
    fn extract_crate(&self, crte: &crte::Crate, version_index: usize) -> Result<String, String> {

        let crate_name = format!("{}-{}.crate", &crte.name, &crte.versions[version_index]);
        command_result(Command::new("tar")
                       .arg("-C")
                       .arg(&self.build_dir)
                       .arg("-xzvf")
                       .arg(crate_name)
                       .output()
                       .unwrap())
    }

    /// Build documentation of a crate in chroot environment
    // FIXME: This is a really naieve implementation, but it works!
    //        I am not sure how can I do this without build.sh
    fn build_doc_in_chroot(&self,
                           crte: &crte::Crate,
                           version_index: usize) -> Result<String, String> {
        let crate_name = format!("{}-{}", &crte.name, &crte.versions[version_index]);
        command_result(Command::new("sudo")
                       .arg("chroot")
                       .arg(&self.chroot_path)
                       .arg("su").arg("--").arg("onur").arg("/home/onur/.build.sh").arg("build")
                       .arg(crate_name)
                       .env_clear()
                       .output()
                       .unwrap())
    }

}

/// a simple function to capture command output
fn command_result(output: Output) -> Result<String, String> {
    let mut command_out = String::from_utf8_lossy(&output.stdout).into_owned();
    command_out.push_str(&String::from_utf8_lossy(&output.stderr).into_owned()[..]);
    match output.status.success() {
        true => Ok(command_out),
        false => Err(command_out)
    }
}

/// Get's local_dependencies from dependencies_table
fn get_local_dependencies(dependencies_table: &collections::BTreeMap<String, toml::Value>) ->
Option<Vec<(crte::Crate, String)>>  {

    let mut local_dependencies: Vec<(crte::Crate, String)> = Vec::new();

    for key in dependencies_table.keys() {

        dependencies_table.get(key)
            .and_then(|key_val| key_val.as_table())
            .map(|key_table| {
                key_table.get("path").and_then(|p| p.as_str()).map(|path| {
                    key_table.get("version").and_then(|p| p.as_str())
                        .map(|version| {
                            let dep_crate = crte::Crate::new(key.clone(),
                            vec![version.to_string()]);
                            local_dependencies.push((dep_crate, path.to_string()));
                        });
                });
            });

    }
    Some(local_dependencies)
}

/// A simple function to copy files from source to destination
fn copy_files(source: &PathBuf, destination: &PathBuf) -> Result<(), DocBuilderError> {

    for file in try!(source.read_dir().map_err(DocBuilderError::LocalDependencyIoError)) {

        let file = try!(file.map_err(DocBuilderError::LocalDependencyIoError));
        let mut destination_full_path = PathBuf::from(&destination);
        destination_full_path.push(file.file_name());

        let metadata = try!(file.metadata().map_err(DocBuilderError::LocalDependencyIoError));

        if metadata.is_dir() {
            try!(fs::create_dir(&destination_full_path)
                 .map_err(DocBuilderError::LocalDependencyIoError));
            try!(copy_files(&file.path(), &destination_full_path));
        } else {
            try!(fs::copy(&file.path(), &destination_full_path)
                 .map_err(DocBuilderError::LocalDependencyIoError));
        }

    }
    Ok(())
}

fn generate_paths(prefix: PathBuf) -> (PathBuf, PathBuf, PathBuf, PathBuf, PathBuf) {

    let mut destination = PathBuf::from(&prefix);
    destination.push("public_html/crates");

    let mut chroot_path = PathBuf::from(&prefix);
    chroot_path.push("chroot");

    let mut build_dir = PathBuf::from(&prefix);
    build_dir.push(&chroot_path);
    build_dir.push("home/onur");

    let mut crates_io_index_path = PathBuf::from(&prefix);
    crates_io_index_path.push("crates.io-index");

    let mut logs_path = PathBuf::from(&prefix);
    logs_path.push("logs");

    (destination, chroot_path, build_dir, crates_io_index_path, logs_path)
}
<|endoftext|>


<commit_before>//! Build indexes as appropriate for the markdown pass

use doc::ItemUtils;

export mk_pass;

fn mk_pass(config: config::Config) -> Pass {
    {
        name: ~"markdown_index",
        f: fn~(srv: astsrv::Srv, doc: doc::Doc) -> doc::Doc {
            run(srv, doc, config)
        }
    }
}

fn run(
    _srv: astsrv::Srv,
    doc: doc::Doc,
    config: config::Config
) -> doc::Doc {
    let fold = fold::Fold({
        fold_mod: fold_mod,
        fold_nmod: fold_nmod,
        .. *fold::default_any_fold(config)
    });
    fold.fold_doc(fold, doc)
}

fn fold_mod(
    fold: fold::Fold<config::Config>,
    doc: doc::ModDoc
) -> doc::ModDoc {

    let doc = fold::default_any_fold_mod(fold, doc);

    doc::ModDoc_({
        index: Some(build_mod_index(doc, fold.ctxt)),
        .. *doc
    })
}

fn fold_nmod(
    fold: fold::Fold<config::Config>,
    doc: doc::NmodDoc
) -> doc::NmodDoc {

    let doc = fold::default_any_fold_nmod(fold, doc);

    {
        index: Some(build_nmod_index(doc, fold.ctxt)),
        .. doc
    }
}

fn build_mod_index(
    doc: doc::ModDoc,
    config: config::Config
) -> doc::Index {
    {
        entries: par::map(doc.items, |doc| {
            item_to_entry(doc, config)
        })
    }
}

fn build_nmod_index(
    doc: doc::NmodDoc,
    config: config::Config
) -> doc::Index {
    {
        entries: par::map(doc.fns, |doc| {
            item_to_entry(doc::FnTag(doc), config)
        })
    }
}

fn item_to_entry(
    doc: doc::ItemTag,
    config: config::Config
) -> doc::IndexEntry {
    let link = match doc {
      doc::ModTag(_) | doc::NmodTag(_)
      if config.output_style == config::DocPerMod => {
        markdown_writer::make_filename(config, doc::ItemPage(doc)).to_str()
      }
      _ => {
        ~"#" + pandoc_header_id(markdown_pass::header_text(doc))
      }
    };

    {
        kind: markdown_pass::header_kind(doc),
        name: markdown_pass::header_name(doc),
        brief: doc.brief(),
        link: link
    }
}

fn pandoc_header_id(header: ~str) -> ~str {

    // http://johnmacfarlane.net/pandoc/README.html#headers

    let header = remove_formatting(header);
    let header = remove_punctuation(header);
    let header = replace_with_hyphens(header);
    let header = convert_to_lowercase(header);
    let header = remove_up_to_first_letter(header);
    let header = maybe_use_section_id(header);
    return header;

    fn remove_formatting(s: ~str) -> ~str {
        str::replace(s, ~"`", ~"")
    }
    fn remove_punctuation(s: ~str) -> ~str {
        let s = str::replace(s, ~"<", ~"");
        let s = str::replace(s, ~">", ~"");
        let s = str::replace(s, ~"[", ~"");
        let s = str::replace(s, ~"]", ~"");
        let s = str::replace(s, ~"(", ~"");
        let s = str::replace(s, ~")", ~"");
        let s = str::replace(s, ~"@~", ~"");
        let s = str::replace(s, ~"~", ~"");
        let s = str::replace(s, ~"/", ~"");
        let s = str::replace(s, ~":", ~"");
        let s = str::replace(s, ~"&", ~"");
        let s = str::replace(s, ~"^", ~"");
        return s;
    }
    fn replace_with_hyphens(s: ~str) -> ~str {
        str::replace(s, ~" ", ~"-")
    }
    fn convert_to_lowercase(s: ~str) -> ~str { str::to_lower(s) }
    fn remove_up_to_first_letter(s: ~str) -> ~str { s }
    fn maybe_use_section_id(s: ~str) -> ~str { s }
}

#[test]
fn should_remove_punctuation_from_headers() {
    assert pandoc_header_id(~"impl foo of bar<A>") == ~"impl-foo-of-bara";
    assert pandoc_header_id(~"fn@(~[~A])") == ~"fna";
    assert pandoc_header_id(~"impl of num::num for int")
        == ~"impl-of-numnum-for-int";
    assert pandoc_header_id(~"impl of num::num for int/&")
        == ~"impl-of-numnum-for-int";
    assert pandoc_header_id(~"impl of num::num for ^int")
        == ~"impl-of-numnum-for-int";
}

#[test]
fn should_index_mod_contents() {
    let doc = test::mk_doc(
        config::DocPerCrate,
        ~"mod a { } fn b() { }"
    );
    assert doc.cratemod().index.get().entries[0] == {
        kind: ~"Module",
        name: ~"a",
        brief: None,
        link: ~"#module-a"
    };
    assert doc.cratemod().index.get().entries[1] == {
        kind: ~"Function",
        name: ~"b",
        brief: None,
        link: ~"#function-b"
    };
}

#[test]
fn should_index_mod_contents_multi_page() {
    let doc = test::mk_doc(
        config::DocPerMod,
        ~"mod a { } fn b() { }"
    );
    assert doc.cratemod().index.get().entries[0] == {
        kind: ~"Module",
        name: ~"a",
        brief: None,
        link: ~"a.html"
    };
    assert doc.cratemod().index.get().entries[1] == {
        kind: ~"Function",
        name: ~"b",
        brief: None,
        link: ~"#function-b"
    };
}

#[test]
fn should_index_foreign_mod_pages() {
    let doc = test::mk_doc(
        config::DocPerMod,
        ~"extern mod a { }"
    );
    assert doc.cratemod().index.get().entries[0] == {
        kind: ~"Foreign module",
        name: ~"a",
        brief: None,
        link: ~"a.html"
    };
}

#[test]
fn should_add_brief_desc_to_index() {
    let doc = test::mk_doc(
        config::DocPerMod,
        ~"#[doc = \"test\"] mod a { }"
    );
    assert doc.cratemod().index.get().entries[0].brief
        == Some(~"test");
}

#[test]
fn should_index_foreign_mod_contents() {
    let doc = test::mk_doc(
        config::DocPerCrate,
        ~"extern mod a { fn b(); }"
    );
    assert doc.cratemod().nmods()[0].index.get().entries[0] == {
        kind: ~"Function",
        name: ~"b",
        brief: None,
        link: ~"#function-b"
    };
}

#[cfg(test)]
mod test {
    #[legacy_exports];
    fn mk_doc(output_style: config::OutputStyle, source: ~str) -> doc::Doc {
        do astsrv::from_str(source) |srv| {
            let config = {
                output_style: output_style,
                .. config::default_config(&Path("whatever"))
            };
            let doc = extract::from_srv(srv, ~"");
            let doc = attr_pass::mk_pass().f(srv, doc);
            let doc = desc_to_brief_pass::mk_pass().f(srv, doc);
            let doc = path_pass::mk_pass().f(srv, doc);
            run(srv, doc, config)
        }
    }
}
<commit_msg>rustdoc: Fix for generations of anchors. #3570<commit_after>//! Build indexes as appropriate for the markdown pass

use doc::ItemUtils;

export mk_pass;

fn mk_pass(config: config::Config) -> Pass {
    {
        name: ~"markdown_index",
        f: fn~(srv: astsrv::Srv, doc: doc::Doc) -> doc::Doc {
            run(srv, doc, config)
        }
    }
}

fn run(
    _srv: astsrv::Srv,
    doc: doc::Doc,
    config: config::Config
) -> doc::Doc {
    let fold = fold::Fold({
        fold_mod: fold_mod,
        fold_nmod: fold_nmod,
        .. *fold::default_any_fold(config)
    });
    fold.fold_doc(fold, doc)
}

fn fold_mod(
    fold: fold::Fold<config::Config>,
    doc: doc::ModDoc
) -> doc::ModDoc {

    let doc = fold::default_any_fold_mod(fold, doc);

    doc::ModDoc_({
        index: Some(build_mod_index(doc, fold.ctxt)),
        .. *doc
    })
}

fn fold_nmod(
    fold: fold::Fold<config::Config>,
    doc: doc::NmodDoc
) -> doc::NmodDoc {

    let doc = fold::default_any_fold_nmod(fold, doc);

    {
        index: Some(build_nmod_index(doc, fold.ctxt)),
        .. doc
    }
}

fn build_mod_index(
    doc: doc::ModDoc,
    config: config::Config
) -> doc::Index {
    {
        entries: par::map(doc.items, |doc| {
            item_to_entry(doc, config)
        })
    }
}

fn build_nmod_index(
    doc: doc::NmodDoc,
    config: config::Config
) -> doc::Index {
    {
        entries: par::map(doc.fns, |doc| {
            item_to_entry(doc::FnTag(doc), config)
        })
    }
}

fn item_to_entry(
    doc: doc::ItemTag,
    config: config::Config
) -> doc::IndexEntry {
    let link = match doc {
      doc::ModTag(_) | doc::NmodTag(_)
      if config.output_style == config::DocPerMod => {
        markdown_writer::make_filename(config, doc::ItemPage(doc)).to_str()
      }
      _ => {
        ~"#" + pandoc_header_id(markdown_pass::header_text(doc))
      }
    };

    {
        kind: markdown_pass::header_kind(doc),
        name: markdown_pass::header_name(doc),
        brief: doc.brief(),
        link: link
    }
}

fn pandoc_header_id(header: ~str) -> ~str {

    // http://johnmacfarlane.net/pandoc/README.html#headers

    let header = remove_formatting(header);
    let header = remove_punctuation(header);
    let header = replace_with_hyphens(header);
    let header = convert_to_lowercase(header);
    let header = remove_up_to_first_letter(header);
    let header = maybe_use_section_id(header);
    return header;

    fn remove_formatting(s: ~str) -> ~str {
        str::replace(s, ~"`", ~"")
    }
    fn remove_punctuation(s: ~str) -> ~str {
        let s = str::replace(s, ~"<", ~"");
        let s = str::replace(s, ~">", ~"");
        let s = str::replace(s, ~"[", ~"");
        let s = str::replace(s, ~"]", ~"");
        let s = str::replace(s, ~"(", ~"");
        let s = str::replace(s, ~")", ~"");
        let s = str::replace(s, ~"@~", ~"");
        let s = str::replace(s, ~"~", ~"");
        let s = str::replace(s, ~"/", ~"");
        let s = str::replace(s, ~":", ~"");
        let s = str::replace(s, ~"&", ~"");
        let s = str::replace(s, ~"^", ~"");
        return s;
    }
    fn replace_with_hyphens(s: ~str) -> ~str {
        // Collapse sequences of whitespace to a single dash
        // XXX: Hacky implementation here that only covers
        // one or two spaces.
        let s = str::replace(s, ~"  ", ~"-");
        let s = str::replace(s, ~" ", ~"-");
        return s;
    }
    fn convert_to_lowercase(s: ~str) -> ~str { str::to_lower(s) }
    fn remove_up_to_first_letter(s: ~str) -> ~str { s }
    fn maybe_use_section_id(s: ~str) -> ~str { s }
}

#[test]
fn should_remove_punctuation_from_headers() {
    assert pandoc_header_id(~"impl foo of bar<A>") == ~"impl-foo-of-bara";
    assert pandoc_header_id(~"fn@(~[~A])") == ~"fna";
    assert pandoc_header_id(~"impl of num::num for int")
        == ~"impl-of-numnum-for-int";
    assert pandoc_header_id(~"impl of num::num for int/&")
        == ~"impl-of-numnum-for-int";
    assert pandoc_header_id(~"impl of num::num for ^int")
        == ~"impl-of-numnum-for-int";
    assert pandoc_header_id(~"impl for & condvar")
        == ~"impl-for-condvar";
}

#[test]
fn should_index_mod_contents() {
    let doc = test::mk_doc(
        config::DocPerCrate,
        ~"mod a { } fn b() { }"
    );
    assert doc.cratemod().index.get().entries[0] == {
        kind: ~"Module",
        name: ~"a",
        brief: None,
        link: ~"#module-a"
    };
    assert doc.cratemod().index.get().entries[1] == {
        kind: ~"Function",
        name: ~"b",
        brief: None,
        link: ~"#function-b"
    };
}

#[test]
fn should_index_mod_contents_multi_page() {
    let doc = test::mk_doc(
        config::DocPerMod,
        ~"mod a { } fn b() { }"
    );
    assert doc.cratemod().index.get().entries[0] == {
        kind: ~"Module",
        name: ~"a",
        brief: None,
        link: ~"a.html"
    };
    assert doc.cratemod().index.get().entries[1] == {
        kind: ~"Function",
        name: ~"b",
        brief: None,
        link: ~"#function-b"
    };
}

#[test]
fn should_index_foreign_mod_pages() {
    let doc = test::mk_doc(
        config::DocPerMod,
        ~"extern mod a { }"
    );
    assert doc.cratemod().index.get().entries[0] == {
        kind: ~"Foreign module",
        name: ~"a",
        brief: None,
        link: ~"a.html"
    };
}

#[test]
fn should_add_brief_desc_to_index() {
    let doc = test::mk_doc(
        config::DocPerMod,
        ~"#[doc = \"test\"] mod a { }"
    );
    assert doc.cratemod().index.get().entries[0].brief
        == Some(~"test");
}

#[test]
fn should_index_foreign_mod_contents() {
    let doc = test::mk_doc(
        config::DocPerCrate,
        ~"extern mod a { fn b(); }"
    );
    assert doc.cratemod().nmods()[0].index.get().entries[0] == {
        kind: ~"Function",
        name: ~"b",
        brief: None,
        link: ~"#function-b"
    };
}

#[cfg(test)]
mod test {
    #[legacy_exports];
    fn mk_doc(output_style: config::OutputStyle, source: ~str) -> doc::Doc {
        do astsrv::from_str(source) |srv| {
            let config = {
                output_style: output_style,
                .. config::default_config(&Path("whatever"))
            };
            let doc = extract::from_srv(srv, ~"");
            let doc = attr_pass::mk_pass().f(srv, doc);
            let doc = desc_to_brief_pass::mk_pass().f(srv, doc);
            let doc = path_pass::mk_pass().f(srv, doc);
            run(srv, doc, config)
        }
    }
}
<|endoftext|>


<commit_before>// Implements http://rosettacode.org/wiki/Fibonacci_word
extern crate collections;

use entropy::shannon_entropy;
mod entropy;

fn main() {
    println!("{:>2s}:{:>10s} {:s}", "N", "length", "entropy");

    let mut previous = StrBuf::from_str("1");
    println!("{:>2i}:{:>10u} {:f}", 1, previous.len(),
             shannon_entropy(previous.as_slice()));

    let mut next = StrBuf::from_str("0");
    println!("{:>2i}:{:>10u} {:f}", 2, next.len(),
             shannon_entropy(next.as_slice()));

    for i in range(3, 38) {
        let temp = next.clone();
        next.push_str(previous.as_slice());
        previous = temp;
        println!("{:>2i}:{:>10u} {:.15f}", i, next.len(),
                 shannon_entropy(next.as_slice()));
    }
}
<commit_msg>Commented and refactored fibonacci word<commit_after>// Implements http://rosettacode.org/wiki/Fibonacci_word
extern crate collections;

use entropy::shannon_entropy;
use std::iter::range_inclusive;
mod entropy;

// Returns "amount" fibonacci words as a vector of tuples
// The first value of the tuple is the length of the word
// and the second one its entropy
fn fib_words(amount: uint) -> Vec<(uint, f64)> {
    let mut data = Vec::with_capacity(amount);
    let mut previous = StrBuf::from_str("1");
    let mut next = StrBuf::from_str("0");

    // The first two words (we need to add them manually because
    // they are the base of the sequence)
    data.push((previous.len(), shannon_entropy(previous.as_slice())));
    data.push((next.len(), shannon_entropy(next.as_slice())));

    // The rest of the words
    for _ in range_inclusive(3, amount) {
        let temp = next.clone();
        next.push_str(previous.as_slice());
        previous = temp;
        data.push((next.len(), shannon_entropy(next.as_slice())));
    }

    data
}

fn main() {
    println!("Calculating... This may take a couple of minutes...\n");

    let words = fib_words(18);
    let mut i = 1;

    println!("{:>2}:{:>10} {}", "N", "length", "entropy");
    for &(length, entropy) in words.iter() {
        println!("{:>2i}:{:>10u} {:.15f}", i, length, entropy);
        i += 1;
    }
}<|endoftext|>


<commit_before>// Implements http://rosettacode.org/wiki/Four_bit_adder
#![allow(non_snake_case_functions)]

extern crate arena;

use arena::Arena;

static TRUE: bool = true;
static FALSE: bool = false;

trait Gate {
  fn get(&self) -> bool;
}

impl Gate for bool {
  fn get(&self) -> bool {
    *self
  }
}

struct NotGate<'a>{
  inp: &'a Gate,
}

impl<'a> Gate for NotGate<'a> {
 fn get(&self) -> bool {
    !self.inp.get()
  }
}

struct OrGate<'a>{
  inp1: &'a Gate,
  inp2: &'a Gate,
}

impl<'a> Gate for OrGate<'a> {
  fn get(&self) -> bool {
    self.inp1.get() || self.inp2.get()
  }
}

struct AndGate<'a>{
  inp1: &'a Gate,
  inp2: &'a Gate,
}

impl<'a> Gate for AndGate<'a> {
  fn get(&self) -> bool {
    self.inp1.get() && self.inp2.get()
  }
}

fn not<'a>(ar: &'a Arena, inp: &'a Gate) -> &'a Gate {
  ar.alloc(||NotGate{inp:inp}) as &'a Gate
}

fn or<'a>(ar: &'a Arena, inp1: &'a Gate, inp2: &'a Gate) -> &'a Gate {
  ar.alloc(||OrGate{inp1:inp1,inp2:inp2}) as &'a Gate
}

fn and<'a>(ar: &'a Arena, inp1: &'a Gate, inp2: &'a Gate) -> &'a Gate {
  ar.alloc(||AndGate{inp1:inp1,inp2:inp2}) as &'a Gate
}

fn xor<'a>(ar: &'a Arena, inp1: &'a Gate, inp2: &'a Gate) -> &'a Gate {
  or(ar,and(ar,inp1,not(ar,inp2)),and(ar,not(ar,inp1),inp2))
}

fn half_add<'a>(ar: &'a Arena, inp1: &'a Gate, inp2: &'a Gate) -> (&'a Gate, &'a Gate) {
  (xor(ar,inp1,inp2),and(ar,inp1,inp2))
}

fn full_add<'a>(ar: &'a Arena, inp1: &'a Gate, inp2: &'a Gate, inp3: &'a Gate)
    -> (&'a Gate, &'a Gate) {
  let (ha1_s,ha1_c) = half_add(ar,inp1, inp3);
  let (ha2_s,ha2_c) = half_add(ar,ha1_s,inp2);
  (ha2_s,or(ar,ha1_c,ha2_c))
}

type Nibble<'a> = (&'a Gate, &'a Gate, &'a Gate, &'a Gate);

fn four_bit_adder<'a>(ar: &'a Arena, a: Nibble<'a>, b: Nibble<'a>, ci: &'a Gate)
    -> (&'a Gate, Nibble<'a>) {
  let (a4,a3,a2,a1) = a;
  let (b4,b3,b2,b1) = b;
  let (fa1_s,fa1_c) = full_add(ar,a1,b1,ci);
  let (fa2_s,fa2_c) = full_add(ar,a2,b2,fa1_c);
  let (fa3_s,fa3_c) = full_add(ar,a3,b3,fa2_c);
  let (fa4_s,fa4_c) = full_add(ar,a4,b4,fa3_c);
  (fa4_c, (fa4_s,fa3_s,fa2_s,fa1_s))
}

#[cfg(not(test))]
fn main() {
  fn show_nibble<'a>(nib: Nibble<'a>) -> (bool,bool,bool,bool) {
    let (n4,n3,n2,n1) = nib;
    (n4.get(),n3.get(),n2.get(),n1.get())
  }
  fn show_result<'a>((c,nib): (&'a Gate,Nibble<'a>)) -> (bool,(bool,bool,bool,bool)) {
    (c.get(), show_nibble(nib))
  }
  let ref ar = Arena::new();
  let gTrue = &TRUE as &Gate;
  let gFalse = &FALSE as &Gate;
  let inp1 = (gTrue,gFalse,gTrue,gTrue);
  let inp2 = (gFalse,gTrue,gTrue,gFalse);
  let (oflow,res) = show_result(four_bit_adder(ar,inp1,inp2,gFalse));
  println!("{} + {} = {}, overflow: {}", show_nibble(inp1), show_nibble(inp2), res, oflow);
}

#[test]
fn test_not() {
  let ref ar = Arena::new();
  let gTrue = &TRUE as &Gate;
  let gFalse = &FALSE as &Gate;
  assert_eq!(true, not(ar,gFalse).get());
  assert_eq!(false, not(ar,gTrue).get());
}

#[test]
fn test_or() {
  let ref ar = Arena::new();
  let gTrue = &TRUE as &Gate;
  let gFalse = &FALSE as &Gate;
  assert_eq!(false, or(ar,gFalse,gFalse).get());
  assert_eq!(true, or(ar,gTrue,gFalse).get());
  assert_eq!(true, or(ar,gFalse,gTrue).get());
  assert_eq!(true, or(ar,gTrue,gTrue).get());
}

#[test]
fn test_and() {
  let ref ar = Arena::new();
  let gTrue = &TRUE as &Gate;
  let gFalse = &FALSE as &Gate;
  assert_eq!(false, and(ar,gFalse,gFalse).get());
  assert_eq!(false, and(ar,gFalse,gTrue).get());
  assert_eq!(false, and(ar,gTrue,gFalse).get());
  assert_eq!(true, and(ar,gTrue,gTrue).get());
}

#[test]
fn test_xor() {
  let ref ar = Arena::new();
  let gTrue = &TRUE as &Gate;
  let gFalse = &FALSE as &Gate;
  assert_eq!(false, xor(ar,gFalse,gFalse).get());
  assert_eq!(true, xor(ar,gFalse,gTrue).get());
  assert_eq!(true, xor(ar,gTrue,gFalse).get());
  assert_eq!(false, xor(ar,gTrue,gTrue).get());
}

#[test]
fn test_full_add() {
  let ref ar = Arena::new();
  let gTrue = &TRUE as &Gate;
  let gFalse = &FALSE as &Gate;
  fn eval((a,b): (&Gate,&Gate)) -> (bool,bool) { (a.get(),b.get()) }
  assert_eq!( (false,false), eval(full_add(ar,gFalse,gFalse,gFalse)) );
  assert_eq!( (true,false), eval(full_add(ar,gFalse,gFalse,gTrue)) );
  assert_eq!( (true,false), eval(full_add(ar,gFalse,gTrue,gFalse)) );
  assert_eq!( (true,false), eval(full_add(ar,gTrue,gFalse,gFalse)) );
  assert_eq!( (false,true), eval(full_add(ar,gFalse,gTrue,gTrue)) );
  assert_eq!( (false,true), eval(full_add(ar,gTrue,gFalse,gTrue)) );
  assert_eq!( (false,true), eval(full_add(ar,gTrue,gTrue,gFalse)) );
  assert_eq!( (true,true), eval(full_add(ar,gTrue,gTrue,gTrue)) );
}

#[test]
fn test_full_add4() {
 fn to_nib(n:u8)->(Nibble){
    let (gt,gf) = (&TRUE as &Gate, &FALSE as &Gate);
    (
      if n&8!=0 {gt} else {gf},
      if n&4!=0 {gt} else {gf},
      if n&2!=0 {gt} else {gf},
      if n&1!=0 {gt} else {gf}
    )
  }
  fn from_result<'a>((c,(n4,n3,n2,n1)): (&'a Gate,Nibble<'a>)) -> u8 {
    let mut n = 0u8;
    if c.get()  { n += 16};
    if n4.get() { n += 8 };
    if n3.get() { n += 4 };
    if n2.get() { n += 2 };
    if n1.get() { n += 1 };
    n
  }
  let ref ar = Arena::new();
  let gTrue = &TRUE as &Gate;
  let gFalse = &FALSE as &Gate;
  for n in range(0,std::u8::MAX) {
    let (nib1,nib2) = (n >> 4,n & 15);
    assert_eq!( nib1+nib2, from_result(four_bit_adder(ar,to_nib(nib1),to_nib(nib2),gFalse)) );
    assert_eq!(nib1+nib2+1,from_result(four_bit_adder(ar,to_nib(nib1),to_nib(nib2),gTrue )) );
  }
}
<commit_msg>Update four_bit_adder.rs<commit_after>// Implements http://rosettacode.org/wiki/Four_bit_adder
extern crate arena;

use arena::Arena;

static TRUE: bool = true;
static FALSE: bool = false;

trait Gate {
  fn get(&self) -> bool;
}

impl Gate for bool {
  fn get(&self) -> bool {
    *self
  }
}

struct NotGate<'a>{
  inp: &'a Gate,
}

impl<'a> Gate for NotGate<'a> {
 fn get(&self) -> bool {
    !self.inp.get()
  }
}

struct OrGate<'a>{
  inp1: &'a Gate,
  inp2: &'a Gate,
}

impl<'a> Gate for OrGate<'a> {
  fn get(&self) -> bool {
    self.inp1.get() || self.inp2.get()
  }
}

struct AndGate<'a>{
  inp1: &'a Gate,
  inp2: &'a Gate,
}

impl<'a> Gate for AndGate<'a> {
  fn get(&self) -> bool {
    self.inp1.get() && self.inp2.get()
  }
}

fn not<'a>(ar: &'a Arena, inp: &'a Gate) -> &'a Gate {
  ar.alloc(||NotGate{inp:inp}) as &'a Gate
}

fn or<'a>(ar: &'a Arena, inp1: &'a Gate, inp2: &'a Gate) -> &'a Gate {
  ar.alloc(||OrGate{inp1:inp1,inp2:inp2}) as &'a Gate
}

fn and<'a>(ar: &'a Arena, inp1: &'a Gate, inp2: &'a Gate) -> &'a Gate {
  ar.alloc(||AndGate{inp1:inp1,inp2:inp2}) as &'a Gate
}

fn xor<'a>(ar: &'a Arena, inp1: &'a Gate, inp2: &'a Gate) -> &'a Gate {
  or(ar,and(ar,inp1,not(ar,inp2)),and(ar,not(ar,inp1),inp2))
}

fn half_add<'a>(ar: &'a Arena, inp1: &'a Gate, inp2: &'a Gate) -> (&'a Gate, &'a Gate) {
  (xor(ar,inp1,inp2),and(ar,inp1,inp2))
}

fn full_add<'a>(ar: &'a Arena, inp1: &'a Gate, inp2: &'a Gate, inp3: &'a Gate)
    -> (&'a Gate, &'a Gate) {
  let (ha1_s,ha1_c) = half_add(ar,inp1, inp3);
  let (ha2_s,ha2_c) = half_add(ar,ha1_s,inp2);
  (ha2_s,or(ar,ha1_c,ha2_c))
}

type Nibble<'a> = (&'a Gate, &'a Gate, &'a Gate, &'a Gate);

fn four_bit_adder<'a>(ar: &'a Arena, a: Nibble<'a>, b: Nibble<'a>, ci: &'a Gate)
    -> (&'a Gate, Nibble<'a>) {
  let (a4,a3,a2,a1) = a;
  let (b4,b3,b2,b1) = b;
  let (fa1_s,fa1_c) = full_add(ar,a1,b1,ci);
  let (fa2_s,fa2_c) = full_add(ar,a2,b2,fa1_c);
  let (fa3_s,fa3_c) = full_add(ar,a3,b3,fa2_c);
  let (fa4_s,fa4_c) = full_add(ar,a4,b4,fa3_c);
  (fa4_c, (fa4_s,fa3_s,fa2_s,fa1_s))
}

#[cfg(not(test))]
fn main() {
  fn show_nibble<'a>(nib: Nibble<'a>) -> (bool,bool,bool,bool) {
    let (n4,n3,n2,n1) = nib;
    (n4.get(),n3.get(),n2.get(),n1.get())
  }
  fn show_result<'a>((c,nib): (&'a Gate,Nibble<'a>)) -> (bool,(bool,bool,bool,bool)) {
    (c.get(), show_nibble(nib))
  }
  let ref ar = Arena::new();
  let gTrue = &TRUE as &Gate;
  let gFalse = &FALSE as &Gate;
  let inp1 = (gTrue,gFalse,gTrue,gTrue);
  let inp2 = (gFalse,gTrue,gTrue,gFalse);
  let (oflow,res) = show_result(four_bit_adder(ar,inp1,inp2,gFalse));
  println!("{} + {} = {}, overflow: {}", show_nibble(inp1), show_nibble(inp2), res, oflow);
}

#[test]
fn test_not() {
  let ref ar = Arena::new();
  let gTrue = &TRUE as &Gate;
  let gFalse = &FALSE as &Gate;
  assert_eq!(true, not(ar,gFalse).get());
  assert_eq!(false, not(ar,gTrue).get());
}

#[test]
fn test_or() {
  let ref ar = Arena::new();
  let gTrue = &TRUE as &Gate;
  let gFalse = &FALSE as &Gate;
  assert_eq!(false, or(ar,gFalse,gFalse).get());
  assert_eq!(true, or(ar,gTrue,gFalse).get());
  assert_eq!(true, or(ar,gFalse,gTrue).get());
  assert_eq!(true, or(ar,gTrue,gTrue).get());
}

#[test]
fn test_and() {
  let ref ar = Arena::new();
  let gTrue = &TRUE as &Gate;
  let gFalse = &FALSE as &Gate;
  assert_eq!(false, and(ar,gFalse,gFalse).get());
  assert_eq!(false, and(ar,gFalse,gTrue).get());
  assert_eq!(false, and(ar,gTrue,gFalse).get());
  assert_eq!(true, and(ar,gTrue,gTrue).get());
}

#[test]
fn test_xor() {
  let ref ar = Arena::new();
  let gTrue = &TRUE as &Gate;
  let gFalse = &FALSE as &Gate;
  assert_eq!(false, xor(ar,gFalse,gFalse).get());
  assert_eq!(true, xor(ar,gFalse,gTrue).get());
  assert_eq!(true, xor(ar,gTrue,gFalse).get());
  assert_eq!(false, xor(ar,gTrue,gTrue).get());
}

#[test]
fn test_full_add() {
  let ref ar = Arena::new();
  let gTrue = &TRUE as &Gate;
  let gFalse = &FALSE as &Gate;
  fn eval((a,b): (&Gate,&Gate)) -> (bool,bool) { (a.get(),b.get()) }
  assert_eq!( (false,false), eval(full_add(ar,gFalse,gFalse,gFalse)) );
  assert_eq!( (true,false), eval(full_add(ar,gFalse,gFalse,gTrue)) );
  assert_eq!( (true,false), eval(full_add(ar,gFalse,gTrue,gFalse)) );
  assert_eq!( (true,false), eval(full_add(ar,gTrue,gFalse,gFalse)) );
  assert_eq!( (false,true), eval(full_add(ar,gFalse,gTrue,gTrue)) );
  assert_eq!( (false,true), eval(full_add(ar,gTrue,gFalse,gTrue)) );
  assert_eq!( (false,true), eval(full_add(ar,gTrue,gTrue,gFalse)) );
  assert_eq!( (true,true), eval(full_add(ar,gTrue,gTrue,gTrue)) );
}

#[test]
fn test_full_add4() {
 fn to_nib(n:u8)->(Nibble){
    let (gt,gf) = (&TRUE as &Gate, &FALSE as &Gate);
    (
      if n&8!=0 {gt} else {gf},
      if n&4!=0 {gt} else {gf},
      if n&2!=0 {gt} else {gf},
      if n&1!=0 {gt} else {gf}
    )
  }
  fn from_result<'a>((c,(n4,n3,n2,n1)): (&'a Gate,Nibble<'a>)) -> u8 {
    let mut n = 0u8;
    if c.get()  { n += 16};
    if n4.get() { n += 8 };
    if n3.get() { n += 4 };
    if n2.get() { n += 2 };
    if n1.get() { n += 1 };
    n
  }
  let ref ar = Arena::new();
  let gTrue = &TRUE as &Gate;
  let gFalse = &FALSE as &Gate;
  for n in range(0,std::u8::MAX) {
    let (nib1,nib2) = (n >> 4,n & 15);
    assert_eq!( nib1+nib2, from_result(four_bit_adder(ar,to_nib(nib1),to_nib(nib2),gFalse)) );
    assert_eq!(nib1+nib2+1,from_result(four_bit_adder(ar,to_nib(nib1),to_nib(nib2),gTrue )) );
  }
}
<|endoftext|>


<commit_before>use super::ast as pc_ast;
use crate::base::parser::{get_buffer, expect_token};
use crate::js::parser::parse as parse_js;
use crate::js::ast as js_ast;
use crate::base::tokenizer::{Token, Tokenizer};
use crate::css::parser::parse as parse_css;

/*

void elements: [ 'area',
  'base',
  'basefont',
  'bgsound',
  'br',
  'col',
  'command',
  'embed',
  'frame',
  'hr',
  'image',
  'img',
  'input',
  'isindex',
  'keygen',
  'link',
  'menuitem',
  'meta',
  'nextid',
  'param',
  'source',
  'track',
  'wbr' ]
*/

pub fn parse<'a>(source: &'a str) -> Result<pc_ast::Node, &'static str> {
  parse_fragment(&mut Tokenizer::new(source))
}

fn parse_fragment<'a>(tokenizer: &mut Tokenizer<'a>) -> Result<pc_ast::Node, &'static str> {
  let mut children: Vec<pc_ast::Node> = vec![];

  while !tokenizer.is_eof() {
    children.push(parse_node(tokenizer)?);
    tokenizer.eat_whitespace();
  }

  if children.len() == 1 {
    Ok(children.pop().unwrap())
  } else {
    Ok(pc_ast::Node::Fragment(pc_ast::Fragment { children }))
  }
}

fn parse_node<'a>(tokenizer: &mut Tokenizer<'a>) -> Result<pc_ast::Node, &'static str> {
  tokenizer.eat_whitespace();
  let pos = tokenizer.pos;
  let token = tokenizer.next()?;
  match token {
    Token::SlotOpen => { parse_slot(tokenizer) },
    Token::LessThan => { parse_element(tokenizer) },
    Token::HtmlCommentOpen => {
      let buffer = get_buffer(tokenizer, |tokenizer| {
        let tok = tokenizer.peek(1)?;
        Ok(tok != Token::HtmlCommentClose)
      })?.to_string();
      tokenizer.next()?; // eat -->
      Ok(pc_ast::Node::Comment(pc_ast::ValueObject { value: buffer }))
    },
    Token::BlockOpen => {
      parse_block(tokenizer)
    }
    _ => {
      tokenizer.pos = pos;
      Ok(pc_ast::Node::Text(pc_ast::ValueObject {
        value: get_buffer(tokenizer, |tokenizer| {
          let tok = tokenizer.peek(1)?;
          Ok(
            tok != Token::SlotOpen &&
            tok != Token::LessThan &&
            tok != Token::CloseTag &&
            tok != Token::HtmlCommentOpen &&
            tok != Token::BlockOpen &&
            tok != Token::BlockClose
          )
        })?.to_string()
      }))
    }
  }
}

fn parse_slot<'a>(tokenizer: &mut Tokenizer<'a>) -> Result<pc_ast::Node, &'static str> {
  let script = parse_slot_script(tokenizer)?;
  Ok(pc_ast::Node::Slot(script))
}

fn parse_slot_script<'a>(tokenizer: &mut Tokenizer<'a>) -> Result<js_ast::Statement, &'static str> {
  let script = get_buffer(tokenizer, |tokenizer| { Ok(tokenizer.peek(1)? != Token::SlotClose) })?.to_string();
  expect_token(tokenizer.next()?, Token::SlotClose)?;
  parse_js(script.as_str())
}

fn parse_element<'a>(tokenizer: &mut Tokenizer<'a>) -> Result<pc_ast::Node, &'static str> {
  let tag_name = parse_tag_name(tokenizer)?;
  let attributes = parse_attributes(tokenizer)?;

  if tag_name == "style" {
    parse_next_style_element_parts(attributes, tokenizer)
  } else {
    parse_next_basic_element_parts(tag_name, attributes, tokenizer)
  }
}

fn parse_next_basic_element_parts<'a>(tag_name: String, attributes: Vec<pc_ast::Attribute>, tokenizer: &mut Tokenizer<'a>) -> Result<pc_ast::Node, &'static str> {
  let mut children: Vec<pc_ast::Node> = vec![];

  tokenizer.eat_whitespace();

  match tokenizer.next()? {
    Token::SelfCloseTag => {
    },
    Token::GreaterThan => {
      tokenizer.eat_whitespace();
      while !tokenizer.is_eof() && tokenizer.peek(1)? != Token::CloseTag {
        children.push(parse_node(tokenizer)?);
        tokenizer.eat_whitespace();
      }

      expect_token(tokenizer.next()?, Token::CloseTag)?;
      parse_tag_name(tokenizer)?;
      expect_token(tokenizer.next()?, Token::GreaterThan)?;
    },
    _ => {
      return Err("Unexpected token")
    }
  }

  Ok(pc_ast::Node::Element(pc_ast::Element {
    tag_name,
    attributes,
    children
  }))
}

fn parse_block<'a>(tokenizer: &mut Tokenizer<'a>) -> Result<pc_ast::Node, &'static str> {
  let token = tokenizer.next()?; // eat {{# or {{/
  if let Token::Word(keyword) = token {
    match keyword {
      "if" => parse_if_block(tokenizer),
      _ => {
        Err("Unexpected token")
      }
    }
  } else {
    Err("Unxpected token")
  }
}

fn parse_if_block<'a>(tokenizer: &mut Tokenizer<'a>) -> Result<pc_ast::Node, &'static str> {
  Ok(pc_ast::Node::Block(pc_ast::Block::Conditional(
    parse_pass_fail_block(tokenizer)?
  )))
}

fn parse_pass_fail_block<'a>(tokenizer: &mut Tokenizer<'a>) -> Result<pc_ast::ConditionalBlock, &'static str> {
  tokenizer.eat_whitespace();
  let js_source = get_buffer(tokenizer, |tokenizer| { Ok(tokenizer.peek(1)? != Token::SlotClose) })?;
  let condition = parse_js(js_source)?;
  expect_token(tokenizer.next()?, Token::SlotClose)?;

  let node = parse_block_children(tokenizer)?;
  let fail = parse_else_block(tokenizer)?;

  Ok(pc_ast::ConditionalBlock::PassFailBlock(
    pc_ast::PassFailBlock {
      condition,
      node,
      fail,
    }
  ))
}

fn parse_block_children<'a>(tokenizer: &mut Tokenizer<'a>) -> Result<Option<Box<pc_ast::Node>>, &'static str> {

  let mut children = vec![];

  // TODO - we don't really want this since whitespace technically renders. Though, right
  // now it's not handled.
  tokenizer.eat_whitespace();

  while !tokenizer.is_eof() && tokenizer.peek(1)? != Token::BlockClose {
    children.push(parse_node(tokenizer)?);
    tokenizer.eat_whitespace();
  }

  let node = if children.len() == 0 {
    None
  } else if children.len() == 1 {
    Some(Box::new(children.pop().unwrap()))
  } else {
    Some(Box::new(pc_ast::Node::Fragment(pc_ast::Fragment {
      children
    })))
  };

  Ok(node)
}

fn parse_else_block<'a>(tokenizer: &mut Tokenizer<'a>) -> Result<Option<Box<pc_ast::ConditionalBlock>>, &'static str> {
  tokenizer.eat_whitespace();
  expect_token(tokenizer.next()?, Token::BlockClose)?;
  tokenizer.eat_whitespace();
  match tokenizer.next()? {
    Token::Word(value) => {
      match value {
        "else" => {
          tokenizer.eat_whitespace();
          match tokenizer.next()? {
            Token::Word(value2) => {
              if value2 == "if" {
                Ok(Some(Box::new(parse_pass_fail_block(tokenizer)?)))
              } else {
                Err("Unexpected token")
              }
            },
            Token::SlotClose => {
              Ok(Some(Box::new(parse_final_condition_block(tokenizer)?)))
            }
            _ => {
              Err("Unexpected token")
            }
          }
        },
        _ => {
          Err("Unexpected token")
        }
      }
    },
    Token::SlotClose => {
      Ok(None)
    },
    _ => {
      Err("Unexpected token")
    }
  }
}

fn parse_final_condition_block<'a>(tokenizer: &mut Tokenizer<'a>) -> Result<pc_ast::ConditionalBlock, &'static str> {
  let node =  parse_block_children(tokenizer)?;
  expect_token(tokenizer.next()?, Token::BlockClose)?;
  expect_token(tokenizer.next()?, Token::SlotClose)?;
  Ok(pc_ast::ConditionalBlock::FinalBlock(pc_ast::FinalBlock {
    node
  }))
}

fn parse_next_style_element_parts<'a>(attributes: Vec<pc_ast::Attribute>, tokenizer: &mut Tokenizer<'a>) -> Result<pc_ast::Node, &'static str> {
  tokenizer.next()?; // eat >
  let sheet_source = get_buffer(tokenizer, |tokenizer| {
    Ok(tokenizer.peek(1)? != Token::CloseTag && tokenizer.peek(2)? != Token::Word("style"))
  })?;

  // TODO - assert tokens equal these
  expect_token(tokenizer.next()?, Token::CloseTag)?; // eat </
  expect_token(tokenizer.next()?, Token::Word("style"))?; // eat style
  expect_token(tokenizer.next()?, Token::GreaterThan)?; // eat >

  Ok(pc_ast::Node::StyleElement(pc_ast::StyleElement {
    attributes,
    sheet: parse_css(&sheet_source)?
  }))
}

fn parse_tag_name<'a>(tokenizer: &mut Tokenizer<'a>) -> Result<String, &'static str> {
  Ok(get_buffer(tokenizer, |tokenizer| { Ok(!matches!(tokenizer.peek(1)?, Token::Whitespace | Token::GreaterThan | Token::Equals)) })?.to_string())
}

fn parse_attributes<'a>(tokenizer: &mut Tokenizer<'a>) -> Result<Vec<pc_ast::Attribute>, &'static str> {

  let mut attributes: Vec<pc_ast::Attribute> = vec![];

  loop {
    tokenizer.eat_whitespace();
    match tokenizer.peek(1)? {
      Token::SelfCloseTag | Token::GreaterThan => break,
      _ => {
        attributes.push(parse_attribute(tokenizer)?);
      }
    }
  }

  Ok(attributes)
}

fn parse_attribute<'a>(tokenizer: &mut Tokenizer<'a>) -> Result<pc_ast::Attribute, &'static str> {
  if tokenizer.peek(1)? == Token::SlotOpen {
    tokenizer.next()?;
    parse_shorthand_attribute(tokenizer)
  } else {
    parse_key_value_attribute(tokenizer)
  }
}

fn parse_shorthand_attribute<'a>(tokenizer: &mut Tokenizer<'a>) -> Result<pc_ast::Attribute, &'static str> {
  let reference = parse_slot_script(tokenizer)?;

  // TODO - expect script to be reference with path.length === 1

  Ok(pc_ast::Attribute::ShorthandAttribute(pc_ast::ShorthandAttribute {
    reference,
  }))
}

fn parse_key_value_attribute<'a>(tokenizer: &mut Tokenizer<'a>) -> Result<pc_ast::Attribute, &'static str> {

  let name = parse_tag_name(tokenizer)?;
  let mut value = None;

  if tokenizer.peek(1)? == Token::Equals {
    tokenizer.next()?; // eat =
    value = Some(parse_attribute_value(tokenizer)?);
  }

  Ok(pc_ast::Attribute::KeyValueAttribute(pc_ast::KeyValueAttribute {
    name,
    value
  }))
}

fn parse_attribute_value<'a>(tokenizer: &mut Tokenizer<'a>) -> Result<pc_ast::AttributeValue, &'static str> {
  match tokenizer.peek(1)? {
    Token::SingleQuote | Token::DoubleQuote => parse_string(tokenizer),
    Token::SlotOpen => parse_attribute_slot(tokenizer),
    _ => Err("Unexpected token")
  }
}

fn parse_attribute_slot<'a>(tokenizer: &mut Tokenizer<'a>) -> Result<pc_ast::AttributeValue, &'static str> {
  expect_token(tokenizer.next()?, Token::SlotOpen)?;
  let script = parse_slot_script(tokenizer)?;
  Ok(pc_ast::AttributeValue::Slot(script))
}

fn parse_string<'a>(tokenizer: &mut Tokenizer<'a>) -> Result<pc_ast::AttributeValue, &'static str> {
  let quote = tokenizer.next()?;
  let value = get_buffer(tokenizer, |tokenizer| { Ok(tokenizer.peek(1)? != quote) })?.to_string();
  tokenizer.next()?; // eat
  Ok(pc_ast::AttributeValue::String(pc_ast::AttributeStringValue { value }))
}

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

  #[test]
  fn can_parse_various_nodes() {

    let cases = [

      // text blocks
      "text",

      // comments
      "ab ",

      // slots
      "{{ok}}",

      // elements
      "

",
      "

",
      "

",
      "

",

      "

          c {{block}} d {{block}}

          color {{block}}

",

      // mixed elements
    ];

    for i in 0..cases.len() {
      let case = cases[i];

      // TODO - strip whitespace
      let expr = parse(case).unwrap();
      assert_eq!(expr.to_string().replace("\n", "").replace(" ", ""), case.replace("\n", "").replace(" ", ""));
    }
  }
}<commit_msg>use more peek<commit_after>use super::ast as pc_ast;
use crate::base::parser::{get_buffer, expect_token};
use crate::js::parser::parse as parse_js;
use crate::js::ast as js_ast;
use crate::base::tokenizer::{Token, Tokenizer};
use crate::css::parser::parse as parse_css;

/*

void elements: [ 'area',
  'base',
  'basefont',
  'bgsound',
  'br',
  'col',
  'command',
  'embed',
  'frame',
  'hr',
  'image',
  'img',
  'input',
  'isindex',
  'keygen',
  'link',
  'menuitem',
  'meta',
  'nextid',
  'param',
  'source',
  'track',
  'wbr' ]
*/

pub fn parse<'a>(source: &'a str) -> Result<pc_ast::Node, &'static str> {
  parse_fragment(&mut Tokenizer::new(source))
}

fn parse_fragment<'a>(tokenizer: &mut Tokenizer<'a>) -> Result<pc_ast::Node, &'static str> {
  let mut children: Vec<pc_ast::Node> = vec![];

  while !tokenizer.is_eof() {
    children.push(parse_node(tokenizer)?);
    tokenizer.eat_whitespace();
  }

  if children.len() == 1 {
    Ok(children.pop().unwrap())
  } else {
    Ok(pc_ast::Node::Fragment(pc_ast::Fragment { children }))
  }
}

fn parse_node<'a>(tokenizer: &mut Tokenizer<'a>) -> Result<pc_ast::Node, &'static str> {
  tokenizer.eat_whitespace();
  let token = tokenizer.peek(1)?;
  match token {
    Token::SlotOpen => { parse_slot(tokenizer) },
    Token::LessThan => { parse_element(tokenizer) },
    Token::HtmlCommentOpen => {
      tokenizer.next()?; // eat HTML comment open
      let buffer = get_buffer(tokenizer, |tokenizer| {
        let tok = tokenizer.peek(1)?;
        Ok(tok != Token::HtmlCommentClose)
      })?.to_string();
      tokenizer.next()?; // eat -->
      Ok(pc_ast::Node::Comment(pc_ast::ValueObject { value: buffer }))
    },
    Token::BlockOpen => {
      parse_block(tokenizer)
    }
    _ => {
      Ok(pc_ast::Node::Text(pc_ast::ValueObject {
        value: get_buffer(tokenizer, |tokenizer| {
          let tok = tokenizer.peek(1)?;
          Ok(
            tok != Token::SlotOpen &&
            tok != Token::LessThan &&
            tok != Token::CloseTag &&
            tok != Token::HtmlCommentOpen &&
            tok != Token::BlockOpen &&
            tok != Token::BlockClose
          )
        })?.to_string()
      }))
    }
  }
}

fn parse_slot<'a>(tokenizer: &mut Tokenizer<'a>) -> Result<pc_ast::Node, &'static str> {
  let script = parse_slot_script(tokenizer)?;
  Ok(pc_ast::Node::Slot(script))
}

fn parse_slot_script<'a>(tokenizer: &mut Tokenizer<'a>) -> Result<js_ast::Statement, &'static str> {
  expect_token(tokenizer.next()?, Token::SlotOpen)?;
  let script = get_buffer(tokenizer, |tokenizer| { Ok(tokenizer.peek(1)? != Token::SlotClose) })?.to_string();
  expect_token(tokenizer.next()?, Token::SlotClose)?;
  parse_js(script.as_str())
}

fn parse_element<'a>(tokenizer: &mut Tokenizer<'a>) -> Result<pc_ast::Node, &'static str> {
  expect_token(tokenizer.next()?, Token::LessThan)?;
  let tag_name = parse_tag_name(tokenizer)?;
  let attributes = parse_attributes(tokenizer)?;

  if tag_name == "style" {
    parse_next_style_element_parts(attributes, tokenizer)
  } else {
    parse_next_basic_element_parts(tag_name, attributes, tokenizer)
  }
}

fn parse_next_basic_element_parts<'a>(tag_name: String, attributes: Vec<pc_ast::Attribute>, tokenizer: &mut Tokenizer<'a>) -> Result<pc_ast::Node, &'static str> {
  let mut children: Vec<pc_ast::Node> = vec![];

  tokenizer.eat_whitespace();

  match tokenizer.next()? {
    Token::SelfCloseTag => {
    },
    Token::GreaterThan => {
      tokenizer.eat_whitespace();
      while !tokenizer.is_eof() && tokenizer.peek(1)? != Token::CloseTag {
        children.push(parse_node(tokenizer)?);
        tokenizer.eat_whitespace();
      }

      expect_token(tokenizer.next()?, Token::CloseTag)?;
      parse_tag_name(tokenizer)?;
      expect_token(tokenizer.next()?, Token::GreaterThan)?;
    },
    _ => {
      return Err("Unexpected token")
    }
  }

  Ok(pc_ast::Node::Element(pc_ast::Element {
    tag_name,
    attributes,
    children
  }))
}

fn parse_block<'a>(tokenizer: &mut Tokenizer<'a>) -> Result<pc_ast::Node, &'static str> {
  expect_token(tokenizer.next()?, Token::BlockOpen)?;
  let token = tokenizer.next()?; // eat {{# or {{/
  if let Token::Word(keyword) = token {
    match keyword {
      "if" => parse_if_block(tokenizer),
      _ => {
        Err("Unexpected token")
      }
    }
  } else {
    Err("Unxpected token")
  }
}

fn parse_if_block<'a>(tokenizer: &mut Tokenizer<'a>) -> Result<pc_ast::Node, &'static str> {
  Ok(pc_ast::Node::Block(pc_ast::Block::Conditional(
    parse_pass_fail_block(tokenizer)?
  )))
}

fn parse_pass_fail_block<'a>(tokenizer: &mut Tokenizer<'a>) -> Result<pc_ast::ConditionalBlock, &'static str> {
  tokenizer.eat_whitespace();
  let js_source = get_buffer(tokenizer, |tokenizer| { Ok(tokenizer.peek(1)? != Token::SlotClose) })?;
  let condition = parse_js(js_source)?;
  expect_token(tokenizer.next()?, Token::SlotClose)?;
  let node = parse_block_children(tokenizer)?;
  let fail = parse_else_block(tokenizer)?;

  Ok(pc_ast::ConditionalBlock::PassFailBlock(
    pc_ast::PassFailBlock {
      condition,
      node,
      fail,
    }
  ))
}

fn parse_block_children<'a>(tokenizer: &mut Tokenizer<'a>) -> Result<Option<Box<pc_ast::Node>>, &'static str> {

  let mut children = vec![];

  // TODO - we don't really want this since whitespace technically renders. Though, right
  // now it's not handled.
  tokenizer.eat_whitespace();

  while !tokenizer.is_eof() && tokenizer.peek(1)? != Token::BlockClose {
    children.push(parse_node(tokenizer)?);
    tokenizer.eat_whitespace();
  }

  let node = if children.len() == 0 {
    None
  } else if children.len() == 1 {
    Some(Box::new(children.pop().unwrap()))
  } else {
    Some(Box::new(pc_ast::Node::Fragment(pc_ast::Fragment {
      children
    })))
  };

  Ok(node)
}

fn parse_else_block<'a>(tokenizer: &mut Tokenizer<'a>) -> Result<Option<Box<pc_ast::ConditionalBlock>>, &'static str> {
  tokenizer.eat_whitespace();
  expect_token(tokenizer.next()?, Token::BlockClose)?;
  tokenizer.eat_whitespace();
  match tokenizer.next()? {
    Token::Word(value) => {
      match value {
        "else" => {
          tokenizer.eat_whitespace();
          match tokenizer.next()? {
            Token::Word(value2) => {
              if value2 == "if" {
                Ok(Some(Box::new(parse_pass_fail_block(tokenizer)?)))
              } else {
                Err("Unexpected token")
              }
            },
            Token::SlotClose => {
              Ok(Some(Box::new(parse_final_condition_block(tokenizer)?)))
            }
            _ => {
              Err("Unexpected token")
            }
          }
        },
        _ => {
          Err("Unexpected token")
        }
      }
    },
    Token::SlotClose => {
      Ok(None)
    },
    _ => {
      Err("Unexpected token")
    }
  }
}

fn parse_final_condition_block<'a>(tokenizer: &mut Tokenizer<'a>) -> Result<pc_ast::ConditionalBlock, &'static str> {
  let node =  parse_block_children(tokenizer)?;
  expect_token(tokenizer.next()?, Token::BlockClose)?;
  expect_token(tokenizer.next()?, Token::SlotClose)?;
  Ok(pc_ast::ConditionalBlock::FinalBlock(pc_ast::FinalBlock {
    node
  }))
}

fn parse_next_style_element_parts<'a>(attributes: Vec<pc_ast::Attribute>, tokenizer: &mut Tokenizer<'a>) -> Result<pc_ast::Node, &'static str> {
  tokenizer.next()?; // eat >
  let sheet_source = get_buffer(tokenizer, |tokenizer| {
    Ok(tokenizer.peek(1)? != Token::CloseTag && tokenizer.peek(2)? != Token::Word("style"))
  })?;

  // TODO - assert tokens equal these
  expect_token(tokenizer.next()?, Token::CloseTag)?; // eat </
  expect_token(tokenizer.next()?, Token::Word("style"))?; // eat style
  expect_token(tokenizer.next()?, Token::GreaterThan)?; // eat >

  Ok(pc_ast::Node::StyleElement(pc_ast::StyleElement {
    attributes,
    sheet: parse_css(&sheet_source)?
  }))
}

fn parse_tag_name<'a>(tokenizer: &mut Tokenizer<'a>) -> Result<String, &'static str> {
  Ok(get_buffer(tokenizer, |tokenizer| { Ok(!matches!(tokenizer.peek(1)?, Token::Whitespace | Token::GreaterThan | Token::Equals)) })?.to_string())
}

fn parse_attributes<'a>(tokenizer: &mut Tokenizer<'a>) -> Result<Vec<pc_ast::Attribute>, &'static str> {

  let mut attributes: Vec<pc_ast::Attribute> = vec![];

  loop {
    tokenizer.eat_whitespace();
    match tokenizer.peek(1)? {
      Token::SelfCloseTag | Token::GreaterThan => break,
      _ => {
        attributes.push(parse_attribute(tokenizer)?);
      }
    }
  }

  Ok(attributes)
}

fn parse_attribute<'a>(tokenizer: &mut Tokenizer<'a>) -> Result<pc_ast::Attribute, &'static str> {
  if tokenizer.peek(1)? == Token::SlotOpen {
    parse_shorthand_attribute(tokenizer)
  } else {
    parse_key_value_attribute(tokenizer)
  }
}

fn parse_shorthand_attribute<'a>(tokenizer: &mut Tokenizer<'a>) -> Result<pc_ast::Attribute, &'static str> {
  let reference = parse_slot_script(tokenizer)?;

  // TODO - expect script to be reference with path.length === 1

  Ok(pc_ast::Attribute::ShorthandAttribute(pc_ast::ShorthandAttribute {
    reference,
  }))
}

fn parse_key_value_attribute<'a>(tokenizer: &mut Tokenizer<'a>) -> Result<pc_ast::Attribute, &'static str> {

  let name = parse_tag_name(tokenizer)?;
  let mut value = None;

  if tokenizer.peek(1)? == Token::Equals {
    tokenizer.next()?; // eat =
    value = Some(parse_attribute_value(tokenizer)?);
  }

  Ok(pc_ast::Attribute::KeyValueAttribute(pc_ast::KeyValueAttribute {
    name,
    value
  }))
}

fn parse_attribute_value<'a>(tokenizer: &mut Tokenizer<'a>) -> Result<pc_ast::AttributeValue, &'static str> {
  match tokenizer.peek(1)? {
    Token::SingleQuote | Token::DoubleQuote => parse_string(tokenizer),
    Token::SlotOpen => parse_attribute_slot(tokenizer),
    _ => Err("Unexpected token")
  }
}

fn parse_attribute_slot<'a>(tokenizer: &mut Tokenizer<'a>) -> Result<pc_ast::AttributeValue, &'static str> {
  expect_token(tokenizer.next()?, Token::SlotOpen)?;
  let script = parse_slot_script(tokenizer)?;
  Ok(pc_ast::AttributeValue::Slot(script))
}

fn parse_string<'a>(tokenizer: &mut Tokenizer<'a>) -> Result<pc_ast::AttributeValue, &'static str> {
  let quote = tokenizer.next()?;
  let value = get_buffer(tokenizer, |tokenizer| { Ok(tokenizer.peek(1)? != quote) })?.to_string();
  tokenizer.next()?; // eat
  Ok(pc_ast::AttributeValue::String(pc_ast::AttributeStringValue { value }))
}

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

  #[test]
  fn can_parse_various_nodes() {

    let cases = [

      // text blocks
      "text",

      // comments
      "ab ",

      // slots
      "{{ok}}",

      // elements
      "

",
      "

",
      "

",
      "

",

      "

          c {{block}} d {{block}}

          color {{block}}

",

      // mixed elements
    ];

    for i in 0..cases.len() {
      let case = cases[i];

      // TODO - strip whitespace
      let expr = parse(case).unwrap();
      assert_eq!(expr.to_string().replace("\n", "").replace(" ", ""), case.replace("\n", "").replace(" ", ""));
    }
  }
}<|endoftext|>