<commit_before>use std::str;
use std::kinds::marker;

use {raw, Oid, Repository, Error};

/// A structure to represent a git [submodule][1]
///
/// [1]: http://git-scm.com/book/en/Git-Tools-Submodules
pub struct Submodule<'a> {
    raw: *mut raw::git_submodule,
    marker1: marker::ContravariantLifetime<'a>,
    marker2: marker::NoSend,
    marker3: marker::NoSync,
}

impl<'a> Submodule<'a> {
    /// Create a new object from its raw component.
    ///
    /// This method is unsafe as there is no guarantee that `raw` is a valid
    /// pointer.
    pub unsafe fn from_raw(_repo: &Repository,
                           raw: *mut raw::git_submodule) -> Submodule {
        Submodule {
            raw: raw,
            marker1: marker::ContravariantLifetime,
            marker2: marker::NoSend,
            marker3: marker::NoSync,
        }
    }

    /// Set up a new git submodule for checkout.
    ///
    /// This does "git submodule add" up to the fetch and checkout of the
    /// submodule contents. It preps a new submodule, creates an entry in
    /// `.gitmodules` and creates an empty initialized repository either at the
    /// given path in the working directory or in `.git/modules` with a gitlink
    /// from the working directory to the new repo.
    ///
    /// To fully emulate "git submodule add" call this function, then `open()`
    /// the submodule repo and perform the clone step as needed. Lastly, call
    /// `finalize()` to wrap up adding the new submodule and `.gitmodules` to
    /// the index to be ready to commit.
    pub fn new<'a>(repo: &'a Repository, url: &str, path: &Path,
                   use_gitlink: bool) -> Result<Submodule<'a>, Error> {
        let mut raw = 0 as *mut raw::git_submodule;
        unsafe {
            try_call!(raw::git_submodule_add_setup(&mut raw, repo.raw(),
                                                   url.to_c_str(),
                                                   path.to_c_str(),
                                                   use_gitlink));
            Ok(Submodule::from_raw(repo, raw))
        }
    }

    /// Lookup submodule information by name or path.
    ///
    /// Given either the submodule name or path (they are usually the same),
    /// this returns a structure describing the submodule.
    pub fn lookup<'a>(repo: &'a Repository, name: &str)
                      -> Result<Submodule<'a>, Error> {
        let mut raw = 0 as *mut raw::git_submodule;
        unsafe {
            try_call!(raw::git_submodule_lookup(&mut raw, repo.raw(),
                                                name.to_c_str()));
            Ok(Submodule::from_raw(repo, raw))
        }
    }

    /// Get the submodule's branch.
    ///
    /// Returns `None` if the branch is not valid utf-8 or if the branch is not
    /// yet available.
    pub fn branch(&self) -> Option<&str> {
        self.branch_bytes().and_then(str::from_utf8)
    }

    /// Get the branch for the submodule.
    ///
    /// Returns `None` if the branch is not yet available.
    pub fn branch_bytes(&self) -> Option<&[u8]> {
        unsafe {
            ::opt_bytes(self, raw::git_submodule_branch(self.raw))
        }
    }

    /// Get the submodule's url.
    ///
    /// Returns `None` if the url is not valid utf-8
    pub fn url(&self) -> Option<&str> { str::from_utf8(self.url_bytes()) }

    /// Get the url for the submodule.
    pub fn url_bytes(&self) -> &[u8] {
        unsafe {
            ::opt_bytes(self, raw::git_submodule_url(self.raw)).unwrap()
        }
    }

    /// Get the submodule's name.
    ///
    /// Returns `None` if the name is not valid utf-8
    pub fn name(&self) -> Option<&str> { str::from_utf8(self.name_bytes()) }

    /// Get the name for the submodule.
    pub fn name_bytes(&self) -> &[u8] {
        unsafe {
            ::opt_bytes(self, raw::git_submodule_name(self.raw)).unwrap()
        }
    }

    /// Get the path for the submodule.
    pub fn path(&self) -> Path {
        let bytes = unsafe {
            ::opt_bytes(self, raw::git_submodule_name(self.raw)).unwrap()
        };
        Path::new(bytes)
    }

    /// Get the OID for the submodule in the current HEAD tree.
    pub fn head_id(&self) -> Option<Oid> {
        unsafe {
            let ptr = raw::git_submodule_head_id(self.raw);
            if ptr.is_null() {
                None
            } else {
                Some(Oid::from_raw(ptr))
            }
        }
    }

    /// Get the OID for the submodule in the index.
    pub fn index_id(&self) -> Option<Oid> {
        unsafe {
            let ptr = raw::git_submodule_index_id(self.raw);
            if ptr.is_null() {
                None
            } else {
                Some(Oid::from_raw(ptr))
            }
        }
    }

    /// Get the OID for the submodule in the current working directory.
    ///
    /// This returns the OID that corresponds to looking up 'HEAD' in the
    /// checked out submodule. If there are pending changes in the index or
    /// anything else, this won't notice that.
    pub fn workdir_id(&self) -> Option<Oid> {
        unsafe {
            let ptr = raw::git_submodule_wd_id(self.raw);
            if ptr.is_null() {
                None
            } else {
                Some(Oid::from_raw(ptr))
            }
        }
    }

    /// Copy submodule info into ".git/config" file.
    ///
    /// Just like "git submodule init", this copies information about the
    /// submodule into ".git/config". You can use the accessor functions above
    /// to alter the in-memory git_submodule object and control what is written
    /// to the config, overriding what is in .gitmodules.
    ///
    /// By default, existing entries will not be overwritten, but passing `true`
    /// for `overwrite` forces them to be updated.
    pub fn init(&mut self, overwrite: bool) -> Result<(), Error> {
        unsafe {
            try_call!(raw::git_submodule_init(self.raw, overwrite));
        }
        Ok(())
    }

    /// Open the repository for a submodule.
    ///
    /// This will only work if the submodule is checked out into the working
    /// directory.
    pub fn open(&self) -> Result<Repository, Error> {
        let mut raw = 0 as *mut raw::git_repository;
        unsafe {
            try_call!(raw::git_submodule_open(&mut raw, self.raw));
        }
        Ok(unsafe { Repository::from_raw(raw) })
    }

    /// Access the underlying raw git submodule pointer.
    pub fn raw(&self) -> *mut raw::git_submodule { self.raw }

    /// Reread submodule info from config, index, and HEAD.
    ///
    /// Call this to reread cached submodule information for this submodule if
    /// you have reason to believe that it has changed.
    ///
    /// If `force` is `true`, then data will be reloaded even if it doesn't seem
    /// out of date
    pub fn reload(&mut self, force: bool) -> Result<(), Error> {
        unsafe {
            try_call!(raw::git_submodule_reload(self.raw, force));
        }
        Ok(())
    }

    /// Write submodule settings to .gitmodules file.
    ///
    /// This commits any in-memory changes to the submodule to the gitmodules
    /// file on disk. You may also be interested in `init()` which
    /// writes submodule info to ".git/config" (which is better for local
    /// changes to submodule settings) and/or `sync()` which writes
    /// settings about remotes to the actual submodule repository.
    pub fn save(&mut self) -> Result<(), Error> {
        unsafe { try_call!(raw::git_submodule_save(self.raw)); }
        Ok(())
    }

    /// Copy submodule remote info into submodule repo.
    ///
    /// This copies the information about the submodules URL into the checked
    /// out submodule config, acting like "git submodule sync". This is useful
    /// if you have altered the URL for the submodule (or it has been altered
    /// by a fetch of upstream changes) and you need to update your local repo.
    pub fn sync(&mut self) -> Result<(), Error> {
        unsafe { try_call!(raw::git_submodule_sync(self.raw)); }
        Ok(())
    }
}

#[unsafe_destructor]
impl<'a> Drop for Submodule<'a> {
    fn drop(&mut self) {
        unsafe { raw::git_submodule_free(self.raw) }
    }
}

#[cfg(test)]
mod tests {
    use {Submodule, Repository};
    use std::io::TempDir;

    #[test]
    fn smoke() {
        let td = TempDir::new("test").unwrap();
        let repo = Repository::init(td.path()).unwrap();
        let mut s1 = Submodule::new(&repo, "/path/to/nowhere",
                                    &Path::new("foo"), true).unwrap();
        s1.init(false).unwrap();
        let s2 = Submodule::new(&repo, "/path/to/nowhere",
                                &Path::new("bar"), true).unwrap();
        drop((s1, s2));

        let mut submodules = repo.submodules().unwrap();
        assert_eq!(submodules.len(), 2);
        let mut s = submodules.remove(0).unwrap();
        assert_eq!(s.name(), Some("bar"));
        assert_eq!(s.url(), Some("/path/to/nowhere"));
        assert_eq!(s.branch(), None);
        assert!(s.head_id().is_none());
        assert!(s.index_id().is_none());
        assert!(s.workdir_id().is_none());

        Submodule::lookup(&repo, "bar").unwrap();
        s.open().unwrap();
        assert!(s.path() == Path::new("bar"));
        s.reload(true).unwrap();
    }
}
<commit_msg>Use the correct method...<commit_after>use std::str;
use std::kinds::marker;

use {raw, Oid, Repository, Error};

/// A structure to represent a git [submodule][1]
///
/// [1]: http://git-scm.com/book/en/Git-Tools-Submodules
pub struct Submodule<'a> {
    raw: *mut raw::git_submodule,
    marker1: marker::ContravariantLifetime<'a>,
    marker2: marker::NoSend,
    marker3: marker::NoSync,
}

impl<'a> Submodule<'a> {
    /// Create a new object from its raw component.
    ///
    /// This method is unsafe as there is no guarantee that `raw` is a valid
    /// pointer.
    pub unsafe fn from_raw(_repo: &Repository,
                           raw: *mut raw::git_submodule) -> Submodule {
        Submodule {
            raw: raw,
            marker1: marker::ContravariantLifetime,
            marker2: marker::NoSend,
            marker3: marker::NoSync,
        }
    }

