<commit_before>//
// imag - the personal information management suite for the commandline
// Copyright (C) 2015, 2016 Matthias Beyer <mail@beyermatthias.de> and contributors
//
// This library is free software; you can redistribute it and/or
// modify it under the terms of the GNU Lesser General Public
// License as published by the Free Software Foundation; version
// 2.1 of the License.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
//

use std::collections::BTreeMap;
use std::ops::{Deref, DerefMut};
use std::io::BufRead;
use std::result::Result as RResult;

use toml::Value;
use uuid::Uuid;

use task_hookrs::task::Task as TTask;
use task_hookrs::import::{import_task, import_tasks};

use libimagstore::store::{FileLockEntry, Store};
use libimagstore::storeid::{IntoStoreId, StoreIdIterator, StoreId};
use module_path::ModuleEntryPath;

use error::{TodoErrorKind as TEK, MapErrInto};
use result::Result;

/// Task struct containing a `FileLockEntry`
#[derive(Debug)]
pub struct Task<'a>(FileLockEntry<'a>);

impl<'a> Task<'a> {

    /// Concstructs a new `Task` with a `FileLockEntry`
    pub fn new(fle: FileLockEntry<'a>) -> Task<'a> {
        Task(fle)
    }

    pub fn import<R: BufRead>(store: &'a Store, mut r: R) -> Result<(Task<'a>, String, Uuid)> {
        let mut line = String::new();
        try!(r.read_line(&mut line).map_err_into(TEK::UTF8Error));
        import_task(&line.as_str())
            .map_err_into(TEK::ImportError)
            .and_then(|t| {
                let uuid = t.uuid().clone();
                t.into_task(store).map(|t| (t, line, uuid))
            })
    }

    /// Get a task from an import string. That is: read the imported string, get the UUID from it
    /// and try to load this UUID from store.
    ///
    /// Possible return values are:
    ///
    /// * Ok(Ok(Task))
    /// * Ok(Err(String)) - where the String is the String read from the `r` parameter
    /// * Err(_)          - where the error is an error that happened during evaluation
    ///
    pub fn get_from_import<R>(store: &'a Store, mut r: R) -> Result<RResult<Task<'a>, String>>
        where R: BufRead
    {
        let mut line = String::new();
        try!(r.read_line(&mut line).map_err_into(TEK::UTF8Error));
        Task::get_from_string(store, line)
    }

    /// Get a task from a String. The String is expected to contain the JSON-representation of the
    /// Task to get from the store (only the UUID really matters in this case)
    ///
    /// For an explanation on the return values see `Task::get_from_import()`.
    pub fn get_from_string(store: &'a Store, s: String) -> Result<RResult<Task<'a>, String>> {
        import_task(s.as_str())
            .map_err_into(TEK::ImportError)
            .map(|t| t.uuid().clone())
            .and_then(|uuid| Task::get_from_uuid(store, uuid))
            .and_then(|o| match o {
                None    => Ok(Err(s)),
                Some(t) => Ok(Ok(t)),
            })
    }

    /// Get a task from an UUID.
    ///
    /// If there is no task with this UUID, this returns `Ok(None)`.
    pub fn get_from_uuid(store: &'a Store, uuid: Uuid) -> Result<Option<Task<'a>>> {
        ModuleEntryPath::new(format!("taskwarrior/{}", uuid))
            .into_storeid()
            .and_then(|store_id| store.get(store_id))
            .map(|o| o.map(Task::new))
            .map_err_into(TEK::StoreError)
    }

    /// Same as Task::get_from_import() but uses Store::retrieve() rather than Store::get(), to
    /// implicitely create the task if it does not exist.
    pub fn retrieve_from_import<R: BufRead>(store: &'a Store, mut r: R) -> Result<Task<'a>> {
        let mut line = String::new();
        try!(r.read_line(&mut line).map_err_into(TEK::UTF8Error));
        Task::retrieve_from_string(store, line)
    }

    /// Retrieve a task from a String. The String is expected to contain the JSON-representation of
    /// the Task to retrieve from the store (only the UUID really matters in this case)
    pub fn retrieve_from_string(store: &'a Store, s: String) -> Result<Task<'a>> {
        Task::get_from_string(store, s)
            .and_then(|opt| match opt {
                Ok(task)    => Ok(task),
                Err(string) => import_task(string.as_str())
                    .map_err_into(TEK::ImportError)
                    .and_then(|t| t.into_task(store)),
            })
    }

    pub fn delete_by_imports<R: BufRead>(store: &Store, r: R) -> Result<()> {
        use serde_json::ser::to_string as serde_to_string;
        use task_hookrs::status::TaskStatus;

        for (counter, res_ttask) in import_tasks(r).into_iter().enumerate() {
            match res_ttask {
                Ok(ttask) => {
                    if counter % 2 == 1 {
                        // Only every second task is needed, the first one is the
                        // task before the change, and the second one after
                        // the change. The (maybe modified) second one is
                        // expected by taskwarrior.
                        match serde_to_string(&ttask).map_err_into(TEK::ImportError) {
                            // use println!() here, as we talk with TW
                            Ok(val) => println!("{}", val),
                            Err(e)  => return Err(e),
                        }

                        // Taskwarrior does not have the concept of deleted tasks, but only modified
                        // ones.
                        //
                        // Here we check if the status of a task is deleted and if yes, we delete it
                        // from the store.
                        if *ttask.status() == TaskStatus::Deleted {
                            match Task::delete_by_uuid(store, *ttask.uuid()) {
                                Ok(_)  => info!("Deleted task {}", *ttask.uuid()),
                                Err(e) => return Err(e),
                            }
                        }
                    } // end if c % 2
                },
                Err(e) => return Err(e).map_err_into(TEK::ImportError),
            }
        }
        Ok(())
    }

    pub fn delete_by_uuid(store: &Store, uuid: Uuid) -> Result<()> {
        ModuleEntryPath::new(format!("taskwarrior/{}", uuid))
            .into_storeid()
            .and_then(|id| store.delete(id))
            .map_err_into(TEK::StoreError)
    }

    pub fn all_as_ids(store: &Store) -> Result<StoreIdIterator> {
        store.retrieve_for_module("todo/taskwarrior")
            .map_err_into(TEK::StoreError)
    }

    pub fn all(store: &Store) -> Result<TaskIterator> {
        Task::all_as_ids(store)
            .map(|iter| TaskIterator::new(store, iter))
    }

}

impl<'a> Deref for Task<'a> {
    type Target = FileLockEntry<'a>;

    fn deref(&self) -> &FileLockEntry<'a> {
        &self.0
    }

}

impl<'a> DerefMut for Task<'a> {

    fn deref_mut(&mut self) -> &mut FileLockEntry<'a> {
        &mut self.0
    }

}

/// A trait to get a `libimagtodo::task::Task` out of the implementing object.
pub trait IntoTask<'a> {

    /// # Usage
    /// ```ignore
    /// use std::io::stdin;
    ///
    /// use task_hookrs::task::Task;
    /// use task_hookrs::import::import;
    /// use libimagstore::store::{Store, FileLockEntry};
    ///
    /// if let Ok(task_hookrs_task) = import(stdin()) {
    ///     // Store is given at runtime
    ///     let task = task_hookrs_task.into_filelockentry(store);
    ///     println!("Task with uuid: {}", task.flentry.get_header().get("todo.uuid"));
    /// }
    /// ```
    fn into_task(self, store : &'a Store) -> Result<Task<'a>>;

}

impl<'a> IntoTask<'a> for TTask {

    fn into_task(self, store : &'a Store) -> Result<Task<'a>> {
        use toml_query::read::TomlValueReadExt;
        use toml_query::set::TomlValueSetExt;

        // Helper for toml_query::read::TomlValueReadExt::read() return value, which does only
        // return Result<T> instead of Result<Option<T>>, which is a real inconvenience.
        //
        let no_identifier = |e: &::toml_query::error::Error| -> bool {
            is_match!(e.kind(), &::toml_query::error::ErrorKind::IdentifierNotFoundInDocument(_))
        };

        let uuid     = self.uuid();
        ModuleEntryPath::new(format!("taskwarrior/{}", uuid))
            .into_storeid()
            .map_err_into(TEK::StoreIdError)
            .and_then(|id| {
                store.retrieve(id)
                    .map_err_into(TEK::StoreError)
                    .and_then(|mut fle| {
                        {
                            let hdr = fle.get_header_mut();
                            if try!(hdr.read("todo").map_err_into(TEK::StoreError)).is_none() {
                                try!(hdr
                                    .set("todo", Value::Table(BTreeMap::new()))
                                    .map_err_into(TEK::StoreError));
                            }

                            try!(hdr.set("todo.uuid", Value::String(format!("{}",uuid)))
                                 .map_err_into(TEK::StoreError));
                        }

                        // If none of the errors above have returned the function, everything is fine
                        Ok(Task::new(fle))
                    })
            })
    }

}

trait FromStoreId {
    fn from_storeid<'a>(&'a Store, StoreId) -> Result<Task<'a>>;
}

impl<'a> FromStoreId for Task<'a> {

    fn from_storeid<'b>(store: &'b Store, id: StoreId) -> Result<Task<'b>> {
        store.retrieve(id)
            .map_err_into(TEK::StoreError)
            .map(Task::new)
    }
}

pub struct TaskIterator<'a> {
    store: &'a Store,
    iditer: StoreIdIterator,
}

impl<'a> TaskIterator<'a> {

    pub fn new(store: &'a Store, iditer: StoreIdIterator) -> TaskIterator<'a> {
        TaskIterator {
            store: store,
            iditer: iditer,
        }
    }

}

impl<'a> Iterator for TaskIterator<'a> {
    type Item = Result<Task<'a>>;

    fn next(&mut self) -> Option<Result<Task<'a>>> {
        self.iditer.next().map(|id| Task::from_storeid(self.store, id))
    }
}

<commit_msg>Remove unused variable<commit_after>//
// imag - the personal information management suite for the commandline
// Copyright (C) 2015, 2016 Matthias Beyer <mail@beyermatthias.de> and contributors
//
// This library is free software; you can redistribute it and/or
// modify it under the terms of the GNU Lesser General Public
// License as published by the Free Software Foundation; version
// 2.1 of the License.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
//

use std::collections::BTreeMap;
use std::ops::{Deref, DerefMut};
use std::io::BufRead;
use std::result::Result as RResult;

use toml::Value;
use uuid::Uuid;

use task_hookrs::task::Task as TTask;
use task_hookrs::import::{import_task, import_tasks};

use libimagstore::store::{FileLockEntry, Store};
use libimagstore::storeid::{IntoStoreId, StoreIdIterator, StoreId};
use module_path::ModuleEntryPath;

use error::{TodoErrorKind as TEK, MapErrInto};
use result::Result;

/// Task struct containing a `FileLockEntry`
#[derive(Debug)]
pub struct Task<'a>(FileLockEntry<'a>);

impl<'a> Task<'a> {

