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
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
#![feature(test)]

pub mod cli_options;
pub mod indexer;
pub mod score;

pub use cli_options::{CliOptions, PresentationOptions, IndexingOptions};
use indexer::{Index, LineNumber};
use score::ScoringAlgo;

/// Show the headers depending on the [CliOptions].
fn show_headers_maybe(opts: &CliOptions) {
    // Show headers if requested
    if opts.presentation.headers {
        if opts.presentation.line_numbers {
            print!("Line#\t");
        }

        if opts.presentation.count {
            print!("Count\t");
        }

        println!("Input");
        println!("----------------------");
    }
}

/// Display (or not) a single line depending on the [matches](Index::get_matches) from the [Index].
fn show_line(lines: &Vec<&str>, i: LineNumber, matches: &Option<Vec<LineNumber>>, opts: &CliOptions) {
    let matches_count = match matches {
        None => 0,
        Some(vec) => vec.len(),
    };

    if opts.presentation.line_numbers {
        print!("{:05}\t", i);
    }

    if opts.presentation.count {
        print!("{}\t", matches_count + 1); // without +1 we get only the number of dups without the ref item
    }

    println!("{}", lines[i]);
}

/// After the [Index] was created, we can use it to filter and display the filtered input.
pub fn display(lines: &Vec<&str>, index: &Index, opts: &CliOptions) {
    let seq: Vec<usize> = (0usize..lines.len()).collect();
    let mut used = Vec::<usize>::with_capacity(lines.len());
    
    show_headers_maybe(opts);

    for i in seq {
        let matches = index.get_matches(&i, &opts.presentation);
        match matches {
            None => {},
            Some(ref m) => {
                m.iter()
                .for_each(|line_ref| {
                    used.push(*line_ref);   
                });
            }
        }

        if !used.contains(&i) && !opts.presentation.inverted {
            show_line(lines, i, &matches, opts);
        } else {
             if used.contains(&i) &&  opts.presentation.inverted {
                 show_line(lines, i, &matches, opts);
            }
        }
    }
}

/// Process the [input] using the given 
pub fn run(input: &str, options: &CliOptions) {
    let mut index = Index::new(ScoringAlgo::Levenshtein);
    let lines: Vec<&str> = input.split("\n").collect();

    index.create_index(&lines, &options.indexing);
    display(&lines, &index, &options);
}

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

    #[test]
    fn test_not_inverted() {
        let opts = CliOptions {
            indexing: IndexingOptions {
                scope: 10, 
                ..IndexingOptions::default()
            },
            
            presentation: PresentationOptions {
                threshold: 3,
                line_numbers: true,
                count: true,
                inverted: false,
                headers: true,
                ..PresentationOptions::default()
            }
        };

        let content = vec![
            "Line1",        // #0 OK
            "XXX1",         // #1 OK
            "Line2",        // #2 DUP #1
            "LIne3",        // #3 DUP #1
            "LINe4",        // #4 DUP #1
            "XXX2",         // #5 DUP #2
            "Mega Junk",    // #6 OK
        ]
        .join("\n");
        run(&content, &opts)
    }

    #[test]
    fn test_inverted() {
        let opts = CliOptions {
            indexing: IndexingOptions {
                scope: 10, 
                ..IndexingOptions::default()
            },
            
            presentation: PresentationOptions {
                threshold: 3,
                line_numbers: true,
                count: false,
                inverted: true,
                headers: true,
                ..PresentationOptions::default()
            }
        };

        let content = vec![
            "Line1",        // #0 OK
            "XXX1",         // #1 OK
            "Line2",        // #2 DUP #1
            "LIne3",        // #3 DUP #1
            "LINe4",        // #4 DUP #1
            "XXX2",         // #5 DUP #2
            "Mega Junk",    // #6 OK
        ]
        .join("\n");
        run(&content, &opts)
    }
}