    /// Set up a new git submodule for checkout.
    ///
    /// This does "git submodule add" up to the fetch and checkout of the
    /// submodule contents. It preps a new submodule, creates an entry in
    /// `.gitmodules` and creates an empty initialized repository either at the
    /// given path in the working directory or in `.git/modules` with a gitlink
    /// from the working directory to the new repo.
    ///
    /// To fully emulate "git submodule add" call this function, then `open()`
    /// the submodule repo and perform the clone step as needed. Lastly, call
    /// `finalize()` to wrap up adding the new submodule and `.gitmodules` to
    /// the index to be ready to commit.
    pub fn new<'a>(repo: &'a Repository, url: &str, path: &Path,
                   use_gitlink: bool) -> Result<Submodule<'a>, Error> {
        let mut raw = 0 as *mut raw::git_submodule;
        unsafe {
            try_call!(raw::git_submodule_add_setup(&mut raw, repo.raw(),
                                                   url.to_c_str(),
                                                   path.to_c_str(),
                                                   use_gitlink));
            Ok(Submodule::from_raw(repo, raw))
        }
    }

    /// Lookup submodule information by name or path.
    ///
    /// Given either the submodule name or path (they are usually the same),
    /// this returns a structure describing the submodule.
    pub fn lookup<'a>(repo: &'a Repository, name: &str)
                      -> Result<Submodule<'a>, Error> {
        let mut raw = 0 as *mut raw::git_submodule;
        unsafe {
            try_call!(raw::git_submodule_lookup(&mut raw, repo.raw(),
                                                name.to_c_str()));
            Ok(Submodule::from_raw(repo, raw))
        }
    }

    /// Get the submodule's branch.
    ///
    /// Returns `None` if the branch is not valid utf-8 or if the branch is not
    /// yet available.
    pub fn branch(&self) -> Option<&str> {
        self.branch_bytes().and_then(str::from_utf8)
    }

    /// Get the branch for the submodule.
    ///
    /// Returns `None` if the branch is not yet available.
    pub fn branch_bytes(&self) -> Option<&[u8]> {
        unsafe {
            ::opt_bytes(self, raw::git_submodule_branch(self.raw))
        }
    }

    /// Get the submodule's url.
    ///
    /// Returns `None` if the url is not valid utf-8
    pub fn url(&self) -> Option<&str> { str::from_utf8(self.url_bytes()) }

    /// Get the url for the submodule.
    pub fn url_bytes(&self) -> &[u8] {
        unsafe {
            ::opt_bytes(self, raw::git_submodule_url(self.raw)).unwrap()
        }
    }

    /// Get the submodule's name.
    ///
    /// Returns `None` if the name is not valid utf-8
    pub fn name(&self) -> Option<&str> { str::from_utf8(self.name_bytes()) }

    /// Get the name for the submodule.
    pub fn name_bytes(&self) -> &[u8] {
        unsafe {
            ::opt_bytes(self, raw::git_submodule_name(self.raw)).unwrap()
        }
    }

    /// Get the path for the submodule.
    pub fn path(&self) -> Path {
        let bytes = unsafe {
            ::opt_bytes(self, raw::git_submodule_path(self.raw)).unwrap()
        };
        Path::new(bytes)
    }

    /// Get the OID for the submodule in the current HEAD tree.
    pub fn head_id(&self) -> Option<Oid> {
        unsafe {
            let ptr = raw::git_submodule_head_id(self.raw);
            if ptr.is_null() {
                None
            } else {
                Some(Oid::from_raw(ptr))
            }
        }
    }

    /// Get the OID for the submodule in the index.
    pub fn index_id(&self) -> Option<Oid> {
        unsafe {
            let ptr = raw::git_submodule_index_id(self.raw);
            if ptr.is_null() {
                None
            } else {
                Some(Oid::from_raw(ptr))
            }
        }
    }

    /// Get the OID for the submodule in the current working directory.
    ///
    /// This returns the OID that corresponds to looking up 'HEAD' in the
    /// checked out submodule. If there are pending changes in the index or
    /// anything else, this won't notice that.
    pub fn workdir_id(&self) -> Option<Oid> {
        unsafe {
            let ptr = raw::git_submodule_wd_id(self.raw);
            if ptr.is_null() {
                None
            } else {
                Some(Oid::from_raw(ptr))
            }
        }
    }

    /// Copy submodule info into ".git/config" file.
    ///
    /// Just like "git submodule init", this copies information about the
    /// submodule into ".git/config". You can use the accessor functions above
    /// to alter the in-memory git_submodule object and control what is written
    /// to the config, overriding what is in .gitmodules.
    ///
    /// By default, existing entries will not be overwritten, but passing `true`
    /// for `overwrite` forces them to be updated.
    pub fn init(&mut self, overwrite: bool) -> Result<(), Error> {
        unsafe {
            try_call!(raw::git_submodule_init(self.raw, overwrite));
        }
        Ok(())
    }

    /// Open the repository for a submodule.
    ///
    /// This will only work if the submodule is checked out into the working
    /// directory.
    pub fn open(&self) -> Result<Repository, Error> {
        let mut raw = 0 as *mut raw::git_repository;
        unsafe {
            try_call!(raw::git_submodule_open(&mut raw, self.raw));
        }
        Ok(unsafe { Repository::from_raw(raw) })
    }

    /// Access the underlying raw git submodule pointer.
    pub fn raw(&self) -> *mut raw::git_submodule { self.raw }

    /// Reread submodule info from config, index, and HEAD.
    ///
    /// Call this to reread cached submodule information for this submodule if
    /// you have reason to believe that it has changed.
    ///
    /// If `force` is `true`, then data will be reloaded even if it doesn't seem
    /// out of date
    pub fn reload(&mut self, force: bool) -> Result<(), Error> {
        unsafe {
            try_call!(raw::git_submodule_reload(self.raw, force));
        }
        Ok(())
    }

    /// Write submodule settings to .gitmodules file.
    ///
    /// This commits any in-memory changes to the submodule to the gitmodules
    /// file on disk. You may also be interested in `init()` which
    /// writes submodule info to ".git/config" (which is better for local
    /// changes to submodule settings) and/or `sync()` which writes
    /// settings about remotes to the actual submodule repository.
    pub fn save(&mut self) -> Result<(), Error> {
        unsafe { try_call!(raw::git_submodule_save(self.raw)); }
        Ok(())
    }

    /// Copy submodule remote info into submodule repo.
    ///
    /// This copies the information about the submodules URL into the checked
    /// out submodule config, acting like "git submodule sync". This is useful
    /// if you have altered the URL for the submodule (or it has been altered
    /// by a fetch of upstream changes) and you need to update your local repo.
    pub fn sync(&mut self) -> Result<(), Error> {
        unsafe { try_call!(raw::git_submodule_sync(self.raw)); }
        Ok(())
    }
}

#[unsafe_destructor]
impl<'a> Drop for Submodule<'a> {
    fn drop(&mut self) {
        unsafe { raw::git_submodule_free(self.raw) }
    }
}

#[cfg(test)]
mod tests {
    use {Submodule, Repository};
    use std::io::TempDir;

    #[test]
    fn smoke() {
        let td = TempDir::new("test").unwrap();
        let repo = Repository::init(td.path()).unwrap();
        let mut s1 = Submodule::new(&repo, "/path/to/nowhere",
                                    &Path::new("foo"), true).unwrap();
        s1.init(false).unwrap();
        let s2 = Submodule::new(&repo, "/path/to/nowhere",
                                &Path::new("bar"), true).unwrap();
        drop((s1, s2));

        let mut submodules = repo.submodules().unwrap();
        assert_eq!(submodules.len(), 2);
        let mut s = submodules.remove(0).unwrap();
        assert_eq!(s.name(), Some("bar"));
        assert_eq!(s.url(), Some("/path/to/nowhere"));
        assert_eq!(s.branch(), None);
        assert!(s.head_id().is_none());
        assert!(s.index_id().is_none());
        assert!(s.workdir_id().is_none());

        Submodule::lookup(&repo, "bar").unwrap();
        s.open().unwrap();
        assert!(s.path() == Path::new("bar"));
        s.reload(true).unwrap();
    }
}
<|endoftext|>


<commit_before>//!
//!	This module is responsible for reading mappability information produced using tallymer.
//!
use std::io::prelude::*;
use std::ffi::OsString;
use std::io::Result;
use std::io::Error;
use std::io::ErrorKind;
use std::io::Lines;
use std::collections::BTreeMap;
use std::mem::swap;

/// Specifies if a position is unmappable in each strand.
#[derive(Copy, Clone)]
pub struct UnMapPosition {
    pub plus: bool,
    pub minus: bool,
}

pub struct UnMap<R: BufRead> {
    data: BTreeMap<u64, UnMapPosition>, // map from position to pair (unmappable in plus, unmappable in minus)
    data_next: BTreeMap<u64, UnMapPosition>, // will contain the data for the next sequence, if any
    lines: Lines<R>,
    seqnumber: u64,
    next_seqnumber: u64,
}

fn parse_line(bytes: Vec<u8>) -> Result<(u64, u64, bool)> {
    let mut seq_idx = 0u64;
    let mut pos = 0u64;
    let mut idx = 0;

    while bytes[idx] >= b'0' && bytes[idx] <= b'9' {
        seq_idx = seq_idx * 10 + (bytes[idx] - b'0') as u64;
        idx += 1;
    }

    if bytes[idx] != b'\t' {
        return Err(Error::new(ErrorKind::Other, "unexpected byte in tallymer line"));
    }

    // parse sign
    idx += 1;
    let is_minus = match bytes[idx] {
        b'-' => true,
        b'+' => false,
        _ => return Err(Error::new(ErrorKind::Other, "unexpected byte in tallymer line"))
    };
    idx += 1;

    // parse position
    while bytes[idx] >= b'0' && bytes[idx] <= b'9' {
        pos = pos * 10 + (bytes[idx] - b'0') as u64;
        idx += 1;
    }

    Ok((seq_idx, pos, is_minus))
}

impl<R: BufRead> UnMap<R> {

	/// Open mappability file, pre-loading data for the first sequence
	pub fn open(reader: R) -> Result<UnMap<R>> {
        let mut result = UnMap {
            data: BTreeMap::new(),
            data_next: BTreeMap::new(),
            lines: reader.lines(),
            seqnumber: 0,
            next_seqnumber: 0,
        };
        try!(result.read_sequence_data());

        return Ok(result);
	}

    fn insert_value(map: &mut BTreeMap<u64, UnMapPosition>, pos: u64, is_minus: bool) {
        if let Some(entry) = map.get_mut(&pos) {
            if is_minus {
                entry.minus = true;
            } else {
                entry.plus = true;
            }
            return;
        }

        // else (not written as an else to satisfy the borrow checker!)
        map.insert(pos, UnMapPosition { plus: !is_minus, minus: is_minus });
    }

    fn read_sequence_data(&mut self) -> Result<()> {
        // read until either no more data or if find a row for the next sequence
        while let Some(line) = self.lines.next() {
            let line = try!(line);
            let (seq, pos, is_minus) = try!(parse_line(line.into_bytes()));

            if seq == self.seqnumber {
                Self::insert_value(&mut self.data, pos, is_minus);
            } else {
                Self::insert_value(&mut self.data_next, pos, is_minus);
                self.next_seqnumber = seq;
                break;
            }
        }
        Ok(())
    }

	/// Read the information for the next sequence from disk
	pub fn read_next_sequence(&mut self) -> Result<()> {
        // if we haven't reached the next sequence number yet
		if self.next_seqnumber > self.seqnumber + 1 {
            self.seqnumber = self.seqnumber + 1;
            self.data.clear();
            return Ok(());
        } else if self.seqnumber == self.next_seqnumber {
            // there is no more data to read
            self.data.clear();
            return Ok(());
        }
        self.seqnumber = self.next_seqnumber;

        // prepare
        swap(&mut self.data, &mut self.data_next);
        self.data_next.clear();

        // read until either no more data or if find a row for the next sequence
        try!(self.read_sequence_data());

        Ok(())
    }

