<commit_before>/// Contains all functionality pertaining to parsing, tokenizing, and generating input arguments.
pub mod errors;
mod jobs;
mod man;
mod quote;

use std::env;
use std::ffi::OsStr;
use std::fs;
use std::io::{self, BufRead, BufReader};
use std::path::Path;
use std::process::exit;
use permutate::Permutator;
use num_cpus;

use super::disk_buffer::{self, DiskBufferTrait};
use super::filepaths;
use super::input_iterator::InputIterator;
use super::tokenizer::{Token, tokenize};
use self::errors::ParseErr;

// Re-export key items from internal modules.
pub use self::errors::{FileErr, InputIteratorErr};

#[derive(PartialEq)]
enum Mode { Arguments, Command, Inputs, Files }

pub const INPUTS_ARE_COMMANDS: u8 = 1;
pub const PIPE_IS_ENABLED:     u8 = 2;
pub const SHELL_ENABLED:       u8 = 4;
pub const QUIET_MODE:          u8 = 8;
pub const VERBOSE_MODE:        u8 = 16;
pub const DASH_EXISTS:         u8 = 32;

/// Defines what quoting mode to use when expanding the command.
enum Quoting { None, Basic, Shell }

/// `Args` is a collection of critical options and arguments that were collected at
/// startup of the application.
pub struct Args {
    pub flags:        u8,
    pub ncores:       usize,
    pub arguments:    Vec<Token>,
    pub piped_values: Option<Vec<String>>,
    pub ninputs:      usize,
}

impl Args {
    pub fn new() -> Args {
        Args {
            ncores:       num_cpus::get(),
            flags:        SHELL_ENABLED,
            arguments:    Vec::new(),
            piped_values: None,
            ninputs:      0,
        }
    }

    pub fn parse(&mut self) -> Result<InputIterator, ParseErr> {
        let unprocessed_path = match filepaths::unprocessed() {
            Some(path) => path,
            None => return Err(ParseErr::File(FileErr::Path))
        };

        let mut quote = Quoting::None;

        // Create a write buffer that automatically writes data to the disk when the buffer is full.
        let mut disk_buffer = disk_buffer::DiskBuffer::new(&unprocessed_path).write()
            .map_err(|why| ParseErr::File(FileErr::Open(unprocessed_path.clone(), why)))?;

        // Temporary stores for input arguments.
        let mut raw_args                    = env::args().skip(1).peekable();
        let mut comm                        = String::with_capacity(128);
        let mut lists: Vec<Vec<String>>     = Vec::new();
        let mut current_inputs: Vec<String> = Vec::new();
        let mut number_of_arguments = 0;

        if env::args().len() > 1 {
            // The purpose of this is to set the initial parsing mode.
            let mut mode = match raw_args.peek().unwrap().as_ref() {
                ":::"  => Mode::Inputs,
                "::::" => Mode::Files,
                _      => Mode::Arguments
            };

            // If there are no arguments to be parsed, then the inputs are commands.
            if mode == Mode::Inputs || mode == Mode::Files {
                self.flags |= INPUTS_ARE_COMMANDS;
            } else {
                self.flags &= 255 * INPUTS_ARE_COMMANDS;
            }

            // Parse each and every input argument supplied to the program.
            while let Some(argument) = raw_args.next() {
                let argument = argument.as_str();
                match mode {
                    Mode::Arguments => {
                        let mut char_iter = argument.chars().peekable();

                        // If the first character is a '-' then it will be processed as an argument.
                        // We can guarantee that there will always be at least one character.
                        if char_iter.next().unwrap() == '-' {
                            // If the second character exists, everything's OK.
                            if let Some(character) = char_iter.next() {
                                // This scope of code allows users to utilize the GNU style
                                // command line arguments, to allow for laziness.
                                if character == 'j' {
                                    // The short-hand job argument needs to be handled specially.
                                    self.ncores = if char_iter.peek().is_some() {
                                        // Each character that follows after `j` will be considered an
                                        // input value.
                                        jobs::parse(&argument[2..])?
                                    } else {
                                        // If there was no character after `j`, the following argument
                                        // must be the job value.
                                        let val = &raw_args.next().ok_or(ParseErr::JobsNoValue)?;
                                        jobs::parse(val)?
                                    }
                                } else if character != '-' {
                                    // NOTE: Short mode versions of arguments
                                    for character in argument[1..].chars() {
                                        match character {
                                            'h' => {
                                                println!("{}", man::MAN_PAGE);
                                                exit(0);
                                            },
                                            'n' => self.flags &= 255 ^ SHELL_ENABLED,
                                            'p' => self.flags |= PIPE_IS_ENABLED,
                                            'q' => quote = Quoting::Basic,
                                            's' => self.flags |= QUIET_MODE,
                                            'v' => self.flags |= VERBOSE_MODE,
                                            _ => {
                                                return Err(ParseErr::InvalidArgument(argument.to_owned()))
                                            }
                                        }
                                    }
                                } else {
                                    // NOTE: Long mode versions of arguments
                                    match &argument[2..] {
                                        "help" => {
                                            println!("{}", man::MAN_PAGE);
                                            exit(0);
                                        },
                                        "jobs" => {
                                            let val = &raw_args.next().ok_or(ParseErr::JobsNoValue)?;
                                            self.ncores = jobs::parse(val)?
                                        },
                                        "no-shell" => self.flags &= 255 ^ SHELL_ENABLED,
                                        "num-cpu-cores" => {
                                            println!("{}", num_cpus::get());
                                            exit(0);
                                        },
                                        "pipe" => self.flags |= PIPE_IS_ENABLED,
                                        "quiet" | "silent" => self.flags |= QUIET_MODE,
                                        "quote" => quote = Quoting::Basic,
                                        "shellquote" => quote = Quoting::Shell,
                                        "verbose" => self.flags |= VERBOSE_MODE,
                                        "version" => {
                                            println!("parallel 0.6.2\n\nCrate Dependencies:");
                                            println!("    libc      0.2.15");
                                            println!("    num_cpus  1.0.0");
                                            println!("    permutate 0.1.3");
                                            exit(0);
                                        }
                                        _ => {
                                            return Err(ParseErr::InvalidArgument(argument.to_owned()));
                                        }
                                    }
                                }
                            } else {
                                // `-` will never be a valid argument
                                return Err(ParseErr::InvalidArgument("-".to_owned()));
                            }
                        } else {
                            match argument {
                                ":::" => {
                                    mode = Mode::Inputs;
                                    self.flags |= INPUTS_ARE_COMMANDS;
                                },
                                "::::" => {
                                    mode = Mode::Files;
                                    self.flags |= INPUTS_ARE_COMMANDS;
                                }
                                _ => {
                                    // The command has been supplied, and argument parsing is over.
                                    comm.push_str(argument);
                                    mode = Mode::Command;
                                }
                            }
                        }
                    },
                    Mode::Command => match argument {
                        // Arguments after `:::` are input values.
                        ":::" | ":::+" => mode = Mode::Inputs,
                        // Arguments after `::::` are files with inputs.
                        "::::" | "::::+" => mode = Mode::Files,
                        // All other arguments are command arguments.
                        _ => {
                            comm.push(' ');
                            comm.push_str(argument);
                        }
                    },
                    _ => match argument {
                        // `:::` denotes that the next set of inputs will be added to a new list.
                        ":::"  => {
                            mode = Mode::Inputs;
                            if !current_inputs.is_empty() {
                                lists.push(current_inputs.clone());
                                current_inputs.clear();
                            }
                        },
                        // `:::+` denotes that the next set of inputs will be added to the current list.
                        ":::+" => mode = Mode::Inputs,
                        // `::::` denotes that the next set of inputs will be added to a new list.
                        "::::"  => {
                            mode = Mode::Files;
                            if !current_inputs.is_empty() {
                                lists.push(current_inputs.clone());
                                current_inputs.clear();
                            }
                        },
                        // `:::+` denotes that the next set of inputs will be added to the current list.
                        "::::+" => mode = Mode::Files,
                        // All other arguments will be added to the current list.
                        _ => match mode {
                            Mode::Inputs => current_inputs.push(argument.to_owned()),
                            Mode::Files => file_parse(&mut current_inputs, argument)?,
                            _ => unreachable!()
                        }
                    }
                }
            }

            if !current_inputs.is_empty() {
                lists.push(current_inputs.clone());
            }

            if lists.len() > 1 {
                // Convert the Vec<Vec<String>> into a Vec<Vec<&str>>
                let tmp: Vec<Vec<&str>> = lists.iter()
                    .map(|list| list.iter().map(AsRef::as_ref).collect::<Vec<&str>>())
                    .collect();

                // Convert the Vec<Vec<&str>> into a Vec<&[&str]>
                let list_array: Vec<&[&str]> = tmp.iter().map(AsRef::as_ref).collect();

                // Create a `Permutator` with the &[&[&str]] as the input.
                let permutator = Permutator::new(&list_array[..]);

                for permutation in permutator {
                    let mut iter = permutation.iter();
                    disk_buffer.write(iter.next().unwrap().as_bytes())
                        .map_err(|why| ParseErr::File(FileErr::Write(disk_buffer.path.clone(), why)))?;
                    for element in iter {
                        disk_buffer.write_byte(b' ')
                            .map_err(|why| ParseErr::File(FileErr::Write(disk_buffer.path.clone(), why)))?;
                        disk_buffer.write(element.as_bytes())
                            .map_err(|why| ParseErr::File(FileErr::Write(disk_buffer.path.clone(), why)))?;
                    }
                    disk_buffer.write_byte(b'\n')
                        .map_err(|why| ParseErr::File(FileErr::Write(disk_buffer.path.clone(), why)))?;
                    number_of_arguments += 1;
                }
            } else {
                for input in current_inputs {
                    disk_buffer.write(input.as_bytes()).map_err(|why|
                        ParseErr::File(FileErr::Write(disk_buffer.path.clone(), why)))?;
                    disk_buffer.write_byte(b'\n').map_err(|why|
                        ParseErr::File(FileErr::Write(disk_buffer.path.clone(), why)))?;
                    number_of_arguments += 1;
                }
            }
        }

        // If no inputs are provided, read from stdin instead.
        if disk_buffer.is_empty() {
            let stdin = io::stdin();
            for line in stdin.lock().lines() {
                if let Ok(line) = line {
                    disk_buffer.write(line.as_bytes()).map_err(|why|
                        ParseErr::File(FileErr::Write(disk_buffer.path.clone(), why)))?;
                    disk_buffer.write_byte(b'\n').map_err(|why|
                        ParseErr::File(FileErr::Write(disk_buffer.path.clone(), why)))?;
                    number_of_arguments += 1;
                }
            }
        }

        if number_of_arguments == 0 { return Err(ParseErr::NoArguments); }

        // Flush the contents of the buffer to the disk before tokenizing the command argument.
        disk_buffer.flush().map_err(|why| ParseErr::File(FileErr::Write(disk_buffer.path.clone(), why)))?;

        // Expand the command if quoting is enabled
        match quote {
            Quoting::None  => (),
            Quoting::Basic => comm = quote::basic(comm),
            Quoting::Shell => comm = quote::shell(comm),
        }

        // Attempt to tokenize the command argument into simple primitive placeholders.
        tokenize(&mut self.arguments, &comm, &unprocessed_path, number_of_arguments).map_err(ParseErr::Token)?;

        if dash_exists() {
            self.flags |= DASH_EXISTS;
        }
        if self.arguments.len() == 1 {
            self.flags &= 255 ^ SHELL_ENABLED;
        }

        // Return an `InputIterator` of the arguments contained within the unprocessed file.
        let path = filepaths::unprocessed().ok_or(ParseErr::File(FileErr::Path))?;
        Ok(InputIterator::new(&path, number_of_arguments).map_err(ParseErr::File)?)
    }
}

