<commit_before>//! The internal representation of a book and infrastructure for loading it from
//! disk and building it.
//!
//! For examples on using `MDBook`, consult the [top-level documentation][1].
//!
//! [1]: ../index.html

#![deny(missing_docs)]

mod summary;
mod book;
mod init;

pub use self::book::{load_book, Book, BookItem, BookItems, Chapter};
pub use self::summary::{parse_summary, Link, SectionNumber, Summary, SummaryItem};
pub use self::init::BookBuilder;

use std::path::PathBuf;
use std::io::Write;
use std::process::Command;
use tempdir::TempDir;
use toml::Value;

use utils;
use renderer::{CmdRenderer, HtmlHandlebars, RenderContext, Renderer};
use preprocess::{self, Preprocessor};
use errors::*;

use config::Config;

/// The object used to manage and build a book.
pub struct MDBook {
    /// The book's root directory.
    pub root: PathBuf,
    /// The configuration used to tweak now a book is built.
    pub config: Config,
    /// A representation of the book's contents in memory.
    pub book: Book,
    renderers: Vec<Box<Renderer>>,

    /// The URL used for live reloading when serving up the book.
    pub livereload: Option<String>,

    /// List of pre-processors to be run on the book
    preprocessors: Vec<Box<Preprocessor>>
}

impl MDBook {
    /// Load a book from its root directory on disk.
    pub fn load<P: Into<PathBuf>>(book_root: P) -> Result<MDBook> {
        let book_root = book_root.into();
        let config_location = book_root.join("book.toml");

        // the book.json file is no longer used, so we should emit a warning to
        // let people know to migrate to book.toml
        if book_root.join("book.json").exists() {
            warn!("It appears you are still using book.json for configuration.");
            warn!("This format is no longer used, so you should migrate to the");
            warn!("book.toml format.");
            warn!("Check the user guide for migration information:");
            warn!("\thttps://rust-lang-nursery.github.io/mdBook/format/config.html");
        }

        let mut config = if config_location.exists() {
            debug!("[*] Loading config from {}", config_location.display());
            Config::from_disk(&config_location)?
        } else {
            Config::default()
        };

        config.update_from_env();

        if log_enabled!(::log::Level::Trace) {
            for line in format!("Config: {:#?}", config).lines() {
                trace!("{}", line);
            }
        }

        MDBook::load_with_config(book_root, config)
    }

    /// Load a book from its root directory using a custom config.
    pub fn load_with_config<P: Into<PathBuf>>(book_root: P, config: Config) -> Result<MDBook> {
        let root = book_root.into();

        let src_dir = root.join(&config.book.src);
        let book = book::load_book(&src_dir, &config.build)?;
        let livereload = None;

        let renderers = determine_renderers(&config);

        let mut md_book = MDBook {
            root,
            config,
            book,
            renderers,
            livereload,
            preprocessors: vec![],
        };

        let preprocessors = determine_preprocessors(&md_book);
        md_book.preprocessors = preprocessors;

        Ok(md_book)
    }

    /// Returns a flat depth-first iterator over the elements of the book,
    /// it returns an [BookItem enum](bookitem.html):
    /// `(section: String, bookitem: &BookItem)`
    ///
    /// ```no_run
    /// # extern crate mdbook;
    /// # use mdbook::MDBook;
    /// # use mdbook::book::BookItem;
    /// # #[allow(unused_variables)]
    /// # fn main() {
    /// # let book = MDBook::load("mybook").unwrap();
    /// for item in book.iter() {
    ///     match *item {
    ///         BookItem::Chapter(ref chapter) => {},
    ///         BookItem::Separator => {},
    ///     }
    /// }
    ///
    /// // would print something like this:
    /// // 1. Chapter 1
    /// // 1.1 Sub Chapter
    /// // 1.2 Sub Chapter
    /// // 2. Chapter 2
    /// //
    /// // etc.
    /// # }
    /// ```
    pub fn iter(&self) -> BookItems {
        self.book.iter()
    }

    /// `init()` gives you a `BookBuilder` which you can use to setup a new book
    /// and its accompanying directory structure.
    ///
    /// The `BookBuilder` creates some boilerplate files and directories to get
    /// you started with your book.
    ///
    /// ```text
    /// book-test/
    /// ├── book
    /// └── src
    ///     ├── chapter_1.md
    ///     └── SUMMARY.md
    /// ```
    ///
    /// It uses the path provided as the root directory for your book, then adds
    /// in a `src/` directory containing a `SUMMARY.md` and `chapter_1.md` file
    /// to get you started.
    pub fn init<P: Into<PathBuf>>(book_root: P) -> BookBuilder {
        BookBuilder::new(book_root)
    }

    /// Tells the renderer to build our book and put it in the build directory.
    pub fn build(&self) -> Result<()> {
        debug!("[fn]: build");

        for renderer in &self.renderers {
            self.run_renderer(renderer.as_ref())?;
        }

        Ok(())
    }

    fn run_renderer(&self, renderer: &Renderer) -> Result<()> {
        let name = renderer.name();
        let build_dir = self.build_dir_for(name);
        if build_dir.exists() {
            debug!(
                "Cleaning build dir for the \"{}\" renderer ({})",
                name,
                build_dir.display()
            );

            utils::fs::remove_dir_content(&build_dir)
                .chain_err(|| "Unable to clear output directory")?;
        }

        let render_context = RenderContext::new(
            self.root.clone(),
            self.book.clone(),
            self.config.clone(),
            build_dir,
        );

        renderer
            .render(&render_context)
            .chain_err(|| "Rendering failed")
    }

    /// You can change the default renderer to another one by using this method.
    /// The only requirement is for your renderer to implement the [Renderer
    /// trait](../../renderer/renderer/trait.Renderer.html)
    pub fn with_renderer<R: Renderer + 'static>(&mut self, renderer: R) -> &mut Self {
        self.renderers.push(Box::new(renderer));
        self
    }

    /// You can add a new preprocessor by using this method.
    /// The only requirement is for your renderer to implement the Preprocessor trait.
    pub fn with_preprecessor<P: Preprocessor + 'static>(&mut self, preprocessor: P) -> &mut Self {
        self.preprocessors.push(Box::new(preprocessor));
        self
    }

    /// Run `rustdoc` tests on the book, linking against the provided libraries.
    pub fn test(&mut self, library_paths: Vec<&str>) -> Result<()> {
        let library_args: Vec<&str> = (0..library_paths.len())
            .map(|_| "-L")
            .zip(library_paths.into_iter())
            .flat_map(|x| vec![x.0, x.1])
            .collect();

        let temp_dir = TempDir::new("mdbook")?;

        let replace_all_preprocessor = preprocess::links::ReplaceAllPreprocessor {
            src_dir: self.source_dir(),
        };

        replace_all_preprocessor.run(&mut self.book)?;

        for item in self.iter() {
            if let BookItem::Chapter(ref ch) = *item {
                if !ch.path.as_os_str().is_empty() {
                    let path = self.source_dir().join(&ch.path);
                    println!("[*]: Testing file: {:?}", path);
                    let content = utils::fs::file_to_string(&path)?;

                    // write preprocessed file to tempdir
                    let path = temp_dir.path().join(&ch.path);
                    let mut tmpf = utils::fs::create_file(&path)?;
                    tmpf.write_all(content.as_bytes())?;

                    let output = Command::new("rustdoc")
                        .arg(&path)
                        .arg("--test")
                        .args(&library_args)
                        .output()?;

                    if !output.status.success() {
                        bail!(ErrorKind::Subprocess(
                            "Rustdoc returned an error".to_string(),
                            output
                        ));
                    }
                }
            }
        }
        Ok(())
    }

    /// The logic for determining where a backend should put its build
    /// artefacts.
    ///
    /// If there is only 1 renderer, put it in the directory pointed to by the
    /// `build.build_dir` key in `Config`. If there is more than one then the
    /// renderer gets its own directory within the main build dir.
    ///
    /// i.e. If there were only one renderer (in this case, the HTML renderer):
    ///
    /// - build/
    ///   - index.html
    ///   - ...
    ///
    /// Otherwise if there are multiple:
    ///
    /// - build/
    ///   - epub/
    ///     - my_awesome_book.epub
    ///   - html/
    ///     - index.html
    ///     - ...
    ///   - latex/
    ///     - my_awesome_book.tex
    ///
    pub fn build_dir_for(&self, backend_name: &str) -> PathBuf {
        let build_dir = self.root.join(&self.config.build.build_dir);

        if self.renderers.len() <= 1 {
            build_dir
        } else {
            build_dir.join(backend_name)
        }
    }

    /// Get the directory containing this book's source files.
    pub fn source_dir(&self) -> PathBuf {
        self.root.join(&self.config.book.src)
    }

    // FIXME: This really belongs as part of the `HtmlConfig`.
    #[doc(hidden)]
    pub fn theme_dir(&self) -> PathBuf {
        match self.config.html_config().and_then(|h| h.theme) {
            Some(d) => self.root.join(d),
            None => self.root.join("theme"),
        }
    }
}

/// Look at the `Config` and try to figure out what renderers to use.
fn determine_renderers(config: &Config) -> Vec<Box<Renderer>> {
    let mut renderers: Vec<Box<Renderer>> = Vec::new();

    if let Some(output_table) = config.get("output").and_then(|o| o.as_table()) {
        for (key, table) in output_table.iter() {
            // the "html" backend has its own Renderer
            if key == "html" {
                renderers.push(Box::new(HtmlHandlebars::new()));
            } else {
                let renderer = interpret_custom_renderer(key, table);
                renderers.push(renderer);
            }
        }
    }

    // if we couldn't find anything, add the HTML renderer as a default
    if renderers.is_empty() {
        renderers.push(Box::new(HtmlHandlebars::new()));
    }

    renderers
}

/// Look at the `MDBook` and try to figure out what preprocessors to run.
fn determine_preprocessors(md_book: &MDBook) -> Vec<Box<Preprocessor>> {
    let mut preprocessors: Vec<Box<Preprocessor>> = Vec::new();

    if let Some(preprocess_array) = md_book.config.get("pre_process").and_then(|o| o.as_array()) {
        for key in preprocess_array.iter() {
            if key.as_str().map_or(false, |key| key == "links") {
                let preprocessor = preprocess::links::ReplaceAllPreprocessor {
                    src_dir: md_book.source_dir(),
                };

                preprocessors.push(Box::new(preprocessor))
            }
        }
    }

    preprocessors
}

fn interpret_custom_renderer(key: &str, table: &Value) -> Box<Renderer> {
    // look for the `command` field, falling back to using the key
    // prepended by "mdbook-"
    let table_dot_command = table
        .get("command")
        .and_then(|c| c.as_str())
        .map(|s| s.to_string());

    let command = table_dot_command.unwrap_or_else(|| format!("mdbook-{}", key));

    Box::new(CmdRenderer::new(key.to_string(), command.to_string()))
}

#[cfg(test)]
mod tests {
    use super::*;
    use toml::value::{Table, Value};

    #[test]
    fn config_defaults_to_html_renderer_if_empty() {
        let cfg = Config::default();

        // make sure we haven't got anything in the `output` table
        assert!(cfg.get("output").is_none());

        let got = determine_renderers(&cfg);

        assert_eq!(got.len(), 1);
        assert_eq!(got[0].name(), "html");
    }

    #[test]
    fn add_a_random_renderer_to_the_config() {
        let mut cfg = Config::default();
        cfg.set("output.random", Table::new()).unwrap();

        let got = determine_renderers(&cfg);

        assert_eq!(got.len(), 1);
        assert_eq!(got[0].name(), "random");
    }