    /// Concstructs a new `Task` with a `FileLockEntry`
    pub fn new(fle: FileLockEntry<'a>) -> Task<'a> {
        Task(fle)
    }

    pub fn import<R: BufRead>(store: &'a Store, mut r: R) -> Result<(Task<'a>, String, Uuid)> {
        let mut line = String::new();
        try!(r.read_line(&mut line).map_err_into(TEK::UTF8Error));
        import_task(&line.as_str())
            .map_err_into(TEK::ImportError)
            .and_then(|t| {
                let uuid = t.uuid().clone();
                t.into_task(store).map(|t| (t, line, uuid))
            })
    }

    /// Get a task from an import string. That is: read the imported string, get the UUID from it
    /// and try to load this UUID from store.
    ///
    /// Possible return values are:
    ///
    /// * Ok(Ok(Task))
    /// * Ok(Err(String)) - where the String is the String read from the `r` parameter
    /// * Err(_)          - where the error is an error that happened during evaluation
    ///
    pub fn get_from_import<R>(store: &'a Store, mut r: R) -> Result<RResult<Task<'a>, String>>
        where R: BufRead
    {
        let mut line = String::new();
        try!(r.read_line(&mut line).map_err_into(TEK::UTF8Error));
        Task::get_from_string(store, line)
    }

    /// Get a task from a String. The String is expected to contain the JSON-representation of the
    /// Task to get from the store (only the UUID really matters in this case)
    ///
    /// For an explanation on the return values see `Task::get_from_import()`.
    pub fn get_from_string(store: &'a Store, s: String) -> Result<RResult<Task<'a>, String>> {
        import_task(s.as_str())
            .map_err_into(TEK::ImportError)
            .map(|t| t.uuid().clone())
            .and_then(|uuid| Task::get_from_uuid(store, uuid))
            .and_then(|o| match o {
                None    => Ok(Err(s)),
                Some(t) => Ok(Ok(t)),
            })
    }

    /// Get a task from an UUID.
    ///
    /// If there is no task with this UUID, this returns `Ok(None)`.
    pub fn get_from_uuid(store: &'a Store, uuid: Uuid) -> Result<Option<Task<'a>>> {
        ModuleEntryPath::new(format!("taskwarrior/{}", uuid))
            .into_storeid()
            .and_then(|store_id| store.get(store_id))
            .map(|o| o.map(Task::new))
            .map_err_into(TEK::StoreError)
    }

    /// Same as Task::get_from_import() but uses Store::retrieve() rather than Store::get(), to
    /// implicitely create the task if it does not exist.
    pub fn retrieve_from_import<R: BufRead>(store: &'a Store, mut r: R) -> Result<Task<'a>> {
        let mut line = String::new();
        try!(r.read_line(&mut line).map_err_into(TEK::UTF8Error));
        Task::retrieve_from_string(store, line)
    }

    /// Retrieve a task from a String. The String is expected to contain the JSON-representation of
    /// the Task to retrieve from the store (only the UUID really matters in this case)
    pub fn retrieve_from_string(store: &'a Store, s: String) -> Result<Task<'a>> {
        Task::get_from_string(store, s)
            .and_then(|opt| match opt {
                Ok(task)    => Ok(task),
                Err(string) => import_task(string.as_str())
                    .map_err_into(TEK::ImportError)
                    .and_then(|t| t.into_task(store)),
            })
    }

    pub fn delete_by_imports<R: BufRead>(store: &Store, r: R) -> Result<()> {
        use serde_json::ser::to_string as serde_to_string;
        use task_hookrs::status::TaskStatus;

        for (counter, res_ttask) in import_tasks(r).into_iter().enumerate() {
            match res_ttask {
                Ok(ttask) => {
                    if counter % 2 == 1 {
                        // Only every second task is needed, the first one is the
                        // task before the change, and the second one after
                        // the change. The (maybe modified) second one is
                        // expected by taskwarrior.
                        match serde_to_string(&ttask).map_err_into(TEK::ImportError) {
                            // use println!() here, as we talk with TW
                            Ok(val) => println!("{}", val),
                            Err(e)  => return Err(e),
                        }

                        // Taskwarrior does not have the concept of deleted tasks, but only modified
                        // ones.
                        //
                        // Here we check if the status of a task is deleted and if yes, we delete it
                        // from the store.
                        if *ttask.status() == TaskStatus::Deleted {
                            match Task::delete_by_uuid(store, *ttask.uuid()) {
                                Ok(_)  => info!("Deleted task {}", *ttask.uuid()),
                                Err(e) => return Err(e),
                            }
                        }
                    } // end if c % 2
                },
                Err(e) => return Err(e).map_err_into(TEK::ImportError),
            }
        }
        Ok(())
    }

    pub fn delete_by_uuid(store: &Store, uuid: Uuid) -> Result<()> {
        ModuleEntryPath::new(format!("taskwarrior/{}", uuid))
            .into_storeid()
            .and_then(|id| store.delete(id))
            .map_err_into(TEK::StoreError)
    }

    pub fn all_as_ids(store: &Store) -> Result<StoreIdIterator> {
        store.retrieve_for_module("todo/taskwarrior")
            .map_err_into(TEK::StoreError)
    }

    pub fn all(store: &Store) -> Result<TaskIterator> {
        Task::all_as_ids(store)
            .map(|iter| TaskIterator::new(store, iter))
    }

}

impl<'a> Deref for Task<'a> {
    type Target = FileLockEntry<'a>;

    fn deref(&self) -> &FileLockEntry<'a> {
        &self.0
    }

}

impl<'a> DerefMut for Task<'a> {

    fn deref_mut(&mut self) -> &mut FileLockEntry<'a> {
        &mut self.0
    }

}

/// A trait to get a `libimagtodo::task::Task` out of the implementing object.
pub trait IntoTask<'a> {

    /// # Usage
    /// ```ignore
    /// use std::io::stdin;
    ///
    /// use task_hookrs::task::Task;
    /// use task_hookrs::import::import;
    /// use libimagstore::store::{Store, FileLockEntry};
    ///
    /// if let Ok(task_hookrs_task) = import(stdin()) {
    ///     // Store is given at runtime
    ///     let task = task_hookrs_task.into_filelockentry(store);
    ///     println!("Task with uuid: {}", task.flentry.get_header().get("todo.uuid"));
    /// }
    /// ```
    fn into_task(self, store : &'a Store) -> Result<Task<'a>>;

}

impl<'a> IntoTask<'a> for TTask {

    fn into_task(self, store : &'a Store) -> Result<Task<'a>> {
        use toml_query::read::TomlValueReadExt;
        use toml_query::set::TomlValueSetExt;

        let uuid     = self.uuid();
        ModuleEntryPath::new(format!("taskwarrior/{}", uuid))
            .into_storeid()
            .map_err_into(TEK::StoreIdError)
            .and_then(|id| {
                store.retrieve(id)
                    .map_err_into(TEK::StoreError)
                    .and_then(|mut fle| {
                        {
                            let hdr = fle.get_header_mut();
                            if try!(hdr.read("todo").map_err_into(TEK::StoreError)).is_none() {
                                try!(hdr
                                    .set("todo", Value::Table(BTreeMap::new()))
                                    .map_err_into(TEK::StoreError));
                            }

                            try!(hdr.set("todo.uuid", Value::String(format!("{}",uuid)))
                                 .map_err_into(TEK::StoreError));
                        }

                        // If none of the errors above have returned the function, everything is fine
                        Ok(Task::new(fle))
                    })
            })
    }

}

trait FromStoreId {
    fn from_storeid<'a>(&'a Store, StoreId) -> Result<Task<'a>>;
}

impl<'a> FromStoreId for Task<'a> {

    fn from_storeid<'b>(store: &'b Store, id: StoreId) -> Result<Task<'b>> {
        store.retrieve(id)
            .map_err_into(TEK::StoreError)
            .map(Task::new)
    }
}

pub struct TaskIterator<'a> {
    store: &'a Store,
    iditer: StoreIdIterator,
}

impl<'a> TaskIterator<'a> {

    pub fn new(store: &'a Store, iditer: StoreIdIterator) -> TaskIterator<'a> {
        TaskIterator {
            store: store,
            iditer: iditer,
        }
    }

}

impl<'a> Iterator for TaskIterator<'a> {
    type Item = Result<Task<'a>>;

    fn next(&mut self) -> Option<Result<Task<'a>>> {
        self.iditer.next().map(|id| Task::from_storeid(self.store, id))
    }
}

<|endoftext|>


<commit_before>use std::path::PathBuf;
use std::fs::File;
use std::error::Error;
use std::fs;
use std::fmt;
use std::io::{Seek, Read, SeekFrom};
use std::collections::HashMap;

#[derive(Debug)]
pub enum FsError {
    FileNotFound,
    InvalidDirectory
}
impl Error for FsError {
    fn description(&self) -> &str {
        match *self {
            FsError::FileNotFound => "the folder does not exist or cannot be read from",
            FsError::InvalidDirectory => "the specified directory is not a valid directory",
        }
    }
}
impl fmt::Display for FsError {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        match *self {
            FsError::FileNotFound => write!(f, "the folder specified could not be found or read from"),
            FsError::InvalidDirectory => write!(f, "the specified directory is not a valid directory"),
        }
    }
}

#[derive(Debug,Clone)]
pub enum CompressionType {
    /// The archive is not compressed and the raw data is the real data.
    None,
    /// The archive is compressed with the Bzip2 compression algorithm.
    Bzip2,
    /// The archive is compressed with (a slightly modified, headerless) Gzip codec.
    Gzip,
    /// The archive is compressed with a modified LZMA2 variant (without size field).
    Lzma
}

#[derive(Debug)]
pub struct FileSystem {
    path: PathBuf,
    mainfile: MainFile,
    indices: HashMap<u32, IndexFile>
}

#[derive(Debug)]
pub struct MainFile {
    file: Option<File>
}

#[derive(Debug)]
pub struct IndexFile {
    id: u32,
    file: File
}

#[derive(Debug,Clone)]
pub struct IndexEntry {
    index: u32,
    id: u32,
    size: u32,
    offset: u64
}

#[derive(Debug,Clone)]
pub struct EntryHeader {
    entry: IndexEntry,
    raw_size: u32,
    real_size: u32,
    compression: CompressionType
}

impl CompressionType {
    /// Fetches the appropriate type of compression based on the header field
    /// in the archive header.
    pub fn from_code(code: u8) -> CompressionType {
        match code {
            1 => CompressionType::Bzip2,
            2 => CompressionType::Gzip,
            3 => CompressionType::Lzma,
            _ => CompressionType::None
        }
    }
}

impl IndexEntry {
    pub fn index(&self) -> u32 {
        self.index
    }

    /// Gets the id of this block.
    pub fn id(&self) -> u32 {
        self.id
    }

    /// Gets the absolute size of the entry data, not counting the 8-10
    /// byte header in the blocks.
    pub fn size(&self) -> u32 {
        self.size
    }

    /// Gets the absolute offset of the very first 520-byte block of this
    /// entry in the main data file.
    pub fn offset(&self) -> u64 {
        self.offset
    }

    pub fn block(&self) -> u32 {
        (self.offset / 520u64) as u32
    }
}

impl IndexFile {
    pub fn last_entry(&self) -> u64 {
         self.file.metadata().unwrap().len() / 6u64
    }