fn dash_exists() -> bool {
    if let Ok(path) = env::var("PATH") {
        for path in path.split(':') {
            if let Ok(directory) = fs::read_dir(path) {
                for entry in directory {
                    if let Ok(entry) = entry {
                        let path = entry.path();
                        if path.is_file() && path.file_name() == Some(OsStr::new("dash")) { return true; }
                    }
                }
            }
        }
    }
    false
}

/// Attempts to open an input argument and adds each line to the `inputs` list.
fn file_parse<P: AsRef<Path>>(inputs: &mut Vec<String>, path: P) -> Result<(), ParseErr> {
    let path = path.as_ref();
    let file = fs::File::open(path).map_err(|err| ParseErr::File(FileErr::Open(path.to_owned(), err)))?;
    for line in BufReader::new(file).lines() {
        if let Ok(line) = line { inputs.push(line); }
    }
    Ok(())
}
<commit_msg>Expand what is allowed to execute without a shell<commit_after>/// Contains all functionality pertaining to parsing, tokenizing, and generating input arguments.
pub mod errors;
mod jobs;
mod man;
mod quote;

use std::env;
use std::ffi::OsStr;
use std::fs;
use std::io::{self, BufRead, BufReader};
use std::path::Path;
use std::process::exit;
use permutate::Permutator;
use num_cpus;

use super::disk_buffer::{self, DiskBufferTrait};
use super::filepaths;
use super::input_iterator::InputIterator;
use super::tokenizer::{Token, tokenize};
use self::errors::ParseErr;

// Re-export key items from internal modules.
pub use self::errors::{FileErr, InputIteratorErr};

#[derive(PartialEq)]
enum Mode { Arguments, Command, Inputs, Files }

pub const INPUTS_ARE_COMMANDS: u8 = 1;
pub const PIPE_IS_ENABLED:     u8 = 2;
pub const SHELL_ENABLED:       u8 = 4;
pub const QUIET_MODE:          u8 = 8;
pub const VERBOSE_MODE:        u8 = 16;
pub const DASH_EXISTS:         u8 = 32;

/// Defines what quoting mode to use when expanding the command.
enum Quoting { None, Basic, Shell }

/// `Args` is a collection of critical options and arguments that were collected at
/// startup of the application.
pub struct Args {
    pub flags:        u8,
    pub ncores:       usize,
    pub arguments:    Vec<Token>,
    pub piped_values: Option<Vec<String>>,
    pub ninputs:      usize,
}

impl Args {
    pub fn new() -> Args {
        Args {
            ncores:       num_cpus::get(),
            flags:        SHELL_ENABLED,
            arguments:    Vec::new(),
            piped_values: None,
            ninputs:      0,
        }
    }

    pub fn parse(&mut self) -> Result<InputIterator, ParseErr> {
        let unprocessed_path = match filepaths::unprocessed() {
            Some(path) => path,
            None => return Err(ParseErr::File(FileErr::Path))
        };

        let mut quote = Quoting::None;

        // Create a write buffer that automatically writes data to the disk when the buffer is full.
        let mut disk_buffer = disk_buffer::DiskBuffer::new(&unprocessed_path).write()
            .map_err(|why| ParseErr::File(FileErr::Open(unprocessed_path.clone(), why)))?;

        // Temporary stores for input arguments.
        let mut raw_args                    = env::args().skip(1).peekable();
        let mut comm                        = String::with_capacity(128);
        let mut lists: Vec<Vec<String>>     = Vec::new();
        let mut current_inputs: Vec<String> = Vec::new();
        let mut number_of_arguments = 0;

        if env::args().len() > 1 {
            // The purpose of this is to set the initial parsing mode.
            let mut mode = match raw_args.peek().unwrap().as_ref() {
                ":::"  => Mode::Inputs,
                "::::" => Mode::Files,
                _      => Mode::Arguments
            };

            // If there are no arguments to be parsed, then the inputs are commands.
            if mode == Mode::Inputs || mode == Mode::Files {
                self.flags |= INPUTS_ARE_COMMANDS;
            } else {
                self.flags &= 255 * INPUTS_ARE_COMMANDS;
            }

            // Parse each and every input argument supplied to the program.
            while let Some(argument) = raw_args.next() {
                let argument = argument.as_str();
                match mode {
                    Mode::Arguments => {
                        let mut char_iter = argument.chars().peekable();

                        // If the first character is a '-' then it will be processed as an argument.
                        // We can guarantee that there will always be at least one character.
                        if char_iter.next().unwrap() == '-' {
                            // If the second character exists, everything's OK.
                            if let Some(character) = char_iter.next() {
                                // This scope of code allows users to utilize the GNU style
                                // command line arguments, to allow for laziness.
                                if character == 'j' {
                                    // The short-hand job argument needs to be handled specially.
                                    self.ncores = if char_iter.peek().is_some() {
                                        // Each character that follows after `j` will be considered an
                                        // input value.
                                        jobs::parse(&argument[2..])?
                                    } else {
                                        // If there was no character after `j`, the following argument
                                        // must be the job value.
                                        let val = &raw_args.next().ok_or(ParseErr::JobsNoValue)?;
                                        jobs::parse(val)?
                                    }
                                } else if character != '-' {
                                    // NOTE: Short mode versions of arguments
                                    for character in argument[1..].chars() {
                                        match character {
                                            'h' => {
                                                println!("{}", man::MAN_PAGE);
                                                exit(0);
                                            },
                                            'n' => self.flags &= 255 ^ SHELL_ENABLED,
                                            'p' => self.flags |= PIPE_IS_ENABLED,
                                            'q' => quote = Quoting::Basic,
                                            's' => self.flags |= QUIET_MODE,
                                            'v' => self.flags |= VERBOSE_MODE,
                                            _ => {
                                                return Err(ParseErr::InvalidArgument(argument.to_owned()))
                                            }
                                        }
                                    }
                                } else {
                                    // NOTE: Long mode versions of arguments
                                    match &argument[2..] {
                                        "help" => {
                                            println!("{}", man::MAN_PAGE);
                                            exit(0);
                                        },
                                        "jobs" => {
                                            let val = &raw_args.next().ok_or(ParseErr::JobsNoValue)?;
                                            self.ncores = jobs::parse(val)?
                                        },
                                        "no-shell" => self.flags &= 255 ^ SHELL_ENABLED,
                                        "num-cpu-cores" => {
                                            println!("{}", num_cpus::get());
                                            exit(0);
                                        },
                                        "pipe" => self.flags |= PIPE_IS_ENABLED,
                                        "quiet" | "silent" => self.flags |= QUIET_MODE,
                                        "quote" => quote = Quoting::Basic,
                                        "shellquote" => quote = Quoting::Shell,
                                        "verbose" => self.flags |= VERBOSE_MODE,
                                        "version" => {
                                            println!("parallel 0.6.2\n\nCrate Dependencies:");
                                            println!("    libc      0.2.15");
                                            println!("    num_cpus  1.0.0");
                                            println!("    permutate 0.1.3");
                                            exit(0);
                                        }
                                        _ => {
                                            return Err(ParseErr::InvalidArgument(argument.to_owned()));
                                        }
                                    }
                                }
                            } else {
                                // `-` will never be a valid argument
                                return Err(ParseErr::InvalidArgument("-".to_owned()));
                            }
                        } else {
                            match argument {
                                ":::" => {
                                    mode = Mode::Inputs;
                                    self.flags |= INPUTS_ARE_COMMANDS;
                                },
                                "::::" => {
                                    mode = Mode::Files;
                                    self.flags |= INPUTS_ARE_COMMANDS;
                                }
                                _ => {
                                    // The command has been supplied, and argument parsing is over.
                                    comm.push_str(argument);
                                    mode = Mode::Command;
                                }
                            }
                        }
                    },
                    Mode::Command => match argument {
                        // Arguments after `:::` are input values.
                        ":::" | ":::+" => mode = Mode::Inputs,
                        // Arguments after `::::` are files with inputs.
                        "::::" | "::::+" => mode = Mode::Files,
                        // All other arguments are command arguments.
                        _ => {
                            comm.push(' ');
                            comm.push_str(argument);
                        }
                    },
                    _ => match argument {
                        // `:::` denotes that the next set of inputs will be added to a new list.
                        ":::"  => {
                            mode = Mode::Inputs;
                            if !current_inputs.is_empty() {
                                lists.push(current_inputs.clone());
                                current_inputs.clear();
                            }
                        },
                        // `:::+` denotes that the next set of inputs will be added to the current list.
                        ":::+" => mode = Mode::Inputs,
                        // `::::` denotes that the next set of inputs will be added to a new list.
                        "::::"  => {
                            mode = Mode::Files;
                            if !current_inputs.is_empty() {
                                lists.push(current_inputs.clone());
                                current_inputs.clear();
                            }
                        },
                        // `:::+` denotes that the next set of inputs will be added to the current list.
                        "::::+" => mode = Mode::Files,
                        // All other arguments will be added to the current list.
                        _ => match mode {
                            Mode::Inputs => current_inputs.push(argument.to_owned()),
                            Mode::Files => file_parse(&mut current_inputs, argument)?,
                            _ => unreachable!()
                        }
                    }
                }
            }

            if !current_inputs.is_empty() {
                lists.push(current_inputs.clone());
            }

            if lists.len() > 1 {
                // Convert the Vec<Vec<String>> into a Vec<Vec<&str>>
                let tmp: Vec<Vec<&str>> = lists.iter()
                    .map(|list| list.iter().map(AsRef::as_ref).collect::<Vec<&str>>())
                    .collect();

                // Convert the Vec<Vec<&str>> into a Vec<&[&str]>
                let list_array: Vec<&[&str]> = tmp.iter().map(AsRef::as_ref).collect();

                // Create a `Permutator` with the &[&[&str]] as the input.
                let permutator = Permutator::new(&list_array[..]);

                for permutation in permutator {
                    let mut iter = permutation.iter();
                    disk_buffer.write(iter.next().unwrap().as_bytes())
                        .map_err(|why| ParseErr::File(FileErr::Write(disk_buffer.path.clone(), why)))?;
                    for element in iter {
                        disk_buffer.write_byte(b' ')
                            .map_err(|why| ParseErr::File(FileErr::Write(disk_buffer.path.clone(), why)))?;
                        disk_buffer.write(element.as_bytes())
                            .map_err(|why| ParseErr::File(FileErr::Write(disk_buffer.path.clone(), why)))?;
                    }
                    disk_buffer.write_byte(b'\n')
                        .map_err(|why| ParseErr::File(FileErr::Write(disk_buffer.path.clone(), why)))?;
                    number_of_arguments += 1;
                }
            } else {
                for input in current_inputs {
                    disk_buffer.write(input.as_bytes()).map_err(|why|
                        ParseErr::File(FileErr::Write(disk_buffer.path.clone(), why)))?;
                    disk_buffer.write_byte(b'\n').map_err(|why|
                        ParseErr::File(FileErr::Write(disk_buffer.path.clone(), why)))?;
                    number_of_arguments += 1;
                }
            }
        }

        // If no inputs are provided, read from stdin instead.
        if disk_buffer.is_empty() {
            let stdin = io::stdin();
            for line in stdin.lock().lines() {
                if let Ok(line) = line {
                    disk_buffer.write(line.as_bytes()).map_err(|why|
                        ParseErr::File(FileErr::Write(disk_buffer.path.clone(), why)))?;
                    disk_buffer.write_byte(b'\n').map_err(|why|
                        ParseErr::File(FileErr::Write(disk_buffer.path.clone(), why)))?;
                    number_of_arguments += 1;
                }
            }
        }

        if number_of_arguments == 0 { return Err(ParseErr::NoArguments); }

        // Flush the contents of the buffer to the disk before tokenizing the command argument.
        disk_buffer.flush().map_err(|why| ParseErr::File(FileErr::Write(disk_buffer.path.clone(), why)))?;

        // Expand the command if quoting is enabled
        match quote {
            Quoting::None  => (),
            Quoting::Basic => comm = quote::basic(comm),
            Quoting::Shell => comm = quote::shell(comm),
        }

        // Attempt to tokenize the command argument into simple primitive placeholders.
        tokenize(&mut self.arguments, &comm, &unprocessed_path, number_of_arguments).map_err(ParseErr::Token)?;

        if dash_exists() {
            self.flags |= DASH_EXISTS;
        }

        if !shell_required(&self.arguments) {
            self.flags &= 255 ^ SHELL_ENABLED;
        }

        // Return an `InputIterator` of the arguments contained within the unprocessed file.
        let path = filepaths::unprocessed().ok_or(ParseErr::File(FileErr::Path))?;
        Ok(InputIterator::new(&path, number_of_arguments).map_err(ParseErr::File)?)
    }
}

