<commit_before>extern crate rustbox;
extern crate chrono;

use rustbox::*;
use chrono::*;

use screen::common::*;
use utility::string::*;
use model::ShowItem;
use reply_model::*;

pub struct Show<'a> {
    rustbox: &'a rustbox::RustBox,
    scrollY: usize,
}

impl<'a> Show<'a> {
    pub fn new(rustbox: &'a rustbox::RustBox) -> Self {
        Show {
            rustbox: &rustbox,
            scrollY: 0,
        }
    }
    pub fn print(&mut self, title: &str, item: &ShowItem) {

        print_header(&self.rustbox,
                     self.rustbox.width(),
                     &format!("{} - {} [{}/{}]", item.title, title, item.page, item.max_page));
        print_body(&self.rustbox,
                   self.body_width(),
                   2,
                   self.body_height(),
                   &item,
                   self.scrollY);
    }

    pub fn resetY(&mut self) {
        self.scrollY = 0;
    }

    pub fn scrollUp(&mut self, value: usize) -> bool {
        let tmp = self.scrollY;
        if tmp > value {
            self.scrollY = tmp - value;
            true
        } else if tmp != 0 {
            self.scrollY = 0;
            true
        } else {
            false
        }
    }

    pub fn scrollDown(&mut self, value: usize) -> bool {
        let tmp = self.scrollY;
        if tmp < 10000 {
            self.scrollY = tmp + value;
            return true;
        }
        false
    }

    pub fn body_height(&self) -> usize {
        if self.rustbox.height() >= 3 {
            self.rustbox.height() - 3
        } else {
            0
        }
    }

    pub fn body_width(&self) -> usize {
        if self.rustbox.width() >= 2 {
            self.rustbox.width() - 2
        } else {
            0
        }
    }
}

fn print_header(rustbox: &rustbox::RustBox, width: usize, text: &str) {
    let title_len = jks_len(text);
    let padding = (if width >= title_len {
        width - title_len
    } else {
        0
    }) / 2;

    let header_bottom = (0..width).map(|_| "─").collect::<Vec<_>>().join("");

    clearline(&rustbox, width, 0, 0);
    rustbox.print(padding,
                  0,
                  rustbox::RB_BOLD,
                  Color::White,
                  Color::Black,
                  text);
    rustbox.print(0,
                  1,
                  rustbox::RB_BOLD,
                  Color::Yellow,
                  Color::Black,
                  &header_bottom);
}

// fn display(vec: Vec<NodeType>, depth: u8) {
//     for (index, node) in vec.iter().enumerate() {
//         let node2 = node.clone();
//         let spaces = (0..depth).map(|_| ">").collect::<Vec<_>>().join("");
//         match node2 {
//             NodeType::Text(n) => {
//                 println!("{}[{}] = Text {}", spaces, index, n.data);
//             }
//             NodeType::Image(n) => {
//                 println!("{}[{}] = Image {}", spaces, index, n.data);
//             }
//             NodeType::BlockQuote(n) => {
//                 println!("{}[{}] = BlockQuote", spaces, index);
//                 display(n.data, depth + 1);
//             }
//             NodeType::Br(n) => {
//                 println!("{}[{}] = Br", spaces, index);
//             }
//         }
//     }
// }

fn print_body(rustbox: &rustbox::RustBox,
              width: usize,
              offset_y: usize,
              rows: usize,
              item: &ShowItem,
              scrollY: usize) {

    let mut y = offset_y;
    let replier_max_width = 14;
    let time_max_width = 5;
    let now = Local::now();

    let separator_width = if rustbox.width() >= 2 {
        rustbox.width() - 2
    } else {
        0
    };
    let separator_padding_width = if rustbox.width() > separator_width {
        rustbox.width() - separator_width
    } else {
        0
    } / 2;

    let separator_padding = (0..separator_padding_width).map(|_| " ").collect::<Vec<_>>().join("");

    let separator_bottom = make_separator_bottom(separator_width, &separator_padding);

    for (i, reply) in item.replies.iter().take(rows).enumerate() {

        let mut m = 0;

        for (j, node) in reply.body.iter().enumerate() {
            if scrollY + 1 < y + m {
                let node2 = node.clone();
                let spaces = (0..1).map(|_| ">").collect::<Vec<_>>().join("");
                let (node2_type, text) = match node2 {
                    NodeType::Text(n) => { ("Text", n.data) }
                    NodeType::Image(n) => { ("Image", n.data) }
                    NodeType::BlockQuote(n) => { ("BlockQuote", "".to_string()) }  // display(n.data, depth + 1);
                    NodeType::Br(n) => { ("Br", "".to_string()) }
                };
                rustbox.print(0,
                              j + y - scrollY,
                              rustbox::RB_NORMAL,
                              Color::White,
                              Color::Black,
                              &format!("{}[{}] = {} {}", spaces, j, node2_type, text));
            }
            m +=1;

        }
        // let contents: Vec<&str> = reply.content.split("\n").collect();
        //
        // for (j, content) in contents.iter().enumerate() {
        //     if scrollY + 1 < y + m {
        //
        //         rustbox.print(0,
        //                       j + y - scrollY,
        //                       rustbox::RB_NORMAL,
        //                       Color::White,
        //                       Color::Black,
        //                       &format!(" {}", content));
        //     }
        //     m += 1;
        // }

        if scrollY + 1 < y + m {

            let replier_name = reply.username.clone();

            let published_at = reply.published_at.clone();

            let published_at_dt = match Local.datetime_from_str(&published_at, "%d/%m/%Y %H:%M") {
                Ok(v) => v,
                Err(e) => now,
            };
            let time = published_at_format(&(now - published_at_dt));

            let separator_top = make_separator_top(separator_width,
                                                   &separator_padding,
                                                   replier_max_width,
                                                   &replier_name,
                                                   time_max_width,
                                                   &time);

            rustbox.print(0,
                          m + y - scrollY,
                          rustbox::RB_NORMAL,
                          Color::Green,
                          Color::Black,
                          &separator_top);
        }
        m += 1;

        if scrollY + 1 < y + m {
            rustbox.print(0,
                          m + y - scrollY,
                          rustbox::RB_NORMAL,
                          Color::Green,
                          Color::Black,
                          &separator_bottom);
        }
        m += 1;
        y += m;
    }
}

fn make_separator_replier_name(separator_width: usize,
                               separator_padding: &str,
                               replier_max_width: usize,
                               replier_name: &str)
                               -> String {
    let replier_name_len = jks_len(&replier_name);
    let replier_name_spacing_width = replier_max_width - replier_name_len;
    let is_replier_name_spacing_width_odd = replier_name_spacing_width & 1 == 1;
    let replier_name_right_spacing_width = replier_name_spacing_width / 2;
    let replier_name_left_spacing_width = if is_replier_name_spacing_width_odd {
        replier_name_right_spacing_width + 1
    } else {
        replier_name_right_spacing_width
    };

    let replier_name_left_spacing = (0..replier_name_left_spacing_width)
                                        .map(|_| "─")
                                        .collect::<Vec<_>>()
                                        .join("");

    let replier_name_right_spacing = (0..replier_name_right_spacing_width)
                                         .map(|_| "─")
                                         .collect::<Vec<_>>()
                                         .join("");

    let separator_replier = format!("{}{}{}{}{}",
                                    "╭",
                                    replier_name_left_spacing,
                                    replier_name,
                                    replier_name_right_spacing,
                                    "");

    return separator_replier;
}

fn make_separator_time(separator_width: usize,
                       separator_padding: &str,
                       time_max_width: usize,
                       time: &str)
                       -> String {
    let time_len = jks_len(&time);
    let time_spacing_width = if time_max_width > time_len {
        time_max_width - time_len
    } else {
        0
    };

    let is_time_spacing_width_odd = time_spacing_width & 1 == 1;
    let time_right_spacing_width = time_spacing_width / 2;
    let time_left_spacing_width = if is_time_spacing_width_odd {
        time_right_spacing_width + 1
    } else {
        time_right_spacing_width
    };

    let time_left_spacing = (0..time_left_spacing_width)
                                .map(|_| "─")
                                .collect::<Vec<_>>()
                                .join("");

    let time_right_spacing = (0..time_right_spacing_width)
                                 .map(|_| "─")
                                 .collect::<Vec<_>>()
                                 .join("");

    let separator_time = format!("{}{}{}{}{}",
                                 "",
                                 time_left_spacing,
                                 time,
                                 time_right_spacing,
                                 "╮");

    return separator_time;
}

fn make_separator_top(separator_width: usize,
                      separator_padding: &str,
                      replier_max_width: usize,
                      replier_name: &str,
                      time_max_width: usize,
                      time: &str)
                      -> String {

    let separator_replier = make_separator_replier_name(separator_width,
                                                        &separator_padding,
                                                        replier_max_width,
                                                        &replier_name);

    let separator_replier_width = jks_len(&separator_replier);

    let separator_time = make_separator_time(separator_width,
                                             &separator_padding,
                                             time_max_width,
                                             &time);

    let separator_time_width = jks_len(&separator_time);

    let separator_top_middle_width = if separator_width >=
                                        (separator_replier_width + separator_time_width) {
        separator_width - separator_replier_width - separator_time_width
    } else {
        0
    };

    let separator_top_middle = (0..separator_top_middle_width)
                                   .map(|_| " ")
                                   .collect::<Vec<_>>()
                                   .join("");

    let separator_top = format!("{}{}{}{}{}",
                                separator_padding,
                                separator_top_middle,
                                separator_replier,
                                separator_time,
                                separator_padding);
    return separator_top;
}

fn make_separator_bottom(separator_width: usize, separator_padding: &str) -> String {
    let style_box_width = 1;
    let separator_bottom_middle_width = if separator_width > style_box_width {
        separator_width - style_box_width
    } else {
        0
    };
    let separator_bottom_middle = (0..separator_bottom_middle_width)
                                      .map(|_| "─")
                                      .collect::<Vec<_>>()
                                      .join("");
    let separator_bottom = format!("{}{}{}{}",
                                   separator_padding,
                                   separator_bottom_middle,
                                   "╯",
                                   separator_padding);
    return separator_bottom;
}

fn published_at_format(duration: &Duration) -> String {
    let weeks = duration.num_weeks();
    let days = duration.num_days();
    let hours = duration.num_hours();
    let minutes = duration.num_minutes();

    if weeks > 0 {
        format!("{}w", weeks)
    } else if days > 0 {
        format!("{}d", days)
    } else if hours > 0 {
        format!("{}h", hours)
    } else if minutes > 0 {
        format!("{}m", minutes)
    } else {
        String::from("1m")
    }
}
<commit_msg>show - print replies correctly with blockqoutes<commit_after>extern crate rustbox;
extern crate chrono;