    pub fn entry(&mut self, id: u32) -> Option<IndexEntry> {
        let ref mut file = self.file;
        let mut tmp: [u8; 6] = [0; 6];

        // Seek to the proper position and read into the temp buffer
        let seek_offset = id as u64 * 6u64;
        let res1 = file.seek(SeekFrom::Start(seek_offset));
        let res2 = file.read(&mut tmp);

        // Check if the seek and read operation succeeded
        if res1.is_err() || res2.is_err() {
            return None;
        }

        // Check if the operations returned the expected results
        if res1.unwrap() != seek_offset || res2.unwrap() != 6 {
            return None;
        }

        // Decode the size and offset from the temp buffer
        let size: u32 = ((tmp[0] as u32) << 16) | ((tmp[1] as u32) << 8) | (tmp[2] as u32);
        let offset: u64 = ((tmp[3] as u64) << 16) | ((tmp[4] as u64) << 8) | (tmp[5] as u64);

        Some(IndexEntry {index: self.id, id: id, size: size, offset: offset * 520u64})
    }
}

impl FileSystem {
    pub fn new(string: &'static str) -> Result<FileSystem, FsError> {
        // Declare some nice variables!!!
        let path = PathBuf::from(string);
        let metadata = fs::metadata(&path);

        // Make sure the folder exists
        if !metadata.is_ok() {
            return Err(FsError::FileNotFound);
        }

        // Make sure it is a directory
        if !metadata.unwrap().is_dir() {
            return Err(FsError::InvalidDirectory);
        }

        // Create mainfile path
        let mut mainfile_path = PathBuf::from(string);
        mainfile_path.push("main_file_cache.dat2");

        // Find all valid index files
        let mut indices: HashMap<u32, IndexFile> = HashMap::new();
        let entries = fs::read_dir(&path).unwrap();
        for entry in entries {
            let e = entry.unwrap();
            let fname = e.file_name().into_string().unwrap();

            // Is this an index?
            if fname.starts_with("main_file_cache.idx") {
                // Parse the index id into an integer
                let idx = fname[19..].parse::<u32>().unwrap();

                // Add the index file to our map with indices
                indices.insert(idx, IndexFile {id: idx, file: File::open(e.path()).unwrap()});
            }
        }

        // Create the filesystem object and return it
        let file = File::open(mainfile_path).ok();
        let mainfile = MainFile{file: file};

        Ok(FileSystem {path: path, mainfile: mainfile, indices: indices})
    }

    /// Gets the mainfile, that is, the main_file_cache.dat2 entry in the folder
    /// that holds the actual binary data of the filesystem entries.
    pub fn mainfile(&mut self) -> &mut MainFile {
        &mut self.mainfile
    }

    /// Gets an index with a specific id if it exists. The index can only exist if the file exists
    /// on the file system.
    pub fn index(&mut self, index: u32) -> Option<&mut IndexFile> {
        self.indices.get_mut(&index)
    }
}

impl MainFile {
    /// Checks if the file exists.
    pub fn exists(&self) -> bool {
        self.file.is_some()
    }

    /// Gets the backing file, if existant. Returns a new instance with a fresh seek pointer.
    pub fn file(&mut self) -> Option<&mut File> {
        self.file.as_mut()
    }

    /// Calculates the number of data blocks in the mainfile (if existant). This is done by
    /// taking the file size and dividing that by 520 (rouding up), because each block
    /// takes up 520 bytes of data.
    pub fn num_blocks(&self) -> Option<u64> {
        match self.file {
            Some(ref x) => Some((x.metadata().unwrap().len() + 519u64) / 520u64),
            None => None
        }
    }

    /// Reads a block of data, specified by the block id. The data is read at 520 * block_id
    /// and is exactly 520 bytes big. It is not guaranteed all 520 bytes are occupied if the
    /// block is the last one, thus possible to be trimmed.
    pub fn read_block(&mut self, block: u32) -> Option<[u8; 520]> {
        // Do we have a valid file?
        if self.file.is_none() {
            return None;
        }

        let mut data: [u8; 520] = [0; 520];
        let file = self.file().unwrap();

        // Seek to the right position and read the data
        file.seek(SeekFrom::Start(block as u64 * 520u64));
        file.read(&mut data);

        return Some(data);
    }

    pub fn read_header(&mut self, entry: IndexEntry) -> Option<EntryHeader> {
        // Do we have a valid file?
        if self.file.is_none() {
            return None;
        }

        let mut hdr: [u8; 9] = [0; 9];
        let file = self.file().unwrap();

        // Seek to the right position and read the data, skipping the block header at start
        let block_header_len = if entry.id() > 0xFFFF { 10 } else { 8 };
        file.seek(SeekFrom::Start(entry.offset() + block_header_len));
        file.read(&mut hdr);

        // Parse the 9 bytes of important info
        let compression_type = hdr[0];
        let raw_size: u32 = ((hdr[1] as u32) << 24) | ((hdr[2] as u32) << 16) | ((hdr[3] as u32) << 8) | (hdr[4] as u32);
        let real_size: u32 = ((hdr[5] as u32) << 24) | ((hdr[6] as u32) << 16) | ((hdr[7] as u32) << 8) | (hdr[8] as u32);

        // Return the new entry header
        return Some(EntryHeader {
            entry: entry,
            raw_size: raw_size,
            real_size: real_size,
            compression: CompressionType::from_code(compression_type)
        });
    }

    pub fn read_entry(&mut self, entry: IndexEntry) -> Option<&[u8]> {
        // Do we have a valid file?
        if self.file.is_none() {
            return None;
        }

        // Create a vec with what we assume is the size. If not, the vec will
        // perfectly resize itself, so it's only an estimation to help us speed up.
        let mut data: Vec<i8> = Vec::with_capacity(entry.size() as usize);
        let mut file = self.file().unwrap();

        let mut current_block = entry.block();
        let mut done = false;
        while !done {
            let block_data = self.read_block(current_block);

            done = true;
        }

        None
    }

}
<commit_msg>Add entry data reading<commit_after>use std::path::PathBuf;
use std::fs::File;
use std::error::Error;
use std::fs;
use std::fmt;
use std::io::{Seek, Read, SeekFrom, copy};
use std::collections::HashMap;

#[derive(Debug)]
pub enum FsError {
    FileNotFound,
    InvalidDirectory,
    NoFileHandle,
    MalformedDataSequence
}
impl Error for FsError {
    fn description(&self) -> &str {
        match *self {
            FsError::FileNotFound => "the folder does not exist or cannot be read from",
            FsError::InvalidDirectory => "the specified directory is not a valid directory",
            FsError::NoFileHandle => "the filesystem did not load a file yet",
            FsError::MalformedDataSequence => "the data sequence did not complete correctly"
        }
    }
}
impl fmt::Display for FsError {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        match *self {
            FsError::FileNotFound => write!(f, "the folder specified could not be found or read from"),
            FsError::InvalidDirectory => write!(f, "the specified directory is not a valid directory"),
            FsError::NoFileHandle => write!(f, "the filesystem did not load a file yet"),
            FsError::MalformedDataSequence => write!(f, "the data sequence did not complete correctly")
        }
    }
}

#[derive(Debug,Clone)]
pub enum CompressionType {
    /// The archive is not compressed and the raw data is the real data.
    None,
    /// The archive is compressed with the Bzip2 compression algorithm.
    Bzip2,
    /// The archive is compressed with (a slightly modified, headerless) Gzip codec.
    Gzip,
    /// The archive is compressed with a modified LZMA2 variant (without size field).
    Lzma
}

#[derive(Debug)]
pub struct FileSystem {
    path: PathBuf,
    mainfile: MainFile,
    indices: HashMap<u32, IndexFile>
}

#[derive(Debug)]
pub struct MainFile {
    file: Option<File>
}

#[derive(Debug)]
pub struct IndexFile {
    id: u32,
    file: File
}

#[derive(Debug,Clone)]
pub struct IndexEntry {
    index: u8,
    id: u32,
    size: u32,
    offset: u64
}

#[derive(Debug,Clone)]
pub struct EntryHeader {
    entry: IndexEntry,
    raw_size: u32,
    real_size: u32,
    compression: CompressionType
}

#[derive(Debug,Clone)]
pub struct BlockHeader {
    big: bool,
    entry_id: u32,
    index_id: u8,

    next_seq: i32,
    next_block: u32
}

impl CompressionType {
    /// Fetches the appropriate type of compression based on the header field
    /// in the archive header.
    pub fn from_code(code: u8) -> CompressionType {
        match code {
            1 => CompressionType::Bzip2,
            2 => CompressionType::Gzip,
            3 => CompressionType::Lzma,
            _ => CompressionType::None
        }
    }
}

impl BlockHeader {
    pub fn from_block(big: bool, data: [u8; 520]) -> BlockHeader {
        match big {
            true => {
                BlockHeader {
                    big: true,
                    entry_id: ((data[0] as u32) << 24) | ((data[1] as u32) << 16) | ((data[2] as u32) << 8) | (data[3] as u32),
                    next_seq: (((data[4] as u32) << 8) | (data[5] as u32)) as i32,
                    next_block: ((data[6] as u32) << 16) | ((data[7] as u32) << 8) | (data[8] as u32),
                    index_id: data[9] as u8
                }
            },
            false => {
                BlockHeader {
                    big: false,
                    entry_id: ((data[0] as u32) << 8) | (data[1] as u32),
                    next_seq: (((data[2] as u32) << 8) | (data[3] as u32)) as i32,
                    next_block: ((data[4] as u32) << 16) | ((data[5] as u32) << 8) | (data[6] as u32),
                    index_id: data[7] as u8
                }
            }
        }
    }
}

impl IndexEntry {
    pub fn index(&self) -> u8 {
        self.index
    }

    /// Gets the id of this block.
    pub fn id(&self) -> u32 {
        self.id
    }

    /// Gets the absolute size of the entry data, not counting the 8-10
    /// byte header in the blocks.
    pub fn size(&self) -> u32 {
        self.size
    }

    /// Gets the absolute offset of the very first 520-byte block of this
    /// entry in the main data file.
    pub fn offset(&self) -> u64 {
        self.offset
    }

    pub fn block(&self) -> u32 {
        (self.offset / 520u64) as u32
    }
}

impl IndexFile {
    pub fn last_entry(&self) -> u64 {
         self.file.metadata().unwrap().len() / 6u64
    }

    pub fn entry(&mut self, id: u32) -> Option<IndexEntry> {
        let ref mut file = self.file;
        let mut tmp: [u8; 6] = [0; 6];

        // Seek to the proper position and read into the temp buffer
        let seek_offset = id as u64 * 6u64;
        let res1 = file.seek(SeekFrom::Start(seek_offset));
        let res2 = file.read(&mut tmp);

        // Check if the seek and read operation succeeded
        if res1.is_err() || res2.is_err() {
            return None;
        }

        // Check if the operations returned the expected results
        if res1.unwrap() != seek_offset || res2.unwrap() != 6 {
            return None;
        }

        // Decode the size and offset from the temp buffer
        let size: u32 = ((tmp[0] as u32) << 16) | ((tmp[1] as u32) << 8) | (tmp[2] as u32);
        let offset: u64 = ((tmp[3] as u64) << 16) | ((tmp[4] as u64) << 8) | (tmp[5] as u64);

        Some(IndexEntry {index: self.id as u8, id: id, size: size, offset: offset * 520u64})
    }
}