    #[test]
    fn add_a_random_renderer_with_custom_command_to_the_config() {
        let mut cfg = Config::default();

        let mut table = Table::new();
        table.insert("command".to_string(), Value::String("false".to_string()));
        cfg.set("output.random", table).unwrap();

        let got = determine_renderers(&cfg);

        assert_eq!(got.len(), 1);
        assert_eq!(got[0].name(), "random");
    }
}
<commit_msg>Add pre-processing step to build method of MDBook<commit_after>//! The internal representation of a book and infrastructure for loading it from
//! disk and building it.
//!
//! For examples on using `MDBook`, consult the [top-level documentation][1].
//!
//! [1]: ../index.html

#![deny(missing_docs)]

mod summary;
mod book;
mod init;

pub use self::book::{load_book, Book, BookItem, BookItems, Chapter};
pub use self::summary::{parse_summary, Link, SectionNumber, Summary, SummaryItem};
pub use self::init::BookBuilder;

use std::path::PathBuf;
use std::io::Write;
use std::process::Command;
use tempdir::TempDir;
use toml::Value;

use utils;
use renderer::{CmdRenderer, HtmlHandlebars, RenderContext, Renderer};
use preprocess::{self, Preprocessor};
use errors::*;

use config::Config;

/// The object used to manage and build a book.
pub struct MDBook {
    /// The book's root directory.
    pub root: PathBuf,
    /// The configuration used to tweak now a book is built.
    pub config: Config,
    /// A representation of the book's contents in memory.
    pub book: Book,
    renderers: Vec<Box<Renderer>>,

    /// The URL used for live reloading when serving up the book.
    pub livereload: Option<String>,

    /// List of pre-processors to be run on the book
    preprocessors: Vec<Box<Preprocessor>>
}

impl MDBook {
    /// Load a book from its root directory on disk.
    pub fn load<P: Into<PathBuf>>(book_root: P) -> Result<MDBook> {
        let book_root = book_root.into();
        let config_location = book_root.join("book.toml");

        // the book.json file is no longer used, so we should emit a warning to
        // let people know to migrate to book.toml
        if book_root.join("book.json").exists() {
            warn!("It appears you are still using book.json for configuration.");
            warn!("This format is no longer used, so you should migrate to the");
            warn!("book.toml format.");
            warn!("Check the user guide for migration information:");
            warn!("\thttps://rust-lang-nursery.github.io/mdBook/format/config.html");
        }

        let mut config = if config_location.exists() {
            debug!("[*] Loading config from {}", config_location.display());
            Config::from_disk(&config_location)?
        } else {
            Config::default()
        };

        config.update_from_env();

        if log_enabled!(::log::Level::Trace) {
            for line in format!("Config: {:#?}", config).lines() {
                trace!("{}", line);
            }
        }

        MDBook::load_with_config(book_root, config)
    }

    /// Load a book from its root directory using a custom config.
    pub fn load_with_config<P: Into<PathBuf>>(book_root: P, config: Config) -> Result<MDBook> {
        let root = book_root.into();

        let src_dir = root.join(&config.book.src);
        let book = book::load_book(&src_dir, &config.build)?;
        let livereload = None;

        let renderers = determine_renderers(&config);

        let mut md_book = MDBook {
            root,
            config,
            book,
            renderers,
            livereload,
            preprocessors: vec![],
        };

        let preprocessors = determine_preprocessors(&md_book);
        md_book.preprocessors = preprocessors;

        Ok(md_book)
    }

    /// Returns a flat depth-first iterator over the elements of the book,
    /// it returns an [BookItem enum](bookitem.html):
    /// `(section: String, bookitem: &BookItem)`
    ///
    /// ```no_run
    /// # extern crate mdbook;
    /// # use mdbook::MDBook;
    /// # use mdbook::book::BookItem;
    /// # #[allow(unused_variables)]
    /// # fn main() {
    /// # let book = MDBook::load("mybook").unwrap();
    /// for item in book.iter() {
    ///     match *item {
    ///         BookItem::Chapter(ref chapter) => {},
    ///         BookItem::Separator => {},
    ///     }
    /// }
    ///
    /// // would print something like this:
    /// // 1. Chapter 1
    /// // 1.1 Sub Chapter
    /// // 1.2 Sub Chapter
    /// // 2. Chapter 2
    /// //
    /// // etc.
    /// # }
    /// ```
    pub fn iter(&self) -> BookItems {
        self.book.iter()
    }

    /// `init()` gives you a `BookBuilder` which you can use to setup a new book
    /// and its accompanying directory structure.
    ///
    /// The `BookBuilder` creates some boilerplate files and directories to get
    /// you started with your book.
    ///
    /// ```text
    /// book-test/
    /// ├── book
    /// └── src
    ///     ├── chapter_1.md
    ///     └── SUMMARY.md
    /// ```
    ///
    /// It uses the path provided as the root directory for your book, then adds
    /// in a `src/` directory containing a `SUMMARY.md` and `chapter_1.md` file
    /// to get you started.
    pub fn init<P: Into<PathBuf>>(book_root: P) -> BookBuilder {
        BookBuilder::new(book_root)
    }

    /// Tells the renderer to build our book and put it in the build directory.
    pub fn build(&self) -> Result<()> {
        debug!("[fn]: build");

        let mut preprocessed_book = self.book.clone();

        for preprocessor in &self.preprocessors {
            preprocessor.run(&mut preprocessed_book)?;
        }

        for renderer in &self.renderers {
            self.run_renderer(&preprocessed_book, renderer.as_ref())?;
        }

        Ok(())
    }

    fn run_renderer(&self, preprocessed_book: &Book, renderer: &Renderer) -> Result<()> {
        let name = renderer.name();
        let build_dir = self.build_dir_for(name);
        if build_dir.exists() {
            debug!(
                "Cleaning build dir for the \"{}\" renderer ({})",
                name,
                build_dir.display()
            );

            utils::fs::remove_dir_content(&build_dir)
                .chain_err(|| "Unable to clear output directory")?;
        }

        let render_context = RenderContext::new(
            self.root.clone(),
            preprocessed_book.clone(),
            self.config.clone(),
            build_dir,
        );

        renderer
            .render(&render_context)
            .chain_err(|| "Rendering failed")
    }

    /// You can change the default renderer to another one by using this method.
    /// The only requirement is for your renderer to implement the [Renderer
    /// trait](../../renderer/renderer/trait.Renderer.html)
    pub fn with_renderer<R: Renderer + 'static>(&mut self, renderer: R) -> &mut Self {
        self.renderers.push(Box::new(renderer));
        self
    }

    /// You can add a new preprocessor by using this method.
    /// The only requirement is for your renderer to implement the Preprocessor trait.
    pub fn with_preprecessor<P: Preprocessor + 'static>(&mut self, preprocessor: P) -> &mut Self {
        self.preprocessors.push(Box::new(preprocessor));
        self
    }

    /// Run `rustdoc` tests on the book, linking against the provided libraries.
    pub fn test(&mut self, library_paths: Vec<&str>) -> Result<()> {
        let library_args: Vec<&str> = (0..library_paths.len())
            .map(|_| "-L")
            .zip(library_paths.into_iter())
            .flat_map(|x| vec![x.0, x.1])
            .collect();

        let temp_dir = TempDir::new("mdbook")?;

        let replace_all_preprocessor = preprocess::links::ReplaceAllPreprocessor {
            src_dir: self.source_dir(),
        };

        replace_all_preprocessor.run(&mut self.book)?;

        for item in self.iter() {
            if let BookItem::Chapter(ref ch) = *item {
                if !ch.path.as_os_str().is_empty() {
                    let path = self.source_dir().join(&ch.path);
                    println!("[*]: Testing file: {:?}", path);
                    let content = utils::fs::file_to_string(&path)?;

                    // write preprocessed file to tempdir
                    let path = temp_dir.path().join(&ch.path);
                    let mut tmpf = utils::fs::create_file(&path)?;
                    tmpf.write_all(content.as_bytes())?;

                    let output = Command::new("rustdoc")
                        .arg(&path)
                        .arg("--test")
                        .args(&library_args)
                        .output()?;

                    if !output.status.success() {
                        bail!(ErrorKind::Subprocess(
                            "Rustdoc returned an error".to_string(),
                            output
                        ));
                    }
                }
            }
        }
        Ok(())
    }

    /// The logic for determining where a backend should put its build
    /// artefacts.
    ///
    /// If there is only 1 renderer, put it in the directory pointed to by the
    /// `build.build_dir` key in `Config`. If there is more than one then the
    /// renderer gets its own directory within the main build dir.
    ///
    /// i.e. If there were only one renderer (in this case, the HTML renderer):
    ///
    /// - build/
    ///   - index.html
    ///   - ...
    ///
    /// Otherwise if there are multiple:
    ///
    /// - build/
    ///   - epub/
    ///     - my_awesome_book.epub
    ///   - html/
    ///     - index.html
    ///     - ...
    ///   - latex/
    ///     - my_awesome_book.tex
    ///
    pub fn build_dir_for(&self, backend_name: &str) -> PathBuf {
        let build_dir = self.root.join(&self.config.build.build_dir);

        if self.renderers.len() <= 1 {
            build_dir
        } else {
            build_dir.join(backend_name)
        }
    }

    /// Get the directory containing this book's source files.
    pub fn source_dir(&self) -> PathBuf {
        self.root.join(&self.config.book.src)
    }

    // FIXME: This really belongs as part of the `HtmlConfig`.
    #[doc(hidden)]
    pub fn theme_dir(&self) -> PathBuf {
        match self.config.html_config().and_then(|h| h.theme) {
            Some(d) => self.root.join(d),
            None => self.root.join("theme"),
        }
    }
}

/// Look at the `Config` and try to figure out what renderers to use.
fn determine_renderers(config: &Config) -> Vec<Box<Renderer>> {
    let mut renderers: Vec<Box<Renderer>> = Vec::new();

    if let Some(output_table) = config.get("output").and_then(|o| o.as_table()) {
        for (key, table) in output_table.iter() {
            // the "html" backend has its own Renderer
            if key == "html" {
                renderers.push(Box::new(HtmlHandlebars::new()));
            } else {
                let renderer = interpret_custom_renderer(key, table);
                renderers.push(renderer);
            }
        }
    }

    // if we couldn't find anything, add the HTML renderer as a default
    if renderers.is_empty() {
        renderers.push(Box::new(HtmlHandlebars::new()));
    }

    renderers
}

/// Look at the `MDBook` and try to figure out what preprocessors to run.
fn determine_preprocessors(md_book: &MDBook) -> Vec<Box<Preprocessor>> {
    let mut preprocessors: Vec<Box<Preprocessor>> = Vec::new();

    if let Some(preprocess_array) = md_book.config.get("pre_process").and_then(|o| o.as_array()) {
        for key in preprocess_array.iter() {
            if key.as_str().map_or(false, |key| key == "links") {
                let preprocessor = preprocess::links::ReplaceAllPreprocessor {
                    src_dir: md_book.source_dir(),
                };

                preprocessors.push(Box::new(preprocessor))
            }
        }
    }

    preprocessors
}

fn interpret_custom_renderer(key: &str, table: &Value) -> Box<Renderer> {
    // look for the `command` field, falling back to using the key
    // prepended by "mdbook-"
    let table_dot_command = table
        .get("command")
        .and_then(|c| c.as_str())
        .map(|s| s.to_string());

    let command = table_dot_command.unwrap_or_else(|| format!("mdbook-{}", key));

    Box::new(CmdRenderer::new(key.to_string(), command.to_string()))
}