fn shell_required(arguments: &[Token]) -> bool {
    for token in arguments {
        if let &Token::Argument(ref arg) = token {
            if arg.contains(';') || arg.contains('&') {
                return true
            }
        }
    }
    false
}

fn dash_exists() -> bool {
    if let Ok(path) = env::var("PATH") {
        for path in path.split(':') {
            if let Ok(directory) = fs::read_dir(path) {
                for entry in directory {
                    if let Ok(entry) = entry {
                        let path = entry.path();
                        if path.is_file() && path.file_name() == Some(OsStr::new("dash")) { return true; }
                    }
                }
            }
        }
    }
    false
}

/// Attempts to open an input argument and adds each line to the `inputs` list.
fn file_parse<P: AsRef<Path>>(inputs: &mut Vec<String>, path: P) -> Result<(), ParseErr> {
    let path = path.as_ref();
    let file = fs::File::open(path).map_err(|err| ParseErr::File(FileErr::Open(path.to_owned(), err)))?;
    for line in BufReader::new(file).lines() {
        if let Ok(line) = line { inputs.push(line); }
    }
    Ok(())
}
<|endoftext|>


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

// Inspired by Paul Woolcock's cargo-fmt (https://github.com/pwoolcoc/cargo-fmt/)

#![cfg(not(test))]
#![deny(warnings)]

extern crate getopts;
extern crate serde_json as json;

use std::env;
use std::io::{self, Write};
use std::path::PathBuf;
use std::process::{Command, ExitStatus};
use std::str;
use std::collections::HashSet;
use std::iter::FromIterator;

use getopts::{Matches, Options};
use json::Value;

fn main() {
    let exit_status = execute();
    std::io::stdout().flush().unwrap();
    std::process::exit(exit_status);
}

fn execute() -> i32 {
    let success = 0;
    let failure = 1;

    let mut opts = getopts::Options::new();
    opts.optflag("h", "help", "show this message");
    opts.optflag("q", "quiet", "no output printed to stdout");
    opts.optflag("v", "verbose", "use verbose output");
    opts.optmulti(
        "p",
        "package",
        "specify package to format (only usable in workspaces)",
        "<package>",
    );
    opts.optflag("", "all", "format all packages (only usable in workspaces)");

    // If there is any invalid argument passed to `cargo fmt`, return without formatting.
    if let Some(arg) = env::args()
        .skip(2)
        .take_while(|a| a != "--")
        .find(|a| !a.starts_with('-'))
    {
        print_usage_to_stderr(&opts, &format!("Invalid argument: `{}`.", arg));
        return failure;
    }

    let matches = match opts.parse(env::args().skip(1).take_while(|a| a != "--")) {
        Ok(m) => m,
        Err(e) => {
            print_usage_to_stderr(&opts, &e.to_string());
            return failure;
        }
    };

    let verbosity = match (matches.opt_present("v"), matches.opt_present("q")) {
        (false, false) => Verbosity::Normal,
        (false, true) => Verbosity::Quiet,
        (true, false) => Verbosity::Verbose,
        (true, true) => {
            print_usage_to_stderr(&opts, "quiet mode and verbose mode are not compatible");
            return failure;
        }
    };

    if matches.opt_present("h") {
        print_usage_to_stdout(&opts, "");
        return success;
    }

    let workspace_hitlist = WorkspaceHitlist::from_matches(&matches);

    match format_crate(verbosity, &workspace_hitlist) {
        Err(e) => {
            print_usage_to_stderr(&opts, &e.to_string());
            failure
        }
        Ok(status) => if status.success() {
            success
        } else {
            status.code().unwrap_or(failure)
        },
    }
}

macro_rules! print_usage {
    ($print:ident, $opts:ident, $reason:expr) => ({
        let msg = format!("{}\nusage: cargo fmt [options]", $reason);
        $print!(
            "{}\nThis utility formats all bin and lib files of the current crate using rustfmt. \
             Arguments after `--` are passed to rustfmt.",
            $opts.usage(&msg)
        );
    })
}

fn print_usage_to_stdout(opts: &Options, reason: &str) {
    print_usage!(println, opts, reason);
}

fn print_usage_to_stderr(opts: &Options, reason: &str) {
    print_usage!(eprintln, opts, reason);
}

#[derive(Debug, Clone, Copy, PartialEq)]
pub enum Verbosity {
    Verbose,
    Normal,
    Quiet,
}

fn format_crate(
    verbosity: Verbosity,
    workspace_hitlist: &WorkspaceHitlist,
) -> Result<ExitStatus, io::Error> {
    let targets = get_targets(workspace_hitlist)?;

    // Currently only bin and lib files get formatted
    let files: Vec<_> = targets
        .into_iter()
        .filter(|t| t.kind.should_format())
        .inspect(|t| if verbosity == Verbosity::Verbose {
            println!("[{:?}] {:?}", t.kind, t.path)
        })
        .map(|t| t.path)
        .collect();

    format_files(&files, &get_fmt_args(), verbosity)
}

fn get_fmt_args() -> Vec<String> {
    // All arguments after -- are passed to rustfmt
    env::args().skip_while(|a| a != "--").skip(1).collect()
}

#[derive(Debug)]
enum TargetKind {
    Lib,         // dylib, staticlib, lib
    Bin,         // bin
    Example,     // example file
    Test,        // test file
    Bench,       // bench file
    CustomBuild, // build script
    ProcMacro,   // a proc macro implementation
    Other,       // plugin,...
}

impl TargetKind {
    fn should_format(&self) -> bool {
        match *self {
            TargetKind::Lib |
            TargetKind::Bin |
            TargetKind::Example |
            TargetKind::Test |
            TargetKind::Bench |
            TargetKind::CustomBuild |
            TargetKind::ProcMacro => true,
            _ => false,
        }
    }
}

#[derive(Debug)]
pub struct Target {
    path: PathBuf,
    kind: TargetKind,
}

impl Target {
    pub fn from_json(json_val: &Value) -> Option<Self> {
        let jtarget = json_val.as_object()?;
        let path = PathBuf::from(jtarget.get("src_path")?.as_str()?);
        let kinds = jtarget.get("kind")?.as_array()?;
        let kind = match kinds[0].as_str()? {
            "bin" => TargetKind::Bin,
            "lib" | "dylib" | "staticlib" | "cdylib" | "rlib" => TargetKind::Lib,
            "test" => TargetKind::Test,
            "example" => TargetKind::Example,
            "bench" => TargetKind::Bench,
            "custom-build" => TargetKind::CustomBuild,
            "proc-macro" => TargetKind::ProcMacro,
            _ => TargetKind::Other,
        };

        Some(Target {
            path: path,
            kind: kind,
        })
    }
}

