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
use std::path::{Path, PathBuf};
use crate::{ConfigParser, Error, ParseError, Properties, PropertiesSource, Section};
/// Convenience wrapper for an [`ConfigParser`] that reads files.
pub struct ConfigFile {
// TODO: Arc<Path>. It's more important to have cheap clones than mutability.
/// The path to the open file.
pub path: PathBuf,
/// A [`ConfigParser`] that reads from the file.
pub reader: ConfigParser<std::io::BufReader<std::fs::File>>,
}
impl ConfigFile {
/// Opens a file for reading and uses it to construct an [`ConfigParser`].
///
/// If the file cannot be opened, wraps the [`std::io::Error`] in a [`ParseError`].
pub fn open(path: impl Into<PathBuf>) -> Result<ConfigFile, ParseError> {
let path = path.into();
let file = std::fs::File::open(&path).map_err(ParseError::Io)?;
let reader = ConfigParser::new_buffered_with_path(file, Some(path.as_ref()))?;
Ok(ConfigFile { path, reader })
}
/// Wraps a [`ParseError`] in an [`Error::InFile`].
///
/// Uses the path and current line number from this instance.
pub fn add_error_context(&self, error: ParseError) -> Error {
Error::InFile(self.path.clone(), self.reader.line_no(), error)
}
}
impl Iterator for ConfigFile {
type Item = Result<Section, ParseError>;
fn next(&mut self) -> Option<Self::Item> {
self.reader.next()
}
}
impl std::iter::FusedIterator for ConfigFile {}
impl PropertiesSource for &mut ConfigFile {
/// Adds properties from the file's sections to the specified [`Properties`] map.
///
/// Uses [`ConfigFile::path`] when determining applicability to stop `**` from going too far.
/// Returns parse errors wrapped in an [`Error::InFile`].
fn apply_to(self, props: &mut Properties, path: impl AsRef<Path>) -> Result<(), crate::Error> {
let get_parent = || self.path.parent();
let path = if let Some(parent) = get_parent() {
let path = path.as_ref();
path.strip_prefix(parent).unwrap_or(path)
} else {
path.as_ref()
};
match self.reader.apply_to(props, path) {
Ok(()) => Ok(()),
Err(crate::Error::Parse(e)) => Err(self.add_error_context(e)),
Err(e) => panic!("unexpected error variant {:?}", e),
}
}
}
/// Directory traverser for finding and opening EditorConfig files.
///
/// All the contained files are open for reading and have not had any sections read.
/// When iterated over, either by using it as an [`Iterator`]
/// or by calling [`ConfigFiles::iter`],
/// returns [`ConfigFile`]s in the order that they would apply to a [`Properties`] map.
pub struct ConfigFiles(Vec<ConfigFile>);
impl ConfigFiles {
/// Searches for EditorConfig files that might apply to a file at the specified path.
///
/// This function does not canonicalize the path,
/// but will join relative paths onto the current working directory.
///
/// EditorConfig files are assumed to be named `.editorconfig`
/// unless an override is supplied as the second argument.
#[allow(clippy::needless_pass_by_value)]
pub fn open(
path: impl AsRef<Path>,
config_path_override: Option<impl AsRef<std::path::Path>>,
) -> Result<ConfigFiles, Error> {
use std::borrow::Cow;
let filename = config_path_override
.as_ref()
.map_or_else(|| ".editorconfig".as_ref(), |f| f.as_ref());
Ok(ConfigFiles(if filename.is_relative() {
let mut abs_path = Cow::from(path.as_ref());
if abs_path.is_relative() {
abs_path = std::env::current_dir()
.map_err(Error::InvalidCwd)?
.join(&path)
.into()
}
let mut path = abs_path.as_ref();
let mut vec = Vec::new();
while let Some(dir) = path.parent() {
if let Ok(file) = ConfigFile::open(dir.join(filename)) {
let should_break = file.reader.is_root;
vec.push(file);
if should_break {
break;
}
}
path = dir;
}
vec
} else {
// TODO: Better errors.
vec![ConfigFile::open(filename).map_err(Error::Parse)?]
}))
}
/// Returns an iterator over the contained [`ConfigFiles`].
pub fn iter(&self) -> impl Iterator<Item = &ConfigFile> {
self.0.iter().rev()
}
// To maintain the invariant that these files have not had any sections read,
// there is no `iter_mut` method.
}
impl Iterator for ConfigFiles {
type Item = ConfigFile;
fn next(&mut self) -> Option<ConfigFile> {
self.0.pop()
}
}
impl std::iter::FusedIterator for ConfigFiles {}
impl PropertiesSource for ConfigFiles {
/// Adds properties from the files' sections to the specified [`Properties`] map.
///
/// Ignores the files' paths when determining applicability.
fn apply_to(self, props: &mut Properties, path: impl AsRef<Path>) -> Result<(), crate::Error> {
let path = path.as_ref();
for mut file in self {
file.apply_to(props, path)?;
}
Ok(())
}
}