buildkit_rs_ignore/
lib.rs

1use path_clean::clean;
2use std::io::{BufRead, BufReader, Read};
3use thiserror::Error;
4
5#[derive(Debug, Error)]
6pub enum Error {
7    #[error("invalid pattern '{0}'")]
8    InvalidPattern(String),
9    #[error("non-UTF8 pattern")]
10    NonUtf8Pattern,
11    #[error(transparent)]
12    Io(#[from] std::io::Error),
13}
14
15/// Read all reads an ignore file and returns the list of file patterns
16/// to ignore. Note this will trim whitespace from each line as well
17/// as use Rust's `PathBuf` to get the cleanest path for each.
18///
19/// Based on the implementation here: https://github.com/moby/buildkit/blob/1077362ebe0fc7e8f9c2634a49e07733a63ea1c9/frontend/dockerfile/dockerignore/dockerignore.go
20pub fn read_ignore_to_list<R: Read>(reader: R) -> Result<Vec<String>, Error> {
21    let mut excludes = Vec::new();
22
23    let mut buf_reader = BufReader::new(reader);
24    let utf8_bom = [0xEF, 0xBB, 0xBF];
25    let mut current_line = 0;
26
27    let mut line = String::new();
28    while buf_reader.read_line(&mut line)? > 0 {
29        // We trim utf8 bom from the first line
30        if current_line == 0 {
31            line = line
32                .trim_start_matches(|c: char| utf8_bom.contains(&(c as u8)))
33                .to_string();
34        }
35        current_line += 1;
36        // Lines starting with # (comments) are ignored before processing
37        if line.starts_with('#') {
38            line.clear();
39            continue;
40        }
41        let mut pattern = line.trim().to_string();
42        if pattern.is_empty() {
43            line.clear();
44            continue;
45        }
46        // normalize absolute paths to paths relative to the context
47        // (taking care of '!' prefix)
48        let invert = pattern.starts_with('!');
49        if invert {
50            pattern = pattern[1..].trim().to_string();
51        }
52
53        if !pattern.is_empty() {
54            pattern = clean(&pattern)
55                .to_str()
56                .ok_or_else(|| Error::NonUtf8Pattern)?
57                .to_owned();
58            if pattern.starts_with('/') {
59                pattern = pattern[1..].to_string();
60            }
61        }
62
63        if invert {
64            pattern = format!("!{}", pattern);
65        }
66
67        excludes.push(pattern);
68        line.clear();
69    }
70
71    Ok(excludes)
72}