#[derive(Debug, PartialEq, Eq)]
pub enum WorkspaceHitlist {
    All,
    Some(Vec<String>),
    None,
}

impl WorkspaceHitlist {
    pub fn get_some(&self) -> Option<&[String]> {
        if let WorkspaceHitlist::Some(ref hitlist) = *self {
            Some(hitlist)
        } else {
            None
        }
    }

    pub fn from_matches(matches: &Matches) -> WorkspaceHitlist {
        match (matches.opt_present("all"), matches.opt_present("p")) {
            (false, false) => WorkspaceHitlist::None,
            (true, _) => WorkspaceHitlist::All,
            (false, true) => WorkspaceHitlist::Some(matches.opt_strs("p")),
        }
    }
}

fn get_cargo_metadata_from_utf8(v: &[u8]) -> Option<Value> {
    json::from_str(str::from_utf8(v).ok()?).ok()
}

fn get_json_array_with<'a>(v: &'a Value, key: &str) -> Option<&'a Vec<Value>> {
    v.as_object()?.get(key)?.as_array()
}

// `cargo metadata --no-deps | jq '.["packages"]'`
fn get_packages(v: &[u8]) -> Result<Vec<Value>, io::Error> {
    let e = io::Error::new(
        io::ErrorKind::NotFound,
        String::from("`cargo metadata` returned json without a 'packages' key"),
    );
    match get_cargo_metadata_from_utf8(v) {
        Some(ref json_obj) => get_json_array_with(json_obj, "packages").cloned().ok_or(e),
        None => Err(e),
    }
}

fn extract_target_from_package(package: &Value) -> Option<Vec<Target>> {
    let jtargets = get_json_array_with(package, "targets")?;
    let mut targets: Vec<Target> = vec![];
    for jtarget in jtargets {
        targets.push(Target::from_json(&jtarget)?);
    }
    Some(targets)
}

fn filter_packages_with_hitlist<'a>(
    packages: Vec<Value>,
    workspace_hitlist: &'a WorkspaceHitlist,
) -> Result<Vec<Value>, &'a String> {
    if *workspace_hitlist == WorkspaceHitlist::All {
        return Ok(packages);
    }
    let mut hitlist: HashSet<&String> = workspace_hitlist
        .get_some()
        .map_or(HashSet::new(), HashSet::from_iter);
    let members: Vec<Value> = packages
        .into_iter()
        .filter(|member| {
            member
                .as_object()
                .and_then(|member_obj| {
                    member_obj
                        .get("name")
                        .and_then(Value::as_str)
                        .map(|member_name| {
                            hitlist.take(&member_name.to_string()).is_some()
                        })
                })
                .unwrap_or(false)
        })
        .collect();
    if hitlist.is_empty() {
        Ok(members)
    } else {
        Err(hitlist.into_iter().next().unwrap())
    }
}

fn get_dependencies_from_package(package: &Value) -> Option<Vec<PathBuf>> {
    let jdependencies = get_json_array_with(package, "dependencies")?;
    let root_path = env::current_dir().ok()?;
    let mut dependencies: Vec<PathBuf> = vec![];
    for jdep in jdependencies {
        let jdependency = jdep.as_object()?;
        if !jdependency.get("source")?.is_null() {
            continue;
        }
        let name = jdependency.get("name")?.as_str()?;
        let mut path = root_path.clone();
        path.push(&name);
        dependencies.push(path);
    }
    Some(dependencies)
}

// Returns a vector of local dependencies under this crate
fn get_path_to_local_dependencies(packages: &[Value]) -> Vec<PathBuf> {
    let mut local_dependencies: Vec<PathBuf> = vec![];
    for package in packages {
        if let Some(mut d) = get_dependencies_from_package(package) {
            local_dependencies.append(&mut d);
        }
    }
    local_dependencies
}

// Returns a vector of all compile targets of a crate
fn get_targets(workspace_hitlist: &WorkspaceHitlist) -> Result<Vec<Target>, io::Error> {
    let output = Command::new("cargo")
        .args(&["metadata", "--no-deps", "--format-version=1"])
        .output()?;
    if output.status.success() {
        let cur_dir = env::current_dir()?;
        let mut targets: Vec<Target> = vec![];
        let packages = get_packages(&output.stdout)?;

        // If we can find any local dependencies, we will try to get targets from those as well.
        if *workspace_hitlist == WorkspaceHitlist::All {
            for path in get_path_to_local_dependencies(&packages) {
                match env::set_current_dir(path) {
                    Ok(..) => match get_targets(workspace_hitlist) {
                        Ok(ref mut t) => targets.append(t),
                        Err(..) => continue,
                    },
                    Err(..) => continue,
                }
            }
        }

        env::set_current_dir(cur_dir)?;
        match filter_packages_with_hitlist(packages, workspace_hitlist) {
            Ok(packages) => {
                for package in packages {
                    if let Some(mut target) = extract_target_from_package(&package) {
                        targets.append(&mut target);
                    }
                }
                Ok(targets)
            }
            Err(package) => {
                // Mimick cargo of only outputting one <package> spec.
                Err(io::Error::new(
                    io::ErrorKind::InvalidInput,
                    format!("package `{}` is not a member of the workspace", package),
                ))
            }
        }
    } else {
        Err(io::Error::new(
            io::ErrorKind::NotFound,
            str::from_utf8(&output.stderr).unwrap(),
        ))
    }
}

fn format_files(
    files: &[PathBuf],
    fmt_args: &[String],
    verbosity: Verbosity,
) -> Result<ExitStatus, io::Error> {
    let stdout = if verbosity == Verbosity::Quiet {
        std::process::Stdio::null()
    } else {
        std::process::Stdio::inherit()
    };
    if verbosity == Verbosity::Verbose {
        print!("rustfmt");
        for a in fmt_args {
            print!(" {}", a);
        }
        for f in files {
            print!(" {}", f.display());
        }
        println!("");
    }
    let mut command = Command::new("rustfmt")
        .stdout(stdout)
        .args(files)
        .args(fmt_args)
        .spawn()
        .map_err(|e| match e.kind() {
            io::ErrorKind::NotFound => io::Error::new(
                io::ErrorKind::Other,
                "Could not run rustfmt, please make sure it is in your PATH.",
            ),
            _ => e,
        })?;
    command.wait()
}
<commit_msg>Filter packages only when '-p' was passed<commit_after>// Copyright 2015-2016 The Rust Project Developers. See the COPYRIGHT
// file at the top-level directory of this distribution and at
// http://rust-lang.org/COPYRIGHT.
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.

// Inspired by Paul Woolcock's cargo-fmt (https://github.com/pwoolcoc/cargo-fmt/)

#![cfg(not(test))]
#![deny(warnings)]

extern crate getopts;
extern crate serde_json as json;

use std::env;
use std::io::{self, Write};
use std::path::PathBuf;
use std::process::{Command, ExitStatus};
use std::str;
use std::collections::HashSet;
use std::iter::FromIterator;

use getopts::{Matches, Options};
use json::Value;

fn main() {
    let exit_status = execute();
    std::io::stdout().flush().unwrap();
    std::process::exit(exit_status);
}

fn execute() -> i32 {
    let success = 0;
    let failure = 1;

    let mut opts = getopts::Options::new();
    opts.optflag("h", "help", "show this message");
    opts.optflag("q", "quiet", "no output printed to stdout");
    opts.optflag("v", "verbose", "use verbose output");
    opts.optmulti(
        "p",
        "package",
        "specify package to format (only usable in workspaces)",
        "<package>",
    );
    opts.optflag("", "all", "format all packages (only usable in workspaces)");

    // If there is any invalid argument passed to `cargo fmt`, return without formatting.
    if let Some(arg) = env::args()
        .skip(2)
        .take_while(|a| a != "--")
        .find(|a| !a.starts_with('-'))
    {
        print_usage_to_stderr(&opts, &format!("Invalid argument: `{}`.", arg));
        return failure;
    }

    let matches = match opts.parse(env::args().skip(1).take_while(|a| a != "--")) {
        Ok(m) => m,
        Err(e) => {
            print_usage_to_stderr(&opts, &e.to_string());
            return failure;
        }
    };

    let verbosity = match (matches.opt_present("v"), matches.opt_present("q")) {
        (false, false) => Verbosity::Normal,
        (false, true) => Verbosity::Quiet,
        (true, false) => Verbosity::Verbose,
        (true, true) => {
            print_usage_to_stderr(&opts, "quiet mode and verbose mode are not compatible");
            return failure;
        }
    };

    if matches.opt_present("h") {
        print_usage_to_stdout(&opts, "");
        return success;
    }

    let workspace_hitlist = WorkspaceHitlist::from_matches(&matches);

    match format_crate(verbosity, &workspace_hitlist) {
        Err(e) => {
            print_usage_to_stderr(&opts, &e.to_string());
            failure
        }
        Ok(status) => if status.success() {
            success
        } else {
            status.code().unwrap_or(failure)
        },
    }
}

macro_rules! print_usage {
    ($print:ident, $opts:ident, $reason:expr) => ({
        let msg = format!("{}\nusage: cargo fmt [options]", $reason);
        $print!(
            "{}\nThis utility formats all bin and lib files of the current crate using rustfmt. \
             Arguments after `--` are passed to rustfmt.",
            $opts.usage(&msg)
        );
    })
}

fn print_usage_to_stdout(opts: &Options, reason: &str) {
    print_usage!(println, opts, reason);
}

fn print_usage_to_stderr(opts: &Options, reason: &str) {
    print_usage!(eprintln, opts, reason);
}

#[derive(Debug, Clone, Copy, PartialEq)]
pub enum Verbosity {
    Verbose,
    Normal,
    Quiet,
}

fn format_crate(
    verbosity: Verbosity,
    workspace_hitlist: &WorkspaceHitlist,
) -> Result<ExitStatus, io::Error> {
    let targets = get_targets(workspace_hitlist)?;

    // Currently only bin and lib files get formatted
    let files: Vec<_> = targets
        .into_iter()
        .filter(|t| t.kind.should_format())
        .inspect(|t| if verbosity == Verbosity::Verbose {
            println!("[{:?}] {:?}", t.kind, t.path)
        })
        .map(|t| t.path)
        .collect();

    format_files(&files, &get_fmt_args(), verbosity)
}

