Skip to content

Commit

Permalink
Add support for more indicators
Browse files Browse the repository at this point in the history
Partially addresses #6.
  • Loading branch information
tavianator authored and sharkdp committed Oct 8, 2021
1 parent e441c13 commit dde45bf
Show file tree
Hide file tree
Showing 2 changed files with 184 additions and 62 deletions.
24 changes: 18 additions & 6 deletions src/fs.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,24 @@
use std::fs;

#[cfg(any(unix, target_os = "redox"))]
pub fn is_executable(md: &fs::Metadata) -> bool {
use std::os::unix::fs::PermissionsExt;
md.permissions().mode() & 0o111 != 0
use std::os::unix::fs::MetadataExt;

/// Get the UNIX-style mode bits from some metadata if available, otherwise 0.
#[allow(unused_variables)]
pub fn mode(md: &fs::Metadata) -> u32 {
#[cfg(any(unix, target_os = "redox"))]
return md.mode();

#[cfg(not(any(unix, target_os = "redox")))]
return 0;
}

#[cfg(any(windows, target_os = "wasi"))]
pub fn is_executable(_: &fs::Metadata) -> bool {
false
/// Get the number of hard links to a file, or 1 if unknown.
#[allow(unused_variables)]
pub fn nlink(md: &fs::Metadata) -> u64 {
#[cfg(any(unix, target_os = "redox"))]
return md.nlink();

#[cfg(not(any(unix, target_os = "redox")))]
return 1;
}
222 changes: 166 additions & 56 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -256,68 +256,107 @@ impl LsColors {
self.style_for_path_with_metadata(path, metadata.as_ref())
}

/// Get the ANSI style for a path, given the corresponding `Metadata` struct.
///
/// *Note:* The `Metadata` struct must have been acquired via `Path::symlink_metadata` in
/// order to colorize symbolic links correctly.
pub fn style_for_path_with_metadata<P: AsRef<Path>>(
&self,
path: P,
metadata: Option<&std::fs::Metadata>,
) -> Option<&Style> {
if let Some(metadata) = metadata {
if metadata.is_dir() {
return self.style_for_indicator(Indicator::Directory);
}
/// Check if an indicator has an associated color.
fn has_color_for(&self, indicator: Indicator) -> bool {
self.indicator_mapping.contains_key(&indicator)
}

if metadata.file_type().is_symlink() {
// This works because `Path::exists` traverses symlinks.
if path.as_ref().exists() {
return self.style_for_indicator(Indicator::SymbolicLink);
/// Get the indicator type for a path with corresponding metadata.
fn indicator_for(&self, path: &Path, metadata: Option<&std::fs::Metadata>) -> Indicator {
if let Some(metadata) = metadata {
let file_type = metadata.file_type();

if file_type.is_file() {
let mode = crate::fs::mode(metadata);
let nlink = crate::fs::nlink(metadata);

if self.has_color_for(Indicator::Setuid) && mode & 0o4000 != 0 {
Indicator::Setuid
} else if self.has_color_for(Indicator::Setgid) && mode & 0o2000 != 0 {
Indicator::Setgid
} else if self.has_color_for(Indicator::ExecutableFile) && mode & 0o0111 != 0 {
Indicator::ExecutableFile
} else if self.has_color_for(Indicator::MultipleHardLinks) && nlink > 1 {
Indicator::MultipleHardLinks
} else {
return self.style_for_indicator(Indicator::OrphanedSymbolicLink);
Indicator::RegularFile
}
}

#[cfg(unix)]
{
use std::os::unix::fs::FileTypeExt;

let filetype = metadata.file_type();
if filetype.is_fifo() {
return self.style_for_indicator(Indicator::FIFO);
}
if filetype.is_socket() {
return self.style_for_indicator(Indicator::Socket);
} else if file_type.is_dir() {
let mode = crate::fs::mode(metadata);

if self.has_color_for(Indicator::StickyAndOtherWritable) && mode & 0o1002 == 0o1002
{
Indicator::StickyAndOtherWritable
} else if self.has_color_for(Indicator::OtherWritable) && mode & 0o0002 != 0 {
Indicator::OtherWritable
} else if self.has_color_for(Indicator::Sticky) && mode & 0o1000 != 0 {
Indicator::Sticky
} else {
Indicator::Directory
}
if filetype.is_block_device() {
return self.style_for_indicator(Indicator::BlockDevice);
} else if file_type.is_symlink() {
// This works because `Path::exists` traverses symlinks.
if self.has_color_for(Indicator::OrphanedSymbolicLink) && !path.exists() {
return Indicator::OrphanedSymbolicLink;
}
if filetype.is_char_device() {
return self.style_for_indicator(Indicator::CharacterDevice);

Indicator::SymbolicLink
} else {
#[cfg(unix)]
{
use std::os::unix::fs::FileTypeExt;

if file_type.is_fifo() {
return Indicator::FIFO;
}
if file_type.is_socket() {
return Indicator::Socket;
}
if file_type.is_block_device() {
return Indicator::BlockDevice;
}
if file_type.is_char_device() {
return Indicator::CharacterDevice;
}
}
}

if crate::fs::is_executable(&metadata) {
return self.style_for_indicator(Indicator::ExecutableFile);
// Treat files of unknown type as errors
Indicator::MissingFile
}
} else {
// Default to a regular file, so we still try the suffix map when no metadata is available
Indicator::RegularFile
}
}

// Note: using '.to_str()' here means that filename
// matching will not work with invalid-UTF-8 paths.
let filename = path.as_ref().file_name()?.to_str()?.to_ascii_lowercase();

// We need to traverse LS_COLORS from back to front
// to be consistent with `ls`:
for (suffix, style) in self.suffix_mapping.iter().rev() {
// Note: For some reason, 'ends_with' is much
// slower if we omit `.as_str()` here:
if filename.ends_with(suffix.as_str()) {
return Some(style);
/// Get the ANSI style for a path, given the corresponding `Metadata` struct.
///
/// *Note:* The `Metadata` struct must have been acquired via `Path::symlink_metadata` in
/// order to colorize symbolic links correctly.
pub fn style_for_path_with_metadata<P: AsRef<Path>>(
&self,
path: P,
metadata: Option<&std::fs::Metadata>,
) -> Option<&Style> {
let indicator = self.indicator_for(path.as_ref(), metadata);

if indicator == Indicator::RegularFile {
// Note: using '.to_str()' here means that filename
// matching will not work with invalid-UTF-8 paths.
let filename = path.as_ref().file_name()?.to_str()?.to_ascii_lowercase();

// We need to traverse LS_COLORS from back to front
// to be consistent with `ls`:
for (suffix, style) in self.suffix_mapping.iter().rev() {
// Note: For some reason, 'ends_with' is much
// slower if we omit `.as_str()` here:
if filename.ends_with(suffix.as_str()) {
return Some(style);
}
}
}

None
self.style_for_indicator(indicator)
}

/// Get ANSI styles for each component of a given path. Components already include the path
Expand All @@ -337,13 +376,27 @@ impl LsColors {
/// For example, the style for `mi` (missing file) falls back to `or` (orphaned symbolic link)
/// if it has not been specified explicitly.
pub fn style_for_indicator(&self, indicator: Indicator) -> Option<&Style> {
match indicator {
Indicator::MissingFile => self
.indicator_mapping
.get(&Indicator::MissingFile)
.or_else(|| self.indicator_mapping.get(&Indicator::OrphanedSymbolicLink)),
_ => self.indicator_mapping.get(&indicator),
}
self.indicator_mapping
.get(&indicator)
.or_else(|| {
self.indicator_mapping.get(&match indicator {
Indicator::Setuid
| Indicator::Setgid
| Indicator::ExecutableFile
| Indicator::MultipleHardLinks => Indicator::RegularFile,

Indicator::StickyAndOtherWritable
| Indicator::OtherWritable
| Indicator::Sticky => Indicator::Directory,

Indicator::OrphanedSymbolicLink => Indicator::SymbolicLink,

Indicator::MissingFile => Indicator::OrphanedSymbolicLink,

_ => indicator,
})
})
.or_else(|| self.indicator_mapping.get(&Indicator::Normal))
}
}

Expand Down Expand Up @@ -512,6 +565,63 @@ mod tests {
assert_eq!(Some(Color::Yellow), style_missing.foreground);
}

#[cfg(unix)]
#[test]
fn style_for_setid() {
use std::fs::{set_permissions, Permissions};
use std::os::unix::fs::PermissionsExt;

let tmp_dir = temp_dir();
let tmp_file = create_file(tmp_dir.path().join("setid"));
let perms = Permissions::from_mode(0o6750);
set_permissions(&tmp_file, perms).unwrap();

let suid_style = get_default_style(&tmp_file).unwrap();
assert_eq!(Some(Color::Red), suid_style.background);

let lscolors = LsColors::from_string("su=0");
let sgid_style = lscolors.style_for_path(&tmp_file).unwrap();
assert_eq!(Some(Color::Yellow), sgid_style.background);
}

#[cfg(unix)]
#[test]
fn style_for_multi_hard_links() {
let tmp_dir = temp_dir();
let tmp_file = create_file(tmp_dir.path().join("file1"));
std::fs::hard_link(&tmp_file, tmp_dir.path().join("file2")).unwrap();

let lscolors = LsColors::from_string("mh=35");
let style = lscolors.style_for_path(&tmp_file).unwrap();
assert_eq!(Some(Color::Magenta), style.foreground);
}

#[cfg(unix)]
#[test]
fn style_for_sticky_other_writable() {
use std::fs::{set_permissions, Permissions};
use std::os::unix::fs::PermissionsExt;

let tmp_root = temp_dir();
let tmp_dir = create_dir(tmp_root.path().join("test-dir"));
let perms = Permissions::from_mode(0o1777);
set_permissions(&tmp_dir, perms).unwrap();

let so_style = get_default_style(&tmp_dir).unwrap();
assert_eq!(Some(Color::Black), so_style.foreground);
assert_eq!(Some(Color::Green), so_style.background);

let lscolors1 = LsColors::from_string("tw=0");
let ow_style = lscolors1.style_for_path(&tmp_dir).unwrap();
assert_eq!(Some(Color::Blue), ow_style.foreground);
assert_eq!(Some(Color::Green), ow_style.background);

let lscolors2 = LsColors::from_string("tw=0:ow=0");
let st_style = lscolors2.style_for_path(&tmp_dir).unwrap();
assert_eq!(Some(Color::White), st_style.foreground);
assert_eq!(Some(Color::Blue), st_style.background);
}

#[test]
fn style_for_path_components() {
use std::ffi::OsString;
Expand Down

0 comments on commit dde45bf

Please sign in to comment.