Skip to content

Commit 92de874

Browse files
committed
Add support for more indicators
Partially addresses #6.
1 parent bc95124 commit 92de874

2 files changed

Lines changed: 124 additions & 62 deletions

File tree

src/fs.rs

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,22 @@
11
use std::fs;
22

33
#[cfg(any(unix, target_os = "redox"))]
4-
pub fn is_executable(md: &fs::Metadata) -> bool {
5-
use std::os::unix::fs::PermissionsExt;
6-
md.permissions().mode() & 0o111 != 0
4+
use std::os::unix::fs::MetadataExt;
5+
6+
/// Get the UNIX-style mode bits from some metadata if available, otherwise 0.
7+
pub fn mode(md: &fs::Metadata) -> u32 {
8+
#[cfg(any(unix, target_os = "redox"))]
9+
return md.mode();
10+
11+
#[cfg(not(any(unix, target_os = "redox")))]
12+
return 0;
713
}
814

9-
#[cfg(any(windows, target_os = "wasi"))]
10-
pub fn is_executable(_: &fs::Metadata) -> bool {
11-
false
15+
/// Get the number of hard links to a file, or 1 if unknown.
16+
pub fn nlink(md: &fs::Metadata) -> u64 {
17+
#[cfg(any(unix, target_os = "redox"))]
18+
return md.nlink();
19+
20+
#[cfg(not(any(unix, target_os = "redox")))]
21+
return 1;
1222
}

src/lib.rs

Lines changed: 108 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -251,68 +251,106 @@ impl LsColors {
251251
self.style_for_path_with_metadata(path, metadata.as_ref())
252252
}
253253

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

268-
if metadata.file_type().is_symlink() {
269-
// This works because `Path::exists` traverses symlinks.
270-
if path.as_ref().exists() {
271-
return self.style_for_indicator(Indicator::SymbolicLink);
259+
/// Get the indicator type for a path with corresponding metadata.
260+
fn indicator_for(&self, path: &Path, metadata: Option<&std::fs::Metadata>) -> Indicator {
261+
if let Some(metadata) = metadata {
262+
let file_type = metadata.file_type();
263+
264+
if file_type.is_file() {
265+
let mode = crate::fs::mode(metadata);
266+
let nlink = crate::fs::nlink(metadata);
267+
268+
if self.has_color_for(Indicator::Setuid) && mode & 0o4000 != 0 {
269+
Indicator::Setuid
270+
} else if self.has_color_for(Indicator::Setgid) && mode & 0o2000 != 0 {
271+
Indicator::Setgid
272+
} else if self.has_color_for(Indicator::ExecutableFile) && mode & 0o0111 != 0 {
273+
Indicator::ExecutableFile
274+
} else if self.has_color_for(Indicator::MultipleHardLinks) && nlink > 1 {
275+
Indicator::MultipleHardLinks
272276
} else {
273-
return self.style_for_indicator(Indicator::OrphanedSymbolicLink);
274-
}
275-
}
276-
277-
#[cfg(unix)]
278-
{
279-
use std::os::unix::fs::FileTypeExt;
280-
281-
let filetype = metadata.file_type();
282-
if filetype.is_fifo() {
283-
return self.style_for_indicator(Indicator::FIFO);
277+
Indicator::RegularFile
284278
}
285-
if filetype.is_socket() {
286-
return self.style_for_indicator(Indicator::Socket);
279+
} else if file_type.is_dir() {
280+
let mode = crate::fs::mode(metadata);
281+
282+
if self.has_color_for(Indicator::StickyAndOtherWritable) && mode & 0o1002 == 0o1002 {
283+
Indicator::StickyAndOtherWritable
284+
} else if self.has_color_for(Indicator::OtherWritable) && mode & 0o0002 != 0 {
285+
Indicator::OtherWritable
286+
} else if self.has_color_for(Indicator::Sticky) && mode & 0o1000 != 0 {
287+
Indicator::Sticky
288+
} else {
289+
Indicator::Directory
287290
}
288-
if filetype.is_block_device() {
289-
return self.style_for_indicator(Indicator::BlockDevice);
291+
} else if file_type.is_symlink() {
292+
// This works because `Path::exists` traverses symlinks.
293+
if self.has_color_for(Indicator::OrphanedSymbolicLink) && !path.exists() {
294+
return Indicator::OrphanedSymbolicLink;
290295
}
291-
if filetype.is_char_device() {
292-
return self.style_for_indicator(Indicator::CharacterDevice);
296+
297+
Indicator::SymbolicLink
298+
} else {
299+
#[cfg(unix)]
300+
{
301+
use std::os::unix::fs::FileTypeExt;
302+
303+
if file_type.is_fifo() {
304+
return Indicator::FIFO;
305+
}
306+
if file_type.is_socket() {
307+
return Indicator::Socket;
308+
}
309+
if file_type.is_block_device() {
310+
return Indicator::BlockDevice;
311+
}
312+
if file_type.is_char_device() {
313+
return Indicator::CharacterDevice;
314+
}
293315
}
294-
}
295316

296-
if crate::fs::is_executable(&metadata) {
297-
return self.style_for_indicator(Indicator::ExecutableFile);
317+
// Treat files of unknown type as errors
318+
Indicator::MissingFile
298319
}
320+
} else {
321+
// Default to a regular file, so we still try the suffix map when no metadata is available
322+
Indicator::RegularFile
299323
}
324+
}
300325

301-
// Note: using '.to_str()' here means that filename
302-
// matching will not work with invalid-UTF-8 paths.
303-
let filename = path.as_ref().file_name()?.to_str()?.to_ascii_lowercase();
304-
305-
// We need to traverse LS_COLORS from back to front
306-
// to be consistent with `ls`:
307-
for (suffix, style) in self.suffix_mapping.iter().rev() {
308-
// Note: For some reason, 'ends_with' is much
309-
// slower if we omit `.as_str()` here:
310-
if filename.ends_with(suffix.as_str()) {
311-
return Some(style);
326+
/// Get the ANSI style for a path, given the corresponding `Metadata` struct.
327+
///
328+
/// *Note:* The `Metadata` struct must have been acquired via `Path::symlink_metadata` in
329+
/// order to colorize symbolic links correctly.
330+
pub fn style_for_path_with_metadata<P: AsRef<Path>>(
331+
&self,
332+
path: P,
333+
metadata: Option<&std::fs::Metadata>,
334+
) -> Option<&Style> {
335+
let indicator = self.indicator_for(path.as_ref(), metadata);
336+
337+
if indicator == Indicator::RegularFile {
338+
// Note: using '.to_str()' here means that filename
339+
// matching will not work with invalid-UTF-8 paths.
340+
let filename = path.as_ref().file_name()?.to_str()?.to_ascii_lowercase();
341+
342+
// We need to traverse LS_COLORS from back to front
343+
// to be consistent with `ls`:
344+
for (suffix, style) in self.suffix_mapping.iter().rev() {
345+
// Note: For some reason, 'ends_with' is much
346+
// slower if we omit `.as_str()` here:
347+
if filename.ends_with(suffix.as_str()) {
348+
return Some(style);
349+
}
312350
}
313351
}
314352

315-
None
353+
self.style_for_indicator(indicator)
316354
}
317355

318356
/// Get ANSI styles for each component of a given path. Components already include the path
@@ -332,13 +370,27 @@ impl LsColors {
332370
/// For example, the style for `mi` (missing file) falls back to `or` (orphaned symbolic link)
333371
/// if it has not been specified explicitly.
334372
pub fn style_for_indicator(&self, indicator: Indicator) -> Option<&Style> {
335-
match indicator {
336-
Indicator::MissingFile => self
337-
.indicator_mapping
338-
.get(&Indicator::MissingFile)
339-
.or_else(|| self.indicator_mapping.get(&Indicator::OrphanedSymbolicLink)),
340-
_ => self.indicator_mapping.get(&indicator),
341-
}
373+
self.indicator_mapping
374+
.get(&indicator)
375+
.or_else(|| {
376+
self.indicator_mapping.get(&match indicator {
377+
Indicator::Setuid
378+
| Indicator::Setgid
379+
| Indicator::ExecutableFile
380+
| Indicator::MultipleHardLinks => Indicator::RegularFile,
381+
382+
Indicator::StickyAndOtherWritable
383+
| Indicator::OtherWritable
384+
| Indicator::Sticky => Indicator::Directory,
385+
386+
Indicator::OrphanedSymbolicLink => Indicator::SymbolicLink,
387+
388+
Indicator::MissingFile => Indicator::OrphanedSymbolicLink,
389+
390+
_ => indicator,
391+
})
392+
})
393+
.or_else(|| self.indicator_mapping.get(&Indicator::Normal))
342394
}
343395
}
344396

0 commit comments

Comments
 (0)