From 900cd41eb646677cd9e0685ac0e15f5ab595db1d Mon Sep 17 00:00:00 2001 From: Mark Karasek Date: Mon, 25 Jul 2016 06:58:33 -0700 Subject: [PATCH 1/2] 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 --- Cargo.lock | 39 ++++ src/ls/Cargo.toml | 5 + src/ls/ls.rs | 570 +++++++++++++++++++++++++++++++++++----------- 3 files changed, 483 insertions(+), 131 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 3b17b9250..a630f0c21 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -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" diff --git a/src/ls/Cargo.toml b/src/ls/Cargo.toml index 1f5574919..5da0b3818 100644 --- a/src/ls/Cargo.toml +++ b/src/ls/Cargo.toml @@ -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" diff --git a/src/ls/ls.rs b/src/ls/ls.rs index 4450cb871..9ae89846a 100644 --- a/src/ls/ls.rs +++ b/src/ls/ls.rs @@ -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) -> i32 { let mut opts = Options::new(); @@ -54,29 +96,55 @@ pub fn uumain(args: Vec) -> 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::::new(); + let mut dirs = Vec::::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, 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, 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::, _>>())); -fn enter_directory(contents: ReadDir, options: &getopts::Matches) { - let contents = contents.collect::>(); - 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 { + 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, 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) -> 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) -> 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()); -} From 59ad388ccec82cd6c4688fb9068ce7545787bd40 Mon Sep 17 00:00:00 2001 From: Mark Karasek Date: Fri, 29 Jul 2016 07:07:28 -0700 Subject: [PATCH 2/2] ls: remove dependence on nightly --- src/ls/ls.rs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/ls/ls.rs b/src/ls/ls.rs index 9ae89846a..858bb736d 100644 --- a/src/ls/ls.rs +++ b/src/ls/ls.rs @@ -8,8 +8,6 @@ // that was distributed with this source code. // -#![feature(slice_patterns)] - extern crate getopts; extern crate pretty_bytes; extern crate termsize; @@ -71,8 +69,8 @@ lazy_static! { let mut map = HashMap::new(); for c in codes { let p: Vec<_> = c.split("=").collect(); - if let [k, v] = p[..] { - map.insert(k,v); + if p.len() == 2 { + map.insert(p[0], p[1]); } } map