    /// Query position
    ///
    /// Returns a pair of boolean values, which are true if the plus or minus strand read, respectively, is mappable at that position.
    pub fn is_unmappable(&self, position: u64) -> UnMapPosition {
        match self.data.get(&position) {
            Some(&result) => result,
            None => UnMapPosition { plus: false, minus: false }, // if not in map, then it's not unmappable
        }
    }
}
<commit_msg>Bug fix: missing guard on tallymer line parser<commit_after>//!
//!	This module is responsible for reading mappability information produced using tallymer.
//!
use std::io::prelude::*;
use std::ffi::OsString;
use std::io::Result;
use std::io::Error;
use std::io::ErrorKind;
use std::io::Lines;
use std::collections::BTreeMap;
use std::mem::swap;

/// Specifies if a position is unmappable in each strand.
#[derive(Copy, Clone)]
pub struct UnMapPosition {
    pub plus: bool,
    pub minus: bool,
}

pub struct UnMap<R: BufRead> {
    data: BTreeMap<u64, UnMapPosition>, // map from position to pair (unmappable in plus, unmappable in minus)
    data_next: BTreeMap<u64, UnMapPosition>, // will contain the data for the next sequence, if any
    lines: Lines<R>,
    seqnumber: u64,
    next_seqnumber: u64,
}

fn parse_line(bytes: Vec<u8>) -> Result<(u64, u64, bool)> {
    let mut seq_idx = 0u64;
    let mut pos = 0u64;
    let mut idx = 0;

    while bytes[idx] >= b'0' && bytes[idx] <= b'9' {
        seq_idx = seq_idx * 10 + (bytes[idx] - b'0') as u64;
        idx += 1;
    }

    if bytes[idx] != b'\t' {
        return Err(Error::new(ErrorKind::Other, "unexpected byte in tallymer line"));
    }

    // parse sign
    idx += 1;
    let is_minus = match bytes[idx] {
        b'-' => true,
        b'+' => false,
        _ => return Err(Error::new(ErrorKind::Other, "unexpected byte in tallymer line"))
    };
    idx += 1;

    // parse position
    while idx < bytes.len() && bytes[idx] >= b'0' && bytes[idx] <= b'9' {
        pos = pos * 10 + (bytes[idx] - b'0') as u64;
        idx += 1;
    }

    Ok((seq_idx, pos, is_minus))
}

impl<R: BufRead> UnMap<R> {

	/// Open mappability file, pre-loading data for the first sequence
	pub fn open(reader: R) -> Result<UnMap<R>> {
        let mut result = UnMap {
            data: BTreeMap::new(),
            data_next: BTreeMap::new(),
            lines: reader.lines(),
            seqnumber: 0,
            next_seqnumber: 0,
        };
        try!(result.read_sequence_data());

        return Ok(result);
	}

    fn insert_value(map: &mut BTreeMap<u64, UnMapPosition>, pos: u64, is_minus: bool) {
        if let Some(entry) = map.get_mut(&pos) {
            if is_minus {
                entry.minus = true;
            } else {
                entry.plus = true;
            }
            return;
        }

        // else (not written as an else to satisfy the borrow checker!)
        map.insert(pos, UnMapPosition { plus: !is_minus, minus: is_minus });
    }

    fn read_sequence_data(&mut self) -> Result<()> {
        // read until either no more data or if find a row for the next sequence
        while let Some(line) = self.lines.next() {
            let line = try!(line);
            let (seq, pos, is_minus) = try!(parse_line(line.into_bytes()));

            if seq == self.seqnumber {
                Self::insert_value(&mut self.data, pos, is_minus);
            } else {
                Self::insert_value(&mut self.data_next, pos, is_minus);
                self.next_seqnumber = seq;
                break;
            }
        }
        Ok(())
    }

	/// Read the information for the next sequence from disk
	pub fn read_next_sequence(&mut self) -> Result<()> {
        // if we haven't reached the next sequence number yet
		if self.next_seqnumber > self.seqnumber + 1 {
            self.seqnumber = self.seqnumber + 1;
            self.data.clear();
            return Ok(());
        } else if self.seqnumber == self.next_seqnumber {
            // there is no more data to read
            self.data.clear();
            return Ok(());
        }
        self.seqnumber = self.next_seqnumber;

        // prepare
        swap(&mut self.data, &mut self.data_next);
        self.data_next.clear();

        // read until either no more data or if find a row for the next sequence
        try!(self.read_sequence_data());

        Ok(())
    }

    /// Query position
    ///
    /// Returns a pair of boolean values, which are true if the plus or minus strand read, respectively, is mappable at that position.
    pub fn is_unmappable(&self, position: u64) -> UnMapPosition {
        match self.data.get(&position) {
            Some(&result) => result,
            None => UnMapPosition { plus: false, minus: false }, // if not in map, then it's not unmappable
        }
    }
}
<|endoftext|>


<commit_before>use piston_window::{Context, G2d};
use piston_window::types::Color;
use rand::{Rng, thread_rng};

use colors::*;
use game::{Renderer, render_square_in_grid};
use settings::WIDTH_IN_BLOCKS;

pub fn create_random_piece() -> Piece {
    let all_tetrominos = [&I, &J, &L, &O, &S, &T, &Z];
    let ptype = thread_rng().choose(&all_tetrominos).unwrap();
    Piece::create(*ptype)
}

pub static I: Tetromino = Tetromino {
    configurations: [
        [Block{x:0, y:1}, Block{x:1, y:1}, Block{x:2, y:1}, Block{x:3, y:1}],
        [Block{x:2, y:0}, Block{x:2, y:1}, Block{x:2, y:2}, Block{x:2, y:3}],
        [Block{x:0, y:2}, Block{x:1, y:2}, Block{x:2, y:2}, Block{x:3, y:2}],
        [Block{x:1, y:0}, Block{x:1, y:1}, Block{x:1, y:2}, Block{x:1, y:3}]
    ],
    color: CYAN
};

pub static J: Tetromino = Tetromino {
    configurations: [
        [Block{x:0, y:0}, Block{x:0, y:1}, Block{x:1, y:1}, Block{x:2, y:1}],
        [Block{x:2, y:0}, Block{x:1, y:0}, Block{x:1, y:1}, Block{x:1, y:2}],
        [Block{x:2, y:2}, Block{x:2, y:1}, Block{x:1, y:1}, Block{x:0, y:1}],
        [Block{x:0, y:2}, Block{x:1, y:2}, Block{x:1, y:1}, Block{x:1, y:0}]
    ],
    color: BLUE
};

pub static L: Tetromino = Tetromino {
    configurations: [
        [Block{x:2, y:0}, Block{x:2, y:1}, Block{x:1, y:1}, Block{x:0, y:1}],
        [Block{x:2, y:2}, Block{x:1, y:2}, Block{x:1, y:1}, Block{x:1, y:0}],
        [Block{x:0, y:2}, Block{x:0, y:1}, Block{x:1, y:1}, Block{x:2, y:1}],
        [Block{x:0, y:0}, Block{x:1, y:0}, Block{x:1, y:1}, Block{x:1, y:2}]
    ],
    color: ORANGE
};

pub static O: Tetromino = Tetromino {
    configurations: [
        [Block{x:1, y:0}, Block{x:1, y:1}, Block{x:2, y:0}, Block{x:2, y:1}],
        [Block{x:1, y:0}, Block{x:1, y:1}, Block{x:2, y:0}, Block{x:2, y:1}],
        [Block{x:1, y:0}, Block{x:1, y:1}, Block{x:2, y:0}, Block{x:2, y:1}],
        [Block{x:1, y:0}, Block{x:1, y:1}, Block{x:2, y:0}, Block{x:2, y:1}]
    ],
    color: YELLOW
};

pub static S: Tetromino = Tetromino {
    configurations: [
        [Block{x:0, y:1}, Block{x:1, y:1}, Block{x:1, y:0}, Block{x:2, y:0}],
        [Block{x:1, y:0}, Block{x:1, y:1}, Block{x:2, y:1}, Block{x:2, y:2}],
        [Block{x:2, y:1}, Block{x:1, y:1}, Block{x:1, y:2}, Block{x:0, y:2}],
        [Block{x:1, y:2}, Block{x:1, y:1}, Block{x:0, y:1}, Block{x:0, y:0}]
    ],
    color: LIME
};

pub static T: Tetromino = Tetromino {
    configurations: [
        [Block{x:1, y:0}, Block{x:0, y:1}, Block{x:1, y:1}, Block{x:2, y:1}],
        [Block{x:2, y:1}, Block{x:1, y:0}, Block{x:1, y:1}, Block{x:1, y:2}],
        [Block{x:1, y:2}, Block{x:2, y:1}, Block{x:1, y:1}, Block{x:0, y:1}],
        [Block{x:0, y:1}, Block{x:1, y:0}, Block{x:1, y:1}, Block{x:1, y:2}]
    ],
    color: PURPLE
};

pub static Z: Tetromino = Tetromino {
    configurations: [
        [Block{x:0, y:0}, Block{x:1, y:0}, Block{x:1, y:1}, Block{x:2, y:1}],
        [Block{x:2, y:0}, Block{x:2, y:1}, Block{x:1, y:1}, Block{x:1, y:2}],
        [Block{x:2, y:2}, Block{x:1, y:2}, Block{x:1, y:1}, Block{x:0, y:1}],
        [Block{x:0, y:2}, Block{x:0, y:1}, Block{x:1, y:1}, Block{x:1, y:0}]
    ],
    color: RED
};

pub type Configuration = [Block; 4];
type Rotation = usize;

pub struct Piece {
    // TODO: Replace with position
    // TODO: Make this private again
    pub x: i32,
    pub y: i32,
    ptype: &'static Tetromino,
    rotation: Rotation
}
impl Piece {
    pub fn create(ptype: &'static Tetromino) -> Piece {
        let x = (WIDTH_IN_BLOCKS as i32 / 2) - 2;
        Piece::new(x, 0, ptype, 0)
    }

    fn new(x: i32, y: i32, ptype: &'static Tetromino, rotation: Rotation) -> Piece {
        Piece {
            x: x,
            y: y,
            ptype: ptype,
            rotation: rotation
        }
    }

    pub fn get_blocks(&self) -> &Configuration {
        self.ptype.get_configuration(self.rotation)
    }

    pub fn rotated(&self) -> Self {
        let rotation = (self.rotation + 1) % self.ptype.configurations.len();
        Self::new(self.x, self.y, self.ptype, rotation)
    }

    pub fn moved(&self, direction: Direction) -> Self {
        let translated_x = match direction {
            Direction::Left => self.x - 1,
            Direction::Right => self.x + 1
        };
        Self::new(translated_x, self.y, self.ptype, self.rotation)
    }

    pub fn is_out_of_bounds(&self) -> bool {
        self.get_blocks().iter().any(|block| {
            let x = self.x + block.x;
            x < 0 || x >= WIDTH_IN_BLOCKS as i32
        })
    }

