use std::borrow::Cow;
use std::marker::PhantomData;
use std::path::Path;
use std::ptr::NonNull;
use crate::error::{self, Error};
use crate::sys;
const PLANE: &str = "walk";
const BATCH: usize = 64;
#[repr(C)]
struct Handle {
_opaque: [u8; 0],
}
#[derive(Clone, Copy)]
#[repr(C)]
struct Term {
kind: u32,
reserved: u32,
text: *const u8,
text_len: usize,
}
#[repr(C)]
struct RawSpec {
struct_size: u32,
flags: u32,
max_depth: u64,
terms: *const Term,
term_count: usize,
}
#[derive(Clone, Copy, Default)]
#[repr(C)]
struct Raw {
path: sys::Text,
size: u64,
genus: u32,
reserved: u32,
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
pub enum Genus {
#[default]
Code,
Docs,
Data,
}
impl Genus {
fn from_abi(raw: u32) -> Self {
match raw {
1 => Self::Docs,
2 => Self::Data,
_ => Self::Code,
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub enum Policy {
Hidden,
NoIgnore,
NoIgnoreVcs,
NoIgnoreDot,
NoIgnoreParent,
NoIgnoreExclude,
NoIgnoreGlobal,
NoIgnoreFiles,
NoRequireGit,
IgnoreFileIcase,
Follow,
OneFileSystem,
GlobIcase,
Members,
TolerateGaps,
}
impl Policy {
const fn bit(self) -> u32 {
1 << match self {
Self::Hidden => 0,
Self::NoIgnore => 1,
Self::NoIgnoreVcs => 2,
Self::NoIgnoreDot => 3,
Self::NoIgnoreParent => 4,
Self::NoIgnoreExclude => 5,
Self::NoIgnoreGlobal => 6,
Self::NoIgnoreFiles => 7,
Self::NoRequireGit => 8,
Self::IgnoreFileIcase => 9,
Self::Follow => 10,
Self::OneFileSystem => 11,
Self::GlobIcase => 12,
Self::Members => 13,
Self::TolerateGaps => 14,
}
}
}
#[derive(Clone, Copy, Debug)]
#[repr(C)]
pub struct Limits {
struct_size: u32,
binary_window: u32,
file_cap: u64,
type_rows: u32,
type_names: u32,
brace_cap: u32,
brace_group_cap: u32,
}
impl Default for Limits {
fn default() -> Self {
Self {
struct_size: size_of::<Self>() as u32,
binary_window: 0,
file_cap: 0,
type_rows: 0,
type_names: 0,
brace_cap: 0,
brace_group_cap: 0,
}
}
}
impl Limits {
#[must_use]
pub fn binary_window(&self) -> usize {
self.binary_window as usize
}
#[must_use]
pub fn file_cap(&self) -> u64 {
self.file_cap
}
#[must_use]
pub fn type_rows(&self) -> usize {
self.type_rows as usize
}
#[must_use]
pub fn type_names(&self) -> usize {
self.type_names as usize
}
#[must_use]
pub fn brace_cap(&self) -> usize {
self.brace_cap as usize
}
#[must_use]
pub fn brace_group_cap(&self) -> usize {
self.brace_group_cap as usize
}
}
#[derive(Clone, Default)]
pub struct Spec<'t> {
terms: Vec<Term>,
flags: u32,
max_depth: u64,
borrowed: PhantomData<&'t [u8]>,
}
impl std::fmt::Debug for Spec<'_> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let terms = self.terms.iter().map(|term| {
let text = unsafe { std::slice::from_raw_parts(term.text, term.text_len) };
(
KINDS.get(term.kind as usize).copied().unwrap_or("?"),
String::from_utf8_lossy(text),
)
});
f.debug_struct("Spec")
.field("terms", &terms.collect::<Vec<_>>())
.field("flags", &format_args!("{:#014b}", self.flags))
.field("max_depth", &self.max_depth)
.finish()
}
}
const KINDS: [&str; 7] = [
"root",
"glob",
"not_glob",
"iglob",
"of_type",
"not_type",
"ignore_file",
];
impl<'t> Spec<'t> {
#[must_use]
pub fn new() -> Self {
Self::default()
}
#[must_use]
pub fn root(self, path: &'t Path) -> Self {
self.term(0, path.as_os_str().as_encoded_bytes())
}
#[must_use]
pub fn glob(self, glob: &'t str) -> Self {
self.term(1, glob.as_bytes())
}
#[must_use]
pub fn not_glob(self, glob: &'t str) -> Self {
self.term(2, glob.as_bytes())
}
#[must_use]
pub fn iglob(self, glob: &'t str) -> Self {
self.term(3, glob.as_bytes())
}
#[must_use]
pub fn of_type(self, name: &'t str) -> Self {
self.term(4, name.as_bytes())
}
#[must_use]
pub fn not_type(self, name: &'t str) -> Self {
self.term(5, name.as_bytes())
}
#[must_use]
pub fn ignore_file(self, name: &'t str) -> Self {
self.term(6, name.as_bytes())
}
#[must_use]
pub fn with(mut self, policy: Policy) -> Self {
self.flags |= policy.bit();
self
}
#[must_use]
pub fn max_depth(mut self, depth: u64) -> Self {
self.max_depth = depth;
self
}
fn term(mut self, kind: u32, text: &'t [u8]) -> Self {
self.terms.push(Term {
kind,
reserved: 0,
text: text.as_ptr(),
text_len: text.len(),
});
self
}
}
pub struct Walk {
handle: NonNull<Handle>,
}
impl Walk {
pub fn limits() -> Result<Limits, Error> {
let mut out = Limits::default();
let status = unsafe { ffi::irgx_walk_limits(&raw mut out) };
if status < 0 {
return Err(error::plane_fault(status, PLANE));
}
Ok(out)
}
pub fn open(spec: &Spec<'_>) -> Result<Self, Error> {
let raw = RawSpec {
struct_size: size_of::<RawSpec>() as u32,
flags: spec.flags,
max_depth: spec.max_depth,
terms: spec.terms.as_ptr(),
term_count: spec.terms.len(),
};
let mut out: *mut Handle = std::ptr::null_mut();
let status = unsafe { ffi::irgx_walk_open(&raw const raw, &raw mut out) };
if status < 0 {
return Err(error::plane_fault(status, PLANE));
}
NonNull::new(out)
.map(|handle| Self { handle })
.ok_or_else(|| Error::Inconsistent {
message: "the walk plane reported success and produced no handle".to_owned(),
})
}
#[must_use]
pub fn len(&self) -> usize {
unsafe { ffi::irgx_walk_count(self.handle.as_ptr()) }
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.len() == 0
}
#[must_use]
pub fn gapped(&self) -> u32 {
unsafe { ffi::irgx_walk_gapped(self.handle.as_ptr()) }
}
#[must_use]
pub fn holds(&self, path: &Path) -> bool {
let bytes = slash_path(path);
let status =
unsafe { ffi::irgx_walk_holds(self.handle.as_ptr(), bytes.as_ptr(), bytes.len()) };
status == sys::MATCH
}
pub fn rewind(&mut self) {
unsafe { ffi::irgx_walk_rewind(self.handle.as_ptr()) };
}
pub fn next_entry(&mut self) -> Result<Option<Entry<'_>>, Error> {
let mut raw = Raw::default();
let status = unsafe { ffi::irgx_walk_next(self.handle.as_ptr(), &raw mut raw) };
if status < 0 {
return Err(error::plane_fault(status, PLANE));
}
Ok((status == sys::MATCH).then_some(Entry {
raw,
owner: PhantomData,
}))
}
pub fn entries(&mut self) -> Entries<'_> {
Entries {
handle: self.handle,
buffer: [Raw::default(); BATCH],
filled: 0,
at: 0,
drained: false,
owner: PhantomData,
}
}
}
impl Drop for Walk {
fn drop(&mut self) {
unsafe { ffi::irgx_walk_close(self.handle.as_ptr()) };
}
}
impl std::fmt::Debug for Walk {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Walk")
.field("files", &self.len())
.field("gapped", &self.gapped())
.finish()
}
}
pub struct Entries<'w> {
handle: NonNull<Handle>,
buffer: [Raw; BATCH],
filled: usize,
at: usize,
drained: bool,
owner: PhantomData<&'w mut ()>,
}
impl<'w> Iterator for Entries<'w> {
type Item = Result<Entry<'w>, Error>;
fn next(&mut self) -> Option<Self::Item> {
if self.at == self.filled {
if self.drained {
return None;
}
let mut written = 0usize;
let status = unsafe {
ffi::irgx_walk_next_batch(
self.handle.as_ptr(),
self.buffer.as_mut_ptr(),
BATCH,
&raw mut written,
)
};
if status < 0 {
self.drained = true;
return Some(Err(error::plane_fault(status, PLANE)));
}
if written == 0 {
self.drained = true;
return None;
}
self.drained = written < BATCH;
self.filled = written;
self.at = 0;
}
let raw = self.buffer[self.at];
self.at += 1;
Some(Ok(Entry {
raw,
owner: PhantomData,
}))
}
}
#[derive(Clone, Copy)]
pub struct Entry<'w> {
raw: Raw,
owner: PhantomData<&'w ()>,
}
impl<'w> Entry<'w> {
#[must_use]
pub fn path(&self) -> &'w [u8] {
unsafe { sys::borrowed(&self.raw.path) }
}
#[must_use]
pub fn path_str(&self) -> Option<&'w str> {
std::str::from_utf8(self.path()).ok()
}
#[must_use]
pub fn size(&self) -> u64 {
self.raw.size
}
#[must_use]
pub fn genus(&self) -> Genus {
Genus::from_abi(self.raw.genus)
}
}
impl std::fmt::Debug for Entry<'_> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Entry")
.field("path", &String::from_utf8_lossy(self.path()))
.field("size", &self.raw.size)
.field("genus", &self.genus())
.finish()
}
}
#[must_use]
pub fn is_binary(bytes: &[u8]) -> bool {
let status = unsafe { ffi::irgx_walk_binary(bytes.as_ptr(), bytes.len()) };
status == sys::MATCH
}
fn slash_path(path: &Path) -> Cow<'_, [u8]> {
let bytes = path.as_os_str().as_encoded_bytes();
#[cfg(windows)]
if bytes.contains(&b'\\') {
return Cow::Owned(
bytes
.iter()
.map(|&byte| if byte == b'\\' { b'/' } else { byte })
.collect(),
);
}
Cow::Borrowed(bytes)
}
pub fn genus(path: &Path) -> Result<Genus, Error> {
let bytes = path.as_os_str().as_encoded_bytes();
let mut out = 0u32;
let status = unsafe { ffi::irgx_walk_genus(bytes.as_ptr(), bytes.len(), &raw mut out) };
if status < 0 {
return Err(error::plane_fault(status, PLANE));
}
Ok(Genus::from_abi(out))
}
mod ffi {
use super::{Handle, Limits, Raw, RawSpec};
unsafe extern "C" {
pub fn irgx_walk_limits(out: *mut Limits) -> i32;
pub fn irgx_walk_open(spec: *const RawSpec, out: *mut *mut Handle) -> i32;
pub fn irgx_walk_count(w: *const Handle) -> usize;
pub fn irgx_walk_gapped(w: *const Handle) -> u32;
pub fn irgx_walk_next(w: *mut Handle, out: *mut Raw) -> i32;
pub fn irgx_walk_next_batch(
w: *mut Handle,
out: *mut Raw,
cap: usize,
written: *mut usize,
) -> i32;
pub fn irgx_walk_rewind(w: *mut Handle);
pub fn irgx_walk_holds(w: *const Handle, path: *const u8, path_len: usize) -> i32;
pub fn irgx_walk_close(w: *mut Handle);
pub fn irgx_walk_binary(bytes: *const u8, len: usize) -> i32;
pub fn irgx_walk_genus(path: *const u8, len: usize, out: *mut u32) -> i32;
}
}