use rustbox::*;
use chrono::*;

use screen::common::*;
use utility::string::*;
use model::ShowItem;
use reply_model::*;

pub struct Show<'a> {
    rustbox: &'a rustbox::RustBox,
    scrollY: usize,
}

impl<'a> Show<'a> {
    pub fn new(rustbox: &'a rustbox::RustBox) -> Self {
        Show {
            rustbox: &rustbox,
            scrollY: 0,
        }
    }
    pub fn print(&mut self, title: &str, item: &ShowItem) {

        print_header(&self.rustbox,
                     self.rustbox.width(),
                     &format!("{} - {} [{}/{}]", item.title, title, item.page, item.max_page));
        print_body(&self.rustbox,
                   self.body_width(),
                   2,
                   self.body_height(),
                   &item,
                   self.scrollY);
    }

    pub fn resetY(&mut self) {
        self.scrollY = 0;
    }

    pub fn scrollUp(&mut self, value: usize) -> bool {
        let tmp = self.scrollY;
        if tmp > value {
            self.scrollY = tmp - value;
            true
        } else if tmp != 0 {
            self.scrollY = 0;
            true
        } else {
            false
        }
    }

    pub fn scrollDown(&mut self, value: usize) -> bool {
        let tmp = self.scrollY;
        if tmp < 10000 {
            self.scrollY = tmp + value;
            return true;
        }
        false
    }

    pub fn body_height(&self) -> usize {
        if self.rustbox.height() >= 3 {
            self.rustbox.height() - 3
        } else {
            0
        }
    }

    pub fn body_width(&self) -> usize {
        if self.rustbox.width() >= 2 {
            self.rustbox.width() - 2
        } else {
            0
        }
    }
}

fn print_header(rustbox: &rustbox::RustBox, width: usize, text: &str) {
    let title_len = jks_len(text);
    let padding = (if width >= title_len {
        width - title_len
    } else {
        0
    }) / 2;

    let header_bottom = (0..width).map(|_| "─").collect::<Vec<_>>().join("");

    clearline(&rustbox, width, 0, 0);
    rustbox.print(padding,
                  0,
                  rustbox::RB_BOLD,
                  Color::White,
                  Color::Black,
                  text);
    rustbox.print(0,
                  1,
                  rustbox::RB_BOLD,
                  Color::Yellow,
                  Color::Black,
                  &header_bottom);
}

// fn display(vec: Vec<NodeType>, depth: u8) {
//     for (index, node) in vec.iter().enumerate() {
//         let node2 = node.clone();
//         let spaces = (0..depth).map(|_| ">").collect::<Vec<_>>().join("");
//         match node2 {
//             NodeType::Text(n) => {
//                 println!("{}[{}] = Text {}", spaces, index, n.data);
//             }
//             NodeType::Image(n) => {
//                 println!("{}[{}] = Image {}", spaces, index, n.data);
//             }
//             NodeType::BlockQuote(n) => {
//                 println!("{}[{}] = BlockQuote", spaces, index);
//                 display(n.data, depth + 1);
//             }
//             NodeType::Br(n) => {
//                 println!("{}[{}] = Br", spaces, index);
//             }
//         }
//     }
// }

fn print_default(rustbox: &rustbox::RustBox, x: usize, y: usize, s: String){
    rustbox.print(0,
                  y,
                  rustbox::RB_NORMAL,
                  Color::White,
                  Color::Black,
                  &s);
}

fn print_reply(vec: &Vec<NodeType>, depth: u8, scrollY: usize, y: usize, rustbox: &rustbox::RustBox) -> usize {
    let mut m = 0; let mut o = 0;
    for (j, node) in vec.iter().enumerate() {
        if scrollY + 1 < y + m + o {

            let node2 = node.clone();
            let spaces = (0..depth).map(|_| ">").collect::<Vec<_>>().join("");
            match node2 {
                NodeType::Text(n) => { print_default(rustbox, 0, j + y + o - scrollY, format!("{}[{}] = Text {}", spaces, j, n.data)) }
                NodeType::Image(n) => { print_default(rustbox, 0, j + y + o - scrollY, format!("{}[{}] = Image {}", spaces, j, n.data)) }
                NodeType::BlockQuote(n) => { print_default(rustbox, 0, j + y + o - scrollY, format!("{}[{}] = BlockQuote", spaces, j)); o+= print_reply(&n.data, depth + 1, scrollY, y + m + o + 1, &rustbox); }
                NodeType::Br(n) => { print_default(rustbox, 0, j + y + o - scrollY, format!("{}[{}] = Br", spaces, j)) }
            }
        }

        m +=1;
    }
    m + o
}

fn print_body(rustbox: &rustbox::RustBox,
              width: usize,
              offset_y: usize,
              rows: usize,
              item: &ShowItem,
              scrollY: usize) {

    let mut y = offset_y;
    let replier_max_width = 14;
    let time_max_width = 5;
    let now = Local::now();

    let separator_width = if rustbox.width() >= 2 {
        rustbox.width() - 2
    } else {
        0
    };
    let separator_padding_width = if rustbox.width() > separator_width {
        rustbox.width() - separator_width
    } else {
        0
    } / 2;

    let separator_padding = (0..separator_padding_width).map(|_| " ").collect::<Vec<_>>().join("");

    let separator_bottom = make_separator_bottom(separator_width, &separator_padding);

    for (i, reply) in item.replies.iter().take(rows).enumerate() {

        let mut m = print_reply(&reply.body, 0, scrollY, y, &rustbox);

        if scrollY + 1 < y + m {

            let replier_name = reply.username.clone();

            let published_at = reply.published_at.clone();

            let published_at_dt = match Local.datetime_from_str(&published_at, "%d/%m/%Y %H:%M") {
                Ok(v) => v,
                Err(e) => now,
            };
            let time = published_at_format(&(now - published_at_dt));

            let separator_top = make_separator_top(separator_width,
                                                   &separator_padding,
                                                   replier_max_width,
                                                   &replier_name,
                                                   time_max_width,
                                                   &time);

            rustbox.print(0,
                          m + y - scrollY,
                          rustbox::RB_NORMAL,
                          Color::Green,
                          Color::Black,
                          &separator_top);
        }
        m += 1;

        if scrollY + 1 < y + m {
            rustbox.print(0,
                          m + y - scrollY,
                          rustbox::RB_NORMAL,
                          Color::Green,
                          Color::Black,
                          &separator_bottom);
        }
        m += 1;
        y += m;
    }
}

fn make_separator_replier_name(separator_width: usize,
                               separator_padding: &str,
                               replier_max_width: usize,
                               replier_name: &str)
                               -> String {
    let replier_name_len = jks_len(&replier_name);
    let replier_name_spacing_width = replier_max_width - replier_name_len;
    let is_replier_name_spacing_width_odd = replier_name_spacing_width & 1 == 1;
    let replier_name_right_spacing_width = replier_name_spacing_width / 2;
    let replier_name_left_spacing_width = if is_replier_name_spacing_width_odd {
        replier_name_right_spacing_width + 1
    } else {
        replier_name_right_spacing_width
    };

    let replier_name_left_spacing = (0..replier_name_left_spacing_width)
                                        .map(|_| "─")
                                        .collect::<Vec<_>>()
                                        .join("");

    let replier_name_right_spacing = (0..replier_name_right_spacing_width)
                                         .map(|_| "─")
                                         .collect::<Vec<_>>()
                                         .join("");

    let separator_replier = format!("{}{}{}{}{}",
                                    "╭",
                                    replier_name_left_spacing,
                                    replier_name,
                                    replier_name_right_spacing,
                                    "");

    return separator_replier;
}

fn make_separator_time(separator_width: usize,
                       separator_padding: &str,
                       time_max_width: usize,
                       time: &str)
                       -> String {
    let time_len = jks_len(&time);
    let time_spacing_width = if time_max_width > time_len {
        time_max_width - time_len
    } else {
        0
    };

    let is_time_spacing_width_odd = time_spacing_width & 1 == 1;
    let time_right_spacing_width = time_spacing_width / 2;
    let time_left_spacing_width = if is_time_spacing_width_odd {
        time_right_spacing_width + 1
    } else {
        time_right_spacing_width
    };

    let time_left_spacing = (0..time_left_spacing_width)
                                .map(|_| "─")
                                .collect::<Vec<_>>()
                                .join("");

    let time_right_spacing = (0..time_right_spacing_width)
                                 .map(|_| "─")
                                 .collect::<Vec<_>>()
                                 .join("");

    let separator_time = format!("{}{}{}{}{}",
                                 "",
                                 time_left_spacing,
                                 time,
                                 time_right_spacing,
                                 "╮");

    return separator_time;
}

fn make_separator_top(separator_width: usize,
                      separator_padding: &str,
                      replier_max_width: usize,
                      replier_name: &str,
                      time_max_width: usize,
                      time: &str)
                      -> String {

    let separator_replier = make_separator_replier_name(separator_width,
                                                        &separator_padding,
                                                        replier_max_width,
                                                        &replier_name);

    let separator_replier_width = jks_len(&separator_replier);

    let separator_time = make_separator_time(separator_width,
                                             &separator_padding,
                                             time_max_width,
                                             &time);

    let separator_time_width = jks_len(&separator_time);

    let separator_top_middle_width = if separator_width >=
                                        (separator_replier_width + separator_time_width) {
        separator_width - separator_replier_width - separator_time_width
    } else {
        0
    };

    let separator_top_middle = (0..separator_top_middle_width)
                                   .map(|_| " ")
                                   .collect::<Vec<_>>()
                                   .join("");

    let separator_top = format!("{}{}{}{}{}",
                                separator_padding,
                                separator_top_middle,
                                separator_replier,
                                separator_time,
                                separator_padding);
    return separator_top;
}

fn make_separator_bottom(separator_width: usize, separator_padding: &str) -> String {
    let style_box_width = 1;
    let separator_bottom_middle_width = if separator_width > style_box_width {
        separator_width - style_box_width
    } else {
        0
    };
    let separator_bottom_middle = (0..separator_bottom_middle_width)
                                      .map(|_| "─")
                                      .collect::<Vec<_>>()
                                      .join("");
    let separator_bottom = format!("{}{}{}{}",
                                   separator_padding,
                                   separator_bottom_middle,
                                   "╯",
                                   separator_padding);
    return separator_bottom;
}