    pub fn wall_kick(&self) -> Self {
        let mut translation = 0;
        let min_block = self.get_blocks().iter().min_by_key(|block| self.x + block.x).unwrap();
        let max_block = self.get_blocks().iter().max_by_key(|block| self.x + block.x).unwrap();
        if self.x + min_block.x < 0 {
            translation = self.x + min_block.x;
        } else if self.x + max_block.x >= WIDTH_IN_BLOCKS as i32 {
            translation = (self.x + max_block.x) - (WIDTH_IN_BLOCKS - 1) as i32;
        }
        Self::new(self.x - translation, self.y, self.ptype, self.rotation)
    }

    fn get_color(&self) -> Color {
        self.ptype.color
    }
}
impl Renderer for Piece {
    fn render(&self, context: Context, graphics: &mut G2d) {
        for block in self.get_blocks() {
            let x = self.x + block.x;
            let y = self.y + block.y;
            render_square_in_grid(x, y, self.get_color(), context, graphics);
        }
    }
}

pub enum Direction {
    Left,
    Right
}

#[derive(Debug)]
pub struct Tetromino {
    configurations: [Configuration; 4],
    color: Color
}
impl Tetromino {
    fn get_configuration(&self, rotation: Rotation) -> &Configuration {
        &self.configurations[rotation]
    }
}

#[derive(Debug)]
pub struct Block {
    pub x: i32,
    pub y: i32
}

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

    #[test]
    fn test_piece_rotated() {
        let mut p = Piece::create(&I);
        assert_eq!(p.rotation, 0);
        p = p.rotated();
        assert_eq!(p.rotation, 1);
        p = p.rotated();
        assert_eq!(p.rotation, 2);
        p = p.rotated();
        assert_eq!(p.rotation, 3);
        p = p.rotated();
        assert_eq!(p.rotation, 0);
    }

    #[test]
    fn test_piece_moved_left() {
        let mut p = Piece::create(&Z);
        let result = p.moved(Direction::Left);
        assert_eq!(result.x, p.x - 1);
    }

    #[test]
    fn test_piece_moved_right() {
        let mut p = Piece::create(&Z);
        let result = p.moved(Direction::Right);
        assert_eq!(result.x, p.x + 1);
    }

    #[test]
    fn test_piece_out_of_bounds_simple() {
        let mut p = Piece::create(&I);
        p.x += 10;
        assert_eq!(p.is_out_of_bounds(), true);
    }

    #[test]
    fn test_piece_out_of_bounds_at_boundary() {
        let mut p = Piece::create(&I);
        p.x = 6;
        assert_eq!(p.is_out_of_bounds(), false);
        p.x = 7;
        assert_eq!(p.is_out_of_bounds(), true);
        p.x = 0;
        assert_eq!(p.is_out_of_bounds(), false);
        p.x = -1;
        assert_eq!(p.is_out_of_bounds(), true);
    }

    #[test]
    fn test_wall_kick() {
        assert_eq!(false, true)
    }
}
<commit_msg>remove extraneous test<commit_after>use piston_window::{Context, G2d};
use piston_window::types::Color;
use rand::{Rng, thread_rng};

use colors::*;
use game::{Renderer, render_square_in_grid};
use settings::WIDTH_IN_BLOCKS;

pub fn create_random_piece() -> Piece {
    let all_tetrominos = [&I, &J, &L, &O, &S, &T, &Z];
    let ptype = thread_rng().choose(&all_tetrominos).unwrap();
    Piece::create(*ptype)
}

pub static I: Tetromino = Tetromino {
    configurations: [
        [Block{x:0, y:1}, Block{x:1, y:1}, Block{x:2, y:1}, Block{x:3, y:1}],
        [Block{x:2, y:0}, Block{x:2, y:1}, Block{x:2, y:2}, Block{x:2, y:3}],
        [Block{x:0, y:2}, Block{x:1, y:2}, Block{x:2, y:2}, Block{x:3, y:2}],
        [Block{x:1, y:0}, Block{x:1, y:1}, Block{x:1, y:2}, Block{x:1, y:3}]
    ],
    color: CYAN
};

pub static J: Tetromino = Tetromino {
    configurations: [
        [Block{x:0, y:0}, Block{x:0, y:1}, Block{x:1, y:1}, Block{x:2, y:1}],
        [Block{x:2, y:0}, Block{x:1, y:0}, Block{x:1, y:1}, Block{x:1, y:2}],
        [Block{x:2, y:2}, Block{x:2, y:1}, Block{x:1, y:1}, Block{x:0, y:1}],
        [Block{x:0, y:2}, Block{x:1, y:2}, Block{x:1, y:1}, Block{x:1, y:0}]
    ],
    color: BLUE
};

pub static L: Tetromino = Tetromino {
    configurations: [
        [Block{x:2, y:0}, Block{x:2, y:1}, Block{x:1, y:1}, Block{x:0, y:1}],
        [Block{x:2, y:2}, Block{x:1, y:2}, Block{x:1, y:1}, Block{x:1, y:0}],
        [Block{x:0, y:2}, Block{x:0, y:1}, Block{x:1, y:1}, Block{x:2, y:1}],
        [Block{x:0, y:0}, Block{x:1, y:0}, Block{x:1, y:1}, Block{x:1, y:2}]
    ],
    color: ORANGE
};

pub static O: Tetromino = Tetromino {
    configurations: [
        [Block{x:1, y:0}, Block{x:1, y:1}, Block{x:2, y:0}, Block{x:2, y:1}],
        [Block{x:1, y:0}, Block{x:1, y:1}, Block{x:2, y:0}, Block{x:2, y:1}],
        [Block{x:1, y:0}, Block{x:1, y:1}, Block{x:2, y:0}, Block{x:2, y:1}],
        [Block{x:1, y:0}, Block{x:1, y:1}, Block{x:2, y:0}, Block{x:2, y:1}]
    ],
    color: YELLOW
};

pub static S: Tetromino = Tetromino {
    configurations: [
        [Block{x:0, y:1}, Block{x:1, y:1}, Block{x:1, y:0}, Block{x:2, y:0}],
        [Block{x:1, y:0}, Block{x:1, y:1}, Block{x:2, y:1}, Block{x:2, y:2}],
        [Block{x:2, y:1}, Block{x:1, y:1}, Block{x:1, y:2}, Block{x:0, y:2}],
        [Block{x:1, y:2}, Block{x:1, y:1}, Block{x:0, y:1}, Block{x:0, y:0}]
    ],
    color: LIME
};

pub static T: Tetromino = Tetromino {
    configurations: [
        [Block{x:1, y:0}, Block{x:0, y:1}, Block{x:1, y:1}, Block{x:2, y:1}],
        [Block{x:2, y:1}, Block{x:1, y:0}, Block{x:1, y:1}, Block{x:1, y:2}],
        [Block{x:1, y:2}, Block{x:2, y:1}, Block{x:1, y:1}, Block{x:0, y:1}],
        [Block{x:0, y:1}, Block{x:1, y:0}, Block{x:1, y:1}, Block{x:1, y:2}]
    ],
    color: PURPLE
};

pub static Z: Tetromino = Tetromino {
    configurations: [
        [Block{x:0, y:0}, Block{x:1, y:0}, Block{x:1, y:1}, Block{x:2, y:1}],
        [Block{x:2, y:0}, Block{x:2, y:1}, Block{x:1, y:1}, Block{x:1, y:2}],
        [Block{x:2, y:2}, Block{x:1, y:2}, Block{x:1, y:1}, Block{x:0, y:1}],
        [Block{x:0, y:2}, Block{x:0, y:1}, Block{x:1, y:1}, Block{x:1, y:0}]
    ],
    color: RED
};

pub type Configuration = [Block; 4];
type Rotation = usize;

pub struct Piece {
    // TODO: Replace with position
    // TODO: Make this private again
    pub x: i32,
    pub y: i32,
    ptype: &'static Tetromino,
    rotation: Rotation
}
impl Piece {
    pub fn create(ptype: &'static Tetromino) -> Piece {
        let x = (WIDTH_IN_BLOCKS as i32 / 2) - 2;
        Piece::new(x, 0, ptype, 0)
    }

    fn new(x: i32, y: i32, ptype: &'static Tetromino, rotation: Rotation) -> Piece {
        Piece {
            x: x,
            y: y,
            ptype: ptype,
            rotation: rotation
        }
    }

    pub fn get_blocks(&self) -> &Configuration {
        self.ptype.get_configuration(self.rotation)
    }

    pub fn rotated(&self) -> Self {
        let rotation = (self.rotation + 1) % self.ptype.configurations.len();
        Self::new(self.x, self.y, self.ptype, rotation)
    }

    pub fn moved(&self, direction: Direction) -> Self {
        let translated_x = match direction {
            Direction::Left => self.x - 1,
            Direction::Right => self.x + 1
        };
        Self::new(translated_x, self.y, self.ptype, self.rotation)
    }

    pub fn is_out_of_bounds(&self) -> bool {
        self.get_blocks().iter().any(|block| {
            let x = self.x + block.x;
            x < 0 || x >= WIDTH_IN_BLOCKS as i32
        })
    }

    pub fn wall_kick(&self) -> Self {
        let mut translation = 0;
        let min_block = self.get_blocks().iter().min_by_key(|block| self.x + block.x).unwrap();
        let max_block = self.get_blocks().iter().max_by_key(|block| self.x + block.x).unwrap();
        if self.x + min_block.x < 0 {
            translation = self.x + min_block.x;
        } else if self.x + max_block.x >= WIDTH_IN_BLOCKS as i32 {
            translation = (self.x + max_block.x) - (WIDTH_IN_BLOCKS - 1) as i32;
        }
        Self::new(self.x - translation, self.y, self.ptype, self.rotation)
    }

    fn get_color(&self) -> Color {
        self.ptype.color
    }
}
impl Renderer for Piece {
    fn render(&self, context: Context, graphics: &mut G2d) {
        for block in self.get_blocks() {
            let x = self.x + block.x;
            let y = self.y + block.y;
            render_square_in_grid(x, y, self.get_color(), context, graphics);
        }
    }
}

pub enum Direction {
    Left,
    Right
}

#[derive(Debug)]
pub struct Tetromino {
    configurations: [Configuration; 4],
    color: Color
}
impl Tetromino {
    fn get_configuration(&self, rotation: Rotation) -> &Configuration {
        &self.configurations[rotation]
    }
}

#[derive(Debug)]
pub struct Block {
    pub x: i32,
    pub y: i32
}

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

    #[test]
    fn test_piece_rotated() {
        let mut p = Piece::create(&I);
        assert_eq!(p.rotation, 0);
        p = p.rotated();
        assert_eq!(p.rotation, 1);
        p = p.rotated();
        assert_eq!(p.rotation, 2);
        p = p.rotated();
        assert_eq!(p.rotation, 3);
        p = p.rotated();
        assert_eq!(p.rotation, 0);
    }

    #[test]
    fn test_piece_moved_left() {
        let mut p = Piece::create(&Z);
        let result = p.moved(Direction::Left);
        assert_eq!(result.x, p.x - 1);
    }

    #[test]
    fn test_piece_moved_right() {
        let mut p = Piece::create(&Z);
        let result = p.moved(Direction::Right);
        assert_eq!(result.x, p.x + 1);
    }

    #[test]
    fn test_piece_out_of_bounds() {
        let mut p = Piece::create(&I);
        p.x = 6;
        assert_eq!(p.is_out_of_bounds(), false);
        p.x = 7;
        assert_eq!(p.is_out_of_bounds(), true);
        p.x = 0;
        assert_eq!(p.is_out_of_bounds(), false);
        p.x = -1;
        assert_eq!(p.is_out_of_bounds(), true);
    }

    #[test]
    fn test_wall_kick() {
        assert_eq!(false, true)
    }
}
<|endoftext|>


