|
2 | 2 |
|
3 | 3 | use std::path::Path; |
4 | 4 |
|
5 | | -use napi::Error; |
| 5 | +use napi::{bindgen_prelude::Uint8Array, Error, Status}; |
6 | 6 | use napi_derive::napi; |
7 | | -use rstack_ignore::{IgnoreMatcher as CoreIgnoreMatcher, IgnoreSource as CoreIgnoreSource}; |
| 7 | +use rstack_ignore::{ |
| 8 | + GitIgnoreMatcher as CoreGitIgnoreMatcher, IgnoreMatcher as CoreIgnoreMatcher, |
| 9 | + IgnoreSource as CoreIgnoreSource, |
| 10 | +}; |
8 | 11 |
|
9 | 12 | /// A Gitignore-compatible pattern source received from JavaScript. |
10 | 13 | #[napi(object, object_to_js = false)] |
@@ -42,3 +45,95 @@ impl IgnoreMatcher { |
42 | 45 | self.inner.is_ignored(Path::new(&file_path), is_directory) |
43 | 46 | } |
44 | 47 | } |
| 48 | + |
| 49 | +/// JavaScript-facing hierarchy for repository `.gitignore` files. |
| 50 | +#[napi] |
| 51 | +pub struct GitIgnoreMatcher { |
| 52 | + inner: CoreGitIgnoreMatcher, |
| 53 | +} |
| 54 | + |
| 55 | +impl Default for GitIgnoreMatcher { |
| 56 | + fn default() -> Self { |
| 57 | + Self { |
| 58 | + inner: CoreGitIgnoreMatcher::new(), |
| 59 | + } |
| 60 | + } |
| 61 | +} |
| 62 | + |
| 63 | +#[napi] |
| 64 | +impl GitIgnoreMatcher { |
| 65 | + /// Creates an empty matcher whose sources can be added during directory traversal. |
| 66 | + #[napi(constructor)] |
| 67 | + pub fn new() -> Self { |
| 68 | + Self::default() |
| 69 | + } |
| 70 | + |
| 71 | + /// Compiles or replaces rules rooted at a repository-relative POSIX directory. |
| 72 | + #[napi] |
| 73 | + pub fn add_source(&mut self, relative_root: String, patterns: String) -> napi::Result<bool> { |
| 74 | + self.inner |
| 75 | + .add_source(&relative_root, &patterns) |
| 76 | + .map_err(|error| { |
| 77 | + Error::from_reason(format!("Failed to compile .gitignore patterns: {error}")) |
| 78 | + }) |
| 79 | + } |
| 80 | + |
| 81 | + /// Returns whether one repository-relative POSIX path is ignored. |
| 82 | + #[napi] |
| 83 | + pub fn is_ignored(&mut self, relative_path: String, is_directory: bool) -> bool { |
| 84 | + self.inner.is_ignored(&relative_path, is_directory) |
| 85 | + } |
| 86 | + |
| 87 | + /// Matches one directory's entries in a native call and returns one byte per name. |
| 88 | + #[napi] |
| 89 | + pub fn is_ignored_batch( |
| 90 | + &mut self, |
| 91 | + relative_parent: String, |
| 92 | + names: Vec<String>, |
| 93 | + directory_flags: Uint8Array, |
| 94 | + ) -> napi::Result<Uint8Array> { |
| 95 | + if names.len() != directory_flags.len() { |
| 96 | + return Err(Error::new( |
| 97 | + Status::InvalidArg, |
| 98 | + "Name and directory flag counts must match.", |
| 99 | + )); |
| 100 | + } |
| 101 | + |
| 102 | + Ok(self |
| 103 | + .inner |
| 104 | + .is_ignored_batch(&relative_parent, &names, directory_flags.as_ref()) |
| 105 | + .into()) |
| 106 | + } |
| 107 | + |
| 108 | + /// Matches up to 32 entries while avoiding per-directory typed-array allocation. |
| 109 | + #[napi] |
| 110 | + pub fn is_ignored_batch_mask( |
| 111 | + &mut self, |
| 112 | + relative_parent: String, |
| 113 | + names: Vec<String>, |
| 114 | + directory_mask: u32, |
| 115 | + ) -> napi::Result<u32> { |
| 116 | + if names.len() > u32::BITS as usize { |
| 117 | + return Err(Error::new( |
| 118 | + Status::InvalidArg, |
| 119 | + "A bit-mask batch cannot contain more than 32 names.", |
| 120 | + )); |
| 121 | + } |
| 122 | + |
| 123 | + Ok(self |
| 124 | + .inner |
| 125 | + .is_ignored_batch_mask(&relative_parent, &names, directory_mask)) |
| 126 | + } |
| 127 | + |
| 128 | + /// Matches a single directory entry without constructing a JavaScript array. |
| 129 | + #[napi] |
| 130 | + pub fn is_ignored_child( |
| 131 | + &mut self, |
| 132 | + relative_parent: String, |
| 133 | + name: String, |
| 134 | + is_directory: bool, |
| 135 | + ) -> bool { |
| 136 | + self.inner |
| 137 | + .is_ignored_child(&relative_parent, &name, is_directory) |
| 138 | + } |
| 139 | +} |
0 commit comments