fn published_at_format(duration: &Duration) -> String {
    let weeks = duration.num_weeks();
    let days = duration.num_days();
    let hours = duration.num_hours();
    let minutes = duration.num_minutes();

    if weeks > 0 {
        format!("{}w", weeks)
    } else if days > 0 {
        format!("{}d", days)
    } else if hours > 0 {
        format!("{}h", hours)
    } else if minutes > 0 {
        format!("{}m", minutes)
    } else {
        String::from("1m")
    }
}
<|endoftext|>


<commit_before>#![cfg(not(windows))]

use screen::Key;
use screen::Key::*;
use std::io::{Read, Write};
use std::fs::{File, OpenOptions};
use std::os::unix::io::AsRawFd;
use std::path::*;
use std::process::Command;
use std::process::Stdio;
use std::iter::repeat;
use std::cmp::min;
use ansi;
use ::NEWLINE;

use std::thread;
use std::sync::mpsc::Receiver;
use std::sync::mpsc;

#[allow(non_camel_case_types)] type c_ushort = u16;
#[allow(non_camel_case_types)] type c_int = i32;
#[allow(non_camel_case_types)] type c_ulong = u64;

pub struct Screen {
    tty: Terminal,
    original_stty_state: Vec<u8>,
    pub height: u16,
    pub width: u16,
    pub visible_choices: u16,
    start_line: u16,
}

impl Screen {
    pub fn open_screen(desired_rows: u16) -> Screen {
        let mut tty = Terminal::open_terminal();
        let current_stty_state = tty.stty(&["-g"]);
        tty.initialize();
        let (cols, rows) = tty.winsize().unwrap();
        let visible_choices = min(desired_rows, rows - 1);
        let start_line = rows - visible_choices - 1;
        for _ in 0..visible_choices {
            tty.write(NEWLINE.as_bytes());
        }
        tty.write(&ansi::save_cursor());

        Screen {
            tty: tty,
            original_stty_state: current_stty_state,
            height: rows,
            width: cols,
            visible_choices: visible_choices,
            start_line: start_line,
        }
    }

    fn restore_tty(&mut self) {
        self.tty.stty(&[&String::from_utf8(self.original_stty_state.clone()).unwrap()]);
    }

    fn reset_cursor(&mut self) {
        self.tty.write(&ansi::restore_cursor());

        // Writing this carriage return works around a rendering bug in
        // Neovim's terminal emulation. Without it, the cursor flies
        // around all over the place while typing, and the input prompt
        // is not rendered correctly. I have no idea why this workaround
        // is effective, or why others are not, or what the root cause
        // of the issue is (likely something involving save/restore
        // support).
        self.tty.write("\r".as_bytes());

        self.tty.write(&ansi::cursor_up(self.visible_choices));
    }

    pub fn move_cursor_to_prompt_line(&mut self, col: u16) {
        self.reset_cursor();
        self.tty.write(&ansi::cursor_right(col));
    }

    pub fn blank_screen(&mut self) {
        self.reset_cursor();
        let blank_line = repeat(' ').take(self.width as usize).collect::<String>();
        for _ in 0..self.visible_choices + 1 {
            self.tty.write(blank_line.as_bytes());
        }
        self.reset_cursor();
    }

    pub fn show_cursor(&mut self) {
        self.tty.write(&ansi::show_cursor());
    }

    pub fn hide_cursor(&mut self) {
        self.tty.write(&ansi::hide_cursor());
    }

    pub fn write(&mut self, s: &str) {
        self.tty.write(s.as_bytes());
    }

    pub fn write_red_inverted(&mut self, s: &str) {
        self.tty.write(&ansi::red());
        self.tty.write(&ansi::inverse());
        self.tty.write(s.as_bytes());
        self.tty.write(&ansi::reset());
    }

    pub fn write_red(&mut self, s: &str) {
        self.tty.write(&ansi::red());
        self.tty.write(s.as_bytes());
        self.tty.write(&ansi::reset());
    }

    pub fn write_inverted(&mut self, s: &str) {
        self.tty.write(&ansi::inverse());
        self.tty.write(s.as_bytes());
        self.tty.write(&ansi::reset());
    }

    // Return all buffered keystrokes, or the next key if buffer is empty.
    pub fn get_buffered_keys(&mut self) -> Vec<Key> {
        let mut ret = Vec::new();
        while let Ok(byte) = self.tty.input.try_recv() {
            ret.push(byte);
        }
        while ret.is_empty() || Screen::more_bytes_needed(&ret) {
            let byte = self.tty.input.recv().unwrap();
            if byte == 0 {
                self.tty.initialize();
                return vec![Nothing];
            } else {
                ret.push(byte);
            }
        }
        Terminal::translate_bytes(ret.clone())
    }

    fn more_bytes_needed(bytes: &Vec<u8>) -> bool {
        if let Err(_) = String::from_utf8(bytes.clone()) {
            return true;
        }
        false
    }
}

impl Drop for Screen {
    fn drop(&mut self) {
        self.restore_tty();
    }
}

struct Terminal {
    input: Receiver<u8>,
    input_fd: i32,
    output: File,
}

impl Terminal {
    fn open_terminal() -> Terminal {
        let term_path = Path::new("/dev/tty");
        let mut input_file = File::open(&term_path).unwrap();
        let output_file = OpenOptions::new().write(true).open(&term_path).unwrap();
        let input_fd = input_file.as_raw_fd();
        let (tx, rx) = mpsc::channel();
        thread::spawn(move || {
            loop {
                let mut buf = [0];
                let result = input_file.read(&mut buf);
                if result.is_ok() {
                    tx.send(buf[0]).unwrap();
                } else {
                    tx.send(0u8).unwrap();
                }
            }
        });
        Terminal {
            input: rx,
            input_fd: input_fd,
            output: output_file,
        }
    }

    fn initialize(&mut self) {
        self.stty(&["raw", "-echo", "cbreak", "opost", "onlcr"]);
    }

    fn stty(&mut self, args: &[&str]) -> Vec<u8> {
        extern { fn dup2(src: c_int, dst: c_int) -> c_int;  }
        unsafe {
            // This is a hack until a replacement for InheritFd from old_io is available.
            dup2(self.input_fd, 0);
        }
        let mut process = match Command::new("stty")
            .args(args)
            .stdin(Stdio::inherit())
            .stdout(Stdio::piped())
            .spawn()
        {
            Ok(p) => p,
            Err(e) => panic!("Spawn failed: {}", e),
        };

        let exit = process.wait();

        let mut buf = Vec::new();
        if exit.unwrap().success() {
            process.stdout.as_mut().unwrap().read_to_end(&mut buf).unwrap();
            let mut str = String::from_utf8(buf).unwrap();

            // The output from `stty -g` may include a newline, which we have to strip off. Otherwise,
            // when we go to restore the tty, stty (on some platforms) will fail with an "invalid
            // argument" error.
            ::trim(&mut str);

            str.into_bytes()
        } else {
            process.stderr.as_mut().unwrap().read_to_end(&mut buf).unwrap();
            panic!(String::from_utf8(buf).unwrap());
        }
    }

    fn translate_bytes(bytes: Vec<u8>) -> Vec<Key> {
        let chars = String::from_utf8(bytes).unwrap().chars().collect::<Vec<char>>();
        chars.into_iter().map(|c| Terminal::translate_char(c)).collect()
    }

    fn translate_char(c: char) -> Key {
        let numeric_char = c as u32;
        if c == '\r' {
            Enter
        } else if numeric_char == 9 {
            Tab
        } else if numeric_char == 127 {
            Backspace
        } else if numeric_char & 96 == 0 && numeric_char <= 128u32 {
            let c = c as u8;
            Control((c + 96u8) as char)
        } else {
            Char(c)
        }
    }

    fn write(&mut self, s: &[u8]) {
        self.output.write_all(&s).unwrap();
    }

    fn writeln(&mut self, s: &str) {
        self.output.write(s.as_bytes()).unwrap();
        self.output.write("\n".as_bytes()).unwrap();
    }

    fn winsize(&self) -> Option<(u16, u16)> {
        extern { fn ioctl(fd: c_int, request: c_ulong, ...) -> c_int; }
        #[cfg(any(target_os = "macos", target_os = "freebsd"))]
        const TIOCGWINSZ: c_ulong = 0x40087468;

        #[cfg(any(target_os = "linux", target_os = "android"))]
        const TIOCGWINSZ: c_ulong = 0x00005413;

        #[repr(C)]
        struct TermSize {
            rows: c_ushort,
            cols: c_ushort,
            x: c_ushort,
            y: c_ushort,
        }

        let size = TermSize { rows: 0, cols: 0, x: 0, y: 0 };
        if unsafe { ioctl(self.output.as_raw_fd(), TIOCGWINSZ, &size) } == 0 {
            Some((size.cols as u16, size.rows as u16))
        } else {
            None
        }
    }
}

#[test]
fn winsize_test() {
    // Travis-CI builds run without a tty, making this test impossible.
    if option_env!("TRAVIS").is_some() {
        // TODO: It should be made obvious from the output that this test was skipped
        return;
    }
    let term = Terminal::open_terminal();
    let (cols, rows) = term.winsize().expect("Failed to get window size!");
    // We don't know the window size a priori, but we can at least
    // assert that it is within some kind of sensible range.
    assert!(cols > 40);
    assert!(rows > 40);
    assert!(cols < 1000);
    assert!(rows < 1000);
}
<commit_msg>Improve input buffering<commit_after>#![cfg(not(windows))]

use screen::Key;
use screen::Key::*;
use std::io::{Read, Write};
use std::fs::{File, OpenOptions};
use std::os::unix::io::AsRawFd;
use std::path::*;
use std::process::Command;
use std::process::Stdio;
use std::iter::repeat;
use std::cmp::min;
use ansi;
use ::NEWLINE;

use std::thread;
use std::sync::mpsc::Receiver;
use std::sync::mpsc;

#[allow(non_camel_case_types)] type c_ushort = u16;
#[allow(non_camel_case_types)] type c_int = i32;
#[allow(non_camel_case_types)] type c_ulong = u64;

pub struct Screen {
    tty: Terminal,
    original_stty_state: Vec<u8>,
    pub height: u16,
    pub width: u16,
    pub visible_choices: u16,
    start_line: u16,
}