<commit_before>use error::{Error,Result};

use std::collections::HashMap;
use std::path::PathBuf;
use std::env;

/// Structure for storing a book option
#[derive(Debug, PartialEq)]
pub enum BookOption {
    String(String), // Stores a string
    Bool(bool), // stores a boolean
    Char(char), // stores a char
    Int(i32), // stores an int
    Path(String), // Stores a path
}

impl BookOption {
    /// Retuns the BookOption as a &str
    pub fn as_str(&self) -> Result<&str> {
        match *self {
            BookOption::String(ref s) => Ok(s),
            _ => Err(Error::BookOption(format!("{:?} is not a string", self)))
        }
    }

    /// Returns the BookOption as a &str, but only if it is a path
    pub fn as_path(&self) -> Result<&str> {
        match *self {
            BookOption::Path(ref s) => Ok(s),
            _ => Err(Error::BookOption(format!("{:?} is not a path", self)))
        }
    }

    /// Retuns the BookOption as a bool
    pub fn as_bool(&self) -> Result<bool> {
        match *self {
            BookOption::Bool(b) => Ok(b),
            _ => Err(Error::BookOption(format!("{:?} is not a bool", self)))
        }
    }

    /// Retuns the BookOption as a char
    pub fn as_char(&self) -> Result<char> {
        match *self {
            BookOption::Char(c) => Ok(c),
            _ => Err(Error::BookOption(format!("{:?} is not a char", self)))
        }
    }

    /// Retuns the BookOption as an i32
    pub fn as_i32(&self) -> Result<i32> {
        match *self {
            BookOption::Int(i) => Ok(i),
            _ => Err(Error::BookOption(format!("{:?} is not an i32", self)))

        }
    }
}

static OPTIONS:&'static str = "
# Metadata
author:str:Anonymous                # The author of the book
title:str:Untitled                  # The title of the book
lang:str:en                         # The language of the book
subject:str                         # Subject of the book (used for EPUB metadata)
description:str                     # Description of the book (used for EPUB metadata)
cover:path                          # File name of the cover of the book
# Output options
output.epub:path                    # Output file name for EPUB rendering
output.html:path                    # Output file name for HTML rendering
output.tex:path                     # Output file name for LaTeX rendering
output.pdf:path                     # Output file name for PDF rendering
output.odt:path                     # Output file name for ODT rendering

# Misc options
zip.command:str:zip                 # Command to use to zip files (for EPUB/ODT)
numbering:int:1                     # The  maximum heading levels to number (0: no numbering, 1: only chapters, ..., 6: all)
display_toc:bool:false              # If true, display a table of content in the document
toc_name:str:Table of contents      # Name of the table of contents if toc is displayed in line
autoclean:bool:true                 # Toggles cleaning of input markdown (not used for LaTeX)
verbose:bool:false                  # If set to true, print warnings in Markdown processing
side_notes:bool:false               # Display footnotes as side notes in HTML/Epub
temp_dir:path:                      # Path where to create a temporary directory (default: uses result from Rust's std::env::temp_dir())
numbering_template:str:{{number}}. {{title}} # Format of numbered titles
nb_char:char:' '                    # The non-breaking character to use for autoclean when lang is set to fr

# HTML options
html.template:path                  # Path of an HTML template
html.css:path                       # Path of a stylesheet to use with HTML rendering

# EPUB options
epub.version:int:2                  # The EPUB version to generate
epub.css:path                       # Path of a stylesheet to use with EPUB rendering
epub.template:path                  # Path of an epub template for chapter

# LaTeX options
tex.links_as_footnotes:bool:true    # If set to true, will add foontotes to URL of links in LaTeX/PDF output
tex.command:str:pdflatex            # LaTeX flavour to use for generating PDF
tex.template:path                   # Path of a LaTeX template file
";

/// Contains the options of a book.
#[derive(Debug)]
pub struct BookOptions {
    options: HashMap<String, BookOption>,
    valid_bools: Vec<&'static str>,
    valid_chars: Vec<&'static str>,
    valid_strings: Vec<&'static str>,
    valid_paths: Vec<&'static str>,
    valid_ints: Vec<&'static str>,

    /// Root path of the book (unnecessary copy :/)
    pub root: PathBuf,
}

impl BookOptions {
    /// Creates a new BookOptions struct from the default compliled string
    pub fn new() -> BookOptions {
        let mut options = BookOptions {
            options: HashMap::new(),
            valid_bools:vec!(),
            valid_chars:vec!(),
            valid_ints:vec!(),
            valid_strings:vec!(),
            valid_paths:vec!(),
            root: PathBuf::new(),
        };

        for (_, key, option_type, default_value) in Self::options_to_vec() {
            if key.is_none() {
                continue;
            }
            let key = key.unwrap();
            match option_type.unwrap() {
                "str" => options.valid_strings.push(key),
                "bool" => options.valid_bools.push(key),
                "int" => options.valid_ints.push(key),
                "char" => options.valid_chars.push(key),
                "path" => options.valid_paths.push(key),
                _ => panic!(format!("Ill-formatted OPTIONS string: unrecognized type '{}'", option_type.unwrap())),
            }
            if key == "temp_dir" {
                options.set(key, &env::temp_dir().to_string_lossy()).unwrap();
                continue;
            }
            if let Some(value) = default_value {
                options.set(key, value).unwrap();
            }
        }
        options
    }

    /// Sets an option
    ///
    /// # Arguments
    /// * `key`: the identifier of the option, e.g.: "author"
    /// * `value`: the value of the option as a string
    ///
    /// **Returns** an error either if `key` is not a valid option or if the
    /// value is not of the right type
    ///
    /// # Examples
    /// ```
    /// use crowbook::Book;
    /// let mut book = Book::new();
    /// book.options.set("author", "Joan Doe").unwrap(); // ok
    /// book.options.set("numbering", "2").unwrap(); // sets numbering to chapters and subsections
    /// let result = book.options.set("autor", "John Smith");
    /// assert!(result.is_err()); // error: "author" was mispelled "autor"
    ///
    /// let result = book.options.set("numbering", "foo");
    /// assert!(result.is_err()); // error: numbering must be an int
    /// ```
    pub fn set(&mut self, key: &str, value: &str) -> Result<()> {
        if self.valid_strings.contains(&key) {
            self.options.insert(key.to_owned(), BookOption::String(value.to_owned()));
            Ok(())
        } else if self.valid_paths.contains(&key) {
            self.options.insert(key.to_owned(), BookOption::Path(value.to_owned()));
            Ok(())
        } else if self.valid_chars.contains(&key) {
            let words: Vec<_> = value.trim().split('\'').collect();
            if words.len() != 3 {
                return Err(Error::ConfigParser("could not parse char", String::from(value)));
            }
            let chars: Vec<_> = words[1].chars().collect();
            if chars.len() != 1 {
                return Err(Error::ConfigParser("could not parse char", String::from(value)));
            }
            self.options.insert(key.to_owned(), BookOption::Char(chars[0]));
            Ok(())
        } else if self.valid_bools.contains(&key) {
            match value.parse::<bool>() {
                Ok(b) => {
                    self.options.insert(key.to_owned(), BookOption::Bool(b));
                    ()
                },
                Err(_) => return Err(Error::ConfigParser("could not parse bool", format!("{}:{}", key, value))),
            }
            Ok(())
        } else if self.valid_ints.contains(&key) {
            match value.parse::<i32>() {
                Ok(i) => {
                    self.options.insert(key.to_owned(), BookOption::Int(i));
                }
                Err(_) => return Err(Error::ConfigParser("could not parse int", format!("{}:{}", key, value))),
            }
            Ok(())
        } else {
            Err(Error::ConfigParser("unrecognized key", String::from(key)))
        }
    }

    /// get an option
    pub fn get(&self, key: &str) -> Result<&BookOption> {
        self.options.get(key).ok_or(Error::InvalidOption(format!("option {} is not persent", key)))
    }

    /// Gets a string option
    pub fn get_str(&self, key: &str) -> Result<&str> {
        try!(self.get(key)).as_str()
    }

    /// Get a path option
    ///
    /// Adds book's root path before it
    pub fn get_path(&self, key: &str) -> Result<String> {
        let path: &str = try!(try!(self.get(key)).as_path());
        let new_path:PathBuf = self.root.join(path);
        if let Some(path) = new_path.to_str() {
            Ok(path.to_owned())
        } else {
            Err(Error::BookOption(format!("'{}''s path contains invalid UTF-8 code", key)))
        }
    }

    /// Get a path option
    ///
    /// Don't add book's root path before it
    pub fn get_relative_path(&self, key: &str) -> Result<&str> {
        try!(self.get(key)).as_path()
    }

    /// gets a bool option
    pub fn get_bool(&self, key: &str) -> Result<bool> {
        try!(self.get(key)).as_bool()
    }

    /// gets a char option
    pub fn get_char(&self, key: &str) -> Result<char> {
        try!(self.get(key)).as_char()
    }

    /// gets an int  option
    pub fn get_i32(&self, key: &str) -> Result<i32> {
        try!(self.get(key)).as_i32()
    }