impl FileSystem {
    pub fn new(string: &'static str) -> Result<FileSystem, FsError> {
        // Declare some nice variables!!!
        let path = PathBuf::from(string);
        let metadata = fs::metadata(&path);

        // Make sure the folder exists
        if !metadata.is_ok() {
            return Err(FsError::FileNotFound);
        }

        // Make sure it is a directory
        if !metadata.unwrap().is_dir() {
            return Err(FsError::InvalidDirectory);
        }

        // Create mainfile path
        let mut mainfile_path = PathBuf::from(string);
        mainfile_path.push("main_file_cache.dat2");

        // Find all valid index files
        let mut indices: HashMap<u32, IndexFile> = HashMap::new();
        let entries = fs::read_dir(&path).unwrap();
        for entry in entries {
            let e = entry.unwrap();
            let fname = e.file_name().into_string().unwrap();

            // Is this an index?
            if fname.starts_with("main_file_cache.idx") {
                // Parse the index id into an integer
                let idx = fname[19..].parse::<u32>().unwrap();

                // Add the index file to our map with indices
                indices.insert(idx, IndexFile {id: idx, file: File::open(e.path()).unwrap()});
            }
        }

        // Create the filesystem object and return it
        let file = File::open(mainfile_path).ok();
        let mainfile = MainFile{file: file};

        Ok(FileSystem {path: path, mainfile: mainfile, indices: indices})
    }

    /// Gets the mainfile, that is, the main_file_cache.dat2 entry in the folder
    /// that holds the actual binary data of the filesystem entries.
    pub fn mainfile(&mut self) -> &mut MainFile {
        &mut self.mainfile
    }

    /// Gets an index with a specific id if it exists. The index can only exist if the file exists
    /// on the file system.
    pub fn index(&mut self, index: u32) -> Option<&mut IndexFile> {
        self.indices.get_mut(&index)
    }
}

impl MainFile {
    /// Checks if the file exists.
    pub fn exists(&self) -> bool {
        self.file.is_some()
    }

    /// Gets the backing file, if existant. Returns a new instance with a fresh seek pointer.
    pub fn file(&mut self) -> Option<&mut File> {
        self.file.as_mut()
    }

    /// Calculates the number of data blocks in the mainfile (if existant). This is done by
    /// taking the file size and dividing that by 520 (rouding up), because each block
    /// takes up 520 bytes of data.
    pub fn num_blocks(&self) -> Option<u64> {
        match self.file {
            Some(ref x) => Some((x.metadata().unwrap().len() + 519u64) / 520u64),
            None => None
        }
    }

    /// Reads a block of data, specified by the block id. The data is read at 520 * block_id
    /// and is exactly 520 bytes big. It is not guaranteed all 520 bytes are occupied if the
    /// block is the last one, thus possible to be trimmed.
    pub fn read_block(&mut self, block: u32) -> Option<[u8; 520]> {
        // Do we have a valid file?
        if self.file.is_none() {
            return None;
        }

        let mut data: [u8; 520] = [0; 520];
        let file = self.file().unwrap();

        // Seek to the right position and read the data
        file.seek(SeekFrom::Start(block as u64 * 520u64));
        file.read(&mut data);

        return Some(data);
    }

    pub fn read_header(&mut self, entry: IndexEntry) -> Option<EntryHeader> {
        // Do we have a valid file?
        if self.file.is_none() {
            return None;
        }

        let mut hdr: [u8; 9] = [0; 9];
        let file = self.file().unwrap();

        // Seek to the right position and read the data, skipping the block header at start
        let block_header_len = if entry.id() > 0xFFFF { 10 } else { 8 };
        file.seek(SeekFrom::Start(entry.offset() + block_header_len));
        file.read(&mut hdr);

        // Parse the 9 bytes of important info
        let compression_type = hdr[0];
        let raw_size: u32 = ((hdr[1] as u32) << 24) | ((hdr[2] as u32) << 16) | ((hdr[3] as u32) << 8) | (hdr[4] as u32);
        let real_size: u32 = ((hdr[5] as u32) << 24) | ((hdr[6] as u32) << 16) | ((hdr[7] as u32) << 8) | (hdr[8] as u32);

        // Return the new entry header
        return Some(EntryHeader {
            entry: entry,
            raw_size: raw_size,
            real_size: real_size,
            compression: CompressionType::from_code(compression_type)
        });
    }

    pub fn read_entry(&mut self, entry: IndexEntry) -> Result<Vec<i8>, FsError> {
        // Do we have a valid file?
        if self.file.is_none() {
            return Err(FsError::NoFileHandle);
        }

        // Create a vec with what we assume is the size. If not, the vec will
        // perfectly resize itself, so it's only an estimation to help us speed up.
        let mut data: Vec<i8> = Vec::with_capacity(entry.size() as usize);

        let mut current_block = entry.block();
        let mut remaining = entry.size();
        let mut current_seq = 0; // We expect a next part to be '1'

        while remaining > 0 {
            let block_data = self.read_block(current_block).unwrap();
            let block_info = BlockHeader::from_block(entry.id() > 65535, block_data);

            let header_size = if block_info.big {10} else {8};
            let available_data = 520 - header_size;
            let consumable = if remaining > available_data {available_data} else {remaining};
            remaining -= consumable;

            // Do some checks to validate this block.
            if remaining > 0 {
                if block_info.index_id != entry.index() || block_info.next_seq != current_seq {
                    return Err(FsError::MalformedDataSequence);
                } else {
                    // TODO this is so inefficient I should probably feel bad. Really bad. Terribly bad.
                    if block_info.big {
                        for i in block_data[10..520].iter() {
                            data.push(*i as i8);
                        }
                    } else {
                        for i in block_data[8..520].iter() {
                            data.push(*i as i8);
                        }
                    }

                    current_block += 1;
                    current_seq += 1;
                }
            }
        }

        Ok(data)
    }

}
<|endoftext|>


<commit_before>use std::fs::Metadata;
use std::path::{Path, PathBuf};

use log::warn;

pub fn walk<P: AsRef<Path>>(root: P) -> std::io::Result<Walk> {
    let iter = std::fs::read_dir(root)?;
    Ok(Walk {
        pending: vec!(iter),
    })
}

pub struct Walk {
    // TODO: Add support for stopping at device boundaries.
    pending: Vec<std::fs::ReadDir>,
}

pub struct WalkEntry {
    path: PathBuf,
    metadata: Metadata,
}

impl Walk {

    fn step(&mut self, entry: std::fs::DirEntry) -> Option<WalkEntry> {
        let path = entry.path();
        let metadata = match entry.metadata() {
            Ok(metadata) => metadata,
            Err(error) => {
                warn!("failed to stat file '{}': {}", path.display(), error);
                return None;
            },
        };

        self.push(&path);

        Some(WalkEntry {
            path: path,
            metadata: metadata,
        })
    }

    fn push(&mut self, path: &Path) {
        match std::fs::read_dir(path) {
            Ok(iter) => {
                self.pending.push(iter);
            },
            Err(error) => {
                warn!("failed to read '{}': {}", path.display(), error);
            },
        }
    }

    fn pop(&mut self) -> Option<std::fs::DirEntry> {
        while let Some(iter) = self.pending.last_mut() {
            for entry in iter {
                match entry {
                    Ok(entry) => return Some(entry),
                    Err(error) => warn!("directory iteration error: {}", error),
                }
            }

            self.pending.pop();
        }

        None
    }
}

impl std::iter::Iterator for Walk {

    type Item = WalkEntry;

    fn next(&mut self) -> Option<WalkEntry> {
        loop {
            let entry = self.pop()?;
            let entry = self.step(entry);
            if entry.is_some() {
                return entry;
            }
        }
    }
}
<commit_msg>Simplify the walking implementation.<commit_after>use std::fs::Metadata;
use std::path::{Path, PathBuf};

use log::warn;

pub fn walk<P: AsRef<Path>>(root: P) -> std::io::Result<Walk> {
    let iter = std::fs::read_dir(root)?;
    Ok(Walk {
        pending: vec!(iter),
    })
}

pub struct Walk {
    // TODO: Add support for stopping at device boundaries.
    pending: Vec<std::fs::ReadDir>,
}

pub struct WalkEntry {
    path: PathBuf,
    metadata: Metadata,
}

impl WalkEntry {

    fn from_dir_entry(entry: std::fs::DirEntry) -> Option<WalkEntry> {
        let path = entry.path();

        let metadata = match entry.metadata() {
            Ok(metadata) => metadata,
            Err(error) => {
                warn!("failed to stat file '{}': {}", path.display(), error);
                return None;
            },
        };

        Some(WalkEntry {
            path: path,
            metadata: metadata,
        })
    }
}

impl Walk {

    fn push(&mut self, entry: &WalkEntry) {
        match std::fs::read_dir(&entry.path) {
            Ok(iter) => {
                self.pending.push(iter);
            },
            Err(error) => {
                warn!("failed to read '{}': {}", entry.path.display(), error);
            },
        }
    }

    fn pop(&mut self) -> Option<WalkEntry> {
        while let Some(iter) = self.pending.last_mut() {
            for entry in iter {
                match entry {
                    Ok(entry) => {
                        let entry = WalkEntry::from_dir_entry(entry);
                        if entry.is_some() {
                            return entry;
                        }
                    },
                    Err(error) => warn!("directory iteration error: {}", error),
                }
            }

            self.pending.pop();
        }

        None
    }
}

impl std::iter::Iterator for Walk {

    type Item = WalkEntry;

    fn next(&mut self) -> Option<WalkEntry> {
        let entry = self.pop()?;
        self.push(&entry);
        Some(entry)
    }
}
<|endoftext|>


<commit_before>use rand::{StdRng, Rng};
use game_state::*;
use settings::*;

pub struct GameLogic {
    game_state: GameState,
    settings: Settings,
    rng: StdRng
}

impl GameLogic {
    pub fn new() -> GameLogic {
        GameLogic{ game_state: GameState::new(), settings: Settings::new(), rng: StdRng::new().unwrap() }
    }

    pub fn process_input(&mut self, line: &str) {
        let parts: Vec<&str> = line.split_whitespace().collect();
        match parts[0] {
            "settings" => self.settings.update(parts[1], parts[2]),
            "action" => self.make_move(parts[2]),
            "update" => self.game_state.update(parts[1], parts[2], parts[3]),
            _ => {}
        }
    }

    fn make_move(&mut self, _time: &str) {
        //TODO:: Change out the random moves for something cooler
        let xpos = self.rng.gen::<u32>() % self.settings.field_width;
        let ypos = self.rng.gen::<u32>() % self.settings.field_height;

        if self.is_move_valid(xpos, ypos) {
            println!("place_move {} {}", xpos, ypos);
        } else {
            println!("pass");
        }
    }