fn get_fmt_args() -> Vec<String> {
    // All arguments after -- are passed to rustfmt
    env::args().skip_while(|a| a != "--").skip(1).collect()
}

#[derive(Debug)]
enum TargetKind {
    Lib,         // dylib, staticlib, lib
    Bin,         // bin
    Example,     // example file
    Test,        // test file
    Bench,       // bench file
    CustomBuild, // build script
    ProcMacro,   // a proc macro implementation
    Other,       // plugin,...
}

impl TargetKind {
    fn should_format(&self) -> bool {
        match *self {
            TargetKind::Lib |
            TargetKind::Bin |
            TargetKind::Example |
            TargetKind::Test |
            TargetKind::Bench |
            TargetKind::CustomBuild |
            TargetKind::ProcMacro => true,
            _ => false,
        }
    }
}

#[derive(Debug)]
pub struct Target {
    path: PathBuf,
    kind: TargetKind,
}

impl Target {
    pub fn from_json(json_val: &Value) -> Option<Self> {
        let jtarget = json_val.as_object()?;
        let path = PathBuf::from(jtarget.get("src_path")?.as_str()?);
        let kinds = jtarget.get("kind")?.as_array()?;
        let kind = match kinds[0].as_str()? {
            "bin" => TargetKind::Bin,
            "lib" | "dylib" | "staticlib" | "cdylib" | "rlib" => TargetKind::Lib,
            "test" => TargetKind::Test,
            "example" => TargetKind::Example,
            "bench" => TargetKind::Bench,
            "custom-build" => TargetKind::CustomBuild,
            "proc-macro" => TargetKind::ProcMacro,
            _ => TargetKind::Other,
        };

        Some(Target {
            path: path,
            kind: kind,
        })
    }
}

#[derive(Debug, PartialEq, Eq)]
pub enum WorkspaceHitlist {
    All,
    Some(Vec<String>),
    None,
}

impl WorkspaceHitlist {
    pub fn get_some(&self) -> Option<&[String]> {
        if let WorkspaceHitlist::Some(ref hitlist) = *self {
            Some(hitlist)
        } else {
            None
        }
    }

    pub fn from_matches(matches: &Matches) -> WorkspaceHitlist {
        match (matches.opt_present("all"), matches.opt_present("p")) {
            (false, false) => WorkspaceHitlist::None,
            (true, _) => WorkspaceHitlist::All,
            (false, true) => WorkspaceHitlist::Some(matches.opt_strs("p")),
        }
    }
}

fn get_cargo_metadata_from_utf8(v: &[u8]) -> Option<Value> {
    json::from_str(str::from_utf8(v).ok()?).ok()
}

fn get_json_array_with<'a>(v: &'a Value, key: &str) -> Option<&'a Vec<Value>> {
    v.as_object()?.get(key)?.as_array()
}

// `cargo metadata --no-deps | jq '.["packages"]'`
fn get_packages(v: &[u8]) -> Result<Vec<Value>, io::Error> {
    let e = io::Error::new(
        io::ErrorKind::NotFound,
        String::from("`cargo metadata` returned json without a 'packages' key"),
    );
    match get_cargo_metadata_from_utf8(v) {
        Some(ref json_obj) => get_json_array_with(json_obj, "packages").cloned().ok_or(e),
        None => Err(e),
    }
}

fn extract_target_from_package(package: &Value) -> Option<Vec<Target>> {
    let jtargets = get_json_array_with(package, "targets")?;
    let mut targets: Vec<Target> = vec![];
    for jtarget in jtargets {
        targets.push(Target::from_json(&jtarget)?);
    }
    Some(targets)
}

fn filter_packages_with_hitlist<'a>(
    packages: Vec<Value>,
    workspace_hitlist: &'a WorkspaceHitlist,
) -> Result<Vec<Value>, &'a String> {
    let some_hitlist: Option<HashSet<&String>> =
        workspace_hitlist.get_some().map(HashSet::from_iter);
    if some_hitlist.is_none() {
        return Ok(packages);
    }
    let mut hitlist = some_hitlist.unwrap();
    let members: Vec<Value> = packages
        .into_iter()
        .filter(|member| {
            member
                .as_object()
                .and_then(|member_obj| {
                    member_obj
                        .get("name")
                        .and_then(Value::as_str)
                        .map(|member_name| {
                            hitlist.take(&member_name.to_string()).is_some()
                        })
                })
                .unwrap_or(false)
        })
        .collect();
    if hitlist.is_empty() {
        Ok(members)
    } else {
        Err(hitlist.into_iter().next().unwrap())
    }
}

fn get_dependencies_from_package(package: &Value) -> Option<Vec<PathBuf>> {
    let jdependencies = get_json_array_with(package, "dependencies")?;
    let root_path = env::current_dir().ok()?;
    let mut dependencies: Vec<PathBuf> = vec![];
    for jdep in jdependencies {
        let jdependency = jdep.as_object()?;
        if !jdependency.get("source")?.is_null() {
            continue;
        }
        let name = jdependency.get("name")?.as_str()?;
        let mut path = root_path.clone();
        path.push(&name);
        dependencies.push(path);
    }
    Some(dependencies)
}

// Returns a vector of local dependencies under this crate
fn get_path_to_local_dependencies(packages: &[Value]) -> Vec<PathBuf> {
    let mut local_dependencies: Vec<PathBuf> = vec![];
    for package in packages {
        if let Some(mut d) = get_dependencies_from_package(package) {
            local_dependencies.append(&mut d);
        }
    }
    local_dependencies
}

// Returns a vector of all compile targets of a crate
fn get_targets(workspace_hitlist: &WorkspaceHitlist) -> Result<Vec<Target>, io::Error> {
    let output = Command::new("cargo")
        .args(&["metadata", "--no-deps", "--format-version=1"])
        .output()?;
    if output.status.success() {
        let cur_dir = env::current_dir()?;
        let mut targets: Vec<Target> = vec![];
        let packages = get_packages(&output.stdout)?;

        // If we can find any local dependencies, we will try to get targets from those as well.
        if *workspace_hitlist == WorkspaceHitlist::All {
            for path in get_path_to_local_dependencies(&packages) {
                match env::set_current_dir(path) {
                    Ok(..) => match get_targets(workspace_hitlist) {
                        Ok(ref mut t) => targets.append(t),
                        Err(..) => continue,
                    },
                    Err(..) => continue,
                }
            }
        }

        env::set_current_dir(cur_dir)?;
        match filter_packages_with_hitlist(packages, workspace_hitlist) {
            Ok(packages) => {
                for package in packages {
                    if let Some(mut target) = extract_target_from_package(&package) {
                        targets.append(&mut target);
                    }
                }
                Ok(targets)
            }
            Err(package) => {
                // Mimick cargo of only outputting one <package> spec.
                Err(io::Error::new(
                    io::ErrorKind::InvalidInput,
                    format!("package `{}` is not a member of the workspace", package),
                ))
            }
        }
    } else {
        Err(io::Error::new(
            io::ErrorKind::NotFound,
            str::from_utf8(&output.stderr).unwrap(),
        ))
    }
}

fn format_files(
    files: &[PathBuf],
    fmt_args: &[String],
    verbosity: Verbosity,
) -> Result<ExitStatus, io::Error> {
    let stdout = if verbosity == Verbosity::Quiet {
        std::process::Stdio::null()
    } else {
        std::process::Stdio::inherit()
    };
    if verbosity == Verbosity::Verbose {
        print!("rustfmt");
        for a in fmt_args {
            print!(" {}", a);
        }
        for f in files {
            print!(" {}", f.display());
        }
        println!("");
    }
    let mut command = Command::new("rustfmt")
        .stdout(stdout)
        .args(files)
        .args(fmt_args)
        .spawn()
        .map_err(|e| match e.kind() {
            io::ErrorKind::NotFound => io::Error::new(
                io::ErrorKind::Other,
                "Could not run rustfmt, please make sure it is in your PATH.",
            ),
            _ => e,
        })?;
    command.wait()
}
<|endoftext|>


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

// Inspired by Paul Woolcock's cargo-fmt (https://github.com/pwoolcoc/cargo-fmt/)

#![cfg(not(test))]
#![deny(warnings)]

extern crate cargo_metadata;
extern crate getopts;
extern crate serde_json as json;

use std::env;
use std::fs;
use std::hash::{Hash, Hasher};
use std::io::{self, Write};
use std::path::{Path, PathBuf};
use std::process::{Command, ExitStatus};
use std::str;
use std::collections::HashSet;
use std::iter::FromIterator;

use getopts::{Matches, Options};

fn main() {
    let exit_status = execute();
    std::io::stdout().flush().unwrap();
    std::process::exit(exit_status);
}

fn execute() -> i32 {
    let success = 0;
    let failure = 1;

    let mut opts = getopts::Options::new();
    opts.optflag("h", "help", "show this message");
    opts.optflag("q", "quiet", "no output printed to stdout");
    opts.optflag("v", "verbose", "use verbose output");
    opts.optmulti(
        "p",
        "package",
        "specify package to format (only usable in workspaces)",
        "<package>",
    );
    opts.optflag("", "all", "format all packages (only usable in workspaces)");

    // If there is any invalid argument passed to `cargo fmt`, return without formatting.
    let mut is_package_arg = false;
    for arg in env::args().skip(2).take_while(|a| a != "--") {
        if arg.starts_with('-') {
            is_package_arg = arg.starts_with("--package");
        } else if !is_package_arg {
            print_usage_to_stderr(&opts, &format!("Invalid argument: `{}`.", arg));
            return failure;
        } else {
            is_package_arg = false;
        }
    }

    let matches = match opts.parse(env::args().skip(1).take_while(|a| a != "--")) {
        Ok(m) => m,
        Err(e) => {
            print_usage_to_stderr(&opts, &e.to_string());
            return failure;
        }
    };

    let verbosity = match (matches.opt_present("v"), matches.opt_present("q")) {
        (false, false) => Verbosity::Normal,
        (false, true) => Verbosity::Quiet,
        (true, false) => Verbosity::Verbose,
        (true, true) => {
            print_usage_to_stderr(&opts, "quiet mode and verbose mode are not compatible");
            return failure;
        }
    };

    if matches.opt_present("h") {
        print_usage_to_stdout(&opts, "");
        return success;
    }

    let strategy = CargoFmtStrategy::from_matches(&matches);

    match format_crate(verbosity, &strategy) {
        Err(e) => {
            print_usage_to_stderr(&opts, &e.to_string());
            failure
        }
        Ok(status) => {
            if status.success() {
                success
            } else {
                status.code().unwrap_or(failure)
            }
        }
    }
}