    /// Returns a description of all options valid to pass to a book.
    ///
    /// # arguments
    /// * `md`: whether the output should be formatted in Markdown
    pub fn description(md: bool) -> String {
        let mut out = String::new();
        let mut previous_is_comment = true;
        for (comment, key, o_type, default) in Self::options_to_vec() {
            if key.is_none() {
                if !previous_is_comment {
                    out.push_str("\n");
                    previous_is_comment = true;
                }
                out.push_str(&format!("### {} ###\n", comment));
                continue;
            }
            previous_is_comment = false;
            let o_type = match o_type.unwrap() {
                "bool" => "boolean",
                "int" => "integer",
                "char" => "char",
                "str" => "string",
                "path" => "path",
                _ => unreachable!()
            };
            let def = if let Some(value) = default {
                value
            } else {
                "not set"
            };
            if md {
                out.push_str(&format!("- **`{}`**
    - **type**: {}
    - **default value**: `{}`
    - {}\n", key.unwrap(), o_type, def, comment));
            } else {
                out.push_str(&format!("- {} (type: {}) (default: {}) {}\n", key.unwrap(), o_type, def,comment));
            }
        }
        out
    }

    /// OPTIONS to a vec of tuples (comment, key, type, default value)
    fn options_to_vec() -> Vec<(&'static str, Option<&'static str>,
                                Option<&'static str>, Option<&'static str>)> {
        let mut out = vec!();
        for line in OPTIONS.lines() {
            let line = line.trim();
            if line.is_empty() {
                continue;
            }
            if line.starts_with('#') {
                out.push((&line[1..], None, None, None));
                continue;
            }
            let v:Vec<_> = line.split('#').collect();
            let content = v[0];
            let comment = v[1];
            let v:Vec<_> = content.split(':').collect();
            let key = Some(v[0].trim());
            let option_type = Some(v[1].trim());
            let default_value = if v.len() > 2 {
                Some(v[2].trim())
            } else {
                None
            };
            out.push((comment, key, option_type, default_value));
        }
        out
    }

}
<commit_msg>Use yaml_rust for BookOptions<commit_after>use error::{Error,Result};

use std::collections::HashMap;
use std::path::PathBuf;
use std::env;
use yaml_rust::{Yaml, YamlLoader};

/// Structure for storing a book option
#[derive(Debug, PartialEq)]
pub enum BookOption {
    String(String), // Stores a string
    Bool(bool), // stores a boolean
    Char(char), // stores a char
    Int(i32), // stores an int
    Path(String), // Stores a path
}

impl BookOption {
    /// Retuns the BookOption as a &str
    pub fn as_str(&self) -> Result<&str> {
        match *self {
            BookOption::String(ref s) => Ok(s),
            _ => Err(Error::BookOption(format!("{:?} is not a string", self)))
        }
    }

    /// Returns the BookOption as a &str, but only if it is a path
    pub fn as_path(&self) -> Result<&str> {
        match *self {
            BookOption::Path(ref s) => Ok(s),
            _ => Err(Error::BookOption(format!("{:?} is not a path", self)))
        }
    }

    /// Retuns the BookOption as a bool
    pub fn as_bool(&self) -> Result<bool> {
        match *self {
            BookOption::Bool(b) => Ok(b),
            _ => Err(Error::BookOption(format!("{:?} is not a bool", self)))
        }
    }

    /// Retuns the BookOption as a char
    pub fn as_char(&self) -> Result<char> {
        match *self {
            BookOption::Char(c) => Ok(c),
            _ => Err(Error::BookOption(format!("{:?} is not a char", self)))
        }
    }

    /// Retuns the BookOption as an i32
    pub fn as_i32(&self) -> Result<i32> {
        match *self {
            BookOption::Int(i) => Ok(i),
            _ => Err(Error::BookOption(format!("{:?} is not an i32", self)))

        }
    }
}

static OPTIONS:&'static str = "
# Metadata
author:str:Anonymous                # The author of the book
title:str:Untitled                  # The title of the book
lang:str:en                         # The language of the book
subject:str                         # Subject of the book (used for EPUB metadata)
description:str                     # Description of the book (used for EPUB metadata)
cover:path                          # File name of the cover of the book
# Output options
output.epub:path                    # Output file name for EPUB rendering
output.html:path                    # Output file name for HTML rendering
output.tex:path                     # Output file name for LaTeX rendering
output.pdf:path                     # Output file name for PDF rendering
output.odt:path                     # Output file name for ODT rendering

# Misc options
zip.command:str:zip                 # Command to use to zip files (for EPUB/ODT)
numbering:int:1                     # The  maximum heading levels to number (0: no numbering, 1: only chapters, ..., 6: all)
display_toc:bool:false              # If true, display a table of content in the document
toc_name:str:Table of contents      # Name of the table of contents if toc is displayed in line
autoclean:bool:true                 # Toggles cleaning of input markdown (not used for LaTeX)
verbose:bool:false                  # If set to true, print warnings in Markdown processing
side_notes:bool:false               # Display footnotes as side notes in HTML/Epub
temp_dir:path:                      # Path where to create a temporary directory (default: uses result from Rust's std::env::temp_dir())
numbering_template:str:\"{{number}}. {{title}}\" # Format of numbered titles
nb_char:char:' '                    # The non-breaking character to use for autoclean when lang is set to fr

# HTML options
html.template:path                  # Path of an HTML template
html.css:path                       # Path of a stylesheet to use with HTML rendering

# EPUB options
epub.version:int:2                  # The EPUB version to generate
epub.css:path                       # Path of a stylesheet to use with EPUB rendering
epub.template:path                  # Path of an epub template for chapter

# LaTeX options
tex.links_as_footnotes:bool:true    # If set to true, will add foontotes to URL of links in LaTeX/PDF output
tex.command:str:pdflatex            # LaTeX flavour to use for generating PDF
tex.template:path                   # Path of a LaTeX template file
";

/// Contains the options of a book.
#[derive(Debug)]
pub struct BookOptions {
    options: HashMap<String, BookOption>,
    valid_bools: Vec<&'static str>,
    valid_chars: Vec<&'static str>,
    valid_strings: Vec<&'static str>,
    valid_paths: Vec<&'static str>,
    valid_ints: Vec<&'static str>,

    /// Root path of the book (unnecessary copy :/)
    pub root: PathBuf,
}

impl BookOptions {
    /// Creates a new BookOptions struct from the default compliled string
    pub fn new() -> BookOptions {
        let mut options = BookOptions {
            options: HashMap::new(),
            valid_bools:vec!(),
            valid_chars:vec!(),
            valid_ints:vec!(),
            valid_strings:vec!(),
            valid_paths:vec!(),
            root: PathBuf::new(),
        };

        for (_, key, option_type, default_value) in Self::options_to_vec() {
            if key.is_none() {
                continue;
            }
            let key = key.unwrap();
            match option_type.unwrap() {
                "str" => options.valid_strings.push(key),
                "bool" => options.valid_bools.push(key),
                "int" => options.valid_ints.push(key),
                "char" => options.valid_chars.push(key),
                "path" => options.valid_paths.push(key),
                _ => panic!(format!("Ill-formatted OPTIONS string: unrecognized type '{}'", option_type.unwrap())),
            }
            if key == "temp_dir" {
                options.set(key, &env::temp_dir().to_string_lossy()).unwrap();
                continue;
            }
            if let Some(value) = default_value {
                options.set(key, value).unwrap();
            }
        }
        options
    }

    /// Sets an option from a Yaml tuple
    ///
    /// # Arguments
    /// * `key`: the identifier of the option, must be Yaml::String(_)
    /// * `value`: the value of the option
    ///
    /// **Returns** an error if `key` is not a String, not a valid option, of if
    /// the value is not of the right type.
    pub fn set_yaml(&mut self, key: Yaml, value: Yaml) -> Result<()> {
        let key = if let Yaml::String(key) = key {
            key
        } else {
            return Err(Error::BookOption(format!("Expected a String as a key, found {:?}", key)));
        };

        if self.valid_strings.contains(&key.as_ref()) {
            // value is a string
            if let Yaml::String(value) = value {
                self.options.insert(key, BookOption::String(value));
            } else {
                return Err(Error::BookOption(format!("Expected a string as value for key {}, found {:?}", &key, &value)));
            }
        } else if self.valid_paths.contains(&key.as_ref()) {
            // value is a path
            if let Yaml::String(value) = value {
                self.options.insert(key, BookOption::Path(value));
            } else {
                return Err(Error::BookOption(format!("Expected a string as value for key {}, found {:?}", &key, &value)));
            }
        } else if self.valid_chars.contains(&key.as_ref()) {
            // value is a char
            if let Yaml::String(value) = value {
                let chars: Vec<_> = value.chars().collect();
                if chars.len() != 1 {
                    return Err(Error::BookOption(format!("could not parse {} as a char: does not contain exactly one char", &value)));
                }
                self.options.insert(key.to_owned(), BookOption::Char(chars[0]));
            } else {
                return Err(Error::BookOption(format!("Expected a string as value containing a char for key {}, found {:?}", &key, &value)));
            }
        } else if self.valid_bools.contains(&key.as_ref()) {
            // value is a bool
            if let Yaml::Boolean(value) = value {
                self.options.insert(key, BookOption::Bool(value));
            } else {
                return Err(Error::BookOption(format!("Expected a boolean as value for key {}, found {:?}", &key, &value)));
            }
        } else if self.valid_ints.contains(&key.as_ref()) {
            // value is an int
            if let Yaml::Integer(value) = value {
                self.options.insert(key, BookOption::Int(value as i32));
            } else {
                return Err(Error::BookOption(format!("Expected an integer as value for key {}, found {:?}", &key, &value)));
            }
        } else {
            // key not recognized
            return Err(Error::BookOption(format!("Unrecognized key: {}", &key)));
        }
        Ok(())
    }

    /// Sets an option
    ///
    /// # Arguments
    /// * `key`: the identifier of the option, e.g.: "author"
    /// * `value`: the value of the option as a string
    ///
    /// **Returns** an error either if `key` is not a valid option or if the
    /// value is not of the right type
    ///
    /// # Examples
    /// ```
    /// use crowbook::Book;
    /// let mut book = Book::new();
    /// book.options.set("author", "Joan Doe").unwrap(); // ok
    /// book.options.set("numbering", "2").unwrap(); // sets numbering to chapters and subsections
    /// let result = book.options.set("autor", "John Smith");
    /// assert!(result.is_err()); // error: "author" was mispelled "autor"
    ///
    /// let result = book.options.set("numbering", "foo");
    /// assert!(result.is_err()); // error: numbering must be an int
    /// ```
    pub fn set(&mut self, key: &str, value: &str) -> Result<()> {
        let result = YamlLoader::load_from_str(value);
        if let Ok(yaml_docs) = result {
            if yaml_docs.len() == 1 {
                let yaml_value = yaml_docs.into_iter().next().unwrap();
                println!("trying to set key {} to yaml {:?}", key, &yaml_value);
                self.set_yaml(Yaml::String(key.to_owned()), yaml_value)
            } else {
                Err(Error::BookOption(format!("value {} for key {} does not contain one and only one YAML value", value, key)))
            }
        } else {
            Err(Error::BookOption(format!("could not parse {} as a valid YAML value", value)))
        }
    }

    /// get an option
    pub fn get(&self, key: &str) -> Result<&BookOption> {
        self.options.get(key).ok_or(Error::InvalidOption(format!("option {} is not persent", key)))
    }

    /// Gets a string option
    pub fn get_str(&self, key: &str) -> Result<&str> {
        try!(self.get(key)).as_str()
    }

    /// Get a path option
    ///
    /// Adds book's root path before it
    pub fn get_path(&self, key: &str) -> Result<String> {
        let path: &str = try!(try!(self.get(key)).as_path());
        let new_path:PathBuf = self.root.join(path);
        if let Some(path) = new_path.to_str() {
            Ok(path.to_owned())
        } else {
            Err(Error::BookOption(format!("'{}''s path contains invalid UTF-8 code", key)))
        }
    }

    /// Get a path option
    ///
    /// Don't add book's root path before it
    pub fn get_relative_path(&self, key: &str) -> Result<&str> {
        try!(self.get(key)).as_path()
    }

    /// gets a bool option
    pub fn get_bool(&self, key: &str) -> Result<bool> {
        try!(self.get(key)).as_bool()
    }

    /// gets a char option
    pub fn get_char(&self, key: &str) -> Result<char> {
        try!(self.get(key)).as_char()
    }

    /// gets an int  option
    pub fn get_i32(&self, key: &str) -> Result<i32> {
        try!(self.get(key)).as_i32()
    }

    /// Returns a description of all options valid to pass to a book.
    ///
    /// # arguments
    /// * `md`: whether the output should be formatted in Markdown
    pub fn description(md: bool) -> String {
        let mut out = String::new();
        let mut previous_is_comment = true;
        for (comment, key, o_type, default) in Self::options_to_vec() {
            if key.is_none() {
                if !previous_is_comment {
                    out.push_str("\n");
                    previous_is_comment = true;
                }
                out.push_str(&format!("### {} ###\n", comment));
                continue;
            }
            previous_is_comment = false;
            let o_type = match o_type.unwrap() {
                "bool" => "boolean",
                "int" => "integer",
                "char" => "char",
                "str" => "string",
                "path" => "path",
                _ => unreachable!()
            };
            let def = if let Some(value) = default {
                value
            } else {
                "not set"
            };
            if md {
                out.push_str(&format!("- **`{}`**
    - **type**: {}
    - **default value**: `{}`
    - {}\n", key.unwrap(), o_type, def, comment));
            } else {
                out.push_str(&format!("- {} (type: {}) (default: {}) {}\n", key.unwrap(), o_type, def,comment));
            }
        }
        out
    }

    /// OPTIONS to a vec of tuples (comment, key, type, default value)
    fn options_to_vec() -> Vec<(&'static str, Option<&'static str>,
                                Option<&'static str>, Option<&'static str>)> {
        let mut out = vec!();
        for line in OPTIONS.lines() {
            let line = line.trim();
            if line.is_empty() {
                continue;
            }
            if line.starts_with('#') {
                out.push((&line[1..], None, None, None));
                continue;
            }
            let v:Vec<_> = line.split('#').collect();
            let content = v[0];
            let comment = v[1];
            let v:Vec<_> = content.split(':').collect();
            let key = Some(v[0].trim());
            let option_type = Some(v[1].trim());
            let default_value = if v.len() > 2 {
                Some(v[2].trim())
            } else {
                None
            };
            out.push((comment, key, option_type, default_value));
        }
        out
    }

}
<|endoftext|>


<commit_before>use event::{Event, Entity, State, SassRule, SassVariable, TopLevelEvent};
use std::borrow::Cow::Borrowed;

fn is_ascii_whitespace(c: u8) -> bool {
   is_newline(c) || is_ascii_whitespace_no_nl(c)
}

fn is_ascii_whitespace_no_nl(c: u8) -> bool {
    c == b'\t' || c == 0x0b || c == 0x0c || c == b' '
}

fn is_newline(c: u8) -> bool {
    c == b'\n' || c == b'\r'
}

fn isnt_newline(c: u8) -> bool {
    !is_newline(c)
}

// unusual among "scan" functions in that it scans from the _back_ of the string
// TODO: should also scan unicode whitespace?
fn scan_trailing_whitespace(data: &str) -> usize {
    match data.as_bytes().iter().rev().position(|&c| !is_ascii_whitespace_no_nl(c)) {
        Some(i) => i,
        None => data.len()
    }
}

#[derive(Debug)]
pub struct Tokenizer<'a> {
    sass: &'a str,
    bytes: &'a [u8],
    offset: usize,
    stack: Vec<Entity>,
    state: State,
    sass_rule_stack: Vec<SassRule<'a>>,
    current_sass_rule: SassRule<'a>,
}

impl<'a> Tokenizer<'a> {
    pub fn new(sass: &'a str) -> Tokenizer<'a> {
        Tokenizer {
            sass: &sass,
            bytes: &sass.as_bytes(),
            offset: 0,
            stack: Vec::new(),
            state: State::OutsideRules,
            sass_rule_stack: Vec::new(),
            current_sass_rule: SassRule::new(),
        }
    }

    fn start_something(&mut self) -> Option<TopLevelEvent<'a>> {
        self.current_sass_rule = SassRule::new();

        self.pick_something();
        while self.state != State::OutsideRules && self.state != State::Eof {
            let curr_state = self.state.clone();
            match curr_state {
                State::InSelectors => {
                    let next_sel = self.next_selector();
                    println!("next sel = {:?}", next_sel);
                    if next_sel.is_some() {
                        self.current_sass_rule.selectors.push(next_sel.unwrap());
                    }
                },
                State::InProperties => {
                    let next_prop = self.next_property();
                    if next_prop.is_some() {
                        self.current_sass_rule.children.push(next_prop.unwrap());
                    }
                },
                State::EndRule => {
                    println!("in endrule");
                    // consume '}', should probably be checking that's what we actually have
                    self.offset += 1;

                    match self.sass_rule_stack.pop() {
                        Some(ref mut rule) => {
                            rule.children.push(Event::ChildRule(self.current_sass_rule.clone()));
                            self.current_sass_rule = (*rule).clone();
                            self.pick_something();
                        },
                        None => self.state = State::OutsideRules,
                    }
                },
                State::InRule => {
                    self.sass_rule_stack.push(self.current_sass_rule.clone());
                    self.current_sass_rule = SassRule::new();
                    self.pick_something();
                },
                State::InVariable => {
                    let next_var = self.next_variable();
                    if next_var.is_some() {
                        return Some(TopLevelEvent::Variable(SassVariable { variable: next_var.unwrap() }))
                    } else {
                       self.state = State::Eof;
                    }
                },
                unknown_state => {
                    println!("i dont know what to do for {:?}", unknown_state);
                    println!("current sass rule = {:?}", self.current_sass_rule);
                    self.state = State::Eof;
                },
            }
        }
        //
        //     State::OutsideRules => {
        //         println!("picked {:?}", self.state);
        //         return self.start_something();
        //     },
        //     State::InRule => return Some(self.next_rule()),
        //     State::InProperties => return Some(self.next_property()),
        //     State::InVariable => return Some(self.next_variable()),
        //     State::InComment => return Some(self.next_comment()),
        //     State::Eof => return None,
        // }

        Some(TopLevelEvent::Rule(self.current_sass_rule.clone()))
    }

    fn pick_something(&mut self) {
        self.skip_leading_whitespace();

        if self.offset == self.sass.len() {
            self.state = State::Eof;
            println!("picked eof");
            return
        }

        let c = self.bytes[self.offset];

        if c == b'}' {
            self.state = State::EndRule;
            println!("picked endrule");
            return
        }

        if c == b'$' {
            self.state = State::InVariable;
            println!("picked variable");
            return
        }

        if c == b'/' && (self.offset + 1) < self.sass.len() {
            let d = self.bytes[self.offset + 1];
            if d == b'*' {
                self.state = State::InComment;
                println!("picked comment");
                return
            }
        }

        println!("picked selectors");

        self.state = State::InSelectors;
    }

    fn scan_while<F>(&mut self, data: &str, f: F) -> usize
            where F: Fn(u8) -> bool {
        match data.as_bytes().iter().position(|&c| !f(c)) {
            Some(i) => i,
            None => data.len()
        }
    }

    fn skip_leading_whitespace(&mut self) {
        let mut i = self.offset;
        let limit = self.sass.len();

        while i < limit {
            let c = self.bytes[i];
            if is_ascii_whitespace(c) {
                i += self.scan_while(&self.sass[i..self.sass.len()], is_ascii_whitespace);
            } else if c == b'/' && i + 1 < limit && self.bytes[i + 1] == b'/' {
                i += self.scan_while(&self.sass[i..self.sass.len()], isnt_newline);
            } else {
                self.offset = i;
                return
            }
        }
        self.offset = limit;
    }

    // fn next_comment(&mut self) -> Event<'a> {
    //     let comment_body_beginning = self.offset;
    //     let mut i = comment_body_beginning + 2;
    //     let limit = self.sass.len();
    //
    //     while i < limit {
    //         match self.bytes[i..limit].iter().position(|&c| c == b'*' ) {
    //             Some(pos) => { i += pos; },
    //             None => { break; },
    //         }
    //
    //         if self.bytes[i+1] == b'/' {
    //             self.offset = i + 2;
    //             return Event::Comment(Borrowed(&self.sass[comment_body_beginning..i + 2]))
    //         } else {
    //             i += 1;
    //         }
    //     }
    //     unreachable!()
    // }

    fn next_variable(&mut self) -> Option<Event<'a>> {
        // TODO: can parts of this be deduplicated with properties?
        let name_beginning = self.offset;
        let mut i = name_beginning;
        let limit = self.sass.len();

        while i < limit {
            match self.bytes[i..limit].iter().position(|&c| c == b':' ) {
                Some(pos) => { i += pos; },
                None => { break; },
            }

            let name_end = i;

            i += 1;
            self.offset = i;
            self.skip_leading_whitespace();

            let value_beginning = self.offset;
            i = value_beginning;

            while i < limit {
                match self.bytes[i..limit].iter().position(|&c| c == b';') {
                    Some(pos) => { i += pos; },
                    None => { i = limit; break; },
                }

                let value_end = i;
                self.offset = i + 1;

                self.skip_leading_whitespace();

                return Some(Event::Variable(
                    Borrowed(&self.sass[name_beginning..name_end]),
                    Borrowed(&self.sass[value_beginning..value_end])
                ))
            }
        }
        self.offset = self.sass.len();
        None
    }

    fn next_property(&mut self) -> Option<Event<'a>> {
        self.skip_leading_whitespace();

        let name_beginning = self.offset;
        let mut i = name_beginning;
        let limit = self.sass.len();

        let c = self.bytes[i];
        if c == b'}' {
            self.state = State::EndRule;
            return None
        }

        let d = self.bytes[i + 1];
        if c == b'/' && d == b'*' {
            self.state = State::InComment;
            return None
        }

        while i < limit {
            match self.bytes[i..limit].iter().position(|&c| c == b':' || c == b'{') {
                Some(pos) => { i += pos; },
                None => { break; },
            }

            // Inefficient since we already skipped the whitespace and we'll have to
            // do it again but oh well
            let c = self.bytes[i];
            if c == b'{' {
                self.state = State::InRule;
                return None
            }

            let name_end = i;

            i += 1;
            self.offset = i;
            self.skip_leading_whitespace();

            let value_beginning = self.offset;
            i = value_beginning;

            while i < limit {
                match self.bytes[i..limit].iter().position(|&c| c == b';') {
                    Some(pos) => { i += pos; },
                    None => { i = limit; break; },
                }

                let value_end = i;
                self.offset = i + 1;

                self.skip_leading_whitespace();

                return Some(Event::Property(
                    Borrowed(&self.sass[name_beginning..name_end]),
                    Borrowed(&self.sass[value_beginning..value_end])
                ))

            }
        }
        self.offset = self.sass.len();
        None
    }

    fn next_selector(&mut self) -> Option<Event<'a>> {
        let beginning = self.offset;
        let mut i = beginning;
        let limit = self.sass.len();

        while i < limit {
            match self.bytes[i..limit].iter().position(|&c| c == b',' || c == b'{' || c == b':') {
                Some(pos) => { i += pos; },
                None => { i = limit; break; },
            }

            let c = self.bytes[i];

            if c == b':' {
                self.state = State::InProperties;
                return None
            }

            if c == b',' || c == b'{' {
                let n = scan_trailing_whitespace(&self.sass[beginning..i]);
                let end = i - n;
                if end > beginning {
                    if c == b'{' {
                        self.state = State::InProperties;
                    }
                    self.offset = i + 1;
                    return Some(Event::Selector(Borrowed(&self.sass[beginning..end])))
                }
            }

            self.offset = i;
            if i > beginning {
                return Some(Event::Selector(Borrowed(&self.sass[beginning..i])))
            }
            i += 1;
        }

        if i > beginning {
            self.offset = i;
            Some(Event::Selector(Borrowed(&self.sass[beginning..i])))
        } else {
            self.state = State::Eof;
            None
        }
    }
}

