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
use crate::{PathWithContent, Result};
use indicatif::{MultiProgress, ProgressBar, ProgressStyle};
use rayon::prelude::*;
use std::ffi::OsStr;
use std::process;
use std::sync::atomic::{AtomicUsize, Ordering};
use std::{
    fs::File,
    io::{BufReader, Read},
    path::PathBuf,
};

static INPUTTED_FILE_NUMBER: AtomicUsize = AtomicUsize::new(0);
const BUFFER_SIZR: usize = 16 * 1024;

trait PathExt {
    fn without_dotted_prefix(&self) -> bool;
    fn add_dotted_prefix(&mut self);
}

impl PathExt for PathBuf {
    fn without_dotted_prefix(&self) -> bool {
        self.is_relative() && !self.starts_with("../") && !self.starts_with("./")
    }

    fn add_dotted_prefix(&mut self) {
        *self = PathBuf::from_iter([OsStr::new("./"), self.as_os_str()]);
    }
}

pub fn read_files(paths: Vec<PathBuf>) -> Result<PathWithContent> {
    println!("Reading files / Getting content from stdin:");

    let result = paths
        .into_par_iter()
        .filter(|path| path.is_file() || path.as_os_str() == "-")
        .map(|mut path| {
            let should_read_from_input = path.as_os_str() == "-";

            let content = get_content(&path, should_read_from_input);

            if path.without_dotted_prefix() {
                path.add_dotted_prefix();
            }

            if should_read_from_input {
                let inputted_file_number = INPUTTED_FILE_NUMBER.fetch_add(1, Ordering::SeqCst);
                path = PathBuf::from(format!("Input/{}", inputted_file_number));
            }

            let content = content.unwrap_or_else(|err| {
                eprintln!("{}: {}", path.display(), err);
                process::exit(1);
            });

            (path, content)
        })
        .collect();
    Ok(result)
}

fn get_content(path: &PathBuf, should_read_from_input: bool) -> Result<String> {
    if should_read_from_input {
        read_from_stdin()
    } else {
        let bars = MultiProgress::new();
        let style =
            ProgressStyle::with_template("[{elapsed}][{percent}%] {bar:45.cyan/blue} {bytes} {wide_msg}")?
                .progress_chars(">-");
        read_file_with_progress(path, style, bars)
    }
}

fn read_file_with_progress(path: &PathBuf, style: ProgressStyle, bars: MultiProgress) -> Result<String> {
    let mut content = String::new();

    let file = File::open(path)?;
    let size = file.metadata()?.len();

    let bar = ProgressBar::new(size).with_message(format! {"Reading {}", path.display()}).with_style(style);
    let bar = bars.add(bar);

    let mut bufreader = BufReader::new(file);
    let mut buf = [0; BUFFER_SIZR];

    while let Ok(n) = bufreader.read(&mut buf) {
        if n == 0 {
            break;
        }
        bar.inc(n as u64);
        content += &String::from_utf8_lossy(&buf[..n]);
    }
    bar.finish_with_message("Done!");

    Ok(content)
}

fn read_from_stdin() -> Result<String> {
    let mut content = vec![];
    std::io::stdin().read_to_end(&mut content)?;
    Ok(String::from_utf8(content)?)
}