impl Screen {
    pub fn open_screen(desired_rows: u16) -> Screen {
        let mut tty = Terminal::open_terminal();
        let current_stty_state = tty.stty(&["-g"]);
        tty.initialize();
        let (cols, rows) = tty.winsize().unwrap();
        let visible_choices = min(desired_rows, rows - 1);
        let start_line = rows - visible_choices - 1;
        for _ in 0..visible_choices {
            tty.write(NEWLINE.as_bytes());
        }
        tty.write(&ansi::save_cursor());

        Screen {
            tty: tty,
            original_stty_state: current_stty_state,
            height: rows,
            width: cols,
            visible_choices: visible_choices,
            start_line: start_line,
        }
    }

    fn restore_tty(&mut self) {
        self.tty.stty(&[&String::from_utf8(self.original_stty_state.clone()).unwrap()]);
    }

    fn reset_cursor(&mut self) {
        self.tty.write(&ansi::restore_cursor());

        // Writing this carriage return works around a rendering bug in
        // Neovim's terminal emulation. Without it, the cursor flies
        // around all over the place while typing, and the input prompt
        // is not rendered correctly. I have no idea why this workaround
        // is effective, or why others are not, or what the root cause
        // of the issue is (likely something involving save/restore
        // support).
        self.tty.write("\r".as_bytes());

        self.tty.write(&ansi::cursor_up(self.visible_choices));
    }

    pub fn move_cursor_to_prompt_line(&mut self, col: u16) {
        self.reset_cursor();
        self.tty.write(&ansi::cursor_right(col));
    }

    pub fn blank_screen(&mut self) {
        self.reset_cursor();
        let blank_line = repeat(' ').take(self.width as usize).collect::<String>();
        for _ in 0..self.visible_choices + 1 {
            self.tty.write(blank_line.as_bytes());
        }
        self.reset_cursor();
    }

    pub fn show_cursor(&mut self) {
        self.tty.write(&ansi::show_cursor());
    }

    pub fn hide_cursor(&mut self) {
        self.tty.write(&ansi::hide_cursor());
    }

    pub fn write(&mut self, s: &str) {
        self.tty.write(s.as_bytes());
    }

    pub fn write_red_inverted(&mut self, s: &str) {
        self.tty.write(&ansi::red());
        self.tty.write(&ansi::inverse());
        self.tty.write(s.as_bytes());
        self.tty.write(&ansi::reset());
    }

    pub fn write_red(&mut self, s: &str) {
        self.tty.write(&ansi::red());
        self.tty.write(s.as_bytes());
        self.tty.write(&ansi::reset());
    }

    pub fn write_inverted(&mut self, s: &str) {
        self.tty.write(&ansi::inverse());
        self.tty.write(s.as_bytes());
        self.tty.write(&ansi::reset());
    }

    // Return all buffered keystrokes, or the next key if buffer is empty.
    pub fn get_buffered_keys(&mut self) -> Vec<Key> {
        let mut ret = Vec::new();
        while let Ok(bytes) = self.tty.input.try_recv() {
            ret.extend(bytes);
        }
        while ret.is_empty() {
            let bytes = self.tty.input.recv().unwrap();
            if bytes.len() == 1 && bytes[0] == 0 {
                self.tty.initialize();
                return vec![Nothing];
            } else {
                ret.extend(bytes);
            }
        }
        Terminal::translate_bytes(ret.clone())
    }

    fn more_bytes_needed(bytes: &Vec<u8>) -> bool {
        if let Err(_) = String::from_utf8(bytes.clone()) {
            return true;
        }
        false
    }
}

impl Drop for Screen {
    fn drop(&mut self) {
        self.restore_tty();
    }
}

struct Terminal {
    input: Receiver<Vec<u8>>,
    input_fd: i32,
    output: File,
}

impl Terminal {
    fn open_terminal() -> Terminal {
        let term_path = Path::new("/dev/tty");
        let mut input_file = File::open(&term_path).unwrap();
        let output_file = OpenOptions::new().write(true).open(&term_path).unwrap();
        let input_fd = input_file.as_raw_fd();
        let (tx, rx) = mpsc::channel();
        thread::spawn(move || {
            loop {
                let mut buf = [0; 255];
                if let Ok(length) = input_file.read(&mut buf) {
                    tx.send(buf[0..length].to_vec()).unwrap();
                } else {
                    tx.send([0].to_vec()).unwrap();
                }
            }
        });
        Terminal {
            input: rx,
            input_fd: input_fd,
            output: output_file,
        }
    }

    fn initialize(&mut self) {
        self.stty(&["raw", "-echo", "cbreak", "opost", "onlcr"]);
    }

    fn stty(&mut self, args: &[&str]) -> Vec<u8> {
        extern { fn dup2(src: c_int, dst: c_int) -> c_int;  }
        unsafe {
            // This is a hack until a replacement for InheritFd from old_io is available.
            dup2(self.input_fd, 0);
        }
        let mut process = match Command::new("stty")
            .args(args)
            .stdin(Stdio::inherit())
            .stdout(Stdio::piped())
            .spawn()
        {
            Ok(p) => p,
            Err(e) => panic!("Spawn failed: {}", e),
        };

        let exit = process.wait();

        let mut buf = Vec::new();
        if exit.unwrap().success() {
            process.stdout.as_mut().unwrap().read_to_end(&mut buf).unwrap();
            let mut str = String::from_utf8(buf).unwrap();

            // The output from `stty -g` may include a newline, which we have to strip off. Otherwise,
            // when we go to restore the tty, stty (on some platforms) will fail with an "invalid
            // argument" error.
            ::trim(&mut str);

            str.into_bytes()
        } else {
            process.stderr.as_mut().unwrap().read_to_end(&mut buf).unwrap();
            panic!(String::from_utf8(buf).unwrap());
        }
    }

    fn translate_bytes(bytes: Vec<u8>) -> Vec<Key> {
        let chars = String::from_utf8(bytes).unwrap().chars().collect::<Vec<char>>();
        chars.into_iter().map(|c| Terminal::translate_char(c)).collect()
    }

    fn translate_char(c: char) -> Key {
        let numeric_char = c as u32;
        if c == '\r' {
            Enter
        } else if numeric_char == 9 {
            Tab
        } else if numeric_char == 127 {
            Backspace
        } else if numeric_char & 96 == 0 && numeric_char <= 128u32 {
            let c = c as u8;
            Control((c + 96u8) as char)
        } else {
            Char(c)
        }
    }

    fn write(&mut self, s: &[u8]) {
        self.output.write_all(&s).unwrap();
    }

    fn writeln(&mut self, s: &str) {
        self.output.write(s.as_bytes()).unwrap();
        self.output.write("\n".as_bytes()).unwrap();
    }

    fn winsize(&self) -> Option<(u16, u16)> {
        extern { fn ioctl(fd: c_int, request: c_ulong, ...) -> c_int; }
        #[cfg(any(target_os = "macos", target_os = "freebsd"))]
        const TIOCGWINSZ: c_ulong = 0x40087468;

        #[cfg(any(target_os = "linux", target_os = "android"))]
        const TIOCGWINSZ: c_ulong = 0x00005413;

        #[repr(C)]
        struct TermSize {
            rows: c_ushort,
            cols: c_ushort,
            x: c_ushort,
            y: c_ushort,
        }

        let size = TermSize { rows: 0, cols: 0, x: 0, y: 0 };
        if unsafe { ioctl(self.output.as_raw_fd(), TIOCGWINSZ, &size) } == 0 {
            Some((size.cols as u16, size.rows as u16))
        } else {
            None
        }
    }
}

#[test]
fn winsize_test() {
    // Travis-CI builds run without a tty, making this test impossible.
    if option_env!("TRAVIS").is_some() {
        // TODO: It should be made obvious from the output that this test was skipped
        return;
    }
    let term = Terminal::open_terminal();
    let (cols, rows) = term.winsize().expect("Failed to get window size!");
    // We don't know the window size a priori, but we can at least
    // assert that it is within some kind of sensible range.
    assert!(cols > 40);
    assert!(rows > 40);
    assert!(cols < 1000);
    assert!(rows < 1000);
}
<|endoftext|>


<commit_before>//! Integration with the [Iron](https://ironframework.io) framework, enabled with the `iron` feature (optional). Includes a `BeforeMiddleware` implementation.
//!
//! Not shown here: `impl `[`HttpRequest`](../trait.HttpRequest.html#implementors)` for
//! iron::Request`.

use iron::headers::ContentType;
use iron::mime::{Mime, TopLevel, SubLevel};
use iron::request::{Body as IronBody, Request as IronRequest};
use iron::typemap::Key;
use iron::{BeforeMiddleware, IronError, IronResult};

use std::path::PathBuf;
use std::{error, fmt};

use super::{Entries, HttpRequest, Multipart, MultipartData};

impl<'r, 'a, 'b> HttpRequest for &'r mut IronRequest<'a, 'b> {
    type Body = &'r mut IronBody<'a, 'b>;

    fn multipart_boundary(&self) -> Option<&str> {
        let content_type = try_opt!(self.headers.get::<ContentType>());
        if let Mime(TopLevel::Multipart, SubLevel::FormData, _) = **content_type {
            content_type.get_param("boundary").map(|b| b.as_str())
        } else {
            None
        }
    }

    fn body(self) -> &'r mut IronBody<'a, 'b> {
        &mut self.body
    }
}

/// The default file size limit for [`Intercept`](struct.Intercept.html), in bytes.
pub const DEFAULT_FILE_SIZE_LIMIT: u64 = 2 * 1024 * 1024;

/// The default file count limit for [`Intercept`](struct.Intercept.html).
pub const DEFAULT_FILE_COUNT_LIMIT: u32 = 16;

/// A `BeforeMiddleware` for Iron which will intercept and read-out multipart requests and store
/// the result in the request.
///
/// Successful reads will be placed in the `extensions: TypeMap` field of `iron::Request` as an
/// [`Entries`](../struct.Entries.html) instance (as both key-type and value):
///
/// ```no_run
/// extern crate iron;
/// extern crate multipart;
///
/// use iron::prelude::*;
///
/// use multipart::server::Entries;
/// use multipart::server::iron::Intercept;
///
/// fn main() {
///     let mut chain = Chain::new(|req: &mut Request| if let Some(entries) =
///         req.extensions.get::<Entries>() {
///
///         Ok(Response::with(format!("{:?}", entries)))
///     } else {
///         Ok(Response::with("Not a multipart request"))
///     });
///
///     chain.link_before(Intercept::default());
///
///     Iron::new(chain).http("localhost:80").unwrap();
/// }
/// ```
///
/// Any errors during which occur during reading will be passed on as `IronError`.
#[derive(Debug)]
pub struct Intercept {
    /// The parent directory for all temporary directories created by this middleware.
    /// Will be created if it doesn't exist (lazy).
    ///
    /// If omitted, uses the OS temporary directory.
    ///
    /// Default value: `None`.
    pub temp_dir_path: Option<PathBuf>,
    /// The size limit of uploaded files, in bytes.
    ///
    /// Files which exceed this size will be rejected.
    /// See the `limit_behavior` field for more info.
    ///
    /// Default value: [`DEFAULT_FILE_SIZE_LIMIT`](constant.default_file_size_limit.html)
    pub file_size_limit: u64,
    /// The limit on the number of files which will be saved from
    /// the request. Requests which exceed this count will be rejected.
    ///
    /// Default value: [`DEFAULT_FILE_COUNT_LIMT`](constant.default_file_count_limit.html)
    pub file_count_limit: u32,
    /// What to do when a file count or size limit has been exceeded.
    ///
    /// See [`LimitBehavior`](enum.limitbehavior.html) for more info.
    pub limit_behavior: LimitBehavior,
}

