pub async fn new(origin: impl AsRef<Path> + Send, files: &[IgnoreFile]) -> Result<Self, Error> {
		let origin = origin.as_ref().to_owned();
		let origin = canonicalize(&origin)
			.await
			.map_err(move |err| Error::Canonicalize { path: origin, err })?;

		let origin = simplify_path(&origin);
		let _span = trace_span!("build_filterer", ?origin);

		trace!(files=%files.len(), "loading file contents");
		let (files_contents, errors): (Vec<_>, Vec<_>) = files
			.iter()
			.map(|file| async move {
				trace!(?file, "loading ignore file");
				let content = read_to_string(&file.path)
					.await
					.map_err(|err| Error::Read {
						file: file.path.clone(),
						err,
					})?;
				Ok((file.clone(), content))
			})
			.collect::<FuturesUnordered<_>>()
			.collect::<Vec<_>>()
			.await
			.into_iter()
			.map(|res| match res {
				Ok(o) => (Some(o), None),
				Err(e) => (None, Some(e)),
			})
			.unzip();

		let errors: Vec<Error> = errors.into_iter().flatten().collect();
		if !errors.is_empty() {
			trace!("found {} errors", errors.len());
			return Err(Error::Multi(errors));
		}

		// TODO: different parser/adapter for non-git-syntax ignore files?

		trace!(files=%files_contents.len(), "building ignore list");

		let mut ignores_trie = Trie::new();

		// add builder for the root of the file system, so that we can handle global ignores and globs
		ignores_trie.insert(
			prefix(&origin),
			Ignore {
				gitignore: Gitignore::empty(),
				builder: Some(GitignoreBuilder::new(&origin)),
			},
		);

		let mut total_num_ignores = 0;
		let mut total_num_whitelists = 0;

		for (file, content) in files_contents.into_iter().flatten() {
			let _span = trace_span!("loading ignore file", ?file).entered();

			let applies_in = get_applies_in_path(&origin, &file);

			let mut builder = ignores_trie
				.get(&applies_in.display().to_string())
				.and_then(|node| node.builder.clone())
				.unwrap_or_else(|| GitignoreBuilder::new(&applies_in));

			for line in content.lines() {
				if line.is_empty() || line.starts_with('#') {
					continue;
				}

				trace!(?line, "adding ignore line");
				builder
					.add_line(Some(applies_in.clone().clone()), line)
					.map_err(|err| Error::Glob {
						file: Some(file.path.clone()),
						err,
					})?;
			}
			trace!("compiling globset");
			let compiled_builder = builder
				.build()
				.map_err(|err| Error::Glob { file: None, err })?;

			total_num_ignores += compiled_builder.num_ignores();
			total_num_whitelists += compiled_builder.num_whitelists();

			ignores_trie.insert(
				applies_in.display().to_string(),
				Ignore {
					gitignore: compiled_builder,
					builder: Some(builder),
				},
			);
		}

		trace!(
			files=%files.len(),
			trie=?ignores_trie,
			ignores=%total_num_ignores,
			allows=%total_num_whitelists,
			"ignore files loaded and compiled",
		);

		Ok(Self {
			origin: origin.clone(),
			ignores: ignores_trie,
		})
	}
Read ignore files from disk and load them for filtering.

Use [`empty()`](IgnoreFilter::empty()) if you want an empty filterer,
or to construct one outside an async environment.
crates/ignore-files/src/filter.rs