    fn is_move_valid(&self, xpos: u32, ypos: u32) -> bool {
        //Note:: This will also handle the KO rule
        let is_empty = self.get_point(xpos, ypos) == Point::Empty;
        if !is_empty { return false; }

        //one side must be empty (e.g. a new peice at this position must have at least one liberty)
        let is_suicide = (xpos == 0                              || self.get_point(xpos - 1, ypos)     != Point::Empty) &&
                         (xpos + 1 >= self.settings.field_width  || self.get_point(xpos + 1, ypos)     != Point::Empty) &&
                         (ypos == 0                              || self.get_point(xpos,     ypos - 1) != Point::Empty) &&
                         (ypos + 1 >= self.settings.field_height || self.get_point(xpos,     ypos + 1) != Point::Empty);
        !is_suicide
    }

    fn get_point(&self, xpos: u32, ypos: u32) -> Point {
        let index = GameLogic::coods_to_index(xpos, ypos, self.settings.field_width) as usize;
        self.game_state.field[index]
    }

    fn coods_to_index(xpos: u32, ypos: u32, width: u32) -> u32 {
        width * ypos + xpos
    }
}
<commit_msg>Update game_logic.rs<commit_after>use rand::{StdRng, Rng};
use game_state::*;
use settings::*;

pub struct GameLogic {
    game_state: GameState,
    settings: Settings,
    rng: StdRng
}

impl GameLogic {
    pub fn new() -> GameLogic {
        GameLogic{ game_state: GameState::new(), settings: Settings::new(), rng: StdRng::new().unwrap() }
    }

    pub fn process_input(&mut self, line: &str) {
        let parts: Vec<&str> = line.split_whitespace().collect();
        match parts[0] {
            "settings" => self.settings.update(parts[1], parts[2]),
            "action" => self.make_move(parts[2]),
            "update" => self.game_state.update(parts[1], parts[2], parts[3]),
            _ => {}
        }
    }

    fn make_move(&mut self, _time: &str) {
        //TODO:: Change out the random moves for something cooler
        let xpos = self.rng.gen::<u32>() % self.settings.field_width;
        let ypos = self.rng.gen::<u32>() % self.settings.field_height;

        if self.is_move_valid(xpos, ypos) {
            println!("place_move {} {}", xpos, ypos);
        } else {
            println!("pass");
        }
    }

    fn is_move_valid(&self, xpos: u32, ypos: u32) -> bool {
        //Note:: This will also handle the KO rule
        let is_empty = self.get_point(xpos, ypos) == Point::Empty;
        if !is_empty { return false; }

        //one side must be empty (e.g. a new piece at this position must have at least one liberty)
        let is_suicide = (xpos == 0                              || self.get_point(xpos - 1, ypos)     != Point::Empty) &&
                         (xpos + 1 >= self.settings.field_width  || self.get_point(xpos + 1, ypos)     != Point::Empty) &&
                         (ypos == 0                              || self.get_point(xpos,     ypos - 1) != Point::Empty) &&
                         (ypos + 1 >= self.settings.field_height || self.get_point(xpos,     ypos + 1) != Point::Empty);
        !is_suicide
    }

    fn get_point(&self, xpos: u32, ypos: u32) -> Point {
        let index = GameLogic::coods_to_index(xpos, ypos, self.settings.field_width) as usize;
        self.game_state.field[index]
    }

    fn coods_to_index(xpos: u32, ypos: u32, width: u32) -> u32 {
        width * ypos + xpos
    }
}
<|endoftext|>


<commit_before>mod literals {
    use crate::*;

    #[test]
    fn comma_sequence() {
        expect("1,2").to_yield(tuple!(1, 2))
    }
    #[test]
    fn comma_sequence_trailing_comma() {
        expect("1,2,").to_yield(tuple!(1, 2))
    }
    #[test]
    fn comma_sequence_three() {
        expect("1,2,3").to_yield(tuple!(1, 2, 3))
    }
    #[test]
    fn comma_left_space() {
        expect("1 ,2").to_yield(tuple!(1, 2))
    }
    #[test]
    fn comma_right_space() {
        expect("1, 2").to_yield(tuple!(1, 2))
    }
    #[test]
    fn comma_both_space() {
        expect("1 , 2").to_yield(tuple!(1, 2))
    }

    #[test]
    fn comma_sequence_bare_parentheses() {
        expect("(1,2)").to_yield(tuple!(1, 2));
    }

    #[test]
    fn only_comma() {
        expect(",").to_error(MissingOperand, 0)
    }
    #[test]
    fn right_comma() {
        expect("1,").to_yield(tuple!(1))
    }
    #[test]
    fn right_comma_nested() {
        expect("(1),").to_yield(tuple!(1))
    }
    #[test]
    fn right_comma_inside_parentheses() {
        expect("(1,)").to_yield(tuple!(1))
    }
    #[test]
    fn right_comma_inside_and_outside_parentheses() {
        expect("(1,),").to_yield(tuple!([1]))
    }
    #[test]
    fn right_comma_nested_multiple() {
        expect("(1,2),").to_yield(tuple!([1, 2]))
    }
    #[test]
    fn left_comma() {
        expect(",1").to_error(MissingOperand, 0)
    }
    #[test]
    fn both_comma() {
        expect(",1,").to_error(MissingOperand, 0)
    }

    #[test]
    fn left_double_comma() {
        expect(",,1").to_error(MissingOperand, 0)
    }
    #[test]
    fn right_double_comma() {
        expect("1,,").to_error(MissingOperand, 1)
    }
    #[test]
    fn right_double_comma_after_multiple() {
        expect("1,2,,").to_error(MissingOperand, 3)
    }
    #[test]
    fn both_double_comma() {
        expect(",,1,,").to_error(MissingOperand, 0)
    }
    #[test]
    fn between_double_comma() {
        expect("1,,2").to_error(MissingOperand, 1)
    }

    #[test]
    fn paren_comma_all_over() {
        expect(",(,(,),),").to_error(MissingOperand, 0)
    }

    #[test]
    fn nested_comma() {
        expect("1,(2,3)").to_yield(tuple!(1, [2, 3]));
    }

    #[test]
    fn nested_comma_first() {
        expect("(1,2),3").to_yield(tuple!([1, 2], 3));
    }

    #[test]
    fn nested_comma_single() {
        expect("(1,2),").to_yield(tuple!([1, 2]));
    }

    #[test]
    #[should_panic]
    fn comma_trailing_comma_is_not_same_as_nested_tuple() {
        expect("(1,2),").to_yield(tuple!(1, 2));
    }

    #[test]
    fn comma_trailing_comma_is_same_as_single_value() {
        expect("1,").to_yield(1);
    }

    #[test]
    #[should_panic]
    fn comma_multiple_values_is_not_same_as_nested_tuple() {
        expect("1,2").to_yield(tuple!([1, 2]));
    }

    #[test]
    #[should_panic]
    fn comma_bare_parentheses_is_not_same_as_nested_tuple() {
        expect("(1,2)").to_yield(tuple!([1, 2]));
    }
}

mod operators_comparison {
    use crate::*;

    #[test]
    fn equal_empty_0() {
        expect("()==0").to_yield(false)
    }

    #[test]
    fn equal_empty_1() {
        expect("()==1").to_yield(false)
    }

    #[test]
    fn equal_empty_false() {
        expect("()==false").to_yield(false)
    }

    #[test]
    fn equal_empty_empty() {
        expect("()==()").to_yield(true)
    }

    #[test]
    fn equal_empty_true() {
        expect("()==true").to_yield(false)
    }

    #[test]
    fn not_equal_empty_0() {
        expect("()!=0").to_yield(true)
    }

    #[test]
    fn not_equal_empty_1() {
        expect("()!=1").to_yield(true)
    }

    #[test]
    fn not_equal_empty_false() {
        expect("()!=false").to_yield(true)
    }

    #[test]
    fn not_equal_empty_empty() {
        expect("()!=()").to_yield(false)
    }

    #[test]
    fn not_equal_empty_true() {
        expect("()!=true").to_yield(true)
    }

    #[test]
    fn greater_or_equal_empty_0() {
        expect("()>=0").to_error(UnsupportedOperator, 2..=3)
    }

    #[test]
    fn greater_or_equal_empty_error() {
        expect("()>=1/0").to_error(UnsupportedOperator, 2..=3)
    }

    #[test]
    fn greater_or_equal_empty_false() {
        expect("()>=false").to_error(UnsupportedOperator, 2..=3)
    }

    #[test]
    fn greater_or_equal_empty_empty() {
        expect("()>=()").to_error(UnsupportedOperator, 2..=3)
    }

    #[test]
    fn greater_or_equal_empty_true() {
        expect("()>=true").to_error(UnsupportedOperator, 2..=3)
    }

    #[test]
    fn greater_than_empty_0() {
        expect("()>0").to_error(UnsupportedOperator, 2)
    }

    #[test]
    fn greater_than_empty_error() {
        expect("()>1/0").to_error(UnsupportedOperator, 2)
    }

    #[test]
    fn greater_than_empty_false() {
        expect("()>false").to_error(UnsupportedOperator, 2)
    }

    #[test]
    fn greater_than_empty_empty() {
        expect("()>()").to_error(UnsupportedOperator, 2)
    }

    #[test]
    fn greater_than_empty_true() {
        expect("()>true").to_error(UnsupportedOperator, 2)
    }

    #[test]
    fn less_or_equal_empty_0() {
        expect("()<=0").to_error(UnsupportedOperator, 2..=3)
    }

    #[test]
    fn less_or_equal_empty_error() {
        expect("1/0<=()").to_error(DivideByZero, 2)
    }

    #[test]
    fn less_or_equal_empty_false() {
        expect("()<=false").to_error(UnsupportedOperator, 2..=3)
    }

    #[test]
    fn less_or_equal_empty_empty() {
        expect("()<=()").to_error(UnsupportedOperator, 2..=3)
    }

    #[test]
    fn less_or_equal_empty_true() {
        expect("()<=true").to_error(UnsupportedOperator, 2..=3)
    }

    #[test]
    fn less_than_empty_0() {
        expect("()<0").to_error(UnsupportedOperator, 2)
    }

    #[test]
    fn less_than_empty_error() {
        expect("()<1/0").to_error(UnsupportedOperator, 2)
    }

    #[test]
    fn less_than_empty_false() {
        expect("()<false").to_error(UnsupportedOperator, 2)
    }

    #[test]
    fn less_than_empty_empty() {
        expect("()<()").to_error(UnsupportedOperator, 2)
    }

    #[test]
    fn less_than_empty_true() {
        expect("()<true").to_error(UnsupportedOperator, 2)
    }
}

mod operators_logical {
    use crate::*;

    #[test]
    fn and_empty_1() {
        expect("()&&1").to_error(UnsupportedOperator, 2..=3)
    }

    #[test]
    fn and_empty_error() {
        expect("()&&(1/0)").to_error(UnsupportedOperator, 2..=3)
    }

    #[test]
    fn and_empty_false() {
        expect("()&&false").to_error(UnsupportedOperator, 2..=3)
    }

    #[test]
    fn and_empty_empty() {
        expect("()&&()").to_error(UnsupportedOperator, 2..=3)
    }

    #[test]
    fn and_empty_true() {
        expect("()&&true").to_error(UnsupportedOperator, 2..=3)
    }

    #[test]
    fn or_empty_1() {
        expect("()||1").to_error(UnsupportedOperator, 2..=3)
    }