impl Intercept {
    /// Set the `temp_dir_path` for this middleware.
    pub fn temp_dir_path<P: Into<PathBuf>>(self, path: P) -> Self {
        Intercept { temp_dir_path: Some(path.into()), .. self }
    }

    /// Set the `file_size_limit` for this middleware.
    pub fn file_size_limit(self, limit: u64) -> Self {
        Intercept { file_size_limit: limit, .. self }
    }

    /// Set the `file_count_limit` for this middleware.
    pub fn file_count_limit(self, limit: u32) -> Self {
        Intercept { file_count_limit: limit, .. self }
    }

    /// Set the `limit_behavior` for this middleware.
    pub fn limit_behavior(self, behavior: LimitBehavior) -> Self {
        Intercept { limit_behavior: behavior, .. self }
    }

    fn read_request(&self, req: &mut IronRequest) -> IronResult<Option<Entries>> {
        macro_rules! try_iron(
            ($try:expr; $($fmt_args:tt)+) => (
                match $try {
                    Ok(ok) => ok,
                    Err(err) => return Err(IronError::new(err, format!($($fmt_args)*))),
                }
            );
            ($try:expr) => (
                try_iron!($try; "");
            )
        );

        let mut multipart = match Multipart::from_request(req) {
            Ok(multipart) => multipart,
            Err(_) => return Ok(None),
        };

        let mut entries = try_iron!(
            self.temp_dir_path.as_ref()
                .map_or_else(Entries::new_tempdir, Entries::new_tempdir_in);
            "Error opening temporary directory for request."
        );

        let mut file_count = 0;

        while let Some(field) = try_iron!(multipart.read_entry()) {
            match field.data {
                MultipartData::File(mut file) => {
                    if self.limit_behavior.throw_error() && file_count >= self.file_count_limit {
                        return Err(FileCountLimitError(self.file_count_limit).into());
                    }

                    let file = try_iron!(
                        file.save_in_limited(&entries.dir, self.file_size_limit);
                        "Error reading field: \"{}\" (filename: \"{}\")",
                        field.name,
                        file.filename.as_ref().map_or("(none)", String::as_ref)
                    );

                    if file.size == self.file_size_limit {
                        if self.limit_behavior.throw_error() {
                            return Err(FileSizeLimitError::new(field.name, file.filename).into());
                        } else {
                            warn!(
                                "File size limit reached for field {:?} (filename: {:?})",
                                field.name, file.filename
                            );
                        }
                    }

                    entries.files.insert(field.name, file);
                    file_count += 1;
                },
                MultipartData::Text(text) => {
                    entries.fields.insert(field.name, text.into());
                },
            }
        }

        Ok(Some(entries))
    }
}

impl Default for Intercept {
    fn default() -> Self {
        Intercept {
            temp_dir_path: None,
            file_size_limit: DEFAULT_FILE_SIZE_LIMIT,
            file_count_limit: DEFAULT_FILE_COUNT_LIMIT,
            limit_behavior: LimitBehavior::ThrowError,
        }
    }
}

impl BeforeMiddleware for Intercept {
    fn before(&self, req: &mut IronRequest) -> IronResult<()> {
        try!(self.read_request(req))
            .map(|entries| req.extensions.insert::<Entries>(entries));

        Ok(())
    }
}

impl Key for Entries {
    type Value = Self;
}

/// The behavior of `Intercept` when a file size or count limit is exceeded.
#[derive(Clone, Copy, Debug)]
#[repr(u32)]
pub enum LimitBehavior {
    /// Return an error from the middleware describing the issue.
    ThrowError,
    /// Ignore the limit.
    ///
    /// In the case of file size limits, the offending file will be truncated
    /// in the result.
    ///
    /// In the case of file count limits, the request will be completed.
    Continue,
}

impl LimitBehavior {
    fn throw_error(self) -> bool {
        use self::LimitBehavior::*;

        match self {
            ThrowError => true,
            Continue => false,
        }
    }
}

/// An error returned from `Intercept` when the size limit
/// for an individual file is exceeded.
#[derive(Debug)]
pub struct FileSizeLimitError {
    /// The field where the error occurred.
    pub field: String,
    /// The filename of the oversize file, if it was provided.
    pub filename: Option<String>,
}

impl FileSizeLimitError {
    fn new(field: String, filename: Option<String>) -> Self {
        FileSizeLimitError {
            field: field,
            filename: filename
        }
    }
}

impl error::Error for FileSizeLimitError {
    fn description(&self) -> &str {
        "file size limit reached"
    }
}

impl fmt::Display for FileSizeLimitError {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        match self.filename {
            Some(ref filename) => write!(f, "File size limit reached for field \"{}\" (filename: \"{}\")", self.field, filename),
            None => write!(f, "File size limit reached for field \"{}\" (no filename)", self.field),
        }
    }
}

impl Into<IronError> for FileSizeLimitError {
    fn into(self) -> IronError {
        let desc_str = self.to_string();
        IronError::new(self, desc_str)
    }
}

/// An error returned from `Intercept` when the file count limit
/// for a single request was exceeded.
#[derive(Debug)]
pub struct FileCountLimitError(u32);

impl error::Error for FileCountLimitError {
    fn description(&self) -> &str {
        "file count limit reached"
    }
}

impl fmt::Display for FileCountLimitError {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f, "File count limit reached for request. Limit: {}", self.0)
    }
}

impl Into<IronError> for FileCountLimitError {
    fn into(self) -> IronError {
        let desc_string = self.to_string();
        IronError::new(self, desc_string)
    }
}
<commit_msg>Fix Iron integration assuming access to a private field of MultipartField<commit_after>//! Integration with the [Iron](https://ironframework.io) framework, enabled with the `iron` feature (optional). Includes a `BeforeMiddleware` implementation.
//!
//! Not shown here: `impl `[`HttpRequest`](../trait.HttpRequest.html#implementors)` for
//! iron::Request`.

use iron::headers::ContentType;
use iron::mime::{Mime, TopLevel, SubLevel};
use iron::request::{Body as IronBody, Request as IronRequest};
use iron::typemap::Key;
use iron::{BeforeMiddleware, IronError, IronResult};

use std::path::PathBuf;
use std::{error, fmt};

use super::{Entries, HttpRequest, Multipart, MultipartData};

impl<'r, 'a, 'b> HttpRequest for &'r mut IronRequest<'a, 'b> {
    type Body = &'r mut IronBody<'a, 'b>;

    fn multipart_boundary(&self) -> Option<&str> {
        let content_type = try_opt!(self.headers.get::<ContentType>());
        if let Mime(TopLevel::Multipart, SubLevel::FormData, _) = **content_type {
            content_type.get_param("boundary").map(|b| b.as_str())
        } else {
            None
        }
    }

    fn body(self) -> &'r mut IronBody<'a, 'b> {
        &mut self.body
    }
}

/// The default file size limit for [`Intercept`](struct.Intercept.html), in bytes.
pub const DEFAULT_FILE_SIZE_LIMIT: u64 = 2 * 1024 * 1024;

/// The default file count limit for [`Intercept`](struct.Intercept.html).
pub const DEFAULT_FILE_COUNT_LIMIT: u32 = 16;

/// A `BeforeMiddleware` for Iron which will intercept and read-out multipart requests and store
/// the result in the request.
///
/// Successful reads will be placed in the `extensions: TypeMap` field of `iron::Request` as an
/// [`Entries`](../struct.Entries.html) instance (as both key-type and value):
///
/// ```no_run
/// extern crate iron;
/// extern crate multipart;
///
/// use iron::prelude::*;
///
/// use multipart::server::Entries;
/// use multipart::server::iron::Intercept;
///
/// fn main() {
///     let mut chain = Chain::new(|req: &mut Request| if let Some(entries) =
///         req.extensions.get::<Entries>() {
///
///         Ok(Response::with(format!("{:?}", entries)))
///     } else {
///         Ok(Response::with("Not a multipart request"))
///     });
///
///     chain.link_before(Intercept::default());
///
///     Iron::new(chain).http("localhost:80").unwrap();
/// }
/// ```
///
/// Any errors during which occur during reading will be passed on as `IronError`.
#[derive(Debug)]
pub struct Intercept {
    /// The parent directory for all temporary directories created by this middleware.
    /// Will be created if it doesn't exist (lazy).
    ///
    /// If omitted, uses the OS temporary directory.
    ///
    /// Default value: `None`.
    pub temp_dir_path: Option<PathBuf>,
    /// The size limit of uploaded files, in bytes.
    ///
    /// Files which exceed this size will be rejected.
    /// See the `limit_behavior` field for more info.
    ///
    /// Default value: [`DEFAULT_FILE_SIZE_LIMIT`](constant.default_file_size_limit.html)
    pub file_size_limit: u64,
    /// The limit on the number of files which will be saved from
    /// the request. Requests which exceed this count will be rejected.
    ///
    /// Default value: [`DEFAULT_FILE_COUNT_LIMT`](constant.default_file_count_limit.html)
    pub file_count_limit: u32,
    /// What to do when a file count or size limit has been exceeded.
    ///
    /// See [`LimitBehavior`](enum.limitbehavior.html) for more info.
    pub limit_behavior: LimitBehavior,
}

impl Intercept {
    /// Set the `temp_dir_path` for this middleware.
    pub fn temp_dir_path<P: Into<PathBuf>>(self, path: P) -> Self {
        Intercept { temp_dir_path: Some(path.into()), .. self }
    }

    /// Set the `file_size_limit` for this middleware.
    pub fn file_size_limit(self, limit: u64) -> Self {
        Intercept { file_size_limit: limit, .. self }
    }

    /// Set the `file_count_limit` for this middleware.
    pub fn file_count_limit(self, limit: u32) -> Self {
        Intercept { file_count_limit: limit, .. self }
    }