impl<'a> Iterator for Tokenizer<'a> {
    type Item = TopLevelEvent<'a>;

    fn next(&mut self) -> Option<TopLevelEvent<'a>> {
        if self.offset < self.sass.len() {
            return self.start_something()
        }
        None
    }
}
<commit_msg>Ok, parsing of 00 is the way I want it again<commit_after>use event::{Event, Entity, State, SassRule, SassVariable, TopLevelEvent};
use std::borrow::Cow::Borrowed;

fn is_ascii_whitespace(c: u8) -> bool {
   is_newline(c) || is_ascii_whitespace_no_nl(c)
}

fn is_ascii_whitespace_no_nl(c: u8) -> bool {
    c == b'\t' || c == 0x0b || c == 0x0c || c == b' '
}

fn is_newline(c: u8) -> bool {
    c == b'\n' || c == b'\r'
}

fn isnt_newline(c: u8) -> bool {
    !is_newline(c)
}

// unusual among "scan" functions in that it scans from the _back_ of the string
// TODO: should also scan unicode whitespace?
fn scan_trailing_whitespace(data: &str) -> usize {
    match data.as_bytes().iter().rev().position(|&c| !is_ascii_whitespace_no_nl(c)) {
        Some(i) => i,
        None => data.len()
    }
}