    #[test]
    fn or_empty_error() {
        expect("()||(1/0)").to_error(UnsupportedOperator, 2..=3)
    }

    #[test]
    fn or_empty_false() {
        expect("()||false").to_error(UnsupportedOperator, 2..=3)
    }

    #[test]
    fn or_empty_empty() {
        expect("()||()").to_error(UnsupportedOperator, 2..=3)
    }

    #[test]
    fn or_empty_true() {
        expect("()||true").to_error(UnsupportedOperator, 2..=3)
    }

    #[test]
    fn not_empty() {
        expect("!()").to_error(UnsupportedOperator, 0)
    }
}

mod operators_math {
    use crate::*;

    #[test]
    fn add_empty_1() {
        expect("()+1").to_error(UnsupportedOperator, 2)
    }

    #[test]
    fn add_empty_error() {
        expect("()+1/0").to_error(UnsupportedOperator, 2)
    }

    #[test]
    fn add_empty_false() {
        expect("()+false").to_error(UnsupportedOperator, 2)
    }

    #[test]
    fn add_empty_empty() {
        expect("()+()").to_error(UnsupportedOperator, 2)
    }

    #[test]
    fn add_empty_true() {
        expect("()+true").to_error(UnsupportedOperator, 2)
    }

    #[test]
    fn sub_empty_1() {
        expect("()-1").to_error(UnsupportedOperator, 2)
    }

    #[test]
    fn sub_empty_error() {
        expect("()-1/0").to_error(UnsupportedOperator, 2)
    }

    #[test]
    fn sub_empty_false() {
        expect("()-false").to_error(UnsupportedOperator, 2)
    }

    #[test]
    fn sub_empty_empty() {
        expect("()-()").to_error(UnsupportedOperator, 2)
    }

    #[test]
    fn sub_empty_true() {
        expect("()-true").to_error(UnsupportedOperator, 2)
    }

    #[test]
    fn mul_empty_1() {
        expect("()*1").to_error(UnsupportedOperator, 2)
    }

    #[test]
    fn mul_empty_error() {
        expect("()*(1/0)").to_error(UnsupportedOperator, 2)
    }

    #[test]
    fn mul_empty_false() {
        expect("()*false").to_error(UnsupportedOperator, 2)
    }

    #[test]
    fn mul_empty_empty() {
        expect("()*()").to_error(UnsupportedOperator, 2)
    }

    #[test]
    fn mul_empty_true() {
        expect("()*true").to_error(UnsupportedOperator, 2)
    }

    #[test]
    fn div_empty_1() {
        expect("()/1").to_error(UnsupportedOperator, 2)
    }

    #[test]
    fn div_empty_error() {
        expect("()/(1/0)").to_error(UnsupportedOperator, 2)
    }

    #[test]
    fn div_empty_false() {
        expect("()/false").to_error(UnsupportedOperator, 2)
    }

    #[test]
    fn div_empty_empty() {
        expect("()/()").to_error(UnsupportedOperator, 2)
    }

    #[test]
    fn div_empty_true() {
        expect("()/true").to_error(UnsupportedOperator, 2)
    }

    #[test]
    fn neg_empty() {
        expect("-()").to_error(UnsupportedOperator, 0)
    }

    #[test]
    fn pos_empty() {
        expect("+()").to_error(UnsupportedOperator, 0)
    }
}
<commit_msg>Test sequences with multiple elements<commit_after>mod literals {
    use crate::*;

    #[test]
    fn comma_sequence() {
        expect("1,2").to_yield(tuple!(1, 2))
    }
    #[test]
    fn comma_sequence_trailing_comma() {
        expect("1,2,").to_yield(tuple!(1, 2))
    }
    #[test]
    fn comma_left_space() {
        expect("1 ,2").to_yield(tuple!(1, 2))
    }
    #[test]
    fn comma_right_space() {
        expect("1, 2").to_yield(tuple!(1, 2))
    }
    #[test]
    fn comma_both_space() {
        expect("1 , 2").to_yield(tuple!(1, 2))
    }

    #[test]
    fn comma_sequence_three() {
        expect("1,2,3").to_yield(tuple!(1, 2, 3))
    }
    #[test]
    fn comma_sequence_three_right_space() {
        expect("1, 2, 3").to_yield(tuple!(1, 2, 3))
    }
    #[test]
    fn comma_sequence_three_both_space() {
        expect("1 , 2 , 3").to_yield(tuple!(1, 2, 3))
    }
    #[test]
    fn comma_sequence_three_left_space() {
        expect("1 ,2 ,3").to_yield(tuple!(1, 2, 3))
    }
    // TODO should these yield [1,[2,3]] or [1,2,3]?
    // #[test]
    // fn comma_sequence_three_first_right_space() {
    //     expect("1, 2,3").to_yield(tuple!(1, 2, 3))
    // }
    // #[test]
    // fn comma_sequence_three_first_left_space() {
    //     expect("1 ,2,3").to_yield(tuple!(1, 2, 3))
    // }
    // #[test]
    // fn comma_sequence_three_first_both_space() {
    //     expect("1 , 2,3").to_yield(tuple!(1, 2, 3))
    // }
    // #[test]
    // fn comma_sequence_three_second_right_space() {
    //     expect("1,2, 3").to_yield(tuple!(1, 2, 3))
    // }
    // #[test]
    // fn comma_sequence_three_second_left_space() {
    //     expect("1,2 ,3").to_yield(tuple!(1, 2, 3))
    // }
    // #[test]
    // fn comma_sequence_three_second_both_space() {
    //     expect("1,2 , 3").to_yield(tuple!(1, 2, 3))
    // }

    #[test]
    fn comma_sequence_bare_parentheses() {
        expect("(1,2)").to_yield(tuple!(1, 2));
    }
    #[test]
    fn only_comma() {
        expect(",").to_error(MissingOperand, 0)
    }
    #[test]
    fn right_comma() {
        expect("1,").to_yield(tuple!(1))
    }
    #[test]
    fn right_comma_space() {
        expect("1 ,").to_yield(tuple!(1))
    }
    #[test]
    fn right_comma_nested() {
        expect("(1),").to_yield(tuple!(1))
    }
    #[test]
    fn right_comma_inside_parentheses() {
        expect("(1,)").to_yield(tuple!(1))
    }
    #[test]
    fn right_comma_inside_and_outside_parentheses() {
        expect("(1,),").to_yield(tuple!([1]))
    }
    #[test]
    fn right_comma_nested_multiple() {
        expect("(1,2),").to_yield(tuple!([1, 2]))
    }
    #[test]
    fn left_comma() {
        expect(",1").to_error(MissingOperand, 0)
    }
    #[test]
    fn both_comma() {
        expect(",1,").to_error(MissingOperand, 0)
    }

    #[test]
    fn left_double_comma() {
        expect(",,1").to_error(MissingOperand, 0)
    }
    #[test]
    fn right_double_comma() {
        expect("1,,").to_error(MissingOperand, 1)
    }
    #[test]
    fn right_double_comma_after_multiple() {
        expect("1,2,,").to_error(MissingOperand, 3)
    }
    #[test]
    fn both_double_comma() {
        expect(",,1,,").to_error(MissingOperand, 0)
    }
    #[test]
    fn between_double_comma() {
        expect("1,,2").to_error(MissingOperand, 1)
    }

    #[test]
    fn paren_comma_all_over() {
        expect(",(,(,),),").to_error(MissingOperand, 0)
    }

    #[test]
    fn nested_comma() {
        expect("1,(2,3)").to_yield(tuple!(1, [2, 3]));
    }

    #[test]
    fn nested_comma_first() {
        expect("(1,2),3").to_yield(tuple!([1, 2], 3));
    }

    #[test]
    fn nested_comma_single() {
        expect("(1,2),").to_yield(tuple!([1, 2]));
    }

    #[test]
    #[should_panic]
    fn comma_trailing_comma_is_not_same_as_nested_tuple() {
        expect("(1,2),").to_yield(tuple!(1, 2));
    }

    #[test]
    fn comma_trailing_comma_is_same_as_single_value() {
        expect("1,").to_yield(1);
    }

    #[test]
    #[should_panic]
    fn comma_multiple_values_is_not_same_as_nested_tuple() {
        expect("1,2").to_yield(tuple!([1, 2]));
    }

    #[test]
    #[should_panic]
    fn comma_bare_parentheses_is_not_same_as_nested_tuple() {
        expect("(1,2)").to_yield(tuple!([1, 2]));
    }
}

mod operators_comparison {
    use crate::*;

    #[test]
    fn equal_empty_0() {
        expect("()==0").to_yield(false)
    }

    #[test]
    fn equal_empty_1() {
        expect("()==1").to_yield(false)
    }

    #[test]
    fn equal_empty_false() {
        expect("()==false").to_yield(false)
    }

    #[test]
    fn equal_empty_empty() {
        expect("()==()").to_yield(true)
    }

    #[test]
    fn equal_empty_true() {
        expect("()==true").to_yield(false)
    }

    #[test]
    fn not_equal_empty_0() {
        expect("()!=0").to_yield(true)
    }

    #[test]
    fn not_equal_empty_1() {
        expect("()!=1").to_yield(true)
    }

    #[test]
    fn not_equal_empty_false() {
        expect("()!=false").to_yield(true)
    }

    #[test]
    fn not_equal_empty_empty() {
        expect("()!=()").to_yield(false)
    }

    #[test]
    fn not_equal_empty_true() {
        expect("()!=true").to_yield(true)
    }

    #[test]
    fn greater_or_equal_empty_0() {
        expect("()>=0").to_error(UnsupportedOperator, 2..=3)
    }

    #[test]
    fn greater_or_equal_empty_error() {
        expect("()>=1/0").to_error(UnsupportedOperator, 2..=3)
    }

    #[test]
    fn greater_or_equal_empty_false() {
        expect("()>=false").to_error(UnsupportedOperator, 2..=3)
    }

    #[test]
    fn greater_or_equal_empty_empty() {
        expect("()>=()").to_error(UnsupportedOperator, 2..=3)
    }

    #[test]
    fn greater_or_equal_empty_true() {
        expect("()>=true").to_error(UnsupportedOperator, 2..=3)
    }

    #[test]
    fn greater_than_empty_0() {
        expect("()>0").to_error(UnsupportedOperator, 2)
    }

    #[test]
    fn greater_than_empty_error() {
        expect("()>1/0").to_error(UnsupportedOperator, 2)
    }

    #[test]
    fn greater_than_empty_false() {
        expect("()>false").to_error(UnsupportedOperator, 2)
    }

    #[test]
    fn greater_than_empty_empty() {
        expect("()>()").to_error(UnsupportedOperator, 2)
    }

    #[test]
    fn greater_than_empty_true() {
        expect("()>true").to_error(UnsupportedOperator, 2)
    }

    #[test]
    fn less_or_equal_empty_0() {
        expect("()<=0").to_error(UnsupportedOperator, 2..=3)
    }

    #[test]
    fn less_or_equal_empty_error() {
        expect("1/0<=()").to_error(DivideByZero, 2)
    }

    #[test]
    fn less_or_equal_empty_false() {
        expect("()<=false").to_error(UnsupportedOperator, 2..=3)
    }