    /// Set the `limit_behavior` for this middleware.
    pub fn limit_behavior(self, behavior: LimitBehavior) -> Self {
        Intercept { limit_behavior: behavior, .. self }
    }

    fn read_request(&self, req: &mut IronRequest) -> IronResult<Option<Entries>> {
        macro_rules! try_iron(
            ($try:expr; $($fmt_args:tt)+) => (
                match $try {
                    Ok(ok) => ok,
                    Err(err) => return Err(IronError::new(err, format!($($fmt_args)*))),
                }
            );
            ($try:expr) => (
                try_iron!($try; "");
            )
        );

        let mut multipart = match Multipart::from_request(req) {
            Ok(multipart) => multipart,
            Err(_) => return Ok(None),
        };

        let mut entries = try_iron!(
            self.temp_dir_path.as_ref()
                .map_or_else(Entries::new_tempdir, Entries::new_tempdir_in);
            "Error opening temporary directory for request."
        );

        let mut file_count = 0;

        while let Some(field) = try_iron!(multipart.read_entry()) {
            match field.data {
                MultipartData::File(mut file) => {
                    if self.limit_behavior.throw_error() && file_count >= self.file_count_limit {
                        return Err(FileCountLimitError(self.file_count_limit).into());
                    }

                    let file = try_iron!(
                        file.save_in_limited(&entries.dir, self.file_size_limit);
                        "Error reading field: \"{}\" (filename: \"{}\")",
                        field.name,
                        file.filename().unwrap_or("(none)")
                    );

                    if file.size == self.file_size_limit {
                        if self.limit_behavior.throw_error() {
                            return Err(FileSizeLimitError::new(field.name, file.filename).into());
                        } else {
                            warn!(
                                "File size limit reached for field {:?} (filename: {:?})",
                                field.name, file.filename
                            );
                        }
                    }

                    entries.files.insert(field.name, file);
                    file_count += 1;
                },
                MultipartData::Text(text) => {
                    entries.fields.insert(field.name, text.into());
                },
            }
        }

        Ok(Some(entries))
    }
}

impl Default for Intercept {
    fn default() -> Self {
        Intercept {
            temp_dir_path: None,
            file_size_limit: DEFAULT_FILE_SIZE_LIMIT,
            file_count_limit: DEFAULT_FILE_COUNT_LIMIT,
            limit_behavior: LimitBehavior::ThrowError,
        }
    }
}

impl BeforeMiddleware for Intercept {
    fn before(&self, req: &mut IronRequest) -> IronResult<()> {
        try!(self.read_request(req))
            .map(|entries| req.extensions.insert::<Entries>(entries));

        Ok(())
    }
}

impl Key for Entries {
    type Value = Self;
}

/// The behavior of `Intercept` when a file size or count limit is exceeded.
#[derive(Clone, Copy, Debug)]
#[repr(u32)]
pub enum LimitBehavior {
    /// Return an error from the middleware describing the issue.
    ThrowError,
    /// Ignore the limit.
    ///
    /// In the case of file size limits, the offending file will be truncated
    /// in the result.
    ///
    /// In the case of file count limits, the request will be completed.
    Continue,
}

impl LimitBehavior {
    fn throw_error(self) -> bool {
        use self::LimitBehavior::*;

        match self {
            ThrowError => true,
            Continue => false,
        }
    }
}

/// An error returned from `Intercept` when the size limit
/// for an individual file is exceeded.
#[derive(Debug)]
pub struct FileSizeLimitError {
    /// The field where the error occurred.
    pub field: String,
    /// The filename of the oversize file, if it was provided.
    pub filename: Option<String>,
}

impl FileSizeLimitError {
    fn new(field: String, filename: Option<String>) -> Self {
        FileSizeLimitError {
            field: field,
            filename: filename
        }
    }
}

impl error::Error for FileSizeLimitError {
    fn description(&self) -> &str {
        "file size limit reached"
    }
}

impl fmt::Display for FileSizeLimitError {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        match self.filename {
            Some(ref filename) => write!(f, "File size limit reached for field \"{}\" (filename: \"{}\")", self.field, filename),
            None => write!(f, "File size limit reached for field \"{}\" (no filename)", self.field),
        }
    }
}

impl Into<IronError> for FileSizeLimitError {
    fn into(self) -> IronError {
        let desc_str = self.to_string();
        IronError::new(self, desc_str)
    }
}

/// An error returned from `Intercept` when the file count limit
/// for a single request was exceeded.
#[derive(Debug)]
pub struct FileCountLimitError(u32);

impl error::Error for FileCountLimitError {
    fn description(&self) -> &str {
        "file count limit reached"
    }
}

impl fmt::Display for FileCountLimitError {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f, "File count limit reached for request. Limit: {}", self.0)
    }
}

impl Into<IronError> for FileCountLimitError {
    fn into(self) -> IronError {
        let desc_string = self.to_string();
        IronError::new(self, desc_string)
    }
}
<|endoftext|>


<commit_before>// Copyright 2020 Google LLC
//
// Use of this source code is governed by an MIT-style license that can be found
// in the LICENSE file or at https://opensource.org/licenses/MIT.

mod error;

use std::convert::{TryFrom, TryInto};

use log::error;

use crate::action;
use crate::message;
pub use self::error::{Error, ParseError};

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

pub fn execute<S, R, H>(session: &mut S, handler: H, payload: Payload) -> Result<()>
where
    S: Session,
    R: action::Request,
    H: FnOnce(&mut S, R) -> Result<()>,
{
    handler(session, payload.parse()?)
}

pub fn handle<M>(message: M)
where
    M: TryInto<Demand, Error=ParseError>,
{
    let demand = match message.try_into() {
        Ok(demand) => demand,
        Err(error) => {
            error!("failed to parse the message: {}", error);
            return;
        }
    };

    let mut session = Action::new(demand.header.clone());
    let result = action::dispatch(&demand.action, &mut session, demand.payload);

    let status = Status {
        header: demand.header,
        result: result,
    };

    let message = match status.try_into() {
        Ok(message) => message,
        Err(error) => {
            // If we cannot encode the final status message, there is nothing
            // we can do to notify the server, as status is responsible for
            // reporting errors. We can only log the error and carry on.
            error!("failed to encode status message: {}", error);
            return;
        }
    };

    message::send(message);
}

pub trait Session {
    fn reply<R: action::Response>(&mut self, response: R) -> Result<()>;
    fn send<R: action::Response>(&mut self, sink: Sink, response: R) -> Result<()>;
}

pub struct Adhoc;

impl Session for Adhoc {

    // TODO: Session trait should be probably split into two traits and then
    // make the actions that do not care about the `reply` method implement the
    // simpler one.
    fn reply<R>(&mut self, response: R) -> Result<()>
    where
        R: action::Response,
    {
        error!("attempted to reply to an ad-hoc session, dropping response");
        drop(response);

        Ok(())
    }

    fn send<R>(&mut self, sink: Sink, response: R) -> Result<()>
    where
        R: action::Response,
    {
        sink.wrap(response).send()
    }
}

pub struct Action {
    header: Header,
    next_response_id: u64,
}

impl Action {

    pub fn new(header: Header) -> Action {
        Action {
            header: header,
            next_response_id: 0,
        }
    }
}

impl Session for Action {

    fn reply<R: action::Response>(&mut self, response: R) -> Result<()> {
        Response {
            session_id: self.header.session_id.clone(),
            request_id: Some(self.header.request_id),
            response_id: Some(self.next_response_id),
            data: response,
        }.send()?;

        self.next_response_id += 1;

        Ok(())
    }

    fn send<R>(&mut self, sink: Sink, response: R) -> Result<()>
    where
        R: action::Response,
    {
        sink.wrap(response).send()
    }
}

pub const STARTUP: Sink = Sink { id: "/flows/F:Startup" };

pub struct Sink {
    id: &'static str,
}

impl Sink {

    fn wrap<R>(&self, response: R) -> Response<R>
    where
        R: action::Response,
    {
        Response {
            session_id: String::from(self.id),
            request_id: None,
            response_id: None,
            data: response,
        }
    }
}

pub struct Demand {
    pub action: String,
    pub header: Header,
    pub payload: Payload,
}

impl TryFrom<rrg_proto::GrrMessage> for Demand {

    type Error = ParseError;

    fn try_from(message: rrg_proto::GrrMessage)
    -> std::result::Result<Demand, ParseError>
    {
        use ParseError::*;

        let header = Header {
            session_id: message.session_id.ok_or(MissingField("session id"))?,
            request_id: message.request_id.ok_or(MissingField("request id"))?,
        };

        Ok(Demand {
            action: message.name.ok_or(MissingField("action name"))?,
            header: header,
            payload: Payload(message.args),
        })
    }
}

#[derive(Clone, Debug)]
pub struct Header {
    pub session_id: String,
    pub request_id: u64,
}

#[derive(Debug)]
pub struct Payload(Option<Vec<u8>>);

impl Payload {

    pub fn parse<R>(&self) -> std::result::Result<R, ParseError>
    where
        R: action::Request,
    {
        let proto = match self {
            Payload(Some(bytes)) => prost::Message::decode(&bytes[..])?,
            Payload(None) => Default::default(),
        };

        Ok(R::from_proto(proto))
    }
}

struct Response<R: action::Response> {
    session_id: String,
    request_id: Option<u64>,
    response_id: Option<u64>,
    data: R,
}

impl<R: action::Response> Response<R> {

    fn send(self) -> Result<()> {
        let message = self.try_into()?;
        message::send(message);

        Ok(())
    }
}

impl<R: action::Response> TryInto<rrg_proto::GrrMessage> for Response<R> {

    type Error = prost::EncodeError;

    fn try_into(self)
    -> std::result::Result<rrg_proto::GrrMessage, prost::EncodeError>
    {
        let mut data = Vec::new();
        prost::Message::encode(&self.data.into_proto(), &mut data)?;

        Ok(rrg_proto::GrrMessage {
            session_id: Some(self.session_id),
            response_id: self.response_id,
            request_id: self.request_id,
            r#type: Some(rrg_proto::grr_message::Type::Message.into()),
            args_rdf_name: R::RDF_NAME.map(String::from),
            args: Some(data),
            ..Default::default()
        })
    }
}

struct Status {
    header: Header,
    result: Result<()>,
}

impl TryInto<rrg_proto::GrrMessage> for Status {

    type Error = prost::EncodeError;

