diff --git a/Cargo.lock b/Cargo.lock index 4625fd64..0b098776 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3,6 +3,7 @@ name = "exa" version = "0.1.0" dependencies = [ "ansi_term 0.3.0 (git+https://github.com/ogham/rust-ansi-term.git)", + "users 0.1.0 (git+https://github.com/ogham/rust-users)", ] [[package]] @@ -10,3 +11,8 @@ name = "ansi_term" version = "0.3.0" source = "git+https://github.com/ogham/rust-ansi-term.git#4b9ea6cf266053e1a771e75b935b4e54c586c139" +[[package]] +name = "users" +version = "0.1.0" +source = "git+https://github.com/ogham/rust-users#221a1463d3e25acac41615186a1c7fdcf0ad36d7" + diff --git a/Cargo.toml b/Cargo.toml index 6d7268a1..97c434b3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,13 +1,13 @@ [package] - name = "exa" version = "0.1.0" authors = [ "ogham@bsago.me" ] [[bin]] - name = "exa" [dependencies.ansi_term] +git = "https://github.com/ogham/rust-ansi-term.git" -git = "https://github.com/ogham/rust-ansi-term.git" \ No newline at end of file +[dependencies.users] +git = "https://github.com/ogham/rust-users.git" \ No newline at end of file diff --git a/src/exa.rs b/src/exa.rs index 82c1eb3e..86f30435 100644 --- a/src/exa.rs +++ b/src/exa.rs @@ -2,30 +2,31 @@ extern crate regex; #[phase(plugin)] extern crate regex_macros; extern crate ansi_term; +extern crate users; extern crate unicode; -use std::os; -use std::io::fs; use std::io::FileType; +use std::io::fs; use std::iter::AdditiveIterator; +use std::os; use std::str::StrVector; -use file::File; -use dir::Dir; -use column::Column; use column::Alignment::Left; +use column::Column; +use dir::Dir; +use file::File; use options::{Options, View}; -use unix::Unix; use ansi_term::Style::Plain; use ansi_term::strip_formatting; +use users::OSUsers; + pub mod column; pub mod dir; pub mod format; pub mod file; pub mod filetype; -pub mod unix; pub mod options; pub mod sort; pub mod term; @@ -42,7 +43,7 @@ fn main() { fn exa(opts: &Options) { let mut dirs: Vec = vec![]; let mut files: Vec = vec![]; - + // Separate the user-supplied paths into directories and files. // Files are shown first, and then each directory is expanded // and listed second. @@ -71,7 +72,7 @@ fn exa(opts: &Options) { if !files.is_empty() { view(opts, files); } - + for dir_name in dirs.into_iter() { if first { first = false; @@ -121,7 +122,7 @@ fn grid_view(across: bool, console_width: uint, files: Vec) { let width = files.iter() .map(|f| f.name.len() + 2) .sum() - 2; - + if width <= console_width { let names: Vec = files.iter() .map(|f| f.file_name().to_string()) @@ -140,7 +141,7 @@ fn grid_view(across: bool, console_width: uint, files: Vec) { if count % num_columns != 0 { num_rows += 1; } - + for y in range(0, num_rows) { for x in range(0, num_columns) { let num = if across { @@ -149,11 +150,11 @@ fn grid_view(across: bool, console_width: uint, files: Vec) { else { y + num_rows * x }; - + if num >= count { continue; } - + let ref file = files[num]; let file_name = file.name.clone(); let styled_name = file.file_colour().paint(file_name.as_slice()).to_string(); @@ -175,7 +176,7 @@ fn details_view(options: &Options, columns: &Vec, files: Vec) { // width of each column based on the length of the results and // padding the fields during output. - let mut cache = Unix::empty_cache(); + let mut cache = OSUsers::empty_cache(); let mut table: Vec> = files.iter() .map(|f| columns.iter().map(|c| f.display(c, &mut cache)).collect()) diff --git a/src/file.rs b/src/file.rs index f87e4a10..ae61ff05 100644 --- a/src/file.rs +++ b/src/file.rs @@ -5,10 +5,11 @@ use ansi_term::{ANSIString, Colour, Style}; use ansi_term::Style::Plain; use ansi_term::Colour::{Red, Green, Yellow, Blue, Purple, Cyan, Fixed}; +use users::{Users, OSUsers}; + use column::Column; use column::Column::*; use format::{format_metric_bytes, format_IEC_bytes}; -use unix::Unix; use sort::SortPart; use dir::Dir; use filetype::HasType; @@ -40,17 +41,17 @@ impl<'a> File<'a> { } pub fn with_stat(stat: io::FileStat, path: Path, parent: Option<&'a Dir>) -> File<'a> { - let v = path.filename().unwrap(); // fails if / or . or .. + let v = path.filename().unwrap(); // fails if / or . or .. let filename = String::from_utf8(v.to_vec()).unwrap_or_else(|_| panic!("Name was not valid UTF-8")); - File { + File { path: path.clone(), dir: parent, stat: stat, name: filename.clone(), ext: File::ext(filename.clone()), parts: SortPart::split_into_parts(filename.clone()), - } + } } fn ext(name: String) -> Option { @@ -103,7 +104,7 @@ impl<'a> File<'a> { } } - pub fn display(&self, column: &Column, unix: &mut Unix) -> String { + pub fn display(&self, column: &Column, users_cache: &mut OSUsers) -> String { match *column { Permissions => { self.permissions_string() @@ -141,18 +142,34 @@ impl<'a> File<'a> { // Display the ID if the user/group doesn't exist, which // usually means it was deleted but its files weren't. User => { - let uid = self.stat.unstable.uid as u32; - unix.load_user(uid); - let user_name = unix.get_user_name(uid).unwrap_or(uid.to_string()); - let style = if unix.uid == uid { Yellow.bold() } else { Plain }; + let uid = self.stat.unstable.uid as i32; + + let user_name = match users_cache.get_user_by_uid(uid) { + Some(user) => user.name, + None => uid.to_string(), + }; + + let style = if users_cache.get_current_uid() == uid { Yellow.bold() } else { Plain }; style.paint(user_name.as_slice()).to_string() }, Group => { let gid = self.stat.unstable.gid as u32; - unix.load_group(gid); - let group_name = unix.get_group_name(gid).unwrap_or(gid.to_string()); - let style = if unix.is_group_member(gid) { Yellow.normal() } else { Plain }; + let mut style = Plain; + + let group_name = match users_cache.get_group_by_gid(gid) { + Some(group) => { + let current_uid = users_cache.get_current_uid(); + if let Some(current_user) = users_cache.get_user_by_uid(current_uid) { + if current_user.primary_group == group.gid || group.members.contains(¤t_user.name) { + style = Yellow.bold(); + } + } + group.name + }, + None => gid.to_string(), + }; + style.paint(group_name.as_slice()).to_string() }, } @@ -164,10 +181,10 @@ impl<'a> File<'a> { if self.stat.kind == io::FileType::Symlink { match fs::readlink(&self.path) { Ok(path) => { - let target_path = match self.dir { - Some(dir) => dir.path.join(path), - None => path, - }; + let target_path = match self.dir { + Some(dir) => dir.path.join(path), + None => path, + }; format!("{} {}", displayed_name, self.target_file_name_and_arrow(target_path)) } Err(_) => displayed_name.to_string(), diff --git a/src/unix.rs b/src/unix.rs deleted file mode 100644 index df0fe1b0..00000000 --- a/src/unix.rs +++ /dev/null @@ -1,141 +0,0 @@ -use std::ptr::read; -use std::ptr; -use std::collections::HashMap; - -mod c { - #![allow(non_camel_case_types)] - extern crate libc; - pub use self::libc::{ - c_char, - c_int, - uid_t, - gid_t, - time_t - }; - - #[repr(C)] - pub struct c_passwd { - pub pw_name: *const c_char, // login name - pub pw_passwd: *const c_char, - pub pw_uid: c_int, // user ID - pub pw_gid: c_int, // group ID - pub pw_change: time_t, - pub pw_class: *const c_char, - pub pw_gecos: *const c_char, // full name - pub pw_dir: *const c_char, // login dir - pub pw_shell: *const c_char, // login shell - pub pw_expire: time_t, // password expiry time - } - - #[repr(C)] - pub struct c_group { - pub gr_name: *const c_char, // group name - pub gr_passwd: *const c_char, // password - pub gr_gid: gid_t, // group id - pub gr_mem: *const *const c_char, // names of users in the group - } - - extern { - pub fn getpwuid(uid: c_int) -> *const c_passwd; - pub fn getgrgid(gid: uid_t) -> *const c_group; - pub fn getuid() -> libc::c_int; - } -} - -pub struct Unix { - user_names: HashMap>, // mapping of user IDs to user names - group_names: HashMap>, // mapping of groups IDs to group names - groups: HashMap, // mapping of group IDs to whether the current user is a member - pub uid: u32, // current user's ID - pub username: String, // current user's name -} - -impl Unix { - pub fn empty_cache() -> Unix { - let uid = unsafe { c::getuid() }; - let infoptr = unsafe { c::getpwuid(uid as i32) }; - let info = unsafe { infoptr.as_ref().unwrap() }; // the user has to have a name - - let username = unsafe { String::from_raw_buf(info.pw_name as *const u8) }; - - let mut user_names = HashMap::new(); - user_names.insert(uid as u32, Some(username.clone())); - - // Unix groups work like this: every group has a list of - // users, referred to by their names. But, every user also has - // a primary group, which isn't in this list. So handle this - // case immediately after we look up the user's details. - let mut groups = HashMap::new(); - groups.insert(info.pw_gid as u32, true); - - Unix { - user_names: user_names, - group_names: HashMap::new(), - uid: uid as u32, - username: username, - groups: groups, - } - } - - pub fn get_user_name(&self, uid: u32) -> Option { - self.user_names[uid].clone() - } - - pub fn get_group_name(&self, gid: u32) -> Option { - self.group_names[gid].clone() - } - - pub fn is_group_member(&self, gid: u32) -> bool { - self.groups[gid] - } - - pub fn load_user(&mut self, uid: u32) { - let pw = unsafe { c::getpwuid(uid as i32) }; - if pw.is_not_null() { - let username = unsafe { Some(String::from_raw_buf(read(pw).pw_name as *const u8)) }; - self.user_names.insert(uid, username); - } - else { - self.user_names.insert(uid, None); - } - } - - fn group_membership(group: *const *const c::c_char, uname: &String) -> bool { - let mut i = 0; - - // The list of members is a pointer to a pointer of - // characters, terminated by a null pointer. - loop { - match unsafe { group.offset(i).as_ref() } { - Some(&username) => { - if username == ptr::null() { - return false; // username was null, weird - } - else if unsafe { String::from_raw_buf(username as *const u8) } == *uname { - return true; // group found! - } - else { - i += 1; // try again with the next group - } - }, - None => return false, // no more groups to check, and none found - } - } - } - - pub fn load_group(&mut self, gid: u32) { - match unsafe { c::getgrgid(gid).as_ref() } { - None => { - self.group_names.insert(gid, None); - self.groups.insert(gid, false); - }, - Some(r) => { - let group_name = unsafe { Some(String::from_raw_buf(r.gr_name as *const u8)) }; - if !self.groups.contains_key(&gid) { - self.groups.insert(gid, Unix::group_membership(r.gr_mem, &self.username)); - } - self.group_names.insert(gid, group_name); - } - } - } -}