ls improvments

* Add options -c, -F, -L, -l, -r, -R, -S, -t, -U, --color
* Fix options -a, -A
* Remove unused options
* Output in columns when not using -l
* Output date with -l
This commit is contained in:
Mark Karasek 2016-07-25 06:58:33 -07:00
parent 6b7254fc63
commit 900cd41eb6
3 changed files with 483 additions and 131 deletions

39
Cargo.lock generated
View file

@ -128,6 +128,16 @@ dependencies = [
"uucore 0.0.1",
]
[[package]]
name = "atty"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
"libc 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)",
"winapi 0.2.7 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "base64"
version = "0.0.1"
@ -453,6 +463,11 @@ dependencies = [
"uucore 0.0.1",
]
[[package]]
name = "lazy_static"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "libc"
version = "0.1.12"
@ -495,8 +510,13 @@ name = "ls"
version = "0.0.1"
dependencies = [
"getopts 0.2.14 (registry+https://github.com/rust-lang/crates.io-index)",
"lazy_static 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
"libc 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)",
"pretty-bytes 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
"term_grid 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)",
"termsize 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
"time 0.1.35 (registry+https://github.com/rust-lang/crates.io-index)",
"unicode-width 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)",
"uucore 0.0.1",
]
@ -1043,6 +1063,25 @@ dependencies = [
"winapi 0.2.7 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "term_grid"
version = "0.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"unicode-width 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "termsize"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"atty 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
"kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
"libc 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)",
"winapi 0.2.7 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "test"
version = "0.0.1"

View file

@ -12,6 +12,11 @@ getopts = "*"
libc = "*"
uucore = { path="../uucore" }
pretty-bytes = "0.1.0"
term_grid = "*"
termsize = "*"
time = "*"
lazy_static = "*"
unicode-width = "*"
[[bin]]
name = "ls"

View file

@ -8,9 +8,20 @@
// that was distributed with this source code.
//
#![feature(slice_patterns)]
extern crate getopts;
extern crate pretty_bytes;
extern crate termsize;
extern crate term_grid;
extern crate time;
extern crate unicode_width;
use pretty_bytes::converter::convert;
use term_grid::{Grid, GridOptions, Direction, Filling, Cell};
use time::{Timespec, strftime};
#[macro_use]
extern crate lazy_static;
#[macro_use]
extern crate uucore;
@ -22,14 +33,24 @@ use self::libc::{S_ISUID, S_ISGID, S_ISVTX, S_IRUSR, S_IWUSR, S_IXUSR, S_IRGRP,
use getopts::Options;
use std::fs;
use std::fs::{ReadDir, DirEntry, FileType, Metadata};
use std::ffi::{OsString, CStr};
use std::path::Path;
use std::fs::{DirEntry, FileType, Metadata};
use std::path::{Path, PathBuf};
use std::io::Write;
use std::ptr;
use std::collections::HashMap;
#[cfg(unix)]
use std::os::unix::fs::MetadataExt;
#[cfg(unix)]
use std::os::unix::fs::FileTypeExt;
#[cfg(unix)]
use std::ptr;
#[cfg(unix)]
use std::ffi::CStr;
#[cfg(unix)]
use unicode_width::UnicodeWidthStr;
#[cfg(windows)]
use std::os::windows::fs::MetadataExt;
#[derive(Copy, Clone, PartialEq)]
enum Mode {
@ -41,6 +62,27 @@ enum Mode {
static NAME: &'static str = "ls";
static VERSION: &'static str = env!("CARGO_PKG_VERSION");
static DEFAULT_COLORS: &'static str = "rs=0:di=01;34:ln=01;36:mh=00:pi=40;33:so=01;35:do=01;35:bd=40;33;01:cd=40;33;01:or=40;31;01:mi=00:su=37;41:sg=30;43:ca=30;41:tw=30;42:ow=34;42:st=37;44:ex=01;32:*.tar=01;31:*.tgz=01;31:*.arc=01;31:*.arj=01;31:*.taz=01;31:*.lha=01;31:*.lz4=01;31:*.lzh=01;31:*.lzma=01;31:*.tlz=01;31:*.txz=01;31:*.tzo=01;31:*.t7z=01;31:*.zip=01;31:*.z=01;31:*.Z=01;31:*.dz=01;31:*.gz=01;31:*.lrz=01;31:*.lz=01;31:*.lzo=01;31:*.xz=01;31:*.bz2=01;31:*.bz=01;31:*.tbz=01;31:*.tbz2=01;31:*.tz=01;31:*.deb=01;31:*.rpm=01;31:*.jar=01;31:*.war=01;31:*.ear=01;31:*.sar=01;31:*.rar=01;31:*.alz=01;31:*.ace=01;31:*.zoo=01;31:*.cpio=01;31:*.7z=01;31:*.rz=01;31:*.cab=01;31:*.jpg=01;35:*.jpeg=01;35:*.gif=01;35:*.bmp=01;35:*.pbm=01;35:*.pgm=01;35:*.ppm=01;35:*.tga=01;35:*.xbm=01;35:*.xpm=01;35:*.tif=01;35:*.tiff=01;35:*.png=01;35:*.svg=01;35:*.svgz=01;35:*.mng=01;35:*.pcx=01;35:*.mov=01;35:*.mpg=01;35:*.mpeg=01;35:*.m2v=01;35:*.mkv=01;35:*.webm=01;35:*.ogm=01;35:*.mp4=01;35:*.m4v=01;35:*.mp4v=01;35:*.vob=01;35:*.qt=01;35:*.nuv=01;35:*.wmv=01;35:*.asf=01;35:*.rm=01;35:*.rmvb=01;35:*.flc=01;35:*.avi=01;35:*.fli=01;35:*.flv=01;35:*.gl=01;35:*.dl=01;35:*.xcf=01;35:*.xwd=01;35:*.yuv=01;35:*.cgm=01;35:*.emf=01;35:*.ogv=01;35:*.ogx=01;35:*.aac=00;36:*.au=00;36:*.flac=00;36:*.m4a=00;36:*.mid=00;36:*.midi=00;36:*.mka=00;36:*.mp3=00;36:*.mpc=00;36:*.ogg=00;36:*.ra=00;36:*.wav=00;36:*.oga=00;36:*.opus=00;36:*.spx=00;36:*.xspf=00;36:";
lazy_static! {
static ref LS_COLORS: String = std::env::var("LS_COLORS").unwrap_or(DEFAULT_COLORS.to_string());
static ref COLOR_MAP: HashMap<&'static str, &'static str> = {
let codes = LS_COLORS.split(":");
let mut map = HashMap::new();
for c in codes {
let p: Vec<_> = c.split("=").collect();
if let [k, v] = p[..] {
map.insert(k,v);
}
}
map
};
static ref RESET_CODE: &'static str = COLOR_MAP.get("rs").unwrap_or(&"0");
static ref LEFT_CODE: &'static str = COLOR_MAP.get("lc").unwrap_or(&"\x1b[");
static ref RIGHT_CODE: &'static str = COLOR_MAP.get("rc").unwrap_or(&"m");
static ref END_CODE: &'static str = COLOR_MAP.get("ec").unwrap_or(&"");
}
pub fn uumain(args: Vec<String>) -> i32 {
let mut opts = Options::new();
@ -54,29 +96,55 @@ pub fn uumain(args: Vec<String>) -> i32 {
"almost-all",
"In a directory, do not ignore all file names that start with '.', only ignore \
'.' and '..'.");
opts.optflag("B",
"ignore-backups",
"Ignore files that end with ~. Equivalent to using `--ignore='*~'` or \
`--ignore='.*~'.");
opts.optflag("c",
"",
"If the long listing format (e.g., -l, -o) is being used, print the status \
change time (the ctime in the inode) instead of the modification time. When \
explicitly sorting by time (--sort=time or -t) or when not using a long listing \
format, sort according to the status change time.");
opts.optflag("d",
"directory",
"Only list the names of directories, rather than listing directory contents. \
This will not follow symbolic links unless one of `--dereference-command-line \
(-H)`, `--dereference (-L)`, or `--dereference-command-line-symlink-to-dir` is \
specified.");
opts.optflag("H",
"dereference-command-line",
"If a command line argument specifies a symbolic link, show information about \
the linked file rather than the link itself.");
opts.optflag("F",
"classify",
"Append a character to each file name indicating the file type. Also, for \
regular files that are executable, append '*'. The file type indicators are \
'/' for directories, '@' for symbolic links, '|' for FIFOs, '=' for sockets, \
'>' for doors, and nothing for regular files.");
opts.optflag("h",
"human-readable",
"Print human readable file sizes (e.g. 1K 234M 56G).");
opts.optflag("L",
"dereference",
"When showing file information for a symbolic link, show information for the \
file the link references rather than the link itself.");
opts.optflag("l", "long", "Display detailed information.");
opts.optflag("r",
"reverse",
"Reverse whatever the sorting method is--e.g., list files in reverse \
alphabetical order, youngest first, smallest first, or whatever.");
opts.optflag("R",
"recursive",
"List the contents of all directories recursively.");
opts.optflag("S", "", "Sort by file size, largest first.");
opts.optflag("t",
"",
"Sort by modification time (the 'mtime' in the inode), newest first.");
opts.optflag("U",
"",
"Do not sort; list the files in whatever order they are stored in the \
directory. This is especially useful when listing very large directories, \
since not doing any sorting can be noticeably faster.");
opts.optflag("", "color", "Color output based on file type.");
let matches = match opts.parse(&args[1..]) {
Ok(m) => m,
Err(e) => {
show_error!("{}", e);
panic!()
disp_err!("{}", e);
return 1;
}
};
@ -103,8 +171,8 @@ fn version() {
fn help() {
let msg = format!("{0} {1}\n\n\
Usage: {0} [OPTION]... DIRECTORY \n \
or: {0} [OPTION]... [FILE]... \n \
Usage: {0} [OPTION]... DIRECTORY\n \
or: {0} [OPTION]... [FILE]...\n \
\n \
By default, ls will list the files and contents of any directories on \
the command line, expect that it will ignore files and directories \
@ -122,102 +190,135 @@ fn list(options: getopts::Matches) {
options.free.iter().cloned().collect()
};
let mut files = Vec::<PathBuf>::new();
let mut dirs = Vec::<PathBuf>::new();
for loc in locs {
let p = Path::new(&loc);
let p = PathBuf::from(&loc);
let mut dir = false;
if !p.exists() {
show_error!("Cannot find path '{}' because it does not exist.", loc);
panic!();
}
if p.is_dir() {
match fs::read_dir(p) {
Err(e) => {
show_error!("Cannot read directory '{}'. \n Reason: {}", loc, e);
panic!();
if p.is_dir() && !options.opt_present("d") {
dir = true;
if !options.opt_present("L") {
if let Ok(md) = p.symlink_metadata() {
if md.file_type().is_symlink() {
dir = false;
}
}
Ok(entries) => enter_directory(entries, &options),
};
}
}
if dir {
dirs.push(p);
} else {
files.push(p);
}
}
sort_entries(&mut files, &options);
display_items(&files, None, &options);
if p.is_file() {
display_item(Path::new(p), &options)
sort_entries(&mut dirs, &options);
for dir in dirs {
if options.free.len() > 1 {
println!("\n{}:", dir.to_string_lossy());
}
enter_directory(&dir, &options);
}
}
#[cfg(unix)]
fn sort_entries(entries: &mut Vec<PathBuf>, options: &getopts::Matches) {
let mut reverse = options.opt_present("r");
if options.opt_present("t") {
if options.opt_present("c") {
entries.sort_by_key(|k| get_metadata(k, options).map(|md| md.ctime()).unwrap_or(0));
} else {
entries.sort_by_key(|k| {
get_metadata(k, options)
.and_then(|md| md.modified())
.unwrap_or(std::time::UNIX_EPOCH)
});
}
} else if options.opt_present("S") {
entries.sort_by_key(|k| get_metadata(k, options).map(|md| md.size()).unwrap_or(0));
reverse = !reverse;
} else if !options.opt_present("U") {
entries.sort();
}
if reverse {
entries.reverse();
}
}
#[cfg(windows)]
fn sort_entries(entries: &mut Vec<PathBuf>, options: &getopts::Matches) {
let mut reverse = options.opt_present("r");
if options.opt_present("t") {
entries.sort_by_key(|k| {
get_metadata(k, options)
.and_then(|md| md.modified())
.unwrap_or(std::time::UNIX_EPOCH)
});
} else if options.opt_present("S") {
entries.sort_by_key(|k| get_metadata(k, options).map(|md| md.file_size()).unwrap_or(0));
reverse = !reverse;
} else if !options.opt_present("U") {
entries.sort();
}
if reverse {
entries.reverse();
}
}
fn max(lhs: usize, rhs: usize) -> usize {
if lhs > rhs {
lhs
lhs
} else {
rhs
rhs
}
}
fn should_cull_dot(file_name: &DirEntry, view_all: bool) -> bool {
let file_name = file_name.file_name();
file_name.to_str().map_or(false, |x| {
if view_all {
false
} else if x.chars().next().unwrap() == '.' {
true
} else {
false
}
})
}
fn enter_directory(dir: &PathBuf, options: &getopts::Matches) {
let mut entries = safe_unwrap!(fs::read_dir(dir)
.and_then(|e| e.collect::<Result<Vec<_>, _>>()));
fn enter_directory(contents: ReadDir, options: &getopts::Matches) {
let contents = contents.collect::<Vec<_>>();
let (mut max_links, mut max_size) = (1, 1);
let culling_dot = options.opt_present("a");
for entry in &contents {
let entry = match *entry {
Err(ref err) => {
show_error!("{}", err);
panic!();
}
Ok(ref en) => en,
};
if should_cull_dot(&entry, culling_dot) {
continue;
}
let (links, size) = display_dir_entry_size(entry, options);
max_links = max(links, max_links);
max_size = max(size, max_size);
if !options.opt_present("a") && !options.opt_present("A") {
entries.retain(|e| !e.file_name().to_string_lossy().starts_with('.'))
}
for entry in &contents {
let entry = match *entry {
Err(ref err) => {
show_error!("{}", err);
panic!();
}
Ok(ref en) => en,
};
if should_cull_dot(&entry, culling_dot) {
continue;
let mut entries: Vec<_> = entries.iter().map(DirEntry::path).collect();
if options.opt_present("a") {
entries.push(dir.join("."));
entries.push(dir.join(".."));
}
sort_entries(&mut entries, options);
display_items(&entries, Some(dir), options);
if options.opt_present("R") {
for e in entries.iter().filter(|p| p.is_dir()) {
println!("\n{}:", e.to_string_lossy());
enter_directory(&e, options);
}
// Currently have a DirEntry that we can believe in.
display_dir_entry(entry, options, max_links, max_size);
}
}
fn get_metadata(entry: &DirEntry) -> Metadata {
match entry.metadata() {
Err(e) => {
show_error!("Unable to retrieve metadata for {}. \n Error: {}",
display_file_name(entry.file_name()),
e);
panic!();
}
Ok(md) => md,
fn get_metadata(entry: &PathBuf, options: &getopts::Matches) -> std::io::Result<Metadata> {
if options.opt_present("L") {
entry.metadata().or(entry.symlink_metadata())
} else {
entry.symlink_metadata()
}
}
fn display_dir_entry_size(entry: &DirEntry, options: &getopts::Matches) -> (usize, usize) {
let md = get_metadata(entry);
(display_symlink_count(&md).len(), display_file_size(&md, options).len())
fn display_dir_entry_size(entry: &PathBuf, options: &getopts::Matches) -> (usize, usize) {
if let Ok(md) = get_metadata(entry, options) {
(display_symlink_count(&md).len(), display_file_size(&md, options).len())
} else {
(0, 0)
}
}
fn pad_left(string: String, count: usize) -> String {
@ -230,16 +331,79 @@ fn pad_left(string: String, count: usize) -> String {
}
}
fn display_dir_entry(entry: &DirEntry, options: &getopts::Matches, max_links: usize, max_size: usize) {
let md = get_metadata(entry);
println!("{}{} {} {} {} {} {}",
display_file_type(entry.file_type()),
fn display_items(items: &Vec<PathBuf>, strip: Option<&Path>, options: &getopts::Matches) {
if options.opt_present("long") {
let (mut max_links, mut max_size) = (1, 1);
for item in items {
let (links, size) = display_dir_entry_size(item, options);
max_links = max(links, max_links);
max_size = max(size, max_size);
}
for item in items {
display_item_long(item, strip, max_links, max_size, options);
}
} else {
let names: Vec<_> = items.iter()
.filter_map(|i| {
let md = get_metadata(i, options);
match md {
Err(e) => {
let filename = get_file_name(i, strip);
show_error!("{}: {}", filename, e);
None
}
Ok(md) => Some(display_file_name(&i, strip, &md, options)),
}
})
.collect();
if let Some(size) = termsize::get() {
let mut grid = Grid::new(GridOptions {
filling: Filling::Spaces(2),
direction: Direction::TopToBottom,
});
for name in names {
grid.add(name);
}
if let Some(output) = grid.fit_into_width(size.cols as usize) {
print!("{}", output);
return;
}
}
// Couldn't display a grid, either because we don't know
// the terminal width or because fit_into_width failed
for i in items {
let md = get_metadata(i, options);
if let Ok(md) = md {
println!("{}", display_file_name(&i, strip, &md, options).contents);
}
}
}
}
fn display_item_long(item: &PathBuf,
strip: Option<&Path>,
max_links: usize,
max_size: usize,
options: &getopts::Matches) {
let md = match get_metadata(item, options) {
Err(e) => {
let filename = get_file_name(&item, strip);
show_error!("{}: {}", filename, e);
return;
}
Ok(md) => md,
};
println!("{}{} {} {} {} {} {} {}",
display_file_type(md.file_type()),
display_permissions(&md),
pad_left(display_symlink_count(&md), max_links),
display_uname(&md),
display_group(&md),
pad_left(display_file_size(&md, options), max_size),
display_file_name(entry.file_name()));
display_date(&md, options),
display_file_name(&item, strip, &md, options).contents);
}
// Currently getpwuid is `linux` target only. If it's broken out into
@ -285,6 +449,31 @@ fn display_group(metadata: &Metadata) -> String {
"somegroup".to_string()
}
#[cfg(unix)]
fn display_date(metadata: &Metadata, options: &getopts::Matches) -> String {
let secs = if options.opt_present("c") {
metadata.ctime()
} else {
metadata.mtime()
};
let time = time::at(Timespec::new(secs, 0));
strftime("%F %R", &time).unwrap()
}
#[cfg(not(unix))]
fn display_date(metadata: &Metadata, options: &getopts::Matches) -> String {
if let Ok(mtime) = metadata.modified() {
let time =
time::at(Timespec::new(mtime.duration_since(std::time::UNIX_EPOCH)
.unwrap()
.as_secs() as i64,
0));
strftime("%F %R", &time).unwrap()
} else {
"???".to_string()
}
}
fn display_file_size(metadata: &Metadata, options: &getopts::Matches) -> String {
if options.opt_present("human-readable") {
convert(metadata.len() as f64)
@ -293,15 +482,7 @@ fn display_file_size(metadata: &Metadata, options: &getopts::Matches) -> String
}
}
fn display_file_type(file_type: Result<FileType, std::io::Error>) -> String {
let file_type = match file_type {
Err(e) => {
show_error!("{}", e);
panic!()
}
Ok(ft) => ft,
};
fn display_file_type(file_type: FileType) -> String {
if file_type.is_dir() {
"d".to_string()
} else if file_type.is_symlink() {
@ -311,11 +492,160 @@ fn display_file_type(file_type: Result<FileType, std::io::Error>) -> String {
}
}
fn display_file_name(name: OsString) -> String {
fn get_file_name(name: &Path, strip: Option<&Path>) -> String {
let mut name = match strip {
Some(prefix) => name.strip_prefix(prefix).unwrap_or(name),
None => name,
};
if name.as_os_str().len() == 0 {
name = Path::new(".");
}
name.to_string_lossy().into_owned()
}
#[cfg(target_family = "windows")]
#[cfg(not(unix))]
fn display_file_name(path: &Path,
strip: Option<&Path>,
metadata: &Metadata,
options: &getopts::Matches)
-> Cell {
let mut name = get_file_name(path, strip);
if options.opt_present("classify") {
let file_type = metadata.file_type();
if file_type.is_dir() {
name.push('/');
} else if file_type.is_symlink() {
name.push('@');
}
}
name.into()
}
fn color_name(name: String, typ: &str) -> String {
let mut typ = typ;
if !COLOR_MAP.contains_key(typ) {
if typ == "or" {
typ = "ln";
} else if typ == "mi" {
typ = "fi";
}
};
if let Some(code) = COLOR_MAP.get(typ) {
format!("{}{}{}{}{}{}{}{}",
*LEFT_CODE,
code,
*RIGHT_CODE,
name,
*END_CODE,
*LEFT_CODE,
*RESET_CODE,
*RIGHT_CODE,
)
} else {
name
}
}
macro_rules! has {
($mode:expr, $perm:expr) => (
$mode & ($perm as mode_t) != 0
)
}
#[cfg(unix)]
fn display_file_name(path: &Path,
strip: Option<&Path>,
metadata: &Metadata,
options: &getopts::Matches)
-> Cell {
let mut name = get_file_name(path, strip);
let mut width = UnicodeWidthStr::width(&*name);
let color = options.opt_present("color");
let classify = options.opt_present("classify");
let ext;
if color || classify {
let file_type = metadata.file_type();
let (code, sym) = if file_type.is_dir() {
("di", Some('/'))
} else if file_type.is_symlink() {
if path.exists() {
("ln", Some('@'))
} else {
("or", Some('@'))
}
} else if file_type.is_socket() {
("so", Some('='))
} else if file_type.is_fifo() {
("pi", Some('|'))
} else if file_type.is_block_device() {
("bd", None)
} else if file_type.is_char_device() {
("cd", None)
} else if file_type.is_file() {
let mode = metadata.mode() as mode_t;
let sym = if has!(mode, S_IXUSR | S_IXGRP | S_IXOTH) {
Some('*')
} else {
None
};
if has!(mode, S_ISUID) {
("su", sym)
} else if has!(mode, S_ISGID) {
("sg", sym)
} else if has!(mode, S_ISVTX) && has!(mode, S_IWOTH) {
("tw", sym)
} else if has!(mode, S_ISVTX) {
("st", sym)
} else if has!(mode, S_IWOTH) {
("ow", sym)
} else if has!(mode, S_IXUSR | S_IXGRP | S_IXOTH) {
("ex", sym)
} else if metadata.nlink() > 1 {
("mh", sym)
} else if let Some(e) = path.extension() {
ext = format!("*.{}", e.to_string_lossy());
(ext.as_str(), None)
} else {
("fi", None)
}
} else {
("", None)
};
if color {
name = color_name(name, code);
}
if classify {
if let Some(s) = sym {
name.push(s);
width += 1;
}
}
}
if options.opt_present("long") && metadata.file_type().is_symlink() {
if let Ok(target) = path.read_link() {
// We don't bother updating width here because it's not used for long listings
let code = if target.exists() {
"fi"
} else {
"mi"
};
let target_name = color_name(target.to_string_lossy().to_string(), code);
name.push_str(" -> ");
name.push_str(&target_name);
}
}
Cell {
contents: name,
width: width,
}
}
#[cfg(not(unix))]
#[allow(unused_variables)]
fn display_symlink_count(metadata: &Metadata) -> String {
// Currently not sure of how to get this on Windows, so I'm punting.
@ -323,23 +653,18 @@ fn display_symlink_count(metadata: &Metadata) -> String {
String::from("1")
}
#[cfg(target_family = "unix")]
#[cfg(unix)]
fn display_symlink_count(metadata: &Metadata) -> String {
metadata.nlink().to_string()
}
#[cfg(target_family = "windows")]
#[cfg(not(unix))]
#[allow(unused_variables)]
fn display_permissions(metadata: &Metadata) -> String {
String::from("---------")
}
macro_rules! has {
($mode:expr, $perm:expr) => (
$mode & $perm != 0
)
}
#[cfg(target_family = "unix")]
#[cfg(unix)]
fn display_permissions(metadata: &Metadata) -> String {
let mode = metadata.mode() as mode_t;
let mut result = String::with_capacity(9);
@ -353,7 +678,7 @@ fn display_permissions(metadata: &Metadata) -> String {
} else {
'-'
});
result.push(if has!(mode, S_ISUID as mode_t) {
result.push(if has!(mode, S_ISUID) {
if has!(mode, S_IXUSR) {
's'
} else {
@ -375,7 +700,7 @@ fn display_permissions(metadata: &Metadata) -> String {
} else {
'-'
});
result.push(if has!(mode, S_ISGID as mode_t) {
result.push(if has!(mode, S_ISGID) {
if has!(mode, S_IXGRP) {
's'
} else {
@ -397,7 +722,7 @@ fn display_permissions(metadata: &Metadata) -> String {
} else {
'-'
});
result.push(if has!(mode, S_ISVTX as mode_t) {
result.push(if has!(mode, S_ISVTX) {
if has!(mode, S_IXOTH) {
't'
} else {
@ -411,20 +736,3 @@ fn display_permissions(metadata: &Metadata) -> String {
result
}
#[allow(unused_variables)]
fn display_item(item: &Path, options: &getopts::Matches) {
// let fileType = item.file
// let mut fileMeta = String::new();
// fileMeta = fileMeta + if item.is_dir() {
// "d"
// } else if item.sy
// } else {
// "-"
// };
// println!("{}{}", displayString, item.display());
}