    fn try_into(self)
    -> std::result::Result<rrg_proto::GrrMessage, prost::EncodeError> {
        let status = match self.result {
            Ok(()) => rrg_proto::GrrStatus {
                status: Some(rrg_proto::grr_status::ReturnedStatus::Ok.into()),
                ..Default::default()
            },
            Err(error) => rrg_proto::GrrStatus {
                status: Some(rrg_proto::grr_status::ReturnedStatus::GenericError.into()),
                error_message: Some(error.to_string()),
                ..Default::default()
            },
        };

        let mut data = Vec::new();
        prost::Message::encode(&status, &mut data)?;

        Ok(rrg_proto::GrrMessage {
            session_id: Some(self.header.session_id),
            response_id: Some(self.header.request_id),
            r#type: Some(rrg_proto::grr_message::Type::Status.into()),
            args_rdf_name: Some(String::from("GrrStatus")),
            args: Some(data),
            ..Default::default()
        })
    }
}
<commit_msg>Create utility methods for the action type.<commit_after>// Copyright 2020 Google LLC
//
// Use of this source code is governed by an MIT-style license that can be found
// in the LICENSE file or at https://opensource.org/licenses/MIT.

mod error;

use std::convert::{TryFrom, TryInto};

use log::error;

use crate::action;
use crate::message;
pub use self::error::{Error, ParseError};

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

pub fn execute<S, R, H>(session: &mut S, handler: H, payload: Payload) -> Result<()>
where
    S: Session,
    R: action::Request,
    H: FnOnce(&mut S, R) -> Result<()>,
{
    handler(session, payload.parse()?)
}

pub fn handle<M>(message: M)
where
    M: TryInto<Demand, Error=ParseError>,
{
    let demand = match message.try_into() {
        Ok(demand) => demand,
        Err(error) => {
            error!("failed to parse the message: {}", error);
            return;
        }
    };

    let mut session = Action::from_demand(&demand);
    let result = action::dispatch(&demand.action, &mut session, demand.payload);

    let status = Status {
        header: demand.header,
        result: result,
    };

    let message = match status.try_into() {
        Ok(message) => message,
        Err(error) => {
            // If we cannot encode the final status message, there is nothing
            // we can do to notify the server, as status is responsible for
            // reporting errors. We can only log the error and carry on.
            error!("failed to encode status message: {}", error);
            return;
        }
    };

    message::send(message);
}

pub trait Session {
    fn reply<R: action::Response>(&mut self, response: R) -> Result<()>;
    fn send<R: action::Response>(&mut self, sink: Sink, response: R) -> Result<()>;
}

pub struct Adhoc;

impl Session for Adhoc {

    // TODO: Session trait should be probably split into two traits and then
    // make the actions that do not care about the `reply` method implement the
    // simpler one.
    fn reply<R>(&mut self, response: R) -> Result<()>
    where
        R: action::Response,
    {
        error!("attempted to reply to an ad-hoc session, dropping response");
        drop(response);

        Ok(())
    }

    fn send<R>(&mut self, sink: Sink, response: R) -> Result<()>
    where
        R: action::Response,
    {
        sink.wrap(response).send()
    }
}

pub struct Action {
    header: Header,
    next_response_id: u64,
}

impl Action {

    pub fn from_demand(demand: &Demand) -> Action {
        Action {
            header: demand.header.clone(),
            next_response_id: 0,
        }
    }

    fn wrap<R>(&self, response: R) -> Response<R>
    where
        R: action::Response
    {
        Response {
            session_id: self.header.session_id.clone(),
            request_id: Some(self.header.request_id),
            response_id: Some(self.next_response_id),
            data: response,
        }
    }
}

impl Session for Action {

    fn reply<R: action::Response>(&mut self, response: R) -> Result<()> {
        self.wrap(response).send()?;
        self.next_response_id += 1;

        Ok(())
    }

    fn send<R>(&mut self, sink: Sink, response: R) -> Result<()>
    where
        R: action::Response,
    {
        sink.wrap(response).send()
    }
}

pub const STARTUP: Sink = Sink { id: "/flows/F:Startup" };

pub struct Sink {
    id: &'static str,
}

impl Sink {

    fn wrap<R>(&self, response: R) -> Response<R>
    where
        R: action::Response,
    {
        Response {
            session_id: String::from(self.id),
            request_id: None,
            response_id: None,
            data: response,
        }
    }
}

pub struct Demand {
    pub action: String,
    pub header: Header,
    pub payload: Payload,
}

impl TryFrom<rrg_proto::GrrMessage> for Demand {

    type Error = ParseError;

    fn try_from(message: rrg_proto::GrrMessage)
    -> std::result::Result<Demand, ParseError>
    {
        use ParseError::*;

        let header = Header {
            session_id: message.session_id.ok_or(MissingField("session id"))?,
            request_id: message.request_id.ok_or(MissingField("request id"))?,
        };

        Ok(Demand {
            action: message.name.ok_or(MissingField("action name"))?,
            header: header,
            payload: Payload(message.args),
        })
    }
}

#[derive(Clone, Debug)]
pub struct Header {
    pub session_id: String,
    pub request_id: u64,
}

#[derive(Debug)]
pub struct Payload(Option<Vec<u8>>);

impl Payload {

    pub fn parse<R>(&self) -> std::result::Result<R, ParseError>
    where
        R: action::Request,
    {
        let proto = match self {
            Payload(Some(bytes)) => prost::Message::decode(&bytes[..])?,
            Payload(None) => Default::default(),
        };

        Ok(R::from_proto(proto))
    }
}

struct Response<R: action::Response> {
    session_id: String,
    request_id: Option<u64>,
    response_id: Option<u64>,
    data: R,
}

impl<R: action::Response> Response<R> {

    fn send(self) -> Result<()> {
        let message = self.try_into()?;
        message::send(message);

        Ok(())
    }
}

impl<R: action::Response> TryInto<rrg_proto::GrrMessage> for Response<R> {

    type Error = prost::EncodeError;

    fn try_into(self)
    -> std::result::Result<rrg_proto::GrrMessage, prost::EncodeError>
    {
        let mut data = Vec::new();
        prost::Message::encode(&self.data.into_proto(), &mut data)?;

        Ok(rrg_proto::GrrMessage {
            session_id: Some(self.session_id),
            response_id: self.response_id,
            request_id: self.request_id,
            r#type: Some(rrg_proto::grr_message::Type::Message.into()),
            args_rdf_name: R::RDF_NAME.map(String::from),
            args: Some(data),
            ..Default::default()
        })
    }
}

struct Status {
    header: Header,
    result: Result<()>,
}

impl TryInto<rrg_proto::GrrMessage> for Status {

    type Error = prost::EncodeError;

    fn try_into(self)
    -> std::result::Result<rrg_proto::GrrMessage, prost::EncodeError> {
        let status = match self.result {
            Ok(()) => rrg_proto::GrrStatus {
                status: Some(rrg_proto::grr_status::ReturnedStatus::Ok.into()),
                ..Default::default()
            },
            Err(error) => rrg_proto::GrrStatus {
                status: Some(rrg_proto::grr_status::ReturnedStatus::GenericError.into()),
                error_message: Some(error.to_string()),
                ..Default::default()
            },
        };

        let mut data = Vec::new();
        prost::Message::encode(&status, &mut data)?;

        Ok(rrg_proto::GrrMessage {
            session_id: Some(self.header.session_id),
            response_id: Some(self.header.request_id),
            r#type: Some(rrg_proto::grr_message::Type::Status.into()),
            args_rdf_name: Some(String::from("GrrStatus")),
            args: Some(data),
            ..Default::default()
        })
    }
}
<|endoftext|>


<commit_before>//! Subcommand implementations and dispatch function `run()`.
pub mod info;
pub mod list;
pub mod scan;
pub mod staged;
pub mod stashed;
pub mod status;
pub mod unstaged;

use crate::config::Config;
use crate::errors::{GitGlobalError, Result};
use crate::report::Report;

/// Run a subcommand, returning a `Report`.
///
/// If `None` is given for the optional subcommand, run `config.default_cmd`.
/// Else, try to match the given `&str` to a known subcommand.
pub fn run(maybe_subcmd: Option<&str>, config: Config) -> Result<Report> {
    let command = maybe_subcmd.unwrap_or(&config.default_cmd);
    match command {
        "info" => info::execute(config),
        "list" => list::execute(config),
        "scan" => scan::execute(config),
        "staged" => staged::execute(config),
        "stashed" => stashed::execute(config),
        "status" => status::execute(config),
        "unstaged" => unstaged::execute(config),
        cmd => Err(GitGlobalError::BadSubcommand(cmd.to_string())),
    }
}

/// Return the list of all subcommand names and descriptions.
///
/// Used for building the clap::App in the cli module.
pub fn get_subcommands() -> Vec<(&'static str, &'static str)> {
    vec![
        ("info", "Shows meta-information about git-global"),
        ("list", "Lists all known repos"),
        ("scan", "Updates cache of known repos"),
        (
            "staged",
            "Show git index status for repos with staged changes",
        ),
        ("stashed", "Shows repos with stashed changes"),
        (
            "status",
            "Shows status (`git status -s`) for repos with any changes",
        ),
        (
            "unstaged",
            "Show working dir status for repos with unstaged changes",
        ),
    ]
}
<commit_msg>Use consistent languae in subcommand descriptions<commit_after>//! Subcommand implementations and dispatch function `run()`.
pub mod info;
pub mod list;
pub mod scan;
pub mod staged;
pub mod stashed;
pub mod status;
pub mod unstaged;

use crate::config::Config;
use crate::errors::{GitGlobalError, Result};
use crate::report::Report;

/// Run a subcommand, returning a `Report`.
///
/// If `None` is given for the optional subcommand, run `config.default_cmd`.
/// Else, try to match the given `&str` to a known subcommand.
pub fn run(maybe_subcmd: Option<&str>, config: Config) -> Result<Report> {
    let command = maybe_subcmd.unwrap_or(&config.default_cmd);
    match command {
        "info" => info::execute(config),
        "list" => list::execute(config),
        "scan" => scan::execute(config),
        "staged" => staged::execute(config),
        "stashed" => stashed::execute(config),
        "status" => status::execute(config),
        "unstaged" => unstaged::execute(config),
        cmd => Err(GitGlobalError::BadSubcommand(cmd.to_string())),
    }
}