#[cfg(test)]
mod tests {
    use super::*;
    use toml::value::{Table, Value};

    #[test]
    fn config_defaults_to_html_renderer_if_empty() {
        let cfg = Config::default();

        // make sure we haven't got anything in the `output` table
        assert!(cfg.get("output").is_none());

        let got = determine_renderers(&cfg);

        assert_eq!(got.len(), 1);
        assert_eq!(got[0].name(), "html");
    }

    #[test]
    fn add_a_random_renderer_to_the_config() {
        let mut cfg = Config::default();
        cfg.set("output.random", Table::new()).unwrap();

        let got = determine_renderers(&cfg);

        assert_eq!(got.len(), 1);
        assert_eq!(got[0].name(), "random");
    }

    #[test]
    fn add_a_random_renderer_with_custom_command_to_the_config() {
        let mut cfg = Config::default();

        let mut table = Table::new();
        table.insert("command".to_string(), Value::String("false".to_string()));
        cfg.set("output.random", table).unwrap();

        let got = determine_renderers(&cfg);

        assert_eq!(got.len(), 1);
        assert_eq!(got[0].name(), "random");
    }
}
<|endoftext|>


<commit_before>#![allow(unstable, dead_code)]

extern crate libc;

mod args;
mod ansi;
mod matching;
mod screen;

use std::os;
use std::io;
use std::cmp::min;
use screen::Screen;
use screen::Key;
use screen::Key::*;
use self::SearchState::*;

fn main() {
  let args = match args::parse_args() {
    Some(args) => args,
    None => {
      os::set_exit_status(1);
      return;
    },
  };

  if args.help { return; }

  let choices = read_choices();
  let initial_search = args.initial_search.clone();
  if args.use_first {
    let matches = matching::compute_matches(&choices, initial_search.as_slice());
    println!("{}", matches[0]);
    return;
  } else {
    event_loop(choices, initial_search.as_slice());
  }
}

fn event_loop(choices: Vec<String>, initial_search: &str) {
  let mut search = Search::new(&choices, initial_search.to_string());
  let mut screen = Screen::open_screen();

  loop {
    search.recompute_matches();

    match search.state {
      InProgress => draw_screen(&mut screen, &search),
      _ => break,
    }

    let keys = screen.get_buffered_keys();
    for key in keys.iter() {
      handle_key(&mut search, key, screen.visible_choices);
    }
  }

  screen.move_cursor_to_bottom();
  println!("{}", search.get_selection());
}

fn handle_key(search: &mut Search, key: &Key, visible_choices: u16) {
  match *key {
    Char(x) => search.append(x),
    Backspace => search.backspace(),
    Control('h') => search.backspace(),
    Control('w') => search.delete_word(),
    Control('u') => search.clear_query(),
    Control('c') => search.cancel(),
    Control('g') => search.cancel(),
    Control('n') => search.down(visible_choices),
    Control('p') => search.up(),
    Enter => search.done(),
    _ => {}
  }
}

struct Search<'a> {
  choices: &'a Vec<String>,
  query: String,
  matches: Vec<&'a String>,
  stale: bool,
  index: usize,
  state: SearchState,
}

#[derive(PartialEq, Eq)]
enum SearchState {
  InProgress,
  Done,
  Canceled,
}

impl<'a> Search<'a> {
  fn new(choices: &'a Vec<String>, initial_search: String) -> Search<'a> {
    Search {
      choices: choices,
      query: initial_search,
      matches: Vec::new(),
      stale: true,
      index: 0,
      state: InProgress,
    }
  }

  fn up(&mut self) {
    self.index = if self.index == 0 { 0 } else { self.index - 1 };
  }

  fn down(&mut self, visible_choices: u16) {
    let limit = min(visible_choices as usize - 1, self.matches.len() - 1);
    self.index = min(self.index + 1, limit);
  }

  fn backspace(&mut self) {
    self.query.pop();
    self.stale = true;
    self.index = 0;
  }

  fn delete_word(&mut self) {
    self.stale = true;
    delete_last_word(&mut self.query);
  }

  fn append(&mut self, c: char) {
    self.query.push(c);
    self.stale = true;
    self.index = 0;
  }

  fn clear_query(&mut self) {
    self.query.clear();
    self.stale = true;
  }

  fn recompute_matches(&mut self) {
    if self.stale {
      self.matches = matching::compute_matches(self.choices, self.query.as_slice());
      self.stale = false;
    }
  }

  fn get_selection(&mut self) -> &str {
    if self.state == Canceled {
      ""
    } else {
      self.recompute_matches();
      self.matches[self.index].as_slice()
    }
  }

  fn cancel(&mut self) {
    self.state = Canceled
  }

  fn done(&mut self) {
    self.state = Done
  }
}

fn draw_screen(screen: &mut Screen, search: &Search) {
  screen.hide_cursor();
  screen.blank_screen();
  screen.write(format!("> {} ({} choices)\n", search.query, search.choices.len()).as_slice());

  print_matches(screen, &search.matches, search.index);

  let start = screen.start_line;
  screen.move_cursor(start, 2 + search.query.len() as u16);
  screen.show_cursor();
}

fn print_matches(screen: &mut Screen, matches: &Vec<&String>, index: usize) {
  let mut i = 1;
  for choice in matches.iter() {
    if i == index + 1 {
      screen.write_inverted(choice.as_slice());
    } else {
      screen.write(choice.as_slice());
    }
    if i >= screen.visible_choices as usize {
      return;
    } else {
      screen.write("\n");
    }
    i += 1;
  }
}

fn read_choices() -> Vec<String> {
  let mut stdin = io::stdio::stdin();
  let mut lines = Vec::new();

  while let Ok(mut s) = stdin.read_line() {
    trim(&mut s);
    lines.push(s);
  }

  lines
}

fn trim(s: &mut String) {
  while let Some(x) = s.pop() {
    if x != '\n' {
      s.push(x);
      return;
    }
  }
}

fn delete_last_word(s: &mut String) {
  let mut deleted_something = false;
  while let Some(x) = s.pop() {
    if x == ' ' {
      if deleted_something {
        s.push(x);
        return;
      }
    } else {
      deleted_something = true;
    }
  }
}

#[test]
fn trim_test() {
  fn should_become(before: &str, after: &str) {
    let mut x = before.to_string();
    trim(&mut x);
    assert_eq!(after.to_string(), x);
  }
  should_become("", "");
  should_become("\n", "");
  should_become("\n\n", "");
  should_become("asdf", "asdf");
  should_become("asdf\n", "asdf");
  should_become("asdf\nasdf\n", "asdf\nasdf");
}

#[test]
fn delete_word_test() {
  fn should_become(before: &str, after: &str) {
    let mut x = before.to_string();
    delete_last_word(&mut x);
    assert_eq!(after.to_string(), x);
  }
  should_become("", "");
  should_become("a", "");
  should_become("asdf", "");
  should_become("asdf asdf asdf", "asdf asdf ");
  should_become("asdf asdf asdf ", "asdf asdf ");
  should_become("asdf asdf asdf  ", "asdf asdf ");
}
<commit_msg>Don't panic on zero matches<commit_after>#![allow(unstable, dead_code)]

extern crate libc;

mod args;
mod ansi;
mod matching;
mod screen;

use std::os;
use std::io;
use std::cmp::min;
use screen::Screen;
use screen::Key;
use screen::Key::*;
use self::SearchState::*;

fn main() {
  let args = match args::parse_args() {
    Some(args) => args,
    None => {
      os::set_exit_status(1);
      return;
    },
  };

  if args.help { return; }

  let choices = read_choices();
  let initial_search = args.initial_search.clone();
  if args.use_first {
    let matches = matching::compute_matches(&choices, initial_search.as_slice());
    println!("{}", matches.get(0).unwrap_or(&&"".to_string()));
    return;
  } else {
    event_loop(choices, initial_search.as_slice());
  }
}

fn event_loop(choices: Vec<String>, initial_search: &str) {
  let mut search = Search::new(&choices, initial_search.to_string());
  let mut screen = Screen::open_screen();

  loop {
    search.recompute_matches();

    match search.state {
      InProgress => draw_screen(&mut screen, &search),
      _ => break,
    }

    let keys = screen.get_buffered_keys();
    for key in keys.iter() {
      handle_key(&mut search, key, screen.visible_choices);
    }
  }

  screen.move_cursor_to_bottom();
  println!("{}", search.get_selection());
}

fn handle_key(search: &mut Search, key: &Key, visible_choices: u16) {
  match *key {
    Char(x) => search.append(x),
    Backspace => search.backspace(),
    Control('h') => search.backspace(),
    Control('w') => search.delete_word(),
    Control('u') => search.clear_query(),
    Control('c') => search.cancel(),
    Control('g') => search.cancel(),
    Control('n') => search.down(visible_choices),
    Control('p') => search.up(),
    Enter => search.done(),
    _ => {}
  }
}

struct Search<'a> {
  choices: &'a Vec<String>,
  query: String,
  matches: Vec<&'a String>,
  stale: bool,
  index: usize,
  state: SearchState,
}

#[derive(PartialEq, Eq)]
enum SearchState {
  InProgress,
  Done,
  Canceled,
}