#[derive(Debug)]
pub struct Tokenizer<'a> {
    sass: &'a str,
    bytes: &'a [u8],
    offset: usize,
    stack: Vec<Entity>,
    state: State,
    sass_rule_stack: Vec<SassRule<'a>>,
    current_sass_rule: SassRule<'a>,
}

impl<'a> Tokenizer<'a> {
    pub fn new(sass: &'a str) -> Tokenizer<'a> {
        Tokenizer {
            sass: &sass,
            bytes: &sass.as_bytes(),
            offset: 0,
            stack: Vec::new(),
            state: State::OutsideRules,
            sass_rule_stack: Vec::new(),
            current_sass_rule: SassRule::new(),
        }
    }

    fn start_something(&mut self) -> Option<TopLevelEvent<'a>> {
        self.current_sass_rule = SassRule::new();

        self.pick_something();
        while self.state != State::OutsideRules {
            let curr_state = self.state.clone();
            match curr_state {
                State::Eof => {
                    return None
                },
                State::InSelectors => {
                    let next_sel = self.next_selector();
                    println!("next sel = {:?}", next_sel);
                    if next_sel.is_some() {
                        self.current_sass_rule.selectors.push(next_sel.unwrap());
                    }
                },
                State::InProperties => {
                    let next_prop = self.next_property();
                    if next_prop.is_some() {
                        self.current_sass_rule.children.push(next_prop.unwrap());
                    }
                },
                State::EndRule => {
                    println!("in endrule");
                    // consume '}', should probably be checking that's what we actually have
                    self.offset += 1;

                    match self.sass_rule_stack.pop() {
                        Some(ref mut rule) => {
                            rule.children.push(Event::ChildRule(self.current_sass_rule.clone()));
                            self.current_sass_rule = (*rule).clone();
                            self.pick_something();
                        },
                        None => self.state = State::OutsideRules,
                    }
                },
                State::InRule => {
                    self.sass_rule_stack.push(self.current_sass_rule.clone());
                    self.current_sass_rule = SassRule::new();
                    self.pick_something();
                },
                State::InVariable => {
                    let next_var = self.next_variable();
                    if next_var.is_some() {
                        return Some(TopLevelEvent::Variable(SassVariable { variable: next_var.unwrap() }))
                    } else {
                       self.state = State::Eof;
                    }
                },
                unknown_state => {
                    println!("i dont know what to do for {:?}", unknown_state);
                    println!("current sass rule = {:?}", self.current_sass_rule);
                    self.state = State::Eof;
                },
            }
        }
        //
        //     State::OutsideRules => {
        //         println!("picked {:?}", self.state);
        //         return self.start_something();
        //     },
        //     State::InRule => return Some(self.next_rule()),
        //     State::InProperties => return Some(self.next_property()),
        //     State::InVariable => return Some(self.next_variable()),
        //     State::InComment => return Some(self.next_comment()),
        //     State::Eof => return None,
        // }

        Some(TopLevelEvent::Rule(self.current_sass_rule.clone()))
    }

    fn pick_something(&mut self) {
        self.skip_leading_whitespace();

        if self.offset == self.sass.len() {
            self.state = State::Eof;
            println!("picked eof");
            return
        }

        let c = self.bytes[self.offset];

        if c == b'}' {
            self.state = State::EndRule;
            println!("picked endrule");
            return
        }

        if c == b'$' {
            self.state = State::InVariable;
            println!("picked variable");
            return
        }

        if c == b'/' && (self.offset + 1) < self.sass.len() {
            let d = self.bytes[self.offset + 1];
            if d == b'*' {
                self.state = State::InComment;
                println!("picked comment");
                return
            }
        }

        println!("picked selectors");

        self.state = State::InSelectors;
    }

    fn scan_while<F>(&mut self, data: &str, f: F) -> usize
            where F: Fn(u8) -> bool {
        match data.as_bytes().iter().position(|&c| !f(c)) {
            Some(i) => i,
            None => data.len()
        }
    }

    fn skip_leading_whitespace(&mut self) {
        let mut i = self.offset;
        let limit = self.sass.len();

        while i < limit {
            let c = self.bytes[i];
            if is_ascii_whitespace(c) {
                i += self.scan_while(&self.sass[i..self.sass.len()], is_ascii_whitespace);
            } else if c == b'/' && i + 1 < limit && self.bytes[i + 1] == b'/' {
                i += self.scan_while(&self.sass[i..self.sass.len()], isnt_newline);
            } else {
                self.offset = i;
                return
            }
        }
        self.offset = limit;
    }

    // fn next_comment(&mut self) -> Event<'a> {
    //     let comment_body_beginning = self.offset;
    //     let mut i = comment_body_beginning + 2;
    //     let limit = self.sass.len();
    //
    //     while i < limit {
    //         match self.bytes[i..limit].iter().position(|&c| c == b'*' ) {
    //             Some(pos) => { i += pos; },
    //             None => { break; },
    //         }
    //
    //         if self.bytes[i+1] == b'/' {
    //             self.offset = i + 2;
    //             return Event::Comment(Borrowed(&self.sass[comment_body_beginning..i + 2]))
    //         } else {
    //             i += 1;
    //         }
    //     }
    //     unreachable!()
    // }

    fn next_variable(&mut self) -> Option<Event<'a>> {
        // TODO: can parts of this be deduplicated with properties?
        let name_beginning = self.offset;
        let mut i = name_beginning;
        let limit = self.sass.len();

        while i < limit {
            match self.bytes[i..limit].iter().position(|&c| c == b':' ) {
                Some(pos) => { i += pos; },
                None => { break; },
            }

            let name_end = i;

            i += 1;
            self.offset = i;
            self.skip_leading_whitespace();

            let value_beginning = self.offset;
            i = value_beginning;

            while i < limit {
                match self.bytes[i..limit].iter().position(|&c| c == b';') {
                    Some(pos) => { i += pos; },
                    None => { i = limit; break; },
                }

                let value_end = i;
                self.offset = i + 1;

                self.skip_leading_whitespace();

                return Some(Event::Variable(
                    Borrowed(&self.sass[name_beginning..name_end]),
                    Borrowed(&self.sass[value_beginning..value_end])
                ))
            }
        }
        self.offset = self.sass.len();
        None
    }

    fn next_property(&mut self) -> Option<Event<'a>> {
        self.skip_leading_whitespace();

        let name_beginning = self.offset;
        let mut i = name_beginning;
        let limit = self.sass.len();

        let c = self.bytes[i];
        if c == b'}' {
            self.state = State::EndRule;
            return None
        }

        let d = self.bytes[i + 1];
        if c == b'/' && d == b'*' {
            self.state = State::InComment;
            return None
        }

        while i < limit {
            match self.bytes[i..limit].iter().position(|&c| c == b':' || c == b'{') {
                Some(pos) => { i += pos; },
                None => { break; },
            }

            // Inefficient since we already skipped the whitespace and we'll have to
            // do it again but oh well
            let c = self.bytes[i];
            if c == b'{' {
                self.state = State::InRule;
                return None
            }

            let name_end = i;

            i += 1;
            self.offset = i;
            self.skip_leading_whitespace();

            let value_beginning = self.offset;
            i = value_beginning;

            while i < limit {
                match self.bytes[i..limit].iter().position(|&c| c == b';') {
                    Some(pos) => { i += pos; },
                    None => { i = limit; break; },
                }

                let value_end = i;
                self.offset = i + 1;

                self.skip_leading_whitespace();

                return Some(Event::Property(
                    Borrowed(&self.sass[name_beginning..name_end]),
                    Borrowed(&self.sass[value_beginning..value_end])
                ))

            }
        }
        self.offset = self.sass.len();
        None
    }

    fn next_selector(&mut self) -> Option<Event<'a>> {
        let beginning = self.offset;
        let mut i = beginning;
        let limit = self.sass.len();

        while i < limit {
            match self.bytes[i..limit].iter().position(|&c| c == b',' || c == b'{' || c == b':') {
                Some(pos) => { i += pos; },
                None => { i = limit; break; },
            }

            let c = self.bytes[i];

            if c == b':' {
                self.state = State::InProperties;
                return None
            }

            if c == b',' || c == b'{' {
                let n = scan_trailing_whitespace(&self.sass[beginning..i]);
                let end = i - n;
                if end > beginning {
                    if c == b'{' {
                        self.state = State::InProperties;
                    }
                    self.offset = i + 1;
                    return Some(Event::Selector(Borrowed(&self.sass[beginning..end])))
                }
            }

            self.offset = i;
            if i > beginning {
                return Some(Event::Selector(Borrowed(&self.sass[beginning..i])))
            }
            i += 1;
        }

        if i > beginning {
            self.offset = i;
            Some(Event::Selector(Borrowed(&self.sass[beginning..i])))
        } else {
            self.state = State::Eof;
            None
        }
    }
}

impl<'a> Iterator for Tokenizer<'a> {
    type Item = TopLevelEvent<'a>;

    fn next(&mut self) -> Option<TopLevelEvent<'a>> {
        if self.offset < self.sass.len() {
            return self.start_something()
        }
        None
    }
}
<|endoftext|>