macro_rules! print_usage {
    ($print:ident, $opts:ident, $reason:expr) => ({
        let msg = format!("{}\nusage: cargo fmt [options]", $reason);
        $print!(
            "{}\nThis utility formats all bin and lib files of the current crate using rustfmt. \
             Arguments after `--` are passed to rustfmt.",
            $opts.usage(&msg)
        );
    })
}

fn print_usage_to_stdout(opts: &Options, reason: &str) {
    print_usage!(println, opts, reason);
}

fn print_usage_to_stderr(opts: &Options, reason: &str) {
    print_usage!(eprintln, opts, reason);
}

#[derive(Debug, Clone, Copy, PartialEq)]
pub enum Verbosity {
    Verbose,
    Normal,
    Quiet,
}

fn format_crate(
    verbosity: Verbosity,
    strategy: &CargoFmtStrategy,
) -> Result<ExitStatus, io::Error> {
    let rustfmt_args = get_fmt_args();
    let targets = if rustfmt_args.iter().any(|s| s == "--dump-default-config") {
        HashSet::new()
    } else {
        get_targets(strategy)?
    };

    // Currently only bin and lib files get formatted
    let files: Vec<_> = targets
        .into_iter()
        .inspect(|t| {
            if verbosity == Verbosity::Verbose {
                println!("[{}] {:?}", t.kind, t.path)
            }
        })
        .map(|t| t.path)
        .collect();

    format_files(&files, &rustfmt_args, verbosity)
}

fn get_fmt_args() -> Vec<String> {
    // All arguments after -- are passed to rustfmt
    env::args().skip_while(|a| a != "--").skip(1).collect()
}

/// Target uses a `path` field for equality and hashing.
#[derive(Debug)]
pub struct Target {
    /// A path to the main source file of the target.
    path: PathBuf,
    /// A kind of target (e.g. lib, bin, example, ...).
    kind: String,
}

impl Target {
    pub fn from_target(target: &cargo_metadata::Target) -> Self {
        let path = PathBuf::from(&target.src_path);
        let canonicalized = fs::canonicalize(&path).unwrap_or(path);

        Target {
            path: canonicalized,
            kind: target.kind[0].clone(),
        }
    }
}

impl PartialEq for Target {
    fn eq(&self, other: &Target) -> bool {
        self.path == other.path
    }
}

impl Eq for Target {}

impl Hash for Target {
    fn hash<H: Hasher>(&self, state: &mut H) {
        self.path.hash(state);
    }
}

#[derive(Debug, PartialEq, Eq)]
pub enum CargoFmtStrategy {
    /// Format every packages and dependencies.
    All,
    /// Format pacakges that are specified by the command line argument.
    Some(Vec<String>),
    /// Format the root packages only.
    Root,
}

impl CargoFmtStrategy {
    pub fn from_matches(matches: &Matches) -> CargoFmtStrategy {
        match (matches.opt_present("all"), matches.opt_present("p")) {
            (false, false) => CargoFmtStrategy::Root,
            (true, _) => CargoFmtStrategy::All,
            (false, true) => CargoFmtStrategy::Some(matches.opt_strs("p")),
        }
    }
}

/// Based on the specified `CargoFmtStrategy`, returns a set of main source files.
fn get_targets(strategy: &CargoFmtStrategy) -> Result<HashSet<Target>, io::Error> {
    let mut targets = HashSet::new();

    match *strategy {
        CargoFmtStrategy::Root => get_targets_root_only(&mut targets)?,
        CargoFmtStrategy::All => get_targets_recursive(None, &mut targets, &mut HashSet::new())?,
        CargoFmtStrategy::Some(ref hitlist) => get_targets_with_hitlist(hitlist, &mut targets)?,
    }

    if targets.is_empty() {
        Err(io::Error::new(
            io::ErrorKind::Other,
            "Failed to find targets".to_owned(),
        ))
    } else {
        Ok(targets)
    }
}

fn get_targets_root_only(targets: &mut HashSet<Target>) -> Result<(), io::Error> {
    let metadata = get_cargo_metadata(None)?;

    for package in metadata.packages {
        for target in package.targets {
            if target.name == package.name {
                targets.insert(Target::from_target(&target));
            }
        }
    }

    Ok(())
}

fn get_targets_recursive(
    manifest_path: Option<&Path>,
    mut targets: &mut HashSet<Target>,
    visited: &mut HashSet<String>,
) -> Result<(), io::Error> {
    let metadata = get_cargo_metadata(manifest_path)?;

    for package in metadata.packages {
        add_targets(&package.targets, &mut targets);

        // Look for local dependencies.
        for dependency in package.dependencies {
            if dependency.source.is_some() || visited.contains(&dependency.name) {
                continue;
            }

            let mut manifest_path = PathBuf::from(&package.manifest_path);

            manifest_path.pop();
            manifest_path.push(&dependency.name);
            manifest_path.push("Cargo.toml");

            if manifest_path.exists() {
                visited.insert(dependency.name);
                get_targets_recursive(Some(&manifest_path), &mut targets, visited)?;
            }
        }
    }

    Ok(())
}

fn get_targets_with_hitlist(
    hitlist: &[String],
    targets: &mut HashSet<Target>,
) -> Result<(), io::Error> {
    let metadata = get_cargo_metadata(None)?;

    let mut workspace_hitlist: HashSet<&String> = HashSet::from_iter(hitlist);

    for package in metadata.packages {
        if workspace_hitlist.remove(&package.name) {
            for target in package.targets {
                targets.insert(Target::from_target(&target));
            }
        }
    }

    if workspace_hitlist.is_empty() {
        Ok(())
    } else {
        let package = workspace_hitlist.iter().next().unwrap();
        Err(io::Error::new(
            io::ErrorKind::InvalidInput,
            format!("package `{}` is not a member of the workspace", package),
        ))
    }
}

fn add_targets(target_paths: &[cargo_metadata::Target], targets: &mut HashSet<Target>) {
    for target in target_paths {
        targets.insert(Target::from_target(target));
    }
}

fn format_files(
    files: &[PathBuf],
    fmt_args: &[String],
    verbosity: Verbosity,
) -> Result<ExitStatus, io::Error> {
    let stdout = if verbosity == Verbosity::Quiet {
        std::process::Stdio::null()
    } else {
        std::process::Stdio::inherit()
    };

    if verbosity == Verbosity::Verbose {
        print!("rustfmt");
        for a in fmt_args {
            print!(" {}", a);
        }
        for f in files {
            print!(" {}", f.display());
        }
        println!();
    }

    let mut command = Command::new("rustfmt")
        .stdout(stdout)
        .args(files)
        .args(fmt_args)
        .spawn()
        .map_err(|e| match e.kind() {
            io::ErrorKind::NotFound => io::Error::new(
                io::ErrorKind::Other,
                "Could not run rustfmt, please make sure it is in your PATH.",
            ),
            _ => e,
        })?;

    command.wait()
}

fn get_cargo_metadata(manifest_path: Option<&Path>) -> Result<cargo_metadata::Metadata, io::Error> {
    match cargo_metadata::metadata(manifest_path) {
        Ok(metadata) => Ok(metadata),
        Err(..) => Err(io::Error::new(
            io::ErrorKind::Other,
            "`cargo manifest` failed.",
        )),
    }
}
<commit_msg>Adding --version option to cargo-fmt<commit_after>// Copyright 2015-2016 The Rust Project Developers. See the COPYRIGHT
// file at the top-level directory of this distribution and at
// http://rust-lang.org/COPYRIGHT.
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.

// Inspired by Paul Woolcock's cargo-fmt (https://github.com/pwoolcoc/cargo-fmt/)

#![cfg(not(test))]
#![deny(warnings)]

extern crate cargo_metadata;
extern crate getopts;
extern crate serde_json as json;

use std::env;
use std::fs;
use std::hash::{Hash, Hasher};
use std::io::{self, Write};
use std::path::{Path, PathBuf};
use std::process::{Command, ExitStatus};
use std::str;
use std::collections::HashSet;
use std::iter::FromIterator;

use getopts::{Matches, Options};

fn main() {
    let exit_status = execute();
    std::io::stdout().flush().unwrap();
    std::process::exit(exit_status);
}

fn execute() -> i32 {
    let success = 0;
    let failure = 1;

    let mut opts = getopts::Options::new();
    opts.optflag("h", "help", "show this message");
    opts.optflag("q", "quiet", "no output printed to stdout");
    opts.optflag("v", "verbose", "use verbose output");
    opts.optmulti(
        "p",
        "package",
        "specify package to format (only usable in workspaces)",
        "<package>",
    );
    opts.optflag("", "version", "print rustfmt version and exit");
    opts.optflag("", "all", "format all packages (only usable in workspaces)");

    // If there is any invalid argument passed to `cargo fmt`, return without formatting.
    let mut is_package_arg = false;
    for arg in env::args().skip(2).take_while(|a| a != "--") {
        if arg.starts_with('-') {
            is_package_arg = arg.starts_with("--package");
        } else if !is_package_arg {
            print_usage_to_stderr(&opts, &format!("Invalid argument: `{}`.", arg));
            return failure;
        } else {
            is_package_arg = false;
        }
    }

    let matches = match opts.parse(env::args().skip(1).take_while(|a| a != "--")) {
        Ok(m) => m,
        Err(e) => {
            print_usage_to_stderr(&opts, &e.to_string());
            return failure;
        }
    };

    let verbosity = match (matches.opt_present("v"), matches.opt_present("q")) {
        (false, false) => Verbosity::Normal,
        (false, true) => Verbosity::Quiet,
        (true, false) => Verbosity::Verbose,
        (true, true) => {
            print_usage_to_stderr(&opts, "quiet mode and verbose mode are not compatible");
            return failure;
        }
    };

    if matches.opt_present("h") {
        print_usage_to_stdout(&opts, "");
        return success;
    }

    if matches.opt_present("version") {
        return handle_command_status(get_version(), &opts);
    }

    let strategy = CargoFmtStrategy::from_matches(&matches);

    match format_crate(verbosity, &strategy) {
        Err(e) => {
            print_usage_to_stderr(&opts, &e.to_string());
            failure
        }
        Ok(status) => {
            if status.success() {
                success
            } else {
                status.code().unwrap_or(failure)
            }
        }
    }
}

