diff --git a/.travis.yml b/.travis.yml index 740f8016..c3c677dc 100644 --- a/.travis.yml +++ b/.travis.yml @@ -4,5 +4,8 @@ before_install: - sudo apt-get install cmake sudo: true language: rust -rust: nightly +rust: + - nightly + - beta + - stable diff --git a/README.md b/README.md index b270a0a6..e6312515 100644 --- a/README.md +++ b/README.md @@ -2,8 +2,7 @@ [exa](http://bsago.me/exa) is a replacement for `ls` written in Rust. -**You'll have to use the Rust 1.6.0 nightly, rather than stable (1.4) or beta (1.5). Sorry about that.** - +Works on all recent Rust versions >= 1.4.0. ## Screenshot @@ -52,6 +51,6 @@ You can sort by **name**, **size**, **ext**, **inode**, **modified**, **created* ## Installation -exa is written in [Rust](http://www.rust-lang.org). You'll have to use the nightly -- I try to keep it up to date with the latest version when possible. Once you have it set up, a simple `make install` will compile exa and install it into `/usr/local/bin`. +exa is written in [Rust](http://www.rust-lang.org). Once you have it set up, a simple `make install` will compile exa and install it into `/usr/local/bin`. exa depends on [libgit2](https://github.com/alexcrichton/git2-rs) for certain features. If you're unable to compile libgit2, you can opt out of Git support by passing `--no-default-features` to Cargo. diff --git a/src/feature/xattr.rs b/src/feature/xattr.rs index 8d940b97..8682087f 100644 --- a/src/feature/xattr.rs +++ b/src/feature/xattr.rs @@ -3,7 +3,7 @@ extern crate libc; use std::io; use std::path::Path; - +use std::ffi::CString; pub const ENABLED: bool = cfg!(feature="git") && cfg!(any(target_os="macos", target_os="linux")); @@ -51,7 +51,7 @@ pub struct Attribute { #[cfg(any(target_os = "macos", target_os = "linux"))] pub fn list_attrs(lister: lister::Lister, path: &Path) -> io::Result> { - let c_path = match path.as_os_str().to_cstring() { + let c_path = match path.to_str().and_then(|s| { CString::new(s).ok() }) { Some(cstring) => cstring, None => return Err(io::Error::new(io::ErrorKind::Other, "Error: path somehow contained a NUL?")), }; diff --git a/src/file.rs b/src/file.rs index 4e2617d2..e52ebaa6 100644 --- a/src/file.rs +++ b/src/file.rs @@ -4,7 +4,6 @@ use std::ascii::AsciiExt; use std::env::current_dir; use std::fs; use std::io; -use std::os::unix; use std::os::unix::fs::{MetadataExt, PermissionsExt}; use std::path::{Component, Path, PathBuf}; @@ -15,6 +14,35 @@ use options::TimeType; use self::fields as f; +// Constant table copied from https://doc.rust-lang.org/src/std/sys/unix/ext/fs.rs.html#11-259 +// which is currently unstable and lacks vision for stabilization, +// see https://github.com/rust-lang/rust/issues/27712 + +#[allow(dead_code)] +mod modes { + use std::os::unix::raw; + + pub const USER_READ: raw::mode_t = 0o400; + pub const USER_WRITE: raw::mode_t = 0o200; + pub const USER_EXECUTE: raw::mode_t = 0o100; + pub const USER_RWX: raw::mode_t = 0o700; + pub const GROUP_READ: raw::mode_t = 0o040; + pub const GROUP_WRITE: raw::mode_t = 0o020; + pub const GROUP_EXECUTE: raw::mode_t = 0o010; + pub const GROUP_RWX: raw::mode_t = 0o070; + pub const OTHER_READ: raw::mode_t = 0o004; + pub const OTHER_WRITE: raw::mode_t = 0o002; + pub const OTHER_EXECUTE: raw::mode_t = 0o001; + pub const OTHER_RWX: raw::mode_t = 0o007; + pub const ALL_READ: raw::mode_t = 0o444; + pub const ALL_WRITE: raw::mode_t = 0o222; + pub const ALL_EXECUTE: raw::mode_t = 0o111; + pub const ALL_RWX: raw::mode_t = 0o777; + pub const SETUID: raw::mode_t = 0o4000; + pub const SETGID: raw::mode_t = 0o2000; + pub const STICKY_BIT: raw::mode_t = 0o1000; +} + /// A **File** is a wrapper around one of Rust's Path objects, along with /// associated data about the file. @@ -104,7 +132,7 @@ impl<'dir> File<'dir> { /// current user. Executable files have different semantics than /// executable directories, and so should be highlighted differently. pub fn is_executable_file(&self) -> bool { - let bit = unix::fs::USER_EXECUTE; + let bit = modes::USER_EXECUTE; self.is_file() && (self.metadata.permissions().mode() & bit) == bit } @@ -141,16 +169,15 @@ impl<'dir> File<'dir> { let components: Vec = self.path.components().collect(); let mut path_prefix = String::new(); - if let Some((_, components_init)) = components.split_last() { - for component in components_init.iter() { - path_prefix.push_str(&*component.as_os_str().to_string_lossy()); + // This slicing is safe as components always has the RootComponent + // as the first element. + for component in components[..(components.len() - 1)].iter() { + path_prefix.push_str(&*component.as_os_str().to_string_lossy()); - if component != &Component::RootDir { - path_prefix.push_str("/"); - } + if component != &Component::RootDir { + path_prefix.push_str("/"); } } - path_prefix } @@ -299,15 +326,15 @@ impl<'dir> File<'dir> { f::Permissions { file_type: self.type_char(), - user_read: has_bit(unix::fs::USER_READ), - user_write: has_bit(unix::fs::USER_WRITE), - user_execute: has_bit(unix::fs::USER_EXECUTE), - group_read: has_bit(unix::fs::GROUP_READ), - group_write: has_bit(unix::fs::GROUP_WRITE), - group_execute: has_bit(unix::fs::GROUP_EXECUTE), - other_read: has_bit(unix::fs::OTHER_READ), - other_write: has_bit(unix::fs::OTHER_WRITE), - other_execute: has_bit(unix::fs::OTHER_EXECUTE), + user_read: has_bit(modes::USER_READ), + user_write: has_bit(modes::USER_WRITE), + user_execute: has_bit(modes::USER_EXECUTE), + group_read: has_bit(modes::GROUP_READ), + group_write: has_bit(modes::GROUP_WRITE), + group_execute: has_bit(modes::GROUP_EXECUTE), + other_read: has_bit(modes::OTHER_READ), + other_write: has_bit(modes::OTHER_WRITE), + other_execute: has_bit(modes::OTHER_EXECUTE), } } @@ -483,6 +510,8 @@ pub mod fields { #[cfg(test)] mod test { use super::ext; + use super::File; + use std::path::Path; #[test] fn extension() { @@ -498,4 +527,28 @@ mod test { fn no_extension() { assert_eq!(None, ext("jarlsberg")) } + + #[test] + fn test_prefix_empty() { + let f = File::from_path(Path::new("Cargo.toml"), None).unwrap(); + assert_eq!("", f.path_prefix()); + } + + #[test] + fn test_prefix_file() { + let f = File::from_path(Path::new("src/main.rs"), None).unwrap(); + assert_eq!("src/", f.path_prefix()); + } + + #[test] + fn test_prefix_path() { + let f = File::from_path(Path::new("src"), None).unwrap(); + assert_eq!("", f.path_prefix()); + } + + #[test] + fn test_prefix_root() { + let f = File::from_path(Path::new("/"), None).unwrap(); + assert_eq!("", f.path_prefix()); + } } diff --git a/src/main.rs b/src/main.rs index a75adf20..66bd5526 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,6 +1,3 @@ -#![feature(iter_arith)] -#![feature(convert, fs_mode)] - #![warn(trivial_casts, trivial_numeric_casts)] #![warn(unused_extern_crates, unused_qualifications)] #![warn(unused_results)] diff --git a/src/output/details.rs b/src/output/details.rs index e3650d57..c2bbfc01 100644 --- a/src/output/details.rs +++ b/src/output/details.rs @@ -115,6 +115,8 @@ use std::error::Error; use std::io; use std::path::PathBuf; use std::string::ToString; +use std::ops::Add; +use std::iter::repeat; use colours::Colours; use column::{Alignment, Column, Cell}; @@ -656,7 +658,7 @@ impl Table where U: Users { .map(|n| self.rows.iter().map(|row| row.column_width(n)).max().unwrap_or(0)) .collect(); - let total_width: usize = self.columns.len() + column_widths.iter().sum::(); + let total_width: usize = self.columns.len() + column_widths.iter().fold(0,Add::add); for row in self.rows.iter() { let mut cell = Cell::empty(); @@ -682,7 +684,14 @@ impl Table where U: Users { // necessary to maintain information about the previously-printed // lines, as the output will change based on whether the // *previous* entry was the last in its directory. - stack.resize(row.depth + 1, TreePart::Edge); + // TODO: Replace this by Vec::resize() when it becomes stable (1.5.0) + let stack_len = stack.len(); + if row.depth + 1 > stack_len { + stack.extend(repeat(TreePart::Edge).take(row.depth + 1 - stack_len)); + } else { + stack = stack[..(row.depth + 1)].into(); + } + stack[row.depth] = if row.last { TreePart::Corner } else { TreePart::Edge }; for i in 1 .. row.depth + 1 {