git_copyright/
file_ops.rs

1//! Check and update copyright of file.
2
3use crate::CError;
4use futures::join;
5use futures::Future;
6use regex::Regex;
7use std::io::{BufRead, BufReader};
8use std::sync::Arc;
9use std::{path::Path, path::PathBuf};
10use tokio::io::{AsyncReadExt, AsyncWriteExt};
11
12pub async fn read_write_copyright(
13    filepath: PathBuf,
14    regex: Arc<Regex>,
15    years_fut: impl Future<Output = String>,
16    copyright_line: impl Future<Output = String>,
17) -> Result<(), CError> {
18    let (years, copyright_line) = join!(years_fut, copyright_line);
19
20    // This could be re-written to read the file asynchronously until EOF or the first n
21    // newlines are found.
22    let file = std::fs::File::open(&filepath)
23        .map_err(|_| CError::ReadError(filepath.display().to_string()))?;
24    let file_header = BufReader::new(file).lines().take(3);
25
26    for (line_nr, line_) in file_header.enumerate() {
27        if let Ok(line_) = line_ {
28            if let Some(cap) = regex.captures_iter(&line_).take(1).next() {
29                if years == &cap[1] {
30                    log::debug!(
31                        "File {} has correct copyright with years {}",
32                        filepath.display(),
33                        years
34                    );
35                    return Ok(());
36                } else {
37                    println!(
38                        "File {} has copyright with year(s) {} on line {} but should have {}",
39                        filepath.display(),
40                        &cap[1],
41                        line_nr,
42                        years
43                    );
44                    return write_copyright(&filepath, &copyright_line, Some(line_nr)).await;
45                }
46            }
47        }
48    }
49
50    println!(
51        "File {} has no copyright but should have {}",
52        filepath.display(),
53        years
54    );
55    write_copyright(&filepath, &copyright_line, None).await
56}
57
58async fn write_copyright(
59    filepath: &Path,
60    copyright_line: &str,
61    line_nr: Option<usize>,
62) -> Result<(), CError> {
63    let mut file = tokio::fs::File::open(filepath)
64        .await
65        .map_err(|_| CError::ReadError(filepath.display().to_string()))?;
66    let mut data = Vec::new();
67    file.read_to_end(&mut data).await?;
68    let mut data: Vec<&str> = std::str::from_utf8(&data)?.split("\n").collect();
69
70    match line_nr {
71        Some(line_nr) => {
72            data[line_nr] = &copyright_line;
73        }
74        None => {
75            if data.len() >= 1 && data[0].starts_with("#!") {
76                // Insert copyright on the second line for shell scripts
77                // that might have a shebang line
78                data.insert(1, copyright_line);
79            } else {
80                data.insert(0, copyright_line);
81            }
82        }
83    }
84
85    let mut file = tokio::fs::File::create(filepath)
86        .await
87        .map_err(|_| CError::WriteError(filepath.display().to_string()))?;
88    file.write_all(data.join("\n").as_bytes())
89        .await
90        .map_err(|_| CError::WriteError(filepath.display().to_string()))?;
91
92    Ok(())
93}