macro_rules! print_usage {
    ($print:ident, $opts:ident, $reason:expr) => ({
        let msg = format!("{}\nusage: cargo fmt [options]", $reason);
        $print!(
            "{}\nThis utility formats all bin and lib files of the current crate using rustfmt. \
             Arguments after `--` are passed to rustfmt.",
            $opts.usage(&msg)
        );
    })
}

fn print_usage_to_stdout(opts: &Options, reason: &str) {
    print_usage!(println, opts, reason);
}

fn print_usage_to_stderr(opts: &Options, reason: &str) {
    print_usage!(eprintln, opts, reason);
}

#[derive(Debug, Clone, Copy, PartialEq)]
pub enum Verbosity {
    Verbose,
    Normal,
    Quiet,
}

fn handle_command_status(status: Result<ExitStatus, io::Error>, opts: &getopts::Options) -> i32 {
    let success = 0;
    let failure = 1;

    match status {
        Err(e) => {
            print_usage_to_stderr(&opts, &e.to_string());
            failure
        }
        Ok(status) => {
            if status.success() {
                success
            } else {
                status.code().unwrap_or(failure)
            }
        }
    }
}

fn get_version() -> Result<ExitStatus, io::Error> {
    let mut command = Command::new("rustfmt")
        .args(vec!["--version"])
        .spawn()
        .map_err(|e| match e.kind() {
            io::ErrorKind::NotFound => io::Error::new(
                io::ErrorKind::Other,
                "Could not run rustfmt, please make sure it is in your PATH.",
            ),
            _ => e,
        })?;

    command.wait()
}

fn format_crate(
    verbosity: Verbosity,
    strategy: &CargoFmtStrategy,
) -> Result<ExitStatus, io::Error> {
    let rustfmt_args = get_fmt_args();
    let targets = if rustfmt_args.iter().any(|s| s == "--dump-default-config") {
        HashSet::new()
    } else {
        get_targets(strategy)?
    };

    // Currently only bin and lib files get formatted
    let files: Vec<_> = targets
        .into_iter()
        .inspect(|t| {
            if verbosity == Verbosity::Verbose {
                println!("[{}] {:?}", t.kind, t.path)
            }
        })
        .map(|t| t.path)
        .collect();

    format_files(&files, &rustfmt_args, verbosity)
}

fn get_fmt_args() -> Vec<String> {
    // All arguments after -- are passed to rustfmt
    env::args().skip_while(|a| a != "--").skip(1).collect()
}

/// Target uses a `path` field for equality and hashing.
#[derive(Debug)]
pub struct Target {
    /// A path to the main source file of the target.
    path: PathBuf,
    /// A kind of target (e.g. lib, bin, example, ...).
    kind: String,
}

impl Target {
    pub fn from_target(target: &cargo_metadata::Target) -> Self {
        let path = PathBuf::from(&target.src_path);
        let canonicalized = fs::canonicalize(&path).unwrap_or(path);

        Target {
            path: canonicalized,
            kind: target.kind[0].clone(),
        }
    }
}

impl PartialEq for Target {
    fn eq(&self, other: &Target) -> bool {
        self.path == other.path
    }
}

impl Eq for Target {}

impl Hash for Target {
    fn hash<H: Hasher>(&self, state: &mut H) {
        self.path.hash(state);
    }
}

#[derive(Debug, PartialEq, Eq)]
pub enum CargoFmtStrategy {
    /// Format every packages and dependencies.
    All,
    /// Format pacakges that are specified by the command line argument.
    Some(Vec<String>),
    /// Format the root packages only.
    Root,
}

impl CargoFmtStrategy {
    pub fn from_matches(matches: &Matches) -> CargoFmtStrategy {
        match (matches.opt_present("all"), matches.opt_present("p")) {
            (false, false) => CargoFmtStrategy::Root,
            (true, _) => CargoFmtStrategy::All,
            (false, true) => CargoFmtStrategy::Some(matches.opt_strs("p")),
        }
    }
}

/// Based on the specified `CargoFmtStrategy`, returns a set of main source files.
fn get_targets(strategy: &CargoFmtStrategy) -> Result<HashSet<Target>, io::Error> {
    let mut targets = HashSet::new();

    match *strategy {
        CargoFmtStrategy::Root => get_targets_root_only(&mut targets)?,
        CargoFmtStrategy::All => get_targets_recursive(None, &mut targets, &mut HashSet::new())?,
        CargoFmtStrategy::Some(ref hitlist) => get_targets_with_hitlist(hitlist, &mut targets)?,
    }

    if targets.is_empty() {
        Err(io::Error::new(
            io::ErrorKind::Other,
            "Failed to find targets".to_owned(),
        ))
    } else {
        Ok(targets)
    }
}

fn get_targets_root_only(targets: &mut HashSet<Target>) -> Result<(), io::Error> {
    let metadata = get_cargo_metadata(None)?;

    for package in metadata.packages {
        for target in package.targets {
            if target.name == package.name {
                targets.insert(Target::from_target(&target));
            }
        }
    }

    Ok(())
}

fn get_targets_recursive(
    manifest_path: Option<&Path>,
    mut targets: &mut HashSet<Target>,
    visited: &mut HashSet<String>,
) -> Result<(), io::Error> {
    let metadata = get_cargo_metadata(manifest_path)?;

    for package in metadata.packages {
        add_targets(&package.targets, &mut targets);

        // Look for local dependencies.
        for dependency in package.dependencies {
            if dependency.source.is_some() || visited.contains(&dependency.name) {
                continue;
            }

            let mut manifest_path = PathBuf::from(&package.manifest_path);

            manifest_path.pop();
            manifest_path.push(&dependency.name);
            manifest_path.push("Cargo.toml");

            if manifest_path.exists() {
                visited.insert(dependency.name);
                get_targets_recursive(Some(&manifest_path), &mut targets, visited)?;
            }
        }
    }

    Ok(())
}

fn get_targets_with_hitlist(
    hitlist: &[String],
    targets: &mut HashSet<Target>,
) -> Result<(), io::Error> {
    let metadata = get_cargo_metadata(None)?;

    let mut workspace_hitlist: HashSet<&String> = HashSet::from_iter(hitlist);

    for package in metadata.packages {
        if workspace_hitlist.remove(&package.name) {
            for target in package.targets {
                targets.insert(Target::from_target(&target));
            }
        }
    }

    if workspace_hitlist.is_empty() {
        Ok(())
    } else {
        let package = workspace_hitlist.iter().next().unwrap();
        Err(io::Error::new(
            io::ErrorKind::InvalidInput,
            format!("package `{}` is not a member of the workspace", package),
        ))
    }
}

fn add_targets(target_paths: &[cargo_metadata::Target], targets: &mut HashSet<Target>) {
    for target in target_paths {
        targets.insert(Target::from_target(target));
    }
}

fn format_files(
    files: &[PathBuf],
    fmt_args: &[String],
    verbosity: Verbosity,
) -> Result<ExitStatus, io::Error> {
    let stdout = if verbosity == Verbosity::Quiet {
        std::process::Stdio::null()
    } else {
        std::process::Stdio::inherit()
    };

    if verbosity == Verbosity::Verbose {
        print!("rustfmt");
        for a in fmt_args {
            print!(" {}", a);
        }
        for f in files {
            print!(" {}", f.display());
        }
        println!();
    }

    let mut command = Command::new("rustfmt")
        .stdout(stdout)
        .args(files)
        .args(fmt_args)
        .spawn()
        .map_err(|e| match e.kind() {
            io::ErrorKind::NotFound => io::Error::new(
                io::ErrorKind::Other,
                "Could not run rustfmt, please make sure it is in your PATH.",
            ),
            _ => e,
        })?;

    command.wait()
}

fn get_cargo_metadata(manifest_path: Option<&Path>) -> Result<cargo_metadata::Metadata, io::Error> {
    match cargo_metadata::metadata(manifest_path) {
        Ok(metadata) => Ok(metadata),
        Err(..) => Err(io::Error::new(
            io::ErrorKind::Other,
            "`cargo manifest` failed.",
        )),
    }
}
<|endoftext|>


<commit_before>/* Copyright (C)  2016 nokaa <nokaa@cock.li>
 * This software is licensed under the terms of the
 * GNU Affero General Public License. You should have
 * received a copy of this license with this software.
 * The license may also be found at https://gnu.org/licenses/agpl.txt
 */

mod command;
mod cursor;
mod insert;

use rustty::{self, Event, Color};
use editor;

/// `Term` represents our client application. This allows
/// us to work with the filesystem and the UI.
pub struct Term<'a> {
    /// Represents the location of the cursor in our UI
    cursor: cursor::Cursor,
    /// Contains file information; allows us to work with
    /// the filesystem
    editor: &'a mut editor::Editor<'a>,
    /// This is the UI itself
    term: rustty::Terminal,
    /// Represents the running status
    quit: bool,
    /// Represents the topmost line in our UI
    line: usize,
    /// Represents the total number of lines in the currently
    /// open file
    total_lines: usize,
}

