gix_glob/search/
pattern.rs1use std::{
2 io::Read,
3 path::{Path, PathBuf},
4};
5
6use bstr::{BStr, BString, ByteSlice, ByteVec};
7
8use crate::{pattern::Case, search::Pattern};
9
10#[derive(PartialEq, Eq, Debug, Hash, Ord, PartialOrd, Clone, Default)]
15pub struct List<T: Pattern> {
16 pub patterns: Vec<Mapping<T::Value>>,
21
22 pub source: Option<PathBuf>,
25
26 pub base: Option<BString>,
29}
30
31#[derive(PartialEq, Eq, Debug, Hash, Ord, PartialOrd, Clone)]
33pub struct Mapping<T> {
34 pub pattern: crate::Pattern,
36 pub value: T,
38 pub sequence_number: usize,
40}
41
42fn read_in_full_ignore_missing(path: &Path, follow_symlinks: bool, buf: &mut Vec<u8>) -> std::io::Result<bool> {
43 buf.clear();
44 let file = if follow_symlinks {
45 std::fs::File::open(path)
46 } else {
47 gix_features::fs::open_options_no_follow().read(true).open(path)
48 };
49 Ok(match file {
50 Ok(mut file) => {
51 if let Err(err) = file.read_to_end(buf) {
52 if io_err_is_dir(&err) {
53 false
54 } else {
55 return Err(err);
56 }
57 } else {
58 true
59 }
60 }
61 Err(err) if err.kind() == std::io::ErrorKind::NotFound || io_err_is_dir(&err) => false,
62 Err(err) => return Err(err),
63 })
64}
65
66fn io_err_is_dir(err: &std::io::Error) -> bool {
67 let raw = err.raw_os_error();
69 raw == Some(if cfg!(windows) { 5 } else { 21 }) || raw == Some(20)
72}
73
74impl<T> List<T>
76where
77 T: Pattern,
78{
79 pub fn from_bytes(bytes: &[u8], source_file: PathBuf, root: Option<&Path>) -> Self {
83 let patterns = T::bytes_to_patterns(bytes, source_file.as_path());
84 let base = root
85 .and_then(|root| source_file.parent().expect("file").strip_prefix(root).ok())
86 .and_then(|base| {
87 (!base.as_os_str().is_empty()).then(|| {
88 let mut base: BString =
89 gix_path::to_unix_separators_on_windows(gix_path::into_bstr(base)).into_owned();
90
91 base.push_byte(b'/');
92 base
93 })
94 });
95 List {
96 patterns,
97 source: Some(source_file),
98 base,
99 }
100 }
101
102 pub fn from_file(
105 source: impl Into<PathBuf>,
106 root: Option<&Path>,
107 follow_symlinks: bool,
108 buf: &mut Vec<u8>,
109 ) -> std::io::Result<Option<Self>> {
110 let source = source.into();
111 Ok(read_in_full_ignore_missing(&source, follow_symlinks, buf)?.then(|| Self::from_bytes(buf, source, root)))
112 }
113}
114
115impl<T> List<T>
117where
118 T: Pattern,
119{
120 pub fn strip_base_handle_recompute_basename_pos<'a>(
126 &self,
127 relative_path: &'a BStr,
128 basename_pos: Option<usize>,
129 case: Case,
130 ) -> Option<(&'a BStr, Option<usize>)> {
131 match self.base.as_deref() {
132 Some(base) => strip_base_handle_recompute_basename_pos(base.as_bstr(), relative_path, basename_pos, case)?,
133 None => (relative_path, basename_pos),
134 }
135 .into()
136 }
137}
138
139pub fn strip_base_handle_recompute_basename_pos<'a>(
144 base: &BStr,
145 relative_path: &'a BStr,
146 basename_pos: Option<usize>,
147 case: Case,
148) -> Option<(&'a BStr, Option<usize>)> {
149 Some((
150 match case {
151 Case::Sensitive => relative_path.strip_prefix(base.as_bytes())?.as_bstr(),
152 Case::Fold => {
153 let rela_dir = relative_path.get(..base.len())?;
154 if !rela_dir.eq_ignore_ascii_case(base) {
155 return None;
156 }
157 &relative_path[base.len()..]
158 }
159 },
160 basename_pos.and_then(|pos| {
161 let pos = pos - base.len();
162 (pos != 0).then_some(pos)
163 }),
164 ))
165}