    #[test]
    fn less_or_equal_empty_empty() {
        expect("()<=()").to_error(UnsupportedOperator, 2..=3)
    }

    #[test]
    fn less_or_equal_empty_true() {
        expect("()<=true").to_error(UnsupportedOperator, 2..=3)
    }

    #[test]
    fn less_than_empty_0() {
        expect("()<0").to_error(UnsupportedOperator, 2)
    }

    #[test]
    fn less_than_empty_error() {
        expect("()<1/0").to_error(UnsupportedOperator, 2)
    }

    #[test]
    fn less_than_empty_false() {
        expect("()<false").to_error(UnsupportedOperator, 2)
    }

    #[test]
    fn less_than_empty_empty() {
        expect("()<()").to_error(UnsupportedOperator, 2)
    }

    #[test]
    fn less_than_empty_true() {
        expect("()<true").to_error(UnsupportedOperator, 2)
    }
}

mod operators_logical {
    use crate::*;

    #[test]
    fn and_empty_1() {
        expect("()&&1").to_error(UnsupportedOperator, 2..=3)
    }

    #[test]
    fn and_empty_error() {
        expect("()&&(1/0)").to_error(UnsupportedOperator, 2..=3)
    }

    #[test]
    fn and_empty_false() {
        expect("()&&false").to_error(UnsupportedOperator, 2..=3)
    }

    #[test]
    fn and_empty_empty() {
        expect("()&&()").to_error(UnsupportedOperator, 2..=3)
    }

    #[test]
    fn and_empty_true() {
        expect("()&&true").to_error(UnsupportedOperator, 2..=3)
    }

    #[test]
    fn or_empty_1() {
        expect("()||1").to_error(UnsupportedOperator, 2..=3)
    }

    #[test]
    fn or_empty_error() {
        expect("()||(1/0)").to_error(UnsupportedOperator, 2..=3)
    }

    #[test]
    fn or_empty_false() {
        expect("()||false").to_error(UnsupportedOperator, 2..=3)
    }

    #[test]
    fn or_empty_empty() {
        expect("()||()").to_error(UnsupportedOperator, 2..=3)
    }

    #[test]
    fn or_empty_true() {
        expect("()||true").to_error(UnsupportedOperator, 2..=3)
    }

    #[test]
    fn not_empty() {
        expect("!()").to_error(UnsupportedOperator, 0)
    }
}

mod operators_math {
    use crate::*;

    #[test]
    fn add_empty_1() {
        expect("()+1").to_error(UnsupportedOperator, 2)
    }

    #[test]
    fn add_empty_error() {
        expect("()+1/0").to_error(UnsupportedOperator, 2)
    }

    #[test]
    fn add_empty_false() {
        expect("()+false").to_error(UnsupportedOperator, 2)
    }

    #[test]
    fn add_empty_empty() {
        expect("()+()").to_error(UnsupportedOperator, 2)
    }

    #[test]
    fn add_empty_true() {
        expect("()+true").to_error(UnsupportedOperator, 2)
    }

    #[test]
    fn sub_empty_1() {
        expect("()-1").to_error(UnsupportedOperator, 2)
    }

    #[test]
    fn sub_empty_error() {
        expect("()-1/0").to_error(UnsupportedOperator, 2)
    }

    #[test]
    fn sub_empty_false() {
        expect("()-false").to_error(UnsupportedOperator, 2)
    }

    #[test]
    fn sub_empty_empty() {
        expect("()-()").to_error(UnsupportedOperator, 2)
    }

    #[test]
    fn sub_empty_true() {
        expect("()-true").to_error(UnsupportedOperator, 2)
    }

    #[test]
    fn mul_empty_1() {
        expect("()*1").to_error(UnsupportedOperator, 2)
    }

    #[test]
    fn mul_empty_error() {
        expect("()*(1/0)").to_error(UnsupportedOperator, 2)
    }

    #[test]
    fn mul_empty_false() {
        expect("()*false").to_error(UnsupportedOperator, 2)
    }

    #[test]
    fn mul_empty_empty() {
        expect("()*()").to_error(UnsupportedOperator, 2)
    }

    #[test]
    fn mul_empty_true() {
        expect("()*true").to_error(UnsupportedOperator, 2)
    }

    #[test]
    fn div_empty_1() {
        expect("()/1").to_error(UnsupportedOperator, 2)
    }

    #[test]
    fn div_empty_error() {
        expect("()/(1/0)").to_error(UnsupportedOperator, 2)
    }

    #[test]
    fn div_empty_false() {
        expect("()/false").to_error(UnsupportedOperator, 2)
    }

    #[test]
    fn div_empty_empty() {
        expect("()/()").to_error(UnsupportedOperator, 2)
    }

    #[test]
    fn div_empty_true() {
        expect("()/true").to_error(UnsupportedOperator, 2)
    }

    #[test]
    fn neg_empty() {
        expect("-()").to_error(UnsupportedOperator, 0)
    }

    #[test]
    fn pos_empty() {
        expect("+()").to_error(UnsupportedOperator, 0)
    }
}
<|endoftext|>


<commit_before>// Copyright (C) 2016 Élisabeth HENRY.
//
// This file is part of Crowbook.
//
// Crowbook is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published
// by the Free Software Foundation, either version 2.1 of the License, or
// (at your option) any later version.
//
// Caribon is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU Lesser General Public License for more details.
//
// You should have received ba copy of the GNU Lesser General Public License
// along with Crowbook.  If not, see <http://www.gnu.org/licenses/>.

use error::{Error,Result};

use std::env;
use std::path::{Path,PathBuf};
use std::io::Write;
use std::process::Command;
use std::fs::{self, File,DirBuilder};
use uuid;
use std::ops::Drop;

/// Struct used to create zip (using filesystem and zip command)
pub struct Zipper {
    args: Vec<String>,
    path: PathBuf,
}

impl Zipper {
    /// creates new zipper
    ///
    /// path: the path to a temporary directory (zipper will create a random dir in it and clean it later)
    /// inner_dirs: a vec of inner directory to create in this directory
    pub fn new(path: &str) -> Result<Zipper> {
        let uuid = uuid::Uuid::new_v4();
        let zipper_path = Path::new(path).join(uuid.to_simple_string());

        try!(DirBuilder::new()
             .recursive(true)
             .create(&zipper_path)
             .map_err(|_| Error::Zipper(format!("could not create temporary directory in {}", path))));

        Ok(Zipper {
            args: vec!(),
            path: zipper_path,
        })
    }

    /// writes a content to a temporary file
    pub fn write(&mut self, file: &str, content: &[u8], add_args: bool) -> Result<()> {
        let dest_file = self.path.join(file);
        let dest_dir = dest_file.parent().expect("This file should have a parent, it has just been joined to a directory!");
        if !fs::metadata(dest_dir).is_ok() { // dir does not exist, create it
            try!(DirBuilder::new()
                 .recursive(true)
                 .create(&dest_dir)
                 .map_err(|_| Error::Zipper(format!("could not create temporary directory in {}", dest_dir.display()))));
        }

        if let Ok(mut f) = File::create(&dest_file) {
            if f.write_all(content).is_ok() {
                if add_args {
                    self.args.push(String::from(file));
                }
                Ok(())
            } else {
                Err(Error::Zipper(format!("could not write to temporary file {}", file)))
            }
        } else {
            Err(Error::Zipper(format!("could not create temporary file {}", file)))
        }
    }

    /// Unzip a file and deletes it afterwards
    pub fn unzip(&mut self, file: &str) -> Result<()> {
        // change to dest directory to unzip file
        let dir = env::current_dir().unwrap();
        try!(env::set_current_dir(&self.path).map_err(|_| Error::Zipper("could not change current directory".to_owned())));
        let output = Command::new("unzip")
                      .arg(file)
                      .output()
                      .map_err(|e| Error::Zipper(format!("failed to execute unzip  on {}: {}", file, e)));

        // change back to original current directory before try! ing anything
        env::set_current_dir(dir).unwrap();
        try!(output);

        fs::remove_file(self.path.join(file))
            .map_err(|_| Error::Zipper(format!("failed to remove file {}", file)))
    }

    /// run command and copy file name (supposed to result from the command) to current dir
    pub fn run_command(&mut self, mut command: Command, in_file: &str, out_file: &str) -> Result<String> {
        let res_output = command.args(&self.args)
            .current_dir(&self.path)
            .output()
            .map_err(|e| Error::Zipper(format!("failed to execute process: {}", e)));
        let output = try!(res_output);
        try!(fs::copy(self.path.join(in_file), out_file).map_err(|_| {
            println!("{}", &String::from_utf8_lossy(&output.stdout));
            Error::Zipper(format!("could not copy file {} to {}", in_file, out_file))
        }));
        Ok(String::from_utf8_lossy(&output.stdout).into_owned())
    }

    /// zip all files in zipper's tmp dir to a given file name and return odt file
    pub fn generate_odt(&mut self, command: &str, odt_file: &str) -> Result<String> {
        let mut command = Command::new(command);
        command.arg("-r");
        command.arg("result.odt");
        command.arg(".");
        self.run_command(command, "result.odt", odt_file)
    }

    /// generate a pdf file into given file name
    pub fn generate_pdf(&mut self, command: &str, tex_file: &str, pdf_file: &str) -> Result<String> {
        // first pass
        let _ = Command::new(command)
            .current_dir(&self.path)
            .arg(tex_file)
            .output();

        // second pass
        let mut command = Command::new(command);
        command.arg(tex_file);
        self.run_command(command, "result.pdf", pdf_file)
    }

    /// generate an epub into given file name
    pub fn generate_epub(&mut self, command: &str, file: &str) -> Result<String> {
        let mut command = Command::new(command);
        command.arg("-X");
        command.arg("result.epub");
        self.run_command(command, "result.epub", file)
    }
}

impl Drop for Zipper {
    fn drop(&mut self) {
        if let Err(err) = fs::remove_dir_all(&self.path) {
            println!("Error in zipper: could not delete temporary directory {}, error: {}", self.path.to_string_lossy(), err);
        }
    }
}
<commit_msg>Make Zipper check that a path is in temporary dir before writing the file<commit_after>// Copyright (C) 2016 Élisabeth HENRY.
//
// This file is part of Crowbook.
//
// Crowbook is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published
// by the Free Software Foundation, either version 2.1 of the License, or
// (at your option) any later version.
//
// Caribon is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU Lesser General Public License for more details.
//
// You should have received ba copy of the GNU Lesser General Public License
// along with Crowbook.  If not, see <http://www.gnu.org/licenses/>.

use error::{Error,Result};

use std::env;
use std::path::{Path,PathBuf};
use std::io::Write;
use std::process::Command;
use std::fs::{self, File,DirBuilder};
use uuid;
use std::ops::Drop;

/// Struct used to create zip (using filesystem and zip command)
pub struct Zipper {
    args: Vec<String>,
    path: PathBuf,
}

impl Zipper {
    /// creates new zipper
    ///
    /// path: the path to a temporary directory (zipper will create a random dir in it and clean it later)
    /// inner_dirs: a vec of inner directory to create in this directory
    pub fn new(path: &str) -> Result<Zipper> {
        let uuid = uuid::Uuid::new_v4();
        let zipper_path = Path::new(path).join(uuid.to_simple_string());

        try!(DirBuilder::new()
             .recursive(true)
             .create(&zipper_path)
             .map_err(|_| Error::Zipper(format!("could not create temporary directory in {}", path))));

        Ok(Zipper {
            args: vec!(),
            path: zipper_path,
        })
    }