impl<'a> Search<'a> {
  fn new(choices: &'a Vec<String>, initial_search: String) -> Search<'a> {
    Search {
      choices: choices,
      query: initial_search,
      matches: Vec::new(),
      stale: true,
      index: 0,
      state: InProgress,
    }
  }

  fn up(&mut self) {
    self.index = if self.index == 0 { 0 } else { self.index - 1 };
  }

  fn down(&mut self, visible_choices: u16) {
    let limit = min(visible_choices as usize - 1, self.matches.len() - 1);
    self.index = min(self.index + 1, limit);
  }

  fn backspace(&mut self) {
    self.query.pop();
    self.stale = true;
    self.index = 0;
  }

  fn delete_word(&mut self) {
    self.stale = true;
    delete_last_word(&mut self.query);
  }

  fn append(&mut self, c: char) {
    self.query.push(c);
    self.stale = true;
    self.index = 0;
  }

  fn clear_query(&mut self) {
    self.query.clear();
    self.stale = true;
  }

  fn recompute_matches(&mut self) {
    if self.stale {
      self.matches = matching::compute_matches(self.choices, self.query.as_slice());
      self.stale = false;
    }
  }

  fn get_selection(&mut self) -> String {
    if self.state == Canceled {
      "".to_string()
    } else {
      self.recompute_matches();
      self.matches.get(self.index).unwrap_or(&&"".to_string()).to_string()
    }
  }

  fn cancel(&mut self) {
    self.state = Canceled
  }

  fn done(&mut self) {
    self.state = Done
  }
}

fn draw_screen(screen: &mut Screen, search: &Search) {
  screen.hide_cursor();
  screen.blank_screen();
  screen.write(format!("> {} ({} choices)\n", search.query, search.choices.len()).as_slice());

  print_matches(screen, &search.matches, search.index);

  let start = screen.start_line;
  screen.move_cursor(start, 2 + search.query.len() as u16);
  screen.show_cursor();
}

fn print_matches(screen: &mut Screen, matches: &Vec<&String>, index: usize) {
  let mut i = 1;
  for choice in matches.iter() {
    if i == index + 1 {
      screen.write_inverted(choice.as_slice());
    } else {
      screen.write(choice.as_slice());
    }
    if i >= screen.visible_choices as usize {
      return;
    } else {
      screen.write("\n");
    }
    i += 1;
  }
}

fn read_choices() -> Vec<String> {
  let mut stdin = io::stdio::stdin();
  let mut lines = Vec::new();

  while let Ok(mut s) = stdin.read_line() {
    trim(&mut s);
    lines.push(s);
  }

  lines
}

fn trim(s: &mut String) {
  while let Some(x) = s.pop() {
    if x != '\n' {
      s.push(x);
      return;
    }
  }
}

fn delete_last_word(s: &mut String) {
  let mut deleted_something = false;
  while let Some(x) = s.pop() {
    if x == ' ' {
      if deleted_something {
        s.push(x);
        return;
      }
    } else {
      deleted_something = true;
    }
  }
}

#[test]
fn trim_test() {
  fn should_become(before: &str, after: &str) {
    let mut x = before.to_string();
    trim(&mut x);
    assert_eq!(after.to_string(), x);
  }
  should_become("", "");
  should_become("\n", "");
  should_become("\n\n", "");
  should_become("asdf", "asdf");
  should_become("asdf\n", "asdf");
  should_become("asdf\nasdf\n", "asdf\nasdf");
}

#[test]
fn delete_word_test() {
  fn should_become(before: &str, after: &str) {
    let mut x = before.to_string();
    delete_last_word(&mut x);
    assert_eq!(after.to_string(), x);
  }
  should_become("", "");
  should_become("a", "");
  should_become("asdf", "");
  should_become("asdf asdf asdf", "asdf asdf ");
  should_become("asdf asdf asdf ", "asdf asdf ");
  should_become("asdf asdf asdf  ", "asdf asdf ");
}
<|endoftext|>


<commit_before>use std::io::{Read, Write};
use std::net::{TcpListener, TcpStream};
use std::sync::mpsc::channel;
use std::thread;

fn main() {
    let listener = TcpListener::bind("127.0.0.1:8080").unwrap();

    // accept connections and process them, spawning a new thread for each one
    for stream in listener.incoming() {
        match stream {
            Ok(stream) => {
                thread::spawn(move|| {
                    // connection succeeded
                    create_client(stream);
                });
            }
            Err(e) => {
                println!("{}", e);
            }
        }
    }

    // close the socket server
    drop(listener);
}

fn create_client(stream: TcpStream) {
    println!("New client");
    let mut stream = stream;
    stream.write(&"Welcome to Smazy".to_string().into_bytes()).unwrap();
    stream.write(&"Please enter a username".to_string().into_bytes()).unwrap();
    stream.flush();
    let mut username = "".to_string();
    stream.read_to_string(&mut username).unwrap();
    println!("Username is {}", username);
}<commit_msg>Create client with each connection<commit_after>use std::io::{BufRead, BufReader, Write};
use std::net::{TcpListener, TcpStream};
use std::sync::mpsc::channel;
use std::thread;

fn main() {
    let listener = TcpListener::bind("127.0.0.1:8080").unwrap();

    // accept connections and process them, spawning a new thread for each one
    for stream in listener.incoming() {
        match stream {
            Ok(stream) => {
                thread::spawn(move|| {
                    // connection succeeded
                    create_client(stream);
                });
            }
            Err(e) => {
                println!("{}", e);
            }
        }
    }

    // close the socket server
    drop(listener);
}

fn create_client(stream: TcpStream) {
    println!("New client");
    let mut stream = stream;
    stream.write(&"Welcome to Smazy\n".to_string().into_bytes()).unwrap();
    stream.write(&"Please enter a username:\n".to_string().into_bytes()).unwrap();
    stream.flush().unwrap();
    let mut reader = BufReader::new(stream);
    let mut username = "".to_string();
    reader.read_line(&mut username).unwrap();
    println!("Username is {}", username);
}
<|endoftext|>


<commit_before>#[macro_use]
extern crate ecs;

use ecs::{World, BuildData, DataHelper, EntityIter, ModifyData, Process, System};
use ecs::system::{EntityProcess, EntitySystem};
use std::io;

#[derive(Clone, Debug, PartialEq)]
pub struct WorldMap {
    pub width: usize,
    pub height: usize,
    pub map: Vec<char>,
}

#[derive(Copy, Clone, Debug, PartialEq)]
pub struct Position {
    pub x: i32,
    pub y: i32,
}

#[derive(Copy, Clone, Debug, PartialEq)]
pub struct Velocity {
    pub dx: i32,
    pub dy: i32,
}

pub struct MotionProcess;

impl System for MotionProcess {
    type Components = WorldComponents;
    type Services = ();
}

impl EntityProcess for MotionProcess {
    fn process(&mut self,
               entities: EntityIter<WorldComponents>,
               data: &mut DataHelper<WorldComponents, ()>) {
        for e in entities {
            let mut position = data.position[e];
            let velocity = data.velocity[e];

            position.x += velocity.dx;
            position.y += velocity.dy;
            data.position[e] = position;
        }
    }
}

pub struct CommandProcess(pub String);

impl System for CommandProcess {
    type Components = WorldComponents;
    type Services = ();
}

impl Process for CommandProcess {
    fn process(&mut self, _: &mut DataHelper<WorldComponents, ()>) {
        println!("{}", &self.0);
    }
}

pub struct RenderView;

impl System for RenderView {
    type Components = WorldComponents;
    type Services = ();
}

impl EntityProcess for RenderView {
    fn process(&mut self,
               entities: EntityIter<WorldComponents>,
               data: &mut DataHelper<WorldComponents, ()>) {
        for e in entities {
            let ref world_map = data.world_map[e];
            let height = world_map.height;
            let width = world_map.width;
            for i in 0..height {
                for j in 0..width {
                    print!("{}", world_map.map[i * width + j]);
                    if j == width - 1 {
                        println!("");
                    }
                }
            }
        }
    }
}

components! {
    struct WorldComponents {
        #[hot] position: Position,
        #[hot] velocity: Velocity,
        #[hot] world_map: WorldMap,
    }
}

systems! {
    struct WorldSystems<WorldComponents, ()> {
        active: {
            motion: EntitySystem<MotionProcess> = EntitySystem::new(
                MotionProcess,
                aspect!(<WorldComponents> all: [position, velocity])
            ),
            render: EntitySystem<RenderView> = EntitySystem::new(
                RenderView,
                aspect!(<WorldComponents> all: [world_map])
            ),
            command: CommandProcess = CommandProcess("idle".to_string()),
        },
        passive: {}
    }
}

fn main() {
    let mut world = World::<WorldSystems>::new();
    let entity = world.create_entity(|entity: BuildData<WorldComponents>,
                                      data: &mut WorldComponents| {
        data.position.add(&entity, Position { x: 0, y: 0 });
        data.velocity.add(&entity, Velocity { dx: 0, dy: 0 });
    });
    let world_map = world.create_entity(|entity: BuildData<WorldComponents>,
                                      data: &mut WorldComponents| {
        let map_matrix = vec![
            '🌲', '🌲', '🌲', '🌳', '🌲', '🌲', '🌲', '🌲', '🌲', '🌲',
            '🌲',  '⬚',  '⬚',  '⬚',  '⬚',  '⬚',  '⬚',  '⬚',  '⬚', '🌲',
            '🌲',  '⬚',  '⬚',  '⬚',  '⬚',  '⬚',  '⬚',  '⬚',  '⬚', '🌲',
            '🌲',  '⬚',  '⬚',  '⬚',  '⬚',  '⬚',  '⬚',  '⬚',  '⬚', '🌲',
            '🌲',  '⬚',  '⬚',  '⬚',  '⬚',  '⬚',  '⬚',  '⬚',  '⬚', '🌲',
            '🌲',  '⬚',  '⬚',  '⬚',  '⬚',  '⬚',  '⬚',  '⬚',  '⬚', '🌲',
            '🌲',  '⬚',  '⬚',  '⬚',  '⬚',  '⬚',  '⬚',  '⬚',  '⬚', '🌲',
            '🌲',  '⬚',  '⬚',  '⬚',  '⬚',  '⬚',  '⬚',  '⬚',  '⬚', '🌲',
            '🌲',  '⬚',  '⬚',  '⬚',  '⬚',  '⬚',  '⬚',  '⬚',  '⬚', '🌲',
            '🌲', '🌲', '🌲', '🌳', '🌲', '🌲', '🌲', '🌲', '🌲', '🌲'
        ];
        data.world_map.add(&entity, WorldMap { width: 10, height: 10, map: map_matrix });
    });

    loop {
        let mut input = String::new();
        if let Ok(_) = io::stdin().read_line(&mut input) {
            world.systems.command.0 = input;
            world.update();
        }
    }
}
<commit_msg>Different characters for the map.<commit_after>#[macro_use]
extern crate ecs;

use ecs::{World, BuildData, DataHelper, EntityIter, ModifyData, Process, System};
use ecs::system::{EntityProcess, EntitySystem};
use std::io;

#[derive(Clone, Debug, PartialEq)]
pub struct WorldMap {
    pub width: usize,
    pub height: usize,
    pub map: Vec<char>,
}

#[derive(Copy, Clone, Debug, PartialEq)]
pub struct Position {
    pub x: i32,
    pub y: i32,
}

#[derive(Copy, Clone, Debug, PartialEq)]
pub struct Velocity {
    pub dx: i32,
    pub dy: i32,
}

pub struct MotionProcess;

impl System for MotionProcess {
    type Components = WorldComponents;
    type Services = ();
}

impl EntityProcess for MotionProcess {
    fn process(&mut self,
               entities: EntityIter<WorldComponents>,
               data: &mut DataHelper<WorldComponents, ()>) {
        for e in entities {
            let mut position = data.position[e];
            let velocity = data.velocity[e];

            position.x += velocity.dx;
            position.y += velocity.dy;
            data.position[e] = position;
        }
    }
}

pub struct CommandProcess(pub String);

impl System for CommandProcess {
    type Components = WorldComponents;
    type Services = ();
}

impl Process for CommandProcess {
    fn process(&mut self, _: &mut DataHelper<WorldComponents, ()>) {
        println!("{}", &self.0);
    }
}

pub struct RenderView;

impl System for RenderView {
    type Components = WorldComponents;
    type Services = ();
}

impl EntityProcess for RenderView {
    fn process(&mut self,
               entities: EntityIter<WorldComponents>,
               data: &mut DataHelper<WorldComponents, ()>) {
        for e in entities {
            let ref world_map = data.world_map[e];
            let height = world_map.height;
            let width = world_map.width;
            for i in 0..height {
                for j in 0..width {
                    print!("{}", world_map.map[i * width + j]);
                    if j == width - 1 {
                        println!("");
                    }
                }
            }
        }
    }
}

components! {
    struct WorldComponents {
        #[hot] position: Position,
        #[hot] velocity: Velocity,
        #[hot] world_map: WorldMap,
    }
}

systems! {
    struct WorldSystems<WorldComponents, ()> {
        active: {
            motion: EntitySystem<MotionProcess> = EntitySystem::new(
                MotionProcess,
                aspect!(<WorldComponents> all: [position, velocity])
            ),
            render: EntitySystem<RenderView> = EntitySystem::new(
                RenderView,
                aspect!(<WorldComponents> all: [world_map])
            ),
            command: CommandProcess = CommandProcess("idle".to_string()),
        },
        passive: {}
    }
}

fn main() {
    let mut world = World::<WorldSystems>::new();
    let entity = world.create_entity(|entity: BuildData<WorldComponents>,
                                      data: &mut WorldComponents| {
        data.position.add(&entity, Position { x: 0, y: 0 });
        data.velocity.add(&entity, Velocity { dx: 0, dy: 0 });
    });
    let world_map = world.create_entity(|entity: BuildData<WorldComponents>,
                                      data: &mut WorldComponents| {
        let map_matrix = vec![
            '🌲', '🌲', '🌲', '🌳', '🌲', '🌲', '🌲', '🌲', '🌲', '🌲',
            '🌲',  '_',  '.',  '.',  '.',  '.',  '.',  '.',  '.', '🌲',
            '🌲',  '.',  '.',  '.',  '.',  '.',  '.',  '.',  '.', '🌲',
            '🌲',  '.',  '.',  '.',  '.',  '.',  '.',  '.',  '.', '🌲',
            '🌲',  '.',  '.',  '.',  '.',  '.',  '.',  '.',  '.', '🌲',
            '🌲',  '.',  '.',  '.',  '.',  '🏠',  '.',  '.',  '.', '🌲',
            '🌲',  '.',  '.',  '.',  '.',  '.',  '.',  '.',  '.', '🌲',
            '🌲',  '.',  '.',  '.',  '.',  '.',  '.',  '.',  '.', '🌲',
            '🌲',  '.',  '.',  '.',  '.',  '.',  '.',  '.',  '.', '🌲',
            '🌲', '🌲', '🌲', '🌳', '🌲', '🌲', '🌲', '🌲', '🌲', '🌲'
        ];
        data.world_map.add(&entity, WorldMap { width: 10, height: 10, map: map_matrix });
    });

    loop {
        let mut input = String::new();
        if let Ok(_) = io::stdin().read_line(&mut input) {
            world.systems.command.0 = input;
            world.update();
        }
    }
}
<|endoftext|>


<commit_before>pub mod download;
pub mod database;
pub mod engine;

extern crate curl;
extern crate flate2;
extern crate rusqlite;
extern crate regex;
extern crate clap;
extern crate term_painter;

use download::*;
use download::decompressor::*;
use database::*;
use database::parser::*;
use engine::*;

fn main() {

    println!("Welcome to Film-O-Mat");

    if !db_exists() {

        print!("Creating Database...");
        create_database();

        print!(" Done \nDownload movies...");
        let movies = decompress(&download_archiv("ftp://ftp.fu-berlin.\
                                                 de/pub/misc/movies/database/ratings.list.gz"));
        print!(" Done. \nDownload genres...");
        let genres = decompress(&download_archiv("ftp://ftp.fu-berlin.\
                                                  de/pub/misc/movies/database/genres.list.gz"));
        print!(" Done. \nStart parsing movies...");
        parse_rating(movies.unwrap());
        print!(" Done. \nStart parsing genres...");
        parse_genre(genres.unwrap());
        println!(" Done.");
    }

    println!("Database ready!");

    output_result(execute(get_search_params()));

}
<commit_msg>improved output<commit_after>pub mod download;
pub mod database;
pub mod engine;

extern crate curl;
extern crate flate2;
extern crate rusqlite;
extern crate regex;
extern crate clap;
extern crate term_painter;

use download::*;
use download::decompressor::*;
use database::*;
use database::parser::*;
use engine::*;

fn main() {

    println!("Welcome to Film-O-Mat");

    if !db_exists() {

        print!("Initialize Database...");
        create_database();

        println!(" Done.");
        print!("Download movies...");
        let movies = decompress(&download_archiv("ftp://ftp.fu-berlin.\
                                                 de/pub/misc/movies/database/ratings.list.gz"));
        println!(" Done.");
        print!("Download genres...");
        let genres = decompress(&download_archiv("ftp://ftp.fu-berlin.\
                                                  de/pub/misc/movies/database/genres.list.gz"));
        println!(" Done.");
        print!("Start parsing movies...");
        parse_rating(movies.unwrap());
        println!(" Done.");
        print!("Start parsing genres...");
        parse_genre(genres.unwrap());
        println!(" Done.");
    }

    println!("Database ready.");

    output_result(execute(get_search_params()));

}
<|endoftext|>


<commit_before>extern crate combine;
extern crate nix; // As in *nix
extern crate rand;

// Reference: https://github.com/NixOS/nixpkgs/blob/b07051ce6c3ba3039c89b6755da279002b0c3ace/nixos/modules/virtualisation/nixos-container.pl

use std::io;
use std::io::Write;
use std::process::Command;

enum Error {
    Usage(&'static str),
    NonZero(String, Option<i32>),
    ControlError(String),
    IoError(io::Error),
    NixError(nix::Error)
}

use Error::*;

impl From<io::Error> for Error {
    fn from(e: io::Error) -> Self { IoError(e) }
}

impl From<nix::Error> for Error {
    fn from(e: nix::Error) -> Self { NixError(e) }
}

type Result<T> = std::result::Result<T, Error>;

trait CommandOut : Sized {
    fn run(&mut Command) -> Result<Self>;
}

impl CommandOut for () {
    fn run(cmd: &mut Command) -> Result<Self> {
        let exit = try!(cmd.spawn().and_then(|mut c| c.wait()));
        if exit.success() {
            Ok(())
        } else {
            Err(NonZero(format!("{:?}", cmd), exit.code()))
        }
    }
}

impl CommandOut for String {
    fn run(cmd: &mut Command) -> Result<Self> {
        use std::process::Stdio;
        use std::io::Read;
        let mut buffer = String::new();
        let mut child = try!(cmd.stderr(Stdio::null()).stdout(Stdio::piped()).spawn());
        let rdres = child.stdout.as_mut().unwrap().read_to_string(&mut buffer);
        let wtres = child.wait();
        let exit = try!(rdres.and(wtres).map_err(IoError));
        if exit.success() {
            Ok(buffer)
        } else {
            Err(NonZero(format!("{:?}", cmd), exit.code()))
        }
    }
}

fn run<T>(cmd: &mut Command) -> Result<T> where T : CommandOut {
    T::run(cmd)
}

fn probably_unique_name(length: usize) -> String {
    use rand::distributions::{IndependentSample, Range};
    let between = Range::new(0, 36);
    let mut rng = rand::thread_rng();
    unsafe {
        std::str::from_utf8_unchecked(&(0..length).map(|_| between.ind_sample(&mut rng)).map(|v| if v < 10 { v + 48 } else { v + 87 }).collect::<Vec<u8>>()).to_string()
    }
}

// Result is of the form "10.233.x"
fn unused_ip_block() -> Result<String> {
    // Can bail early anywhere here because we've got the lock
    let mut used = std::collections::HashSet::new();
    for entry in try!(std::fs::read_dir("/etc/containers")) {
        let entry = try!(entry);
        if try!(entry.file_type()).is_file() {
            let mut str = String::new();
            use std::io::Read;
            try!(try!(std::fs::File::open(entry.path())).read_to_string(&mut str));
            for line in str.lines() {
                use combine::{string, many1, choice, digit, token, eof, Parser, ParserExt};
                let ip_component = || { string("10.233.").with(many1::<String, _>(digit())).skip(token('.')).skip(many1::<String, _>(digit())) };
                if let Ok((c, _)) = choice([string("HOST_ADDRESS="), string("LOCAL_ADDRESS=")]).with(ip_component()).skip(eof()).parse(line) {
                    used.insert(c);
                }
            }
        }
    }
    (0..255).map(|p| p.to_string()).find(|s| !used.contains(s)).map(|s| format!("10.233.{}", s)).ok_or(ControlError("Out of IP addresses".to_string()))
}

fn mkpath(mode : u32, path : &str) -> Result<()> {
    use std::os::unix::fs::DirBuilderExt;
    match std::fs::DirBuilder::new().mode(mode).create(path) {
        Err(e) => if let std::io::ErrorKind::AlreadyExists = e.kind() { Ok(()) } else { Err(IoError(e)) },
        Ok(()) => Ok(())
    }
}

fn system_init() -> Result<()> {
    try!(mkpath(0o0755, "/etc/containers"));
    try!(mkpath(0o0700, "/var/lib/containers"));
    Ok(())
}

fn profile_dir(name : &str) -> String {
    format!("/nix/var/nix/profiles/per-container/{}", name)
}

fn container_root(name : &str) -> String {
    format!("/var/lib/containers/{}", name)
}

fn conf_file(name : &str) -> String {
    format!("/etc/containers/{}.conf", name)
}

fn create(config_file : &str) -> Result<String> {
    fn init_container_unsynced(name : &str) -> Result<()> {
        let ip_block = try!(unused_ip_block());
        // Use create_new to avoid clobbering existing container
        let mut f = try!(std::fs::OpenOptions::new().create_new(true).write(true).open(conf_file(name)));
        write!(&mut f, "PRIVATE_NETWORK=1\nHOST_ADDRESS={0}.1\nLOCAL_ADDRESS={0}.2\nAUTO_START=0\n", ip_block).map_err(IoError)
    }

    fn populate_container(name : &str, config_file : &str) -> Result<()> {
        use std::os::unix::fs::DirBuilderExt;
        // The per-container directory is restricted to prevent users on
        // the host from messing with guest users who happen to have the
        // same uid.
        try!(mkpath(0o0700, "/nix/var/nix/profiles/per-container"));
        let profile_dir = profile_dir(name);
        let mut dir_builder = std::fs::DirBuilder::new();
        try!(dir_builder.mode(0o0755).create(&profile_dir));

        let container_root = container_root(name);
        dir_builder.recursive(true);
        try!(dir_builder.mode(0o0755).create(format!("{}/etc/nixos", container_root)));

        // Write configuration.nix into the container fs
        let configuration_nix = format!("{}/etc/nixos/configuration.nix", container_root);
        {
            let mut f = try!(std::fs::File::create(format!("{}/etc/nixos/configuration.nix", container_root)));
            try!(write!(&mut f, "
    {{ config, lib, pkgs, ... }}:
    with lib;

    {{
        boot.isContainer = true;
        networking.hostName = mkDefault \"{}\";
        networking.useDHCP = false;
        imports = [ {} ];
    }}", name, config_file));
            // TODO: guaranteed sync at end of block?
        }

        // Build the container config
        run(Command::new("nix-env")
            .arg("-p").arg(format!("{}/system", profile_dir))
            .arg("-I").arg(format!("nixos-config={}", configuration_nix))
            .arg("-f").arg("<nixpkgs/nixos>")
            .arg("--set")
            .arg("-A").arg("system")
            .arg("--show-trace"))
    }

    let name = format!("fr{}", probably_unique_name(9));

    // Acquire lock
    use std::os::unix::io::IntoRawFd;
    let lock_fd = try!(std::fs::OpenOptions::new().create(true).append(true).open("/run/lock/nixos-container")).into_raw_fd();
    try!(nix::fcntl::flock(lock_fd, nix::fcntl::FlockArg::LockExclusive));

    let res = init_container_unsynced(&name);

    // Drop lock
    unsafe {
        let _ = nix::libc::close(lock_fd);  // If closing fails, ¯\_(ツ)_/¯
    }

    match res.and_then(|()| populate_container(&name, config_file)) {
        Ok(()) => Ok(name),
        // Don't leave a half-baked container behind
        Err(e) => { let _ = destroy(&name); Err(e) }
    }
}

fn run_test(container_name : &str, args : &[String]) -> Result<i32> {
    try!(run(Command::new("systemctl").arg("start").arg(format!("container@{}", container_name))));
    let leader_pid = try!(run(Command::new("machinectl").arg("show").arg(container_name).arg("-p").arg("Leader")).and_then(|o : String| {
        use combine::{string, many1, digit, spaces, eof, Parser, ParserExt};
        string("Leader=").with(many1::<String, _>(digit())).skip(spaces()).skip(eof()).parse(o.as_str())
            .map(|(ds, _)| ds)
            .map_err(|_| ControlError(format!("Bad machinectl output {}", o)))
    }));
    let su_cmd = args.iter().map(|s| format!("'{}'", s.replace("'", "'\\''"))).collect::<Vec<String>>().join(" ");
    match run(Command::new("nsenter")
            .arg("-t").arg(leader_pid)
            .arg("-m").arg("-u").arg("-i").arg("-n").arg("-p")
            .arg("--").arg("/var/setuid-wrappers/su").arg("root").arg("-l").arg("-c").arg(format!("exec {}", su_cmd))) {
        Ok(()) => Ok(0),
        Err(NonZero(_, Some(code))) => Ok(code),
        Err(e) => Err(e)
    }
}

// Remove a directory while recursively unmounting all mounted filesystems within
// that directory and unmounting/removing that directory afterwards as well.
//
// Specified path shouldn't be a mountpoint.
fn safe_remove_tree(path : &str) -> Result<()> {
    if !std::path::Path::new(path).is_dir() {
        return Ok(());
    }
    try!(run(Command::new("find").arg(path)
        .arg("-mindepth").arg("1")
        .arg("-xdev")
        .arg("(")
            .arg("-type").arg("d")
            .arg("-exec").arg("mountpoint").arg("-q").arg("{}").arg(";")
        .arg(")")
        .arg("-exec").arg("umount").arg("-fR").arg("{}").arg("+")));
    try!(run(Command::new("rm").arg("--one-file-system").arg("-rf").arg(path)));

    Ok(())
}

fn destroy(container_name : &str) -> Result<()> {
    try!(run(Command::new("systemctl").arg("stop").arg(format!("container@{}", container_name))));
    try!(safe_remove_tree(&profile_dir(container_name)));
    try!(safe_remove_tree(&format!("/nix/var/nix/gcroots/per-container/{}", container_name)));
    try!(safe_remove_tree(&container_root(container_name)));
    std::fs::remove_file(conf_file(container_name)).or_else(|e| match e.kind() {
        std::io::ErrorKind::NotFound => Ok(()),
        _ => Err(IoError(e))
    })
}

fn go() -> Result<i32> {
    let mut args = std::env::args();
    args.next(); // Discard program name
    let config_file = try!(args.next().ok_or(Error::Usage("Missing config file argument")));
    let test_args : Vec<String> = args.collect();
    if test_args.len() == 0 {
        return Err(Usage("Missing test args"));
    }

    try!(system_init());
    let container_name = try!(create(&config_file));
    let res = run_test(&container_name, &test_args);
    try!(destroy(&container_name));
    res
}

fn main() {
    std::process::exit(go().unwrap_or_else(|e| match e {
        NonZero(cmd, code) => { writeln!(io::stderr(), "Command {:?} failed with code {:?}", cmd, code).unwrap(); 2 },
        Usage(msg) => { writeln!(io::stderr(), "Usage error: {}", msg).unwrap(); 1 },
        IoError(err) => { writeln!(io::stderr(), "{}", err).unwrap(); 2 },
        NixError(err) => { writeln!(io::stderr(), "{}", err).unwrap(); 2 },
        ControlError(msg) => { writeln!(io::stderr(), "{}", msg).unwrap(); 2 }
    }));
}
<commit_msg>FIX: Canonicalise config path when passing to nix-env<commit_after>extern crate combine;
extern crate nix; // As in *nix
extern crate rand;

// Reference: https://github.com/NixOS/nixpkgs/blob/b07051ce6c3ba3039c89b6755da279002b0c3ace/nixos/modules/virtualisation/nixos-container.pl

use std::io;
use std::io::Write;
use std::process::Command;

enum Error {
    Usage(String),
    NonZero(String, Option<i32>),
    ControlError(String),
    IoError(io::Error),
    NixError(nix::Error)
}

use Error::*;

impl From<io::Error> for Error {
    fn from(e: io::Error) -> Self { IoError(e) }
}

impl From<nix::Error> for Error {
    fn from(e: nix::Error) -> Self { NixError(e) }
}

type Result<T> = std::result::Result<T, Error>;

trait CommandOut : Sized {
    fn run(&mut Command) -> Result<Self>;
}

impl CommandOut for () {
    fn run(cmd: &mut Command) -> Result<Self> {
        let exit = try!(cmd.spawn().and_then(|mut c| c.wait()));
        if exit.success() {
            Ok(())
        } else {
            Err(NonZero(format!("{:?}", cmd), exit.code()))
        }
    }
}

impl CommandOut for String {
    fn run(cmd: &mut Command) -> Result<Self> {
        use std::process::Stdio;
        use std::io::Read;
        let mut buffer = String::new();
        let mut child = try!(cmd.stderr(Stdio::null()).stdout(Stdio::piped()).spawn());
        let rdres = child.stdout.as_mut().unwrap().read_to_string(&mut buffer);
        let wtres = child.wait();
        let exit = try!(rdres.and(wtres).map_err(IoError));
        if exit.success() {
            Ok(buffer)
        } else {
            Err(NonZero(format!("{:?}", cmd), exit.code()))
        }
    }
}

fn run<T>(cmd: &mut Command) -> Result<T> where T : CommandOut {
    T::run(cmd)
}

fn probably_unique_name(length: usize) -> String {
    use rand::distributions::{IndependentSample, Range};
    let between = Range::new(0, 36);
    let mut rng = rand::thread_rng();
    unsafe {
        std::str::from_utf8_unchecked(&(0..length).map(|_| between.ind_sample(&mut rng)).map(|v| if v < 10 { v + 48 } else { v + 87 }).collect::<Vec<u8>>()).to_string()
    }
}

// Result is of the form "10.233.x"
fn unused_ip_block() -> Result<String> {
    // Can bail early anywhere here because we've got the lock
    let mut used = std::collections::HashSet::new();
    for entry in try!(std::fs::read_dir("/etc/containers")) {
        let entry = try!(entry);
        if try!(entry.file_type()).is_file() {
            let mut str = String::new();
            use std::io::Read;
            try!(try!(std::fs::File::open(entry.path())).read_to_string(&mut str));
            for line in str.lines() {
                use combine::{string, many1, choice, digit, token, eof, Parser, ParserExt};
                let ip_component = || { string("10.233.").with(many1::<String, _>(digit())).skip(token('.')).skip(many1::<String, _>(digit())) };
                if let Ok((c, _)) = choice([string("HOST_ADDRESS="), string("LOCAL_ADDRESS=")]).with(ip_component()).skip(eof()).parse(line) {
                    used.insert(c);
                }
            }
        }
    }
    (0..255).map(|p| p.to_string()).find(|s| !used.contains(s)).map(|s| format!("10.233.{}", s)).ok_or(ControlError("Out of IP addresses".to_string()))
}

fn mkpath(mode : u32, path : &str) -> Result<()> {
    use std::os::unix::fs::DirBuilderExt;
    match std::fs::DirBuilder::new().mode(mode).create(path) {
        Err(e) => if let std::io::ErrorKind::AlreadyExists = e.kind() { Ok(()) } else { Err(IoError(e)) },
        Ok(()) => Ok(())
    }
}

fn system_init() -> Result<()> {
    try!(mkpath(0o0755, "/etc/containers"));
    try!(mkpath(0o0700, "/var/lib/containers"));
    Ok(())
}

fn profile_dir(name : &str) -> String {
    format!("/nix/var/nix/profiles/per-container/{}", name)
}

fn container_root(name : &str) -> String {
    format!("/var/lib/containers/{}", name)
}

fn conf_file(name : &str) -> String {
    format!("/etc/containers/{}.conf", name)
}

fn create(config_file : &str) -> Result<String> {
    fn init_container_unsynced(name : &str) -> Result<()> {
        let ip_block = try!(unused_ip_block());
        // Use create_new to avoid clobbering existing container
        let mut f = try!(std::fs::OpenOptions::new().create_new(true).write(true).open(conf_file(name)));
        write!(&mut f, "PRIVATE_NETWORK=1\nHOST_ADDRESS={0}.1\nLOCAL_ADDRESS={0}.2\nAUTO_START=0\n", ip_block).map_err(IoError)
    }

    fn populate_container(name : &str, config_file : &str) -> Result<()> {
        use std::os::unix::fs::DirBuilderExt;
        // The per-container directory is restricted to prevent users on
        // the host from messing with guest users who happen to have the
        // same uid.
        try!(mkpath(0o0700, "/nix/var/nix/profiles/per-container"));
        let profile_dir = profile_dir(name);
        let mut dir_builder = std::fs::DirBuilder::new();
        try!(dir_builder.mode(0o0755).create(&profile_dir));

        let container_root = container_root(name);
        dir_builder.recursive(true);
        try!(dir_builder.mode(0o0755).create(format!("{}/etc/nixos", container_root)));

        // Write configuration.nix into the container fs
        let configuration_nix = format!("{}/etc/nixos/configuration.nix", container_root);
        {
            let mut f = try!(std::fs::File::create(format!("{}/etc/nixos/configuration.nix", container_root)));
            try!(write!(&mut f, "
    {{ config, lib, pkgs, ... }}:
    with lib;

    {{
        boot.isContainer = true;
        networking.hostName = mkDefault \"{}\";
        networking.useDHCP = false;
        imports = [ {} ];
    }}", name, config_file));
            // TODO: guaranteed sync at end of block?
        }

        // Build the container config
        run(Command::new("nix-env")
            .arg("-p").arg(format!("{}/system", profile_dir))
            .arg("-I").arg(format!("nixos-config={}", configuration_nix))
            .arg("-f").arg("<nixpkgs/nixos>")
            .arg("--set")
            .arg("-A").arg("system")
            .arg("--show-trace"))
    }

    let name = format!("fr{}", probably_unique_name(9));

    // Acquire lock
    use std::os::unix::io::IntoRawFd;
    let lock_fd = try!(std::fs::OpenOptions::new().create(true).append(true).open("/run/lock/nixos-container")).into_raw_fd();
    try!(nix::fcntl::flock(lock_fd, nix::fcntl::FlockArg::LockExclusive));

    let res = init_container_unsynced(&name);

    // Drop lock
    unsafe {
        let _ = nix::libc::close(lock_fd);  // If closing fails, ¯\_(ツ)_/¯
    }

    match res.and_then(|()| populate_container(&name, config_file)) {
        Ok(()) => Ok(name),
        // Don't leave a half-baked container behind
        Err(e) => { let _ = destroy(&name); Err(e) }
    }
}

fn run_test(container_name : &str, args : &[String]) -> Result<i32> {
    try!(run(Command::new("systemctl").arg("start").arg(format!("container@{}", container_name))));
    let leader_pid = try!(run(Command::new("machinectl").arg("show").arg(container_name).arg("-p").arg("Leader")).and_then(|o : String| {
        use combine::{string, many1, digit, spaces, eof, Parser, ParserExt};
        string("Leader=").with(many1::<String, _>(digit())).skip(spaces()).skip(eof()).parse(o.as_str())
            .map(|(ds, _)| ds)
            .map_err(|_| ControlError(format!("Bad machinectl output {}", o)))
    }));
    let su_cmd = args.iter().map(|s| format!("'{}'", s.replace("'", "'\\''"))).collect::<Vec<String>>().join(" ");
    match run(Command::new("nsenter")
            .arg("-t").arg(leader_pid)
            .arg("-m").arg("-u").arg("-i").arg("-n").arg("-p")
            .arg("--").arg("/var/setuid-wrappers/su").arg("root").arg("-l").arg("-c").arg(format!("exec {}", su_cmd))) {
        Ok(()) => Ok(0),
        Err(NonZero(_, Some(code))) => Ok(code),
        Err(e) => Err(e)
    }
}

// Remove a directory while recursively unmounting all mounted filesystems within
// that directory and unmounting/removing that directory afterwards as well.
//
// Specified path shouldn't be a mountpoint.
fn safe_remove_tree(path : &str) -> Result<()> {
    if !std::path::Path::new(path).is_dir() {
        return Ok(());
    }
    try!(run(Command::new("find").arg(path)
        .arg("-mindepth").arg("1")
        .arg("-xdev")
        .arg("(")
            .arg("-type").arg("d")
            .arg("-exec").arg("mountpoint").arg("-q").arg("{}").arg(";")
        .arg(")")
        .arg("-exec").arg("umount").arg("-fR").arg("{}").arg("+")));
    try!(run(Command::new("rm").arg("--one-file-system").arg("-rf").arg(path)));

    Ok(())
}

fn destroy(container_name : &str) -> Result<()> {
    try!(run(Command::new("systemctl").arg("stop").arg(format!("container@{}", container_name))));
    try!(safe_remove_tree(&profile_dir(container_name)));
    try!(safe_remove_tree(&format!("/nix/var/nix/gcroots/per-container/{}", container_name)));
    try!(safe_remove_tree(&container_root(container_name)));
    std::fs::remove_file(conf_file(container_name)).or_else(|e| match e.kind() {
        std::io::ErrorKind::NotFound => Ok(()),
        _ => Err(IoError(e))
    })
}

fn go() -> Result<i32> {
    let mut args = std::env::args();
    args.next(); // Discard program name
    let config_file = try!(args.next().ok_or(Error::Usage("Missing config file argument".to_string())));
    let test_args : Vec<String> = args.collect();
    if test_args.len() == 0 {
        return Err(Usage("Missing test args".to_string()));
    }

    let config_file = {
        let p = try!(std::fs::canonicalize(config_file).map_err(|e| Usage(format!("For config file: {}", e))));
        try!(p.to_str().ok_or(Usage("Config file path is invalid UTF-8".to_string()))).to_string()
    };

    try!(system_init());
    let container_name = try!(create(&config_file));
    let res = run_test(&container_name, &test_args);
    try!(destroy(&container_name));
    res
}

fn main() {
    std::process::exit(go().unwrap_or_else(|e| match e {
        NonZero(cmd, code) => { writeln!(io::stderr(), "Command {:?} failed with code {:?}", cmd, code).unwrap(); 2 },
        Usage(msg) => { writeln!(io::stderr(), "Usage error: {}", msg).unwrap(); 1 },
        IoError(err) => { writeln!(io::stderr(), "{}", err).unwrap(); 2 },
        NixError(err) => { writeln!(io::stderr(), "{}", err).unwrap(); 2 },
        ControlError(msg) => { writeln!(io::stderr(), "{}", msg).unwrap(); 2 }
    }));
}
<|endoftext|>


<commit_before>extern crate libc;
extern crate termios;

use libc::STDIN_FILENO;
use termios::{Termios, tcsetattr, TCSAFLUSH, VMIN, VTIME};
use termios::{BRKINT, ICRNL, INPCK, ISTRIP, IXON};
use termios::{OPOST, CS8};
use termios::{ECHO, ICANON, IEXTEN, ISIG};

use std::io::{self, Stdin, Stdout, Read, ErrorKind, Write};

macro_rules! ctrl_key {
    ($k:expr) => ($k & 0x1f);
}

struct TermReset {
    orig_term: Termios
}

struct EditorConfig {
    _term: TermReset
}

impl Drop for TermReset {
    fn drop(&mut self) {
        tcsetattr(STDIN_FILENO, TCSAFLUSH, &self.orig_term).unwrap()
    }
}

impl EditorConfig {
    fn new() -> EditorConfig {
        let term = enable_raw_mode().unwrap();
        EditorConfig { _term: term }
    }
}

fn enable_raw_mode() -> io::Result<TermReset> {
    let mut term = Termios::from_fd(STDIN_FILENO)?;
    let mode = TermReset { orig_term: term.clone() };

    term.c_iflag &= !(BRKINT | ICRNL | INPCK | ISTRIP | IXON);
    term.c_oflag &= !OPOST;
    term.c_cflag |= CS8;
    term.c_lflag &= !(ECHO | ICANON | IEXTEN | ISIG);
    term.c_cc[VMIN] = 0;
    term.c_cc[VTIME] = 1;

    tcsetattr(STDIN_FILENO, TCSAFLUSH, &term)?;
    Ok(mode)
}

fn editor_read_key(stdin: &mut Stdin) -> u8 {
    let mut buf = [0; 1];
    loop {
        let n = stdin.read(&mut buf)
                 .or_else(|e| if e.kind() == ErrorKind::WouldBlock { Ok(0) } else { Err(e) })
                 .expect("read");
        if n == 1 {
            return buf[0];
        }
    }
}

fn clear_screen(stdout: &mut Stdout) -> io::Result<()> {
    stdout.write(b"\x1b[2J")?;
    stdout.write(b"\x1b[H")?;
    stdout.flush()
}

fn editor_draw_rows(stdout: &mut Stdout) -> io::Result<()> {
  for _ in 0..24 {
    stdout.write(b"~\r\n")?;
  }
  Ok(())
}

fn editor_refresh_screen(stdout: &mut Stdout) -> io::Result<()> {
    clear_screen(stdout)?;

    editor_draw_rows(stdout)?;

    stdout.write(b"\x1b[H")?;
    stdout.flush()
}

fn editor_process_keypress(stdin: &mut Stdin) -> bool {
    let c = editor_read_key(stdin);

    match c {
        k if k == ctrl_key!(b'q') => {
            false
        },
        _ => true
    }
}

fn main() {
    let _e = EditorConfig::new();

    let mut stdin = io::stdin();
    let mut stdout = io::stdout();
    editor_refresh_screen(&mut stdout).unwrap();
    while editor_process_keypress(&mut stdin) {
        editor_refresh_screen(&mut stdout).unwrap();
    }
    clear_screen(&mut stdout).unwrap();
}
<commit_msg>Read window size<commit_after>extern crate libc;
extern crate termios;

use libc::{STDIN_FILENO, STDOUT_FILENO, TIOCGWINSZ, winsize};
use termios::{Termios, tcsetattr, TCSAFLUSH, VMIN, VTIME};
use termios::{BRKINT, ICRNL, INPCK, ISTRIP, IXON};
use termios::{OPOST, CS8};
use termios::{ECHO, ICANON, IEXTEN, ISIG};

use std::io::{self, Stdin, Stdout, Read, Error, ErrorKind, Write};

macro_rules! ctrl_key {
    ($k:expr) => ($k & 0x1f);
}

struct TermReset {
    orig_term: Termios
}

struct EditorConfig {
    _term: TermReset,
    screenrows: u16,
    screencols: u16,
}

impl Drop for TermReset {
    fn drop(&mut self) {
        tcsetattr(STDIN_FILENO, TCSAFLUSH, &self.orig_term).unwrap()
    }
}

impl EditorConfig {
    fn new() -> EditorConfig {
        let term = enable_raw_mode().unwrap();
        let (rows, cols) = get_window_size().unwrap();
        EditorConfig { _term: term, screenrows: rows, screencols: cols }
    }
}

fn enable_raw_mode() -> io::Result<TermReset> {
    let mut term = Termios::from_fd(STDIN_FILENO)?;
    let mode = TermReset { orig_term: term.clone() };

    term.c_iflag &= !(BRKINT | ICRNL | INPCK | ISTRIP | IXON);
    term.c_oflag &= !OPOST;
    term.c_cflag |= CS8;
    term.c_lflag &= !(ECHO | ICANON | IEXTEN | ISIG);
    term.c_cc[VMIN] = 0;
    term.c_cc[VTIME] = 1;

    tcsetattr(STDIN_FILENO, TCSAFLUSH, &term)?;
    Ok(mode)
}

fn editor_read_key(stdin: &mut Stdin) -> u8 {
    let mut buf = [0; 1];
    loop {
        let n = stdin.read(&mut buf)
                 .or_else(|e| if e.kind() == ErrorKind::WouldBlock { Ok(0) } else { Err(e) })
                 .expect("read");
        if n == 1 {
            return buf[0];
        }
    }
}

fn get_window_size() -> io::Result<(u16, u16)> {
    let ws = winsize { ws_col: 0, ws_row: 0, ws_xpixel: 0, ws_ypixel: 0 };
    unsafe {
        if libc::ioctl(STDOUT_FILENO, TIOCGWINSZ, &ws) == -1 || ws.ws_col == 0 {
            return Err(Error::new(ErrorKind::Other, "get_window_size: ioctl failed"))
        }
    }
    Ok((ws.ws_row, ws.ws_col))
}

fn clear_screen(stdout: &mut Stdout) -> io::Result<()> {
    stdout.write(b"\x1b[2J")?;
    stdout.write(b"\x1b[H")?;
    stdout.flush()
}

fn editor_draw_rows(stdout: &mut Stdout, e: &EditorConfig) -> io::Result<()> {
    for _ in 0..(e.screenrows - 1) {
        stdout.write(b"~\r\n")?;
    }
    stdout.write(b"~")?;
    Ok(())
}

fn editor_refresh_screen(stdout: &mut Stdout, e: &EditorConfig) -> io::Result<()> {
    clear_screen(stdout)?;

    editor_draw_rows(stdout, e)?;

    stdout.write(b"\x1b[H")?;
    stdout.flush()
}

fn editor_process_keypress(stdin: &mut Stdin) -> bool {
    let c = editor_read_key(stdin);

    match c {
        k if k == ctrl_key!(b'q') => {
            false
        },
        _ => true
    }
}

fn main() {
    let e = EditorConfig::new();

    let mut stdin = io::stdin();
    let mut stdout = io::stdout();
    editor_refresh_screen(&mut stdout, &e).unwrap();
    while editor_process_keypress(&mut stdin) {
        editor_refresh_screen(&mut stdout, &e).unwrap();
    }
    clear_screen(&mut stdout).unwrap();
}
<|endoftext|>


<commit_before>#![feature(io)]
#![feature(collections)]
#![feature(core)]
extern crate docopt;
#[macro_use]
extern crate standard_error;

use docopt::Docopt;
use standard_error::StandardResult as Result;
use std::rc::Rc;
use std::borrow::Cow;

mod config;

static CFG: config::ConfigType = config::ConfigType::File("./partners.cfg");

// Write the Docopt usage string.
static USAGE: &'static str = "
Usage: partners info
       partners list
       partners add --nick=<nick> --name=<name> [--email=<email>]
       partners add
       partners current
       partners set <nick>

Options:
    -a, --archive  Copy everything.
";

#[derive(Debug)]
struct Config {
  domain: String,
  prefix: String,
  separator: String,
}

#[derive(Debug)]
struct Author {
  config: Rc<Config>,
  nick: String,
  name: String,
  email: Option<String>,
}

impl Author {
  fn get_email(&self) -> std::string::CowString {
    match self.email {
      Some(ref email) => Cow::Borrowed(email.as_slice()),
      None => Cow::Owned(format!("{}@{}", self.nick, self.config.domain)),
    }
  }
}

fn print_author_list(list: &[Author]) {
  for item in list.iter() {
    println!("{}:", item.nick);
    println!("  Name:  {}", item.name);
    println!("  Email: {}", item.get_email());
  }
}

fn parse_author_line(config: &Rc<Config>, line: &str) -> Result<Author> {
  let mut parts = line.splitn(1, ' ');
  let nick = parts.next().unwrap().split('.').nth(1).unwrap().to_string();
  let name = parts.next().unwrap().to_string();
  let email = config::get(&CFG, &format!("author.{}.email", nick)[]).ok();

  Ok(Author { config: config.clone(), nick: nick, name: name, email: email })
}

fn get_authors(config: &Rc<Config>) -> Result<Vec<Author>> {
  let lines = try!(config::list(&CFG, "author.\\w+.name"));
  lines.iter().map(|line| parse_author_line(config, &line[])).collect()
}

fn write_author(author: &Author) -> Result<()> {
  try!(config::set(&CFG, &*format!("author.{}.name", author.nick), &*author.name));
  if let Some(ref email) = author.email {
    try!(config::set(&CFG, &*format!("author.{}.email", author.nick), &**email));
  }
  Ok(())
}

fn print_current() {
  println!("Name:  {}", config::get(&config::ConfigType::None, "user.name").unwrap());
  println!("Email: {}", config::get(&config::ConfigType::None, "user.email").unwrap());
}

fn main() {
  let args = Docopt::new(USAGE).and_then(|d| d.parse()).unwrap_or_else(|e| e.exit());

  let config = Rc::new(Config {
    domain: config::get(&CFG, "config.domain").unwrap_or_else(|_| "example.com".to_string()),
    prefix: config::get(&CFG, "config.prefix").unwrap_or_else(|_| "dev".to_string()),
    separator: config::get(&CFG, "config.separator").unwrap_or_else(|_| "+".to_string()),
  });

  if args.get_bool("list") {
    let authors = get_authors(&config).unwrap();
    print_author_list(&authors[]);
  } else if args.get_bool("add") {
    let email = args.get_str("--email");
    let nick = args.get_str("--nick");
    let name = args.get_str("--name");

    let email = if email.is_empty() { None } else { Some(email.to_string()) };

    let author = Author { config: config.clone(), nick: nick.to_string(), name: name.to_string(), email: email };
    write_author(&author).unwrap();
  } else if args.get_bool("current") {
    print_current();
  } else if args.get_bool("set") {
    let nick = args.get_str("<nick>");

    match get_authors(&config).unwrap().iter().find(|a| a.nick == nick) {
      Some(author) => {
        config::set(&config::ConfigType::Global, "partners.current", &*author.nick).unwrap();
        config::set(&config::ConfigType::Global, "user.name", &*author.name).unwrap();
        config::set(&config::ConfigType::Global, "user.email", &*author.get_email()).unwrap();
        print_current();
      }
      None => {
        println!("no such author");
      }
    }

    // println!("Name:  {}", config::get(&config::ConfigType::None, "user.name").unwrap());
    // println!("Email: {}", config::get(&config::ConfigType::None, "user.email").unwrap());
  }
}
<commit_msg>Remove useless stuff<commit_after>#![feature(io)]
#![feature(collections)]
#![feature(core)]
extern crate docopt;
#[macro_use]
extern crate standard_error;

use docopt::Docopt;
use standard_error::StandardResult as Result;
use std::rc::Rc;
use std::borrow::Cow;

mod config;

static CFG: config::ConfigType = config::ConfigType::File("./partners.cfg");

// Write the Docopt usage string.
static USAGE: &'static str = "
Usage: partners info
       partners list
       partners add --nick=<nick> --name=<name> [--email=<email>]
       partners add
       partners current
       partners set <nick>

Options:
    -a, --archive  Copy everything.
";

#[derive(Debug)]
struct Config {
  domain: String,
  prefix: String,
  separator: String,
}

#[derive(Debug)]
struct Author {
  config: Rc<Config>,
  nick: String,
  name: String,
  email: Option<String>,
}

impl Author {
  fn get_email(&self) -> std::string::CowString {
    match self.email {
      Some(ref email) => Cow::Borrowed(email.as_slice()),
      None => Cow::Owned(format!("{}@{}", self.nick, self.config.domain)),
    }
  }
}

fn print_author_list(list: &[Author]) {
  for item in list.iter() {
    println!("{}:", item.nick);
    println!("  Name:  {}", item.name);
    println!("  Email: {}", item.get_email());
  }
}

fn parse_author_line(config: &Rc<Config>, line: &str) -> Result<Author> {
  let mut parts = line.splitn(1, ' ');
  let nick = parts.next().unwrap().split('.').nth(1).unwrap().to_string();
  let name = parts.next().unwrap().to_string();
  let email = config::get(&CFG, &format!("author.{}.email", nick)[]).ok();

  Ok(Author { config: config.clone(), nick: nick, name: name, email: email })
}

fn get_authors(config: &Rc<Config>) -> Result<Vec<Author>> {
  let lines = try!(config::list(&CFG, "author.\\w+.name"));
  lines.iter().map(|line| parse_author_line(config, &line[])).collect()
}

fn write_author(author: &Author) -> Result<()> {
  try!(config::set(&CFG, &*format!("author.{}.name", author.nick), &*author.name));
  if let Some(ref email) = author.email {
    try!(config::set(&CFG, &*format!("author.{}.email", author.nick), &**email));
  }
  Ok(())
}

fn print_current() {
  println!("Name:  {}", config::get(&config::ConfigType::None, "user.name").unwrap());
  println!("Email: {}", config::get(&config::ConfigType::None, "user.email").unwrap());
}

fn main() {
  let args = Docopt::new(USAGE).and_then(|d| d.parse()).unwrap_or_else(|e| e.exit());

  let config = Rc::new(Config {
    domain: config::get(&CFG, "config.domain").unwrap_or_else(|_| "example.com".to_string()),
    prefix: config::get(&CFG, "config.prefix").unwrap_or_else(|_| "dev".to_string()),
    separator: config::get(&CFG, "config.separator").unwrap_or_else(|_| "+".to_string()),
  });

  if args.get_bool("list") {
    let authors = get_authors(&config).unwrap();
    print_author_list(&authors[]);
  } else if args.get_bool("add") {
    let email = args.get_str("--email");
    let nick = args.get_str("--nick");
    let name = args.get_str("--name");

    let email = if email.is_empty() { None } else { Some(email.to_string()) };

    let author = Author { config: config.clone(), nick: nick.to_string(), name: name.to_string(), email: email };
    write_author(&author).unwrap();
  } else if args.get_bool("current") {
    print_current();
  } else if args.get_bool("set") {
    let nick = args.get_str("<nick>");

    match get_authors(&config).unwrap().iter().find(|a| a.nick == nick) {
      Some(author) => {
        config::set(&config::ConfigType::Global, "partners.current", &*author.nick).unwrap();
        config::set(&config::ConfigType::Global, "user.name", &*author.name).unwrap();
        config::set(&config::ConfigType::Global, "user.email", &*author.get_email()).unwrap();
        print_current();
      }
      None => {
        println!("no such author");
      }
    }
  }
}
<|endoftext|>


<commit_before>use std::env;
use std::io::prelude::*;
use std::fs::File;
use std::path::PathBuf;
extern crate uuid;
use uuid::Uuid;
extern crate getopts;
use getopts::Options;
use std::io;

fn print_usage(program: &str, opts: Options) {
    let path = PathBuf::from(program);
    let command = path.file_name().unwrap().to_string_lossy();

    let brief = format!("Usage: {} FILE [options]", command);
    let info = "Safely rewrite contents of FILE with stdin, even\nwhere FILE is being read by \
                upstream command";
    print!("{}", opts.usage(&[&brief, info].join("\n")));
}

fn redirect_to_file(outfile: &str) -> Result<(), io::Error> {
    let mut tempfile = env::temp_dir();
    tempfile.push(Uuid::new_v4().hyphenated().to_string());
    //println!("{}", tempfile.display());

    {
        let mut buffer = [0; 512];
        let mut stdin = io::stdin();
        let mut f = File::create(&tempfile).unwrap();

        loop {
            let read_bytes = stdin.read(&mut buffer).unwrap();
            if read_bytes == 0 {
                break;
            }

            let write_bytes = match f.write(&buffer[0..read_bytes]) {
                Ok(m) => m,
                Err(e) => panic!("{}", e),
            };

            assert!(write_bytes == read_bytes);
        }
    }

    match std::fs::rename(&tempfile, &outfile) {
        Ok(m) => m,
        _ => {
            //fs::rename does not support cross-device linking
            //copy and delete instead
            assert!(std::fs::copy(&tempfile, &outfile).is_ok());
            assert!(std::fs::remove_file(&tempfile).is_ok());
        }
    };

    return Ok(());
}

fn main() {
    let args: Vec<String> = env::args().collect();
    let program = args[0].clone();
    let mut opts = Options::new();
    opts.optflag("h", "help", "prints this help info");
    let matches = match opts.parse(&args[1..]) {
        Ok(m) => m,
        Err(f) => panic!(f.to_string()),
    };
    if matches.opt_present("h") {
        print_usage(&program, opts);
        return;
    }
    let infile = if !matches.free.is_empty() {
        matches.free[0].clone()
    } else {
        print_usage(&program, opts);
        return;
    };

    assert!(redirect_to_file(&infile).is_ok());
}
<commit_msg>rustfmt for comments (ugh!)<commit_after>use std::env;
use std::io::prelude::*;
use std::fs::File;
use std::path::PathBuf;
extern crate uuid;
use uuid::Uuid;
extern crate getopts;
use getopts::Options;
use std::io;

fn print_usage(program: &str, opts: Options) {
    let path = PathBuf::from(program);
    let command = path.file_name().unwrap().to_string_lossy();

    let brief = format!("Usage: {} FILE [options]", command);
    let info = "Safely rewrite contents of FILE with stdin, even\nwhere FILE is being read by \
                upstream command";
    print!("{}", opts.usage(&[&brief, info].join("\n")));
}

fn redirect_to_file(outfile: &str) -> Result<(), io::Error> {
    let mut tempfile = env::temp_dir();
    tempfile.push(Uuid::new_v4().hyphenated().to_string());
    // println!("{}", tempfile.display());

    {
        let mut buffer = [0; 512];
        let mut stdin = io::stdin();
        let mut f = File::create(&tempfile).unwrap();

        loop {
            let read_bytes = stdin.read(&mut buffer).unwrap();
            if read_bytes == 0 {
                break;
            }

            let write_bytes = match f.write(&buffer[0..read_bytes]) {
                Ok(m) => m,
                Err(e) => panic!("{}", e),
            };

            assert!(write_bytes == read_bytes);
        }
    }

    match std::fs::rename(&tempfile, &outfile) {
        Ok(m) => m,
        _ => {
            // fs::rename does not support cross-device linking
            // copy and delete instead
            assert!(std::fs::copy(&tempfile, &outfile).is_ok());
            assert!(std::fs::remove_file(&tempfile).is_ok());
        }
    };

    return Ok(());
}

fn main() {
    let args: Vec<String> = env::args().collect();
    let program = args[0].clone();
    let mut opts = Options::new();
    opts.optflag("h", "help", "prints this help info");
    let matches = match opts.parse(&args[1..]) {
        Ok(m) => m,
        Err(f) => panic!(f.to_string()),
    };
    if matches.opt_present("h") {
        print_usage(&program, opts);
        return;
    }
    let infile = if !matches.free.is_empty() {
        matches.free[0].clone()
    } else {
        print_usage(&program, opts);
        return;
    };

    assert!(redirect_to_file(&infile).is_ok());
}
<|endoftext|>