fls 0.1.0

A fast POSIX ls that doesn't require a libc
use crate::{
    cli::{App, Color, FollowSymlinks},
    utils::memcmp,
    Style,
};
use veneer::{
    fs::{DType, Directory},
    syscalls, CStr,
};

pub struct DirEntry<'a> {
    pub name: CStr<'a>,
    pub inode: libc::c_ulong,
    pub d_type: DType,
}

impl<'a> From<veneer::fs::DirEntry<'a>> for DirEntry<'a> {
    fn from(other: veneer::fs::DirEntry<'a>) -> Self {
        Self {
            name: other.name(),
            inode: other.inode(),
            d_type: other.d_type(),
        }
    }
}

pub trait DirEntryExt {
    fn name(&self) -> CStr;
    fn style(&self, dir: &Directory, app: &App) -> (Style, Option<u8>);
    fn inode(&self) -> u64;
    fn blocks(&self) -> u64;
    fn time(&self) -> libc::time_t;
    fn d_type(&self) -> DType;
    fn size(&self) -> libc::off_t;
}

#[derive(Clone, Copy)]
pub enum EntryType {
    Directory,
    Executable,
    Regular,
    Link,
    BrokenLink,
    Fifo,
    Socket,
    Other,
}

impl EntryType {
    fn style(self, app: &App) -> (Option<Style>, Option<u8>) {
        use crate::cli::Suffixes;
        use EntryType::*;
        use Style::*;
        match (self, app.suffixes) {
            (Directory, Suffixes::None) => (Some(BlueBold), None),
            (Directory, _) => (Some(BlueBold), Some(b'/')),
            (Executable, Suffixes::All) => (Some(GreenBold), Some(b'*')),
            (Executable, _) => (Some(GreenBold), None),
            (Regular, _) => (None, None),
            (Link, Suffixes::All) => (Some(CyanBold), Some(b'@')),
            (Link, _) => (Some(CyanBold), None),
            (BrokenLink, Suffixes::All) => (Some(RedBold), Some(b'@')),
            (BrokenLink, _) => (Some(RedBold), None),
            (Fifo, Suffixes::All) => (Some(YellowBold), Some(b'|')),
            (Socket, _) => (Some(MagentaBold), None),
            (Fifo | Other, _) => (Some(YellowBold), None),
        }
    }
}

impl<'a> DirEntryExt for (DirEntry<'a>, Option<crate::Status>) {
    fn name(&self) -> CStr {
        self.0.name
    }

    fn inode(&self) -> u64 {
        self.0.inode
    }

    fn blocks(&self) -> u64 {
        if let Some(st) = &self.1 {
            st.blocks as u64
        } else {
            0
        }
    }

    fn time(&self) -> libc::time_t {
        if let Some(st) = &self.1 {
            st.time
        } else {
            0
        }
    }

    fn size(&self) -> libc::off_t {
        if let Some(st) = &self.1 {
            st.size
        } else {
            0
        }
    }

    fn d_type(&self) -> DType {
        self.0.d_type
    }

    fn style(&self, dir: &veneer::fs::Directory, app: &App) -> (Style, Option<u8>) {
        use EntryType::*;

        // Deduce the correct entry type
        let entry_type = if let Some(status) = &self.1 {
            entry_type_from_status(status)
        } else if app.color == Color::Never && app.suffixes == crate::cli::Suffixes::None {
            // DO nothing extra if no colors and no suffixes are required
            Regular
        } else if app.color == Color::Auto {
            match self.0.d_type {
                DType::DIR => Directory,
                DType::FIFO => Fifo,
                DType::SOCK => Socket,
                DType::CHR | DType::BLK => Other,
                DType::LNK => Link,
                DType::REG | DType::UNKNOWN => Regular,
            }
        } else {
            match self.0.d_type {
                DType::DIR => Directory,
                DType::FIFO => Fifo,
                DType::SOCK => Socket,
                DType::CHR | DType::BLK => Other,
                DType::REG => syscalls::faccessat(dir.raw_fd(), self.name(), libc::X_OK)
                    .map(|_| Executable)
                    .unwrap_or(Regular),
                DType::LNK => syscalls::faccessat(dir.raw_fd(), self.name(), libc::F_OK)
                    .map(|_| Link)
                    .unwrap_or(BrokenLink),
                DType::UNKNOWN => if app.follow_symlinks == FollowSymlinks::Always {
                    syscalls::fstatat(dir.raw_fd(), self.0.name)
                } else {
                    syscalls::lstatat(dir.raw_fd(), self.0.name)
                }
                .map(|status| {
                    let status = app.convert_status(status);
                    let entry_type = status.mode & libc::S_IFMT;
                    if entry_type == libc::S_IFDIR {
                        Directory
                    } else if entry_type == libc::S_IFIFO {
                        Fifo
                    } else if entry_type == libc::S_IFLNK {
                        if app.color == Color::Always
                            && syscalls::faccessat(dir.raw_fd(), self.0.name, libc::F_OK).is_err()
                        {
                            BrokenLink
                        } else {
                            Link
                        }
                    } else if status.mode & libc::S_IXUSR > 0 {
                        Executable
                    } else {
                        Regular
                    }
                })
                .unwrap_or(BrokenLink),
            }
        };

        let (style, suffix) = entry_type.style(app);

        if app.color == Color::Never {
            return (Style::Reset, suffix);
        }

        if let Some(style) = style {
            (style, suffix)
        } else {
            (extension_style(self.name().as_bytes()), suffix)
        }
    }
}

include!(concat!(env!("OUT_DIR"), "/codegen.rs"));

pub fn extension_style(name: &[u8]) -> Style {
    if name.get(0) == Some(&b'#') || name.last() == Some(&b'~') || name.last() == Some(&b'#') {
        return Style::Fixed(244);
    }
    let extension = match name.rsplit(|b| *b == b'.').next() {
        None => return Style::White,
        Some(ext) => ext,
    };
    if let Ok(i) = EXTENSION_STYLES.binary_search_by(|probe| memcmp(probe.0, extension)) {
        EXTENSION_STYLES[i].1
    } else {
        Style::White
    }
}

fn entry_type_from_status(status: &crate::Status) -> EntryType {
    use EntryType::*;
    let entry_type = status.mode & libc::S_IFMT;
    if entry_type == libc::S_IFDIR {
        Directory
    } else if entry_type == libc::S_IFIFO {
        Fifo
    } else if entry_type == libc::S_IFSOCK {
        Socket
    } else if entry_type == libc::S_IFLNK {
        Link
    } else if status.mode & libc::S_IXUSR > 0 {
        Executable
    } else if entry_type == libc::S_IFREG {
        Regular
    } else {
        Other
    }
}