    /// writes a content to a temporary file
    pub fn write(&mut self, file: &str, content: &[u8], add_args: bool) -> Result<()> {
        let path = Path::new(file);
        if path.starts_with("..") || path.is_absolute() {
            return Err(Error::Zipper(format!("file {} refers to an absolute or a parent path.
This is forbidden because we are supposed to create a temporary file in a temporary dir.", file)));
        }
        let dest_file = self.path.join(path);
        let dest_dir = dest_file.parent().unwrap();
        if !fs::metadata(dest_dir).is_ok() { // dir does not exist, create it
            try!(DirBuilder::new()
                 .recursive(true)
                 .create(&dest_dir)
                 .map_err(|_| Error::Zipper(format!("could not create temporary directory in {}", dest_dir.display()))));
        }

        if let Ok(mut f) = File::create(&dest_file) {
            if f.write_all(content).is_ok() {
                if add_args {
                    self.args.push(String::from(file));
                }
                Ok(())
            } else {
                Err(Error::Zipper(format!("could not write to temporary file {}", file)))
            }
        } else {
            Err(Error::Zipper(format!("could not create temporary file {}", file)))
        }
    }

    /// Unzip a file and deletes it afterwards
    pub fn unzip(&mut self, file: &str) -> Result<()> {
        // change to dest directory to unzip file
        let dir = env::current_dir().unwrap();
        try!(env::set_current_dir(&self.path).map_err(|_| Error::Zipper("could not change current directory".to_owned())));
        let output = Command::new("unzip")
                      .arg(file)
                      .output()
                      .map_err(|e| Error::Zipper(format!("failed to execute unzip  on {}: {}", file, e)));

        // change back to original current directory before try! ing anything
        env::set_current_dir(dir).unwrap();
        try!(output);

        fs::remove_file(self.path.join(file))
            .map_err(|_| Error::Zipper(format!("failed to remove file {}", file)))
    }

    /// run command and copy file name (supposed to result from the command) to current dir
    pub fn run_command(&mut self, mut command: Command, in_file: &str, out_file: &str) -> Result<String> {
        let res_output = command.args(&self.args)
            .current_dir(&self.path)
            .output()
            .map_err(|e| Error::Zipper(format!("failed to execute process: {}", e)));
        let output = try!(res_output);
        try!(fs::copy(self.path.join(in_file), out_file).map_err(|_| {
            println!("{}", &String::from_utf8_lossy(&output.stdout));
            Error::Zipper(format!("could not copy file {} to {}", in_file, out_file))
        }));
        Ok(String::from_utf8_lossy(&output.stdout).into_owned())
    }

    /// zip all files in zipper's tmp dir to a given file name and return odt file
    pub fn generate_odt(&mut self, command: &str, odt_file: &str) -> Result<String> {
        let mut command = Command::new(command);
        command.arg("-r");
        command.arg("result.odt");
        command.arg(".");
        self.run_command(command, "result.odt", odt_file)
    }

    /// generate a pdf file into given file name
    pub fn generate_pdf(&mut self, command: &str, tex_file: &str, pdf_file: &str) -> Result<String> {
        // first pass
        let _ = Command::new(command)
            .current_dir(&self.path)
            .arg(tex_file)
            .output();

        // second pass
        let mut command = Command::new(command);
        command.arg(tex_file);
        self.run_command(command, "result.pdf", pdf_file)
    }

    /// generate an epub into given file name
    pub fn generate_epub(&mut self, command: &str, file: &str) -> Result<String> {
        let mut command = Command::new(command);
        command.arg("-X");
        command.arg("result.epub");
        self.run_command(command, "result.epub", file)
    }
}

impl Drop for Zipper {
    fn drop(&mut self) {
        if let Err(err) = fs::remove_dir_all(&self.path) {
            println!("Error in zipper: could not delete temporary directory {}, error: {}", self.path.to_string_lossy(), err);
        }
    }
}
<|endoftext|>


<commit_before>// Copyright 2016 The Rust Project Developers. See the COPYRIGHT
// file at the top-level directory of this distribution and at
// http://rust-lang.org/COPYRIGHT.
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.

//! Implementation of the install aspects of the compiler.
//!
//! This module is responsible for installing the standard library,
//! compiler, and documentation.

use std::env;
use std::fs;
use std::path::{Path, PathBuf, Component};
use std::process::Command;

use Build;
use dist::{sanitize_sh, tmpdir};

/// Installs everything.
pub fn install(build: &Build, stage: u32, host: &str) {
    let prefix_default = PathBuf::from("/usr/local");
    let docdir_default = PathBuf::from("share/doc/rust");
    let mandir_default = PathBuf::from("share/man");
    let libdir_default = PathBuf::from("lib");
    let prefix = build.config.prefix.as_ref().unwrap_or(&prefix_default);
    let docdir = build.config.docdir.as_ref().unwrap_or(&docdir_default);
    let libdir = build.config.libdir.as_ref().unwrap_or(&libdir_default);
    let mandir = build.config.mandir.as_ref().unwrap_or(&mandir_default);

    let docdir = prefix.join(docdir);
    let libdir = prefix.join(libdir);
    let mandir = prefix.join(mandir);

    let destdir = env::var_os("DESTDIR").map(PathBuf::from);

    let prefix = add_destdir(&prefix, &destdir);
    let docdir = add_destdir(&docdir, &destdir);
    let libdir = add_destdir(&libdir, &destdir);
    let mandir = add_destdir(&mandir, &destdir);

    let empty_dir = build.out.join("tmp/empty_dir");
    t!(fs::create_dir_all(&empty_dir));
    if build.config.docs {
        install_sh(&build, "docs", "rust-docs", stage, host, &prefix,
                   &docdir, &libdir, &mandir, &empty_dir);
    }
    install_sh(&build, "std", "rust-std", stage, host, &prefix,
               &docdir, &libdir, &mandir, &empty_dir);
    install_sh(&build, "rustc", "rustc", stage, host, &prefix,
               &docdir, &libdir, &mandir, &empty_dir);
    t!(fs::remove_dir_all(&empty_dir));
}

fn install_sh(build: &Build, package: &str, name: &str, stage: u32, host: &str,
              prefix: &Path, docdir: &Path, libdir: &Path, mandir: &Path, empty_dir: &Path) {
    println!("Install {} stage{} ({})", package, stage, host);
    let package_name = format!("{}-{}-{}", name, build.rust_package_vers(), host);

    let mut cmd = Command::new("sh");
    cmd.current_dir(empty_dir)
       .arg(sanitize_sh(&tmpdir(build).join(&package_name).join("install.sh")))
       .arg(format!("--prefix={}", sanitize_sh(prefix)))
       .arg(format!("--docdir={}", sanitize_sh(docdir)))
       .arg(format!("--libdir={}", sanitize_sh(libdir)))
       .arg(format!("--mandir={}", sanitize_sh(mandir)))
       .arg("--disable-ldconfig");
    build.run(&mut cmd);
}

fn add_destdir(path: &Path, destdir: &Option<PathBuf>) -> PathBuf {
    let mut ret = match *destdir {
        Some(ref dest) => dest.clone(),
        None => return path.to_path_buf(),
    };
    for part in path.components() {
        match part {
            Component::Normal(s) => ret.push(s),
            _ => {}
        }
    }
    return ret
}
<commit_msg>rustbuild: Add save-analysis to install<commit_after>// Copyright 2016 The Rust Project Developers. See the COPYRIGHT
// file at the top-level directory of this distribution and at
// http://rust-lang.org/COPYRIGHT.
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.

//! Implementation of the install aspects of the compiler.
//!
//! This module is responsible for installing the standard library,
//! compiler, and documentation.

use std::env;
use std::fs;
use std::path::{Path, PathBuf, Component};
use std::process::Command;

use Build;
use dist::{sanitize_sh, tmpdir};

/// Installs everything.
pub fn install(build: &Build, stage: u32, host: &str) {
    let prefix_default = PathBuf::from("/usr/local");
    let docdir_default = PathBuf::from("share/doc/rust");
    let mandir_default = PathBuf::from("share/man");
    let libdir_default = PathBuf::from("lib");
    let prefix = build.config.prefix.as_ref().unwrap_or(&prefix_default);
    let docdir = build.config.docdir.as_ref().unwrap_or(&docdir_default);
    let libdir = build.config.libdir.as_ref().unwrap_or(&libdir_default);
    let mandir = build.config.mandir.as_ref().unwrap_or(&mandir_default);

    let docdir = prefix.join(docdir);
    let libdir = prefix.join(libdir);
    let mandir = prefix.join(mandir);

    let destdir = env::var_os("DESTDIR").map(PathBuf::from);

    let prefix = add_destdir(&prefix, &destdir);
    let docdir = add_destdir(&docdir, &destdir);
    let libdir = add_destdir(&libdir, &destdir);
    let mandir = add_destdir(&mandir, &destdir);

    let empty_dir = build.out.join("tmp/empty_dir");
    t!(fs::create_dir_all(&empty_dir));
    if build.config.docs {
        install_sh(&build, "docs", "rust-docs", stage, host, &prefix,
                   &docdir, &libdir, &mandir, &empty_dir);
    }
    if build.config.rust_save_analysis {
        install_sh(&build, "analysis", "rust-analysis", stage, host, &prefix,
                   &docdir, &libdir, &mandir, &empty_dir);
    }
    install_sh(&build, "std", "rust-std", stage, host, &prefix,
               &docdir, &libdir, &mandir, &empty_dir);
    install_sh(&build, "rustc", "rustc", stage, host, &prefix,
               &docdir, &libdir, &mandir, &empty_dir);
    t!(fs::remove_dir_all(&empty_dir));
}

fn install_sh(build: &Build, package: &str, name: &str, stage: u32, host: &str,
              prefix: &Path, docdir: &Path, libdir: &Path, mandir: &Path, empty_dir: &Path) {
    println!("Install {} stage{} ({})", package, stage, host);
    let package_name = format!("{}-{}-{}", name, build.rust_package_vers(), host);

    let mut cmd = Command::new("sh");
    cmd.current_dir(empty_dir)
       .arg(sanitize_sh(&tmpdir(build).join(&package_name).join("install.sh")))
       .arg(format!("--prefix={}", sanitize_sh(prefix)))
       .arg(format!("--docdir={}", sanitize_sh(docdir)))
       .arg(format!("--libdir={}", sanitize_sh(libdir)))
       .arg(format!("--mandir={}", sanitize_sh(mandir)))
       .arg("--disable-ldconfig");
    build.run(&mut cmd);
}

fn add_destdir(path: &Path, destdir: &Option<PathBuf>) -> PathBuf {
    let mut ret = match *destdir {
        Some(ref dest) => dest.clone(),
        None => return path.to_path_buf(),
    };
    for part in path.components() {
        match part {
            Component::Normal(s) => ret.push(s),
            _ => {}
        }
    }
    return ret
}
<|endoftext|>