/// Return the list of all subcommand names and descriptions.
///
/// Used for building the clap::App in the cli module.
pub fn get_subcommands() -> Vec<(&'static str, &'static str)> {
    vec![
        ("info", "Shows meta-information about git-global"),
        ("list", "Lists all known repos"),
        ("scan", "Updates cache of known repos"),
        (
            "staged",
            "Shows git index status for repos with staged changes",
        ),
        ("stashed", "Shows repos with stashed changes"),
        (
            "status",
            "Shows status (`git status -s`) for repos with any changes",
        ),
        (
            "unstaged",
            "Shows working dir status for repos with unstaged changes",
        ),
    ]
}
<|endoftext|>


<commit_before>#![crate_name = "uu_tsort"]

/*
 * This file is part of the uutils coreutils package.
 *
 * (c) Ben Eggers <ben.eggers36@gmail.com>
 * (c) Akira Hayakawa <ruby.wktk@gmail.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

extern crate getopts;

#[macro_use]
extern crate uucore;

use std::collections::{HashMap, HashSet};
use std::fs::File;
use std::io::{stdin, BufRead, BufReader, Read};
use std::path::Path;

static NAME: &str = "tsort";
static VERSION: &str = env!("CARGO_PKG_VERSION");

pub fn uumain(args: Vec<String>) -> i32 {
    let mut opts = getopts::Options::new();

    opts.optflag("h", "help", "display this help and exit");
    opts.optflag("V", "version", "output version information and exit");

    let matches = match opts.parse(&args[1..]) {
        Ok(m) => m,
        Err(f) => crash!(1, "{}", f),
    };

    if matches.opt_present("h") {
        println!("{} {}", NAME, VERSION);
        println!();
        println!("Usage:");
        println!("  {} [OPTIONS] FILE", NAME);
        println!();
        println!("{}", opts.usage("Topological sort the strings in FILE. Strings are defined as any sequence of tokens separated by whitespace (tab, space, or newline). If FILE is not passed in, stdin is used instead."));
        return 0;
    }

    if matches.opt_present("V") {
        println!("{} {}", NAME, VERSION);
        return 0;
    }

    let files = matches.free.clone();
    let input = if files.len() > 1 {
        crash!(1, "{}, extra operand '{}'", NAME, matches.free[1]);
    } else if files.is_empty() {
        "-".to_owned()
    } else {
        files[0].clone()
    };

    let mut stdin_buf;
    let mut file_buf;
    let mut reader = BufReader::new(if input == "-" {
        stdin_buf = stdin();
        &mut stdin_buf as &mut dyn Read
    } else {
        file_buf = match File::open(Path::new(&input)) {
            Ok(a) => a,
            _ => {
                show_error!("{}: No such file or directory", input);
                return 1;
            }
        };
        &mut file_buf as &mut dyn Read
    });

    let mut g = Graph::new();
    loop {
        let mut line = String::new();
        match reader.read_line(&mut line) {
            Ok(_) => {
                let tokens: Vec<String> = line.trim_end()
                    .split_whitespace()
                    .map(|s| s.to_owned())
                    .collect();
                if tokens.is_empty() {
                    break;
                }
                for ab in tokens.chunks(2) {
                    match ab.len() {
                        2 => g.add_edge(&ab[0], &ab[1]),
                        _ => crash!(1, "{}: input contains an odd number of tokens", input),
                    }
                }
            }
            _ => break,
        }
    }

    g.run_tsort();

    if !g.is_acyclic() {
        crash!(1, "{}, input contains a loop:", input);
    }

    for x in &g.result {
        println!("{}", x);
    }

    0
}

// We use String as a representation of node here
// but using integer may improve performance.
struct Graph {
    in_edges: HashMap<String, HashSet<String>>,
    out_edges: HashMap<String, Vec<String>>,
    result: Vec<String>,
}

impl Graph {
    fn new() -> Graph {
        Graph {
            in_edges: HashMap::new(),
            out_edges: HashMap::new(),
            result: vec![],
        }
    }

    fn has_node(&self, n: &str) -> bool {
        self.in_edges.contains_key(n)
    }

    fn has_edge(&self, from: &str, to: &str) -> bool {
        self.in_edges.get(to).unwrap().contains(from)
    }

    fn init_node(&mut self, n: &String) {
        self.in_edges.insert(n.clone(), HashSet::new());
        self.out_edges.insert(n.clone(), vec![]);
    }

    fn add_edge(&mut self, from: &String, to: &String) {
        if !self.has_node(to) {
            self.init_node(to);
        }

        if !self.has_node(from) {
            self.init_node(from);
        }

        if from != to && !self.has_edge(from, to) {
            self.in_edges.get_mut(to).unwrap().insert(from.clone());
            self.out_edges.get_mut(from).unwrap().push(to.clone());
        }
    }

    // Kahn's algorithm
    // O(|V|+|E|)
    fn run_tsort(&mut self) {
        let mut start_nodes = vec![];
        for (n, edges) in &self.in_edges {
            if edges.is_empty() {
                start_nodes.push(n.clone());
            }
        }

        while !start_nodes.is_empty() {
            let n = start_nodes.remove(0);

            self.result.push(n.clone());

            let n_out_edges = self.out_edges.get_mut(&n).unwrap();
            for m in n_out_edges.iter() {
                let m_in_edges = self.in_edges.get_mut(m).unwrap();
                m_in_edges.remove(&n);

                // If m doesn't have other in-coming edges add it to start_nodes
                if m_in_edges.is_empty() {
                    start_nodes.push(m.clone());
                }
            }
            n_out_edges.clear();
        }
    }

    fn is_acyclic(&self) -> bool {
        for (_, edges) in &self.out_edges {
            if !edges.is_empty() {
                return false;
            }
        }
        true
    }
}
<commit_msg>refactor/polish ~ fix `cargo clippy` complaints (use `values()`)<commit_after>#![crate_name = "uu_tsort"]

/*
 * This file is part of the uutils coreutils package.
 *
 * (c) Ben Eggers <ben.eggers36@gmail.com>
 * (c) Akira Hayakawa <ruby.wktk@gmail.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

extern crate getopts;

#[macro_use]
extern crate uucore;

use std::collections::{HashMap, HashSet};
use std::fs::File;
use std::io::{stdin, BufRead, BufReader, Read};
use std::path::Path;

static NAME: &str = "tsort";
static VERSION: &str = env!("CARGO_PKG_VERSION");

pub fn uumain(args: Vec<String>) -> i32 {
    let mut opts = getopts::Options::new();

    opts.optflag("h", "help", "display this help and exit");
    opts.optflag("V", "version", "output version information and exit");

    let matches = match opts.parse(&args[1..]) {
        Ok(m) => m,
        Err(f) => crash!(1, "{}", f),
    };

    if matches.opt_present("h") {
        println!("{} {}", NAME, VERSION);
        println!();
        println!("Usage:");
        println!("  {} [OPTIONS] FILE", NAME);
        println!();
        println!("{}", opts.usage("Topological sort the strings in FILE. Strings are defined as any sequence of tokens separated by whitespace (tab, space, or newline). If FILE is not passed in, stdin is used instead."));
        return 0;
    }

    if matches.opt_present("V") {
        println!("{} {}", NAME, VERSION);
        return 0;
    }

    let files = matches.free.clone();
    let input = if files.len() > 1 {
        crash!(1, "{}, extra operand '{}'", NAME, matches.free[1]);
    } else if files.is_empty() {
        "-".to_owned()
    } else {
        files[0].clone()
    };

    let mut stdin_buf;
    let mut file_buf;
    let mut reader = BufReader::new(if input == "-" {
        stdin_buf = stdin();
        &mut stdin_buf as &mut dyn Read
    } else {
        file_buf = match File::open(Path::new(&input)) {
            Ok(a) => a,
            _ => {
                show_error!("{}: No such file or directory", input);
                return 1;
            }
        };
        &mut file_buf as &mut dyn Read
    });

    let mut g = Graph::new();
    loop {
        let mut line = String::new();
        match reader.read_line(&mut line) {
            Ok(_) => {
                let tokens: Vec<String> = line.trim_end()
                    .split_whitespace()
                    .map(|s| s.to_owned())
                    .collect();
                if tokens.is_empty() {
                    break;
                }
                for ab in tokens.chunks(2) {
                    match ab.len() {
                        2 => g.add_edge(&ab[0], &ab[1]),
                        _ => crash!(1, "{}: input contains an odd number of tokens", input),
                    }
                }
            }
            _ => break,
        }
    }

    g.run_tsort();

    if !g.is_acyclic() {
        crash!(1, "{}, input contains a loop:", input);
    }

    for x in &g.result {
        println!("{}", x);
    }

    0
}

// We use String as a representation of node here
// but using integer may improve performance.
struct Graph {
    in_edges: HashMap<String, HashSet<String>>,
    out_edges: HashMap<String, Vec<String>>,
    result: Vec<String>,
}

impl Graph {
    fn new() -> Graph {
        Graph {
            in_edges: HashMap::new(),
            out_edges: HashMap::new(),
            result: vec![],
        }
    }

    fn has_node(&self, n: &str) -> bool {
        self.in_edges.contains_key(n)
    }

    fn has_edge(&self, from: &str, to: &str) -> bool {
        self.in_edges.get(to).unwrap().contains(from)
    }

    fn init_node(&mut self, n: &String) {
        self.in_edges.insert(n.clone(), HashSet::new());
        self.out_edges.insert(n.clone(), vec![]);
    }

    fn add_edge(&mut self, from: &String, to: &String) {
        if !self.has_node(to) {
            self.init_node(to);
        }

        if !self.has_node(from) {
            self.init_node(from);
        }

        if from != to && !self.has_edge(from, to) {
            self.in_edges.get_mut(to).unwrap().insert(from.clone());
            self.out_edges.get_mut(from).unwrap().push(to.clone());
        }
    }

    // Kahn's algorithm
    // O(|V|+|E|)
    fn run_tsort(&mut self) {
        let mut start_nodes = vec![];
        for (n, edges) in &self.in_edges {
            if edges.is_empty() {
                start_nodes.push(n.clone());
            }
        }

        while !start_nodes.is_empty() {
            let n = start_nodes.remove(0);

            self.result.push(n.clone());

            let n_out_edges = self.out_edges.get_mut(&n).unwrap();
            for m in n_out_edges.iter() {
                let m_in_edges = self.in_edges.get_mut(m).unwrap();
                m_in_edges.remove(&n);

                // If m doesn't have other in-coming edges add it to start_nodes
                if m_in_edges.is_empty() {
                    start_nodes.push(m.clone());
                }
            }
            n_out_edges.clear();
        }
    }

    fn is_acyclic(&self) -> bool {
        for edges in self.out_edges.values() {
            if !edges.is_empty() {
                return false;
            }
        }
        true
    }
}
<|endoftext|>