mirror of
https://github.com/Llewellynvdm/exa.git
synced 2025-01-14 17:19:56 +00:00
commit
caf5fce1e8
@ -4,5 +4,8 @@ before_install:
|
|||||||
- sudo apt-get install cmake
|
- sudo apt-get install cmake
|
||||||
sudo: true
|
sudo: true
|
||||||
language: rust
|
language: rust
|
||||||
rust: nightly
|
rust:
|
||||||
|
- nightly
|
||||||
|
- beta
|
||||||
|
- stable
|
||||||
|
|
||||||
|
@ -2,8 +2,7 @@
|
|||||||
|
|
||||||
[exa](http://bsago.me/exa) is a replacement for `ls` written in Rust.
|
[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
|
## Screenshot
|
||||||
|
|
||||||
@ -52,6 +51,6 @@ You can sort by **name**, **size**, **ext**, **inode**, **modified**, **created*
|
|||||||
|
|
||||||
## Installation
|
## 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.
|
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.
|
||||||
|
@ -3,7 +3,7 @@ extern crate libc;
|
|||||||
|
|
||||||
use std::io;
|
use std::io;
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
|
use std::ffi::CString;
|
||||||
|
|
||||||
pub const ENABLED: bool = cfg!(feature="git") && cfg!(any(target_os="macos", target_os="linux"));
|
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"))]
|
#[cfg(any(target_os = "macos", target_os = "linux"))]
|
||||||
pub fn list_attrs(lister: lister::Lister, path: &Path) -> io::Result<Vec<Attribute>> {
|
pub fn list_attrs(lister: lister::Lister, path: &Path) -> io::Result<Vec<Attribute>> {
|
||||||
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,
|
Some(cstring) => cstring,
|
||||||
None => return Err(io::Error::new(io::ErrorKind::Other, "Error: path somehow contained a NUL?")),
|
None => return Err(io::Error::new(io::ErrorKind::Other, "Error: path somehow contained a NUL?")),
|
||||||
};
|
};
|
||||||
|
89
src/file.rs
89
src/file.rs
@ -4,7 +4,6 @@ use std::ascii::AsciiExt;
|
|||||||
use std::env::current_dir;
|
use std::env::current_dir;
|
||||||
use std::fs;
|
use std::fs;
|
||||||
use std::io;
|
use std::io;
|
||||||
use std::os::unix;
|
|
||||||
use std::os::unix::fs::{MetadataExt, PermissionsExt};
|
use std::os::unix::fs::{MetadataExt, PermissionsExt};
|
||||||
use std::path::{Component, Path, PathBuf};
|
use std::path::{Component, Path, PathBuf};
|
||||||
|
|
||||||
@ -15,6 +14,35 @@ use options::TimeType;
|
|||||||
|
|
||||||
use self::fields as f;
|
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
|
/// A **File** is a wrapper around one of Rust's Path objects, along with
|
||||||
/// associated data about the file.
|
/// associated data about the file.
|
||||||
@ -104,7 +132,7 @@ impl<'dir> File<'dir> {
|
|||||||
/// current user. Executable files have different semantics than
|
/// current user. Executable files have different semantics than
|
||||||
/// executable directories, and so should be highlighted differently.
|
/// executable directories, and so should be highlighted differently.
|
||||||
pub fn is_executable_file(&self) -> bool {
|
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
|
self.is_file() && (self.metadata.permissions().mode() & bit) == bit
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -141,16 +169,15 @@ impl<'dir> File<'dir> {
|
|||||||
let components: Vec<Component> = self.path.components().collect();
|
let components: Vec<Component> = self.path.components().collect();
|
||||||
let mut path_prefix = String::new();
|
let mut path_prefix = String::new();
|
||||||
|
|
||||||
if let Some((_, components_init)) = components.split_last() {
|
// This slicing is safe as components always has the RootComponent
|
||||||
for component in components_init.iter() {
|
// as the first element.
|
||||||
path_prefix.push_str(&*component.as_os_str().to_string_lossy());
|
for component in components[..(components.len() - 1)].iter() {
|
||||||
|
path_prefix.push_str(&*component.as_os_str().to_string_lossy());
|
||||||
|
|
||||||
if component != &Component::RootDir {
|
if component != &Component::RootDir {
|
||||||
path_prefix.push_str("/");
|
path_prefix.push_str("/");
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
path_prefix
|
path_prefix
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -299,15 +326,15 @@ impl<'dir> File<'dir> {
|
|||||||
|
|
||||||
f::Permissions {
|
f::Permissions {
|
||||||
file_type: self.type_char(),
|
file_type: self.type_char(),
|
||||||
user_read: has_bit(unix::fs::USER_READ),
|
user_read: has_bit(modes::USER_READ),
|
||||||
user_write: has_bit(unix::fs::USER_WRITE),
|
user_write: has_bit(modes::USER_WRITE),
|
||||||
user_execute: has_bit(unix::fs::USER_EXECUTE),
|
user_execute: has_bit(modes::USER_EXECUTE),
|
||||||
group_read: has_bit(unix::fs::GROUP_READ),
|
group_read: has_bit(modes::GROUP_READ),
|
||||||
group_write: has_bit(unix::fs::GROUP_WRITE),
|
group_write: has_bit(modes::GROUP_WRITE),
|
||||||
group_execute: has_bit(unix::fs::GROUP_EXECUTE),
|
group_execute: has_bit(modes::GROUP_EXECUTE),
|
||||||
other_read: has_bit(unix::fs::OTHER_READ),
|
other_read: has_bit(modes::OTHER_READ),
|
||||||
other_write: has_bit(unix::fs::OTHER_WRITE),
|
other_write: has_bit(modes::OTHER_WRITE),
|
||||||
other_execute: has_bit(unix::fs::OTHER_EXECUTE),
|
other_execute: has_bit(modes::OTHER_EXECUTE),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -483,6 +510,8 @@ pub mod fields {
|
|||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod test {
|
mod test {
|
||||||
use super::ext;
|
use super::ext;
|
||||||
|
use super::File;
|
||||||
|
use std::path::Path;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn extension() {
|
fn extension() {
|
||||||
@ -498,4 +527,28 @@ mod test {
|
|||||||
fn no_extension() {
|
fn no_extension() {
|
||||||
assert_eq!(None, ext("jarlsberg"))
|
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());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,3 @@
|
|||||||
#![feature(iter_arith)]
|
|
||||||
#![feature(convert, fs_mode)]
|
|
||||||
|
|
||||||
#![warn(trivial_casts, trivial_numeric_casts)]
|
#![warn(trivial_casts, trivial_numeric_casts)]
|
||||||
#![warn(unused_extern_crates, unused_qualifications)]
|
#![warn(unused_extern_crates, unused_qualifications)]
|
||||||
#![warn(unused_results)]
|
#![warn(unused_results)]
|
||||||
|
@ -115,6 +115,8 @@ use std::error::Error;
|
|||||||
use std::io;
|
use std::io;
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
use std::string::ToString;
|
use std::string::ToString;
|
||||||
|
use std::ops::Add;
|
||||||
|
use std::iter::repeat;
|
||||||
|
|
||||||
use colours::Colours;
|
use colours::Colours;
|
||||||
use column::{Alignment, Column, Cell};
|
use column::{Alignment, Column, Cell};
|
||||||
@ -656,7 +658,7 @@ impl<U> Table<U> where U: Users {
|
|||||||
.map(|n| self.rows.iter().map(|row| row.column_width(n)).max().unwrap_or(0))
|
.map(|n| self.rows.iter().map(|row| row.column_width(n)).max().unwrap_or(0))
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
let total_width: usize = self.columns.len() + column_widths.iter().sum::<usize>();
|
let total_width: usize = self.columns.len() + column_widths.iter().fold(0,Add::add);
|
||||||
|
|
||||||
for row in self.rows.iter() {
|
for row in self.rows.iter() {
|
||||||
let mut cell = Cell::empty();
|
let mut cell = Cell::empty();
|
||||||
@ -682,7 +684,14 @@ impl<U> Table<U> where U: Users {
|
|||||||
// necessary to maintain information about the previously-printed
|
// necessary to maintain information about the previously-printed
|
||||||
// lines, as the output will change based on whether the
|
// lines, as the output will change based on whether the
|
||||||
// *previous* entry was the last in its directory.
|
// *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 };
|
stack[row.depth] = if row.last { TreePart::Corner } else { TreePart::Edge };
|
||||||
|
|
||||||
for i in 1 .. row.depth + 1 {
|
for i in 1 .. row.depth + 1 {
|
||||||
|
Loading…
Reference in New Issue
Block a user