impl<'a> Term<'a> {
    pub fn new(editor: &'a mut editor::Editor<'a>) -> Term<'a> {
        Term {
            cursor: cursor::Cursor::new(Color::Red),
            editor: editor,
            term: rustty::Terminal::new().unwrap(),
            quit: false,
            line: 0,
            total_lines: 0,
        }
    }

    /// Launches the terminal.
    pub fn run(&mut self) {
        self.editor.read_file(self.term.cols());
        let lines = self.editor.contents.len() as isize;
        self.set_total_lines(lines);

        self.term[self.cursor.current_pos()].set_bg(self.cursor.color);
        let current_line = self.current_line();
        self.print_file(current_line);
        self.prompt();
        self.term.swap_buffers().unwrap();

        while !self.quit {
            let evt = self.term.get_event(100).unwrap();
            if let Some(Event::Key(ch)) = evt {
                if self.editor.command_mode() {
                    command::handle(self, ch);
                } else {
                    insert::handle(self, ch);
                }

                if self.cursor.pos.x >= self.term.cols() - 1 {
                    self.term[self.cursor.last_pos()].set_bg(Color::Default);
                    self.cursor.save_pos();
                    self.cursor.pos.x = 0;
                    self.cursor.pos.y += 1;
                }
                if self.cursor.pos.y >= self.term.rows() - 1 {
                    self.term[self.cursor.last_pos()].set_bg(Color::Default);
                    self.cursor.save_pos();
                    self.cursor.pos.x = 0;
                    self.cursor.pos.y = 0;
                }

                self.term[self.cursor.last_pos()].set_bg(Color::Default);
                self.term[self.cursor.current_pos()].set_bg(self.cursor.color);
                self.term.swap_buffers().unwrap();
            }
        }
    }

    /// Prints our prompt to the UI.
    pub fn prompt(&mut self) {
        let w = self.term.cols();
        let h = self.term.rows();

        for i in 0..w {
            self.term[(i, h - 2)].set_bg(Color::Red);
        }

        for (i, c) in self.editor.filename().chars().enumerate() {
            self.term[(i, h - 2)].set_ch(c);
        }
    }

    /// Prints our editor's contents to the UI.
    fn print_file(&mut self, start: usize) {
        let w = self.term.cols();
        let mut i = 0;
        let mut j = 0;
        for line in &self.editor.contents {
            if i < start {
                i += 1;
                continue;
            }

            for &b in line {
                if b == b'\n' {
                    continue;
                }
                self.term[(j, i - start)].set_ch(b as char);
                j += 1;
            }
            while j < w {
                self.term[(j, i - start)].set_ch(' ');
                j += 1;
            }

            i += 1;
            j = 0;
            if i - start == self.term.rows() - 2 {
                break;
            }
        }
    }

    pub fn move_cursor(&mut self, direction: cursor::Direction) {
        match direction {
            cursor::Direction::Up => {
                if self.cursor.pos.y != 0 {
                    self.cursor.save_pos();
                    self.cursor.pos.y -= 1;
                } else if self.current_line() != 0 {
                    let current_line = self.current_line();
                    self.set_current_line(current_line - 1);
                    self.redraw_file();
                }
            }
            cursor::Direction::Down => {
                if self.total_lines() <= self.term.rows() - 3 {
                    if self.cursor.pos.y != self.term.rows() - 3 &&
                       self.cursor.pos.y < self.total_lines() - 1
                    {
                        self.cursor.save_pos();
                        self.cursor.pos.y += 1;
                    }
                } else {
                    if self.cursor.pos.y != self.term.rows() - 3 {
                        self.cursor.save_pos();
                        self.cursor.pos.y += 1;
                    } else if self.cursor.pos.y + self.current_line() <
                              self.total_lines() - 1
                    {
                        let current_line = self.current_line();
                        self.set_current_line(current_line + 1);
                        self.redraw_file();
                    }
                }
            }
            cursor::Direction::Left => {
                if self.cursor.pos.x != 0 {
                    self.cursor.save_pos();
                    self.cursor.pos.x -= 1;
                }
            }
            cursor::Direction::Right => {
                let curr = self.cursor.pos.y + self.current_line();
                if self.cursor.pos.x != self.editor.contents[curr].len() - 1 {
                    self.cursor.save_pos();
                    self.cursor.pos.x += 1;
                }
            }
        }
    }

    /// Changes the value of the `quit` attribute to `true`.
    pub fn quit(&mut self) {
        self.quit = true;
    }

    /// Returns the total lines in our file
    pub fn total_lines(&self) -> usize {
        self.total_lines
    }

    /// Allows us to modify the total number of lines
    /// in our file, e.g. if a new line is added.
    pub fn set_total_lines(&mut self, change: isize) {
        let new = self.total_lines as isize + change;
        self.total_lines = new as usize;
    }

    /// Gives the current line at the top of our UI
    pub fn current_line(&self) -> usize {
        self.line
    }

    /// Allows us to change what the topmost line in the UI is
    pub fn set_current_line(&mut self, line: usize) {
        self.line = line;
    }

    /// This function redraws the portion of our UI displaying
    /// file contents. E.g. this is called when the current line
    /// changes.
    pub fn redraw_file(&mut self) {
        let current_line = self.current_line();
        self.print_file(current_line);
    }
}
<commit_msg>Fixes movement to the right<commit_after>/* Copyright (C)  2016 nokaa <nokaa@cock.li>
 * This software is licensed under the terms of the
 * GNU Affero General Public License. You should have
 * received a copy of this license with this software.
 * The license may also be found at https://gnu.org/licenses/agpl.txt
 */

mod command;
mod cursor;
mod insert;

use rustty::{self, Event, Color};
use editor;

/// `Term` represents our client application. This allows
/// us to work with the filesystem and the UI.
pub struct Term<'a> {
    /// Represents the location of the cursor in our UI
    cursor: cursor::Cursor,
    /// Contains file information; allows us to work with
    /// the filesystem
    editor: &'a mut editor::Editor<'a>,
    /// This is the UI itself
    term: rustty::Terminal,
    /// Represents the running status
    quit: bool,
    /// Represents the topmost line in our UI
    line: usize,
    /// Represents the total number of lines in the currently
    /// open file
    total_lines: usize,
}

impl<'a> Term<'a> {
    pub fn new(editor: &'a mut editor::Editor<'a>) -> Term<'a> {
        Term {
            cursor: cursor::Cursor::new(Color::Red),
            editor: editor,
            term: rustty::Terminal::new().unwrap(),
            quit: false,
            line: 0,
            total_lines: 0,
        }
    }

    /// Launches the terminal.
    pub fn run(&mut self) {
        self.editor.read_file(self.term.cols());
        let lines = self.editor.contents.len() as isize;
        self.set_total_lines(lines);

        self.term[self.cursor.current_pos()].set_bg(self.cursor.color);
        let current_line = self.current_line();
        self.print_file(current_line);
        self.prompt();
        self.term.swap_buffers().unwrap();

        while !self.quit {
            let evt = self.term.get_event(100).unwrap();
            if let Some(Event::Key(ch)) = evt {
                if self.editor.command_mode() {
                    command::handle(self, ch);
                } else {
                    insert::handle(self, ch);
                }

                if self.cursor.pos.x > self.term.cols() - 1 {
                    self.term[self.cursor.last_pos()].set_bg(Color::Default);
                    self.cursor.save_pos();
                    self.cursor.pos.x = 0;
                    self.cursor.pos.y += 1;
                }
                if self.cursor.pos.y >= self.term.rows() - 1 {
                    self.term[self.cursor.last_pos()].set_bg(Color::Default);
                    self.cursor.save_pos();
                    self.cursor.pos.x = 0;
                    self.cursor.pos.y = 0;
                }

                self.term[self.cursor.last_pos()].set_bg(Color::Default);
                self.term[self.cursor.current_pos()].set_bg(self.cursor.color);
                self.term.swap_buffers().unwrap();
            }
        }
    }

    /// Prints our prompt to the UI.
    pub fn prompt(&mut self) {
        let w = self.term.cols();
        let h = self.term.rows();

        for i in 0..w {
            self.term[(i, h - 2)].set_bg(Color::Red);
        }

        for (i, c) in self.editor.filename().chars().enumerate() {
            self.term[(i, h - 2)].set_ch(c);
        }
    }

    /// Prints our editor's contents to the UI.
    fn print_file(&mut self, start: usize) {
        let w = self.term.cols();
        let mut i = 0;
        let mut j = 0;
        for line in &self.editor.contents {
            if i < start {
                i += 1;
                continue;
            }

            for &b in line {
                if b == b'\n' {
                    continue;
                }
                self.term[(j, i - start)].set_ch(b as char);
                j += 1;
            }
            while j < w {
                self.term[(j, i - start)].set_ch(' ');
                j += 1;
            }

            i += 1;
            j = 0;
            if i - start == self.term.rows() - 2 {
                break;
            }
        }
    }

    pub fn move_cursor(&mut self, direction: cursor::Direction) {
        match direction {
            cursor::Direction::Up => {
                if self.cursor.pos.y != 0 {
                    self.cursor.save_pos();
                    self.cursor.pos.y -= 1;
                } else if self.current_line() != 0 {
                    let current_line = self.current_line();
                    self.set_current_line(current_line - 1);
                    self.redraw_file();
                }
            }
            cursor::Direction::Down => {
                if self.total_lines() <= self.term.rows() - 3 {
                    if self.cursor.pos.y != self.term.rows() - 3 &&
                       self.cursor.pos.y < self.total_lines() - 1
                    {
                        self.cursor.save_pos();
                        self.cursor.pos.y += 1;
                    }
                } else {
                    if self.cursor.pos.y != self.term.rows() - 3 {
                        self.cursor.save_pos();
                        self.cursor.pos.y += 1;
                    } else if self.cursor.pos.y + self.current_line() <
                              self.total_lines() - 1
                    {
                        let current_line = self.current_line();
                        self.set_current_line(current_line + 1);
                        self.redraw_file();
                    }
                }
            }
            cursor::Direction::Left => {
                if self.cursor.pos.x != 0 {
                    self.cursor.save_pos();
                    self.cursor.pos.x -= 1;
                }
            }
            cursor::Direction::Right => {
                let curr = self.cursor.pos.y + self.current_line();
                let len = self.editor.contents[curr].len() - 1;
                if self.editor.contents[curr][len] == b'\n' {
                    if self.cursor.pos.x != len - 1 {
                        self.cursor.save_pos();
                        self.cursor.pos.x += 1;
                    }
                } else if self.cursor.pos.x != len {
                    self.cursor.save_pos();
                    self.cursor.pos.x += 1;
                }
            }
        }
    }

    /// Changes the value of the `quit` attribute to `true`.
    pub fn quit(&mut self) {
        self.quit = true;
    }

    /// Returns the total lines in our file
    pub fn total_lines(&self) -> usize {
        self.total_lines
    }

    /// Allows us to modify the total number of lines
    /// in our file, e.g. if a new line is added.
    pub fn set_total_lines(&mut self, change: isize) {
        let new = self.total_lines as isize + change;
        self.total_lines = new as usize;
    }

    /// Gives the current line at the top of our UI
    pub fn current_line(&self) -> usize {
        self.line
    }

    /// Allows us to change what the topmost line in the UI is
    pub fn set_current_line(&mut self, line: usize) {
        self.line = line;
    }

    /// This function redraws the portion of our UI displaying
    /// file contents. E.g. this is called when the current line
    /// changes.
    pub fn redraw_file(&mut self) {
        let current_line = self.current_line();
        self.print_file(current_line);
    }
}
<|endoftext|>