summaryrefslogtreecommitdiffstats
path: root/src/config.rs
blob: 6c9b9b32f3bd506d2a11950f766a8c8e4811279b (plain)
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
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
// This module provides routines for reading ripgrep config "rc" files. The
// primary output of these routines is a sequence of arguments, where each
// argument corresponds precisely to one shell argument.

use std::env;
use std::fs::File;
use std::io;
use std::ffi::OsString;
use std::path::{Path, PathBuf};

use bstr::io::BufReadExt;
use log;
use snafu::{self, ResultExt};

use crate::err::{self, Error, Result};

/// Return a sequence of arguments derived from ripgrep rc configuration files.
pub fn args() -> Vec<OsString> {
    let config_path = match env::var_os("RIPGREP_CONFIG_PATH") {
        None => return vec![],
        Some(config_path) => {
            if config_path.is_empty() {
                return vec![];
            }
            PathBuf::from(config_path)
        }
    };
    let (args, errs) = match parse(&config_path) {
        Ok((args, errs)) => (args, errs),
        Err(err) => {
            message!("{}", err);
            return vec![];
        }
    };
    if !errs.is_empty() {
        for err in errs {
            message!("{}:{}", config_path.display(), err);
        }
    }
    log::debug!(
        "{}: arguments loaded from config file: {:?}",
        config_path.display(),
        args
    );
    args
}

/// Parse a single ripgrep rc file from the given path.
///
/// On success, this returns a set of shell arguments, in order, that should
/// be pre-pended to the arguments given to ripgrep at the command line.
///
/// If the file could not be read, then an error is returned. If there was
/// a problem parsing one or more lines in the file, then errors are returned
/// for each line in addition to successfully parsed arguments.
fn parse<P: AsRef<Path>>(
    path: P,
) -> Result<(Vec<OsString>, Vec<Error>)> {
    let path = path.as_ref();
    let file = File::open(&path).context(err::ConfigIO { path })?;
    let res = parse_reader(file).context(err::ConfigIO { path })?;
    Ok(res)
}

/// Parse a single ripgrep rc file from the given reader.
///
/// Callers should not provided a buffered reader, as this routine will use its
/// own buffer internally.
///
/// On success, this returns a set of shell arguments, in order, that should
/// be pre-pended to the arguments given to ripgrep at the command line.
///
/// If the reader could not be read, then an error is returned. If there was a
/// problem parsing one or more lines, then errors are returned for each line
/// in addition to successfully parsed arguments.
fn parse_reader<R: io::Read>(
    rdr: R,
) -> io::Result<(Vec<OsString>, Vec<Error>)> {
    let bufrdr = io::BufReader::new(rdr);
    let (mut args, mut errs) = (vec![], vec![]);
    let mut line_number = 0u64;
    bufrdr.for_byte_line_with_terminator(|line| {
        line_number += 1;

        let line = line.trim();
        if line.is_empty() || line[0] == b'#' {
            return Ok(true);
        }
        match line.to_os_str() {
            Ok(osstr) => {
                args.push(osstr.to_os_string());
            }
            Err(err) => {
                let ctx = snafu::Context {
                    error: err,
                    context: err::ConfigInvalidUTF8 { line_number },
                };
                errs.push(ctx.into());
            }
        }
        Ok(true)
    })?;
    Ok((args, errs))
}

#[cfg(test)]
mod tests {
    use std::ffi::OsString;
    use super::parse_reader;

    #[test]
    fn basic() {
        let (args, errs) = parse_reader(&b"\
# Test
--context=0
   --smart-case
-u


   # --bar
--foo
"[..]).unwrap();
        assert!(errs.is_empty());
        let args: Vec<String> =
            args.into_iter().map(|s| s.into_string().unwrap()).collect();
        assert_eq!(args, vec![
            "--context=0", "--smart-case", "-u", "--foo",
        ]);
    }

    // We test that we can handle invalid UTF-8 on Unix-like systems.
    #[test]
    #[cfg(unix)]
    fn error() {
        use std::os::unix::ffi::OsStringExt;

        let (args, errs) = parse_reader(&b"\
quux
foo\xFFbar
baz
"[..]).unwrap();
        assert!(errs.is_empty());
        assert_eq!(args, vec![
            OsString::from("quux"),
            OsString::from_vec(b"foo\xFFbar".to_vec()),
            OsString::from("baz"),
        ]);
    }

    // ... but test that invalid UTF-8 fails on Windows.
    #[test]
    #[cfg(not(unix))]
    fn error() {
        let (args, errs) = parse_reader(&b"\
quux
foo\xFFbar
baz
"[..]).unwrap();
        assert_eq!(errs.len(), 1);
        assert_eq!(args, vec![
            OsString::from("quux"),
            OsString::from("baz"),
        ]);
    }
}