1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
use std::io::{self, BufRead};
use std::collections::HashSet;
use std::iter::{FromIterator, Iterator};
use std::str::FromStr;
// use rayon::prelude

use clap::ArgMatches;
use clap::clap_app;

const LOWEST: usize = 0;
const LINES: &str = "LINES";
const REVERSE: &str = "reverse";

type LineNums = Vec<usize>;
type NthStr = (usize, String);
// type NthStrIterable = Box<dyn Iterator<Item = NthStr>>;


fn get_matches_from_cli<'a>() -> ArgMatches<'a> {
    clap_app!(nth =>
        (version: "0.0.1")
        (author: "AlexDeLorenzo.dev")
        (about: 
            "Return the contents of stdin from the line numbers supplied as arguments.")
        (@arg LINES: +required ... "Line numbers to select")
        (@arg reverse: -r --reverse 
            "Write every line, except the line numbers supplied as LINES, from stdin to stdout.")
    )
    .get_matches()
}

pub fn run() {
    let matches = get_matches_from_cli();
    let mut line_nums = get_line_nums(&matches);

    let stdin = io::stdin();
    let nth_lines = stdin.lock()
        .lines()
        .map(|line| line.unwrap())
        .enumerate();

    if matches.is_present(REVERSE) {
        exclude_lines(&line_nums, nth_lines);
    } else {
        include_lines(&mut line_nums, nth_lines);
    }
}

fn to_str(num: &str) -> usize {
    FromStr::from_str(num).unwrap()
}

fn get_line_nums(matches: &ArgMatches) -> LineNums {
    let mut line_nums = 
        if let Some(lines) = matches.values_of(LINES) {
            lines.map(to_str).collect() 
        } else {
            vec![]
        };

    line_nums.sort();
    line_nums
}

fn include_lines<T: IntoIterator<Item = NthStr>>(
    lines: &mut LineNums, 
    content: T
) {
    for (nth, line) in content {
        if nth == lines[LOWEST] {
            println!("{}", line);
            lines.remove(LOWEST);
        }

        if lines.is_empty() {
            break
        }
    } 
}

fn exclude_lines<T: Iterator<Item = NthStr>>(
    lines: &LineNums, 
    content: T
) {
    let mut lines: HashSet<&usize> = 
      lines.iter().collect();
    
    for (nth, line) in content {
        if lines.contains(&nth) {
            lines.remove(&nth);
        } else {
            println!("{}", line);
        }
    }
}