summaryrefslogtreecommitdiffstats
path: root/src/file.rs
diff options
context:
space:
mode:
authorBen S <ogham@bsago.me>2015-04-23 13:00:34 +0100
committerBen S <ogham@bsago.me>2015-04-23 13:00:34 +0100
commitadbaa51cb9421df43625b96ea3ea39b455fd5ee4 (patch)
tree769e5cc63ed3d5759743420a95520e2ccbb90fab /src/file.rs
parenteee49ecefea05025f94193d487d2dae561a8a8f1 (diff)
Use new io + path + fs libraries (LOTS OF CHANGES)
Exa now uses the new IO, Path, and Filesystem libraries that have been out for a while now. Unfortunately, the new libraries don't *entirely* cover the range of the old libraries just yet: in particular, to become more cross-platform, the data in `UnstableFileStat` isn't available in the Unix `MetadataExt` yet. Much of this is contained in rust-lang/rfcs#1044 (which is due to be implemented in rust-lang/rust#14711), but it's not *entirely* there yet. As such, this commits a serious loss of functionality: no symlink viewing, no hard links or blocks, or users or groups. Also, some of the code could now be optimised. I just wanted to commit this to sort out most of the 'teething problems' of having a different path system in advance. Here's an example problem that took ages to fix for you, just because you read this far: when I first got exa to compile, it worked mostly fine, except calling `exa` by itself didn't list the current directory. I traced where the command-line options were being generated, to where files and directories were sorted, to where the threads were spawned... and the problem turned out to be that it was using the full path as the file name, rather than just the last component, and these paths happened to begin with `.`, so it thought they were dotfiles.
Diffstat (limited to 'src/file.rs')
-rw-r--r--src/file.rs189
1 files changed, 100 insertions, 89 deletions
diff --git a/src/file.rs b/src/file.rs
index d1f1c2f..38bd56a 100644
--- a/src/file.rs
+++ b/src/file.rs
@@ -4,13 +4,12 @@
// There's a tracking issue for it:
// https://github.com/rust-lang/rfcs/issues/939
-use std::old_io::{fs, IoResult};
-use std::old_io as io;
-use std::old_path::GenericPath;
-use std::old_path::posix::Path;
use std::ascii::AsciiExt;
use std::env::current_dir;
-use unicode::str::UnicodeStr;
+use std::fs;
+use std::io;
+use std::os::unix::fs::PermissionsExt;
+use std::path::{Component, Path, PathBuf};
use ansi_term::{ANSIString, ANSIStrings, Colour, Style};
use ansi_term::Style::Plain;
@@ -48,8 +47,8 @@ pub struct File<'a> {
pub name: String,
pub dir: Option<&'a Dir>,
pub ext: Option<String>,
- pub path: Path,
- pub stat: io::FileStat,
+ pub path: PathBuf,
+ pub stat: fs::Metadata,
pub xattrs: Vec<Attribute>,
pub this: Option<Dir>,
}
@@ -59,18 +58,18 @@ impl<'a> File<'a> {
/// appropriate. Paths specified directly on the command-line have no Dirs.
///
/// This uses lstat instead of stat, which doesn't follow symbolic links.
- pub fn from_path(path: &Path, parent: Option<&'a Dir>, recurse: bool) -> IoResult<File<'a>> {
- fs::lstat(path).map(|stat| File::with_stat(stat, path, parent, recurse))
+ pub fn from_path(path: &Path, parent: Option<&'a Dir>, recurse: bool) -> io::Result<File<'a>> {
+ fs::metadata(path).map(|stat| File::with_stat(stat, path, parent, recurse)) // todo: lstat
}
/// Create a new File object from the given Stat result, and other data.
- pub fn with_stat(stat: io::FileStat, path: &Path, parent: Option<&'a Dir>, recurse: bool) -> File<'a> {
+ pub fn with_stat(stat: fs::Metadata, path: &Path, parent: Option<&'a Dir>, recurse: bool) -> File<'a> {
let filename = path_filename(path);
// If we are recursing, then the `this` field contains a Dir object
// that represents the current File as a directory, if it is a
// directory. This is used for the --tree option.
- let this = if recurse && stat.kind == io::FileType::Directory {
+ let this = if recurse && stat.is_dir() {
Dir::readdir(path).ok()
}
else {
@@ -78,7 +77,7 @@ impl<'a> File<'a> {
};
File {
- path: path.clone(),
+ path: path.to_path_buf(),
dir: parent,
stat: stat,
ext: ext(&filename),
@@ -88,6 +87,22 @@ impl<'a> File<'a> {
}
}
+ pub fn is_directory(&self) -> bool {
+ self.stat.is_dir()
+ }
+
+ pub fn is_file(&self) -> bool {
+ self.stat.is_file()
+ }
+
+ pub fn is_link(&self) -> bool {
+ false
+ }
+
+ pub fn is_pipe(&self) -> bool {
+ false
+ }
+
/// Whether this file is a dotfile or not.
pub fn is_dotfile(&self) -> bool {
self.name.starts_with(".")
@@ -99,11 +114,6 @@ impl<'a> File<'a> {
name.ends_with("~") || (name.starts_with("#") && name.ends_with("#"))
}
- /// Whether this file is a directory or not.
- pub fn is_directory(&self) -> bool {
- self.stat.kind == io::FileType::Directory
- }
-
/// Get the data for a column, formatted as a coloured string.
pub fn display<U: Users>(&self, column: &Column, users_cache: &mut U, locale: &UserLocale) -> Cell {
match *column {
@@ -125,7 +135,7 @@ impl<'a> File<'a> {
/// It consists of the file name coloured in the appropriate style,
/// with special formatting for a symlink.
pub fn file_name_view(&self) -> String {
- if self.stat.kind == io::FileType::Symlink {
+ if self.is_link() {
self.symlink_file_name_view()
}
else {
@@ -145,9 +155,9 @@ impl<'a> File<'a> {
let name = &*self.name;
let style = self.file_colour();
- if let Ok(path) = fs::readlink(&self.path) {
+ if let Ok(path) = fs::read_link(&self.path) {
let target_path = match self.dir {
- Some(dir) => dir.join(path),
+ Some(dir) => dir.join(&*path),
None => path,
};
@@ -167,14 +177,13 @@ impl<'a> File<'a> {
path_prefix.push_str("/");
}
- let path_bytes: Vec<&[u8]> = file.path.components().collect();
+ let path_bytes: Vec<Component> = file.path.components().collect();
if !path_bytes.is_empty() {
// Use init() to add all but the last component of the
// path to the prefix. init() panics when given an
// empty list, hence the check.
for component in path_bytes.init().iter() {
- let string = String::from_utf8_lossy(component).to_string();
- path_prefix.push_str(&string);
+ path_prefix.push_str(&*component.as_os_str().to_string_lossy());
path_prefix.push_str("/");
}
}
@@ -220,9 +229,9 @@ impl<'a> File<'a> {
let filename = path_filename(target_path);
// Use stat instead of lstat - we *want* to follow links.
- if let Ok(stat) = fs::stat(target_path) {
+ if let Ok(stat) = fs::metadata(target_path) {
Ok(File {
- path: target_path.clone(),
+ path: target_path.to_path_buf(),
dir: self.dir,
stat: stat,
ext: ext(&filename),
@@ -239,7 +248,7 @@ impl<'a> File<'a> {
/// This file's number of hard links as a coloured string.
fn hard_links(&self, locale: &locale::Numeric) -> Cell {
let style = if self.has_multiple_links() { Red.on(Yellow) } else { Red.normal() };
- Cell::paint(style, &locale.format_int(self.stat.unstable.nlink as isize)[..])
+ Cell::paint(style, &locale.format_int(0 /*self.stat.unstable.nlink*/ as isize)[..])
}
/// Whether this is a regular file with more than one link.
@@ -248,18 +257,19 @@ impl<'a> File<'a> {
/// while you can come across directories and other types with multiple
/// links much more often.
fn has_multiple_links(&self) -> bool {
- self.stat.kind == io::FileType::RegularFile && self.stat.unstable.nlink > 1
+ self.is_file() && (0 /*self.stat.unstable.nlink*/) > 1
}
/// This file's inode as a coloured string.
fn inode(&self) -> Cell {
- Cell::paint(Purple.normal(), &*self.stat.unstable.inode.to_string())
+ let inode = 0i32; /* self.stat.unstable.inode */
+ Cell::paint(Purple.normal(), &inode.to_string()[..])
}
/// This file's number of filesystem blocks (if available) as a coloured string.
fn blocks(&self, locale: &locale::Numeric) -> Cell {
- if self.stat.kind == io::FileType::RegularFile || self.stat.kind == io::FileType::Symlink {
- Cell::paint(Cyan.normal(), &locale.format_int(self.stat.unstable.blocks as isize)[..])
+ if self.is_file() || self.is_link() {
+ Cell::paint(Cyan.normal(), &locale.format_int(0 /*self.stat.unstable.blocks*/)[..])
}
else {
Cell { text: GREY.paint("-").to_string(), length: 1 }
@@ -272,11 +282,11 @@ impl<'a> File<'a> {
/// instead. This usually happens when a user is deleted, but still owns
/// files.
fn user<U: Users>(&self, users_cache: &mut U) -> Cell {
- let uid = self.stat.unstable.uid as i32;
+ let uid = 0; // self.stat.unstable.uid as u32
let user_name = match users_cache.get_user_by_uid(uid) {
Some(user) => user.name,
- None => self.stat.unstable.uid.to_string(),
+ None => uid.to_string(),
};
let style = if users_cache.get_current_uid() == uid { Yellow.bold() } else { Plain };
@@ -287,10 +297,10 @@ impl<'a> File<'a> {
///
/// As above, if not present, it formats the gid as a number instead.
fn group<U: Users>(&self, users_cache: &mut U) -> Cell {
- let gid = self.stat.unstable.gid as u32;
+ let gid = 0; // self.stat.unstable.gid as u32;
let mut style = Plain;
- let group_name = match users_cache.get_group_by_gid(gid) {
+ let group_name = match users_cache.get_group_by_gid(gid as u32) {
Some(group) => {
let current_uid = users_cache.get_current_uid();
if let Some(current_user) = users_cache.get_user_by_uid(current_uid) {
@@ -300,7 +310,7 @@ impl<'a> File<'a> {
}
group.name
},
- None => self.stat.unstable.gid.to_string(),
+ None => gid.to_string(),
};
Cell::paint(style, &*group_name)
@@ -318,9 +328,9 @@ impl<'a> File<'a> {
}
else {
let result = match size_format {
- SizeFormat::DecimalBytes => decimal_prefix(self.stat.size as f64),
- SizeFormat::BinaryBytes => binary_prefix(self.stat.size as f64),
- SizeFormat::JustBytes => return Cell::paint(Green.bold(), &locale.format_int(self.stat.size as isize)[..]),
+ SizeFormat::DecimalBytes => decimal_prefix(self.stat.len() as f64),
+ SizeFormat::BinaryBytes => binary_prefix(self.stat.len() as f64),
+ SizeFormat::JustBytes => return Cell::paint(Green.bold(), &locale.format_int(self.stat.len())[..]),
};
match result {
@@ -342,9 +352,9 @@ impl<'a> File<'a> {
// Need to convert these values from milliseconds into seconds.
let time_in_seconds = match time_type {
- TimeType::FileAccessed => self.stat.accessed,
- TimeType::FileModified => self.stat.modified,
- TimeType::FileCreated => self.stat.created,
+ TimeType::FileAccessed => self.stat.accessed(),
+ TimeType::FileModified => self.stat.modified(),
+ TimeType::FileCreated => 0 // self.stat.created(),
} as i64 / 1000;
let date = LocalDateTime::at(time_in_seconds);
@@ -364,19 +374,26 @@ impl<'a> File<'a> {
/// Although the file type can usually be guessed from the colour of the
/// file, `ls` puts this character there, so people will expect it.
fn type_char(&self) -> ANSIString {
- return match self.stat.kind {
- io::FileType::RegularFile => Plain.paint("."),
- io::FileType::Directory => Blue.paint("d"),
- io::FileType::NamedPipe => Yellow.paint("|"),
- io::FileType::BlockSpecial => Purple.paint("s"),
- io::FileType::Symlink => Cyan.paint("l"),
- io::FileType::Unknown => Plain.paint("?"),
+ if self.is_file() {
+ Plain.paint(".")
+ }
+ else if self.is_directory() {
+ Blue.paint("d")
+ }
+ else if self.is_pipe() {
+ Yellow.paint("|")
+ }
+ else if self.is_link() {
+ Cyan.paint("l")
+ }
+ else {
+ Purple.paint("?")
}
}
/// Marker indicating that the file contains extended attributes
///
- /// Returns “@” or “ ” depending on wheter the file contains an extented
+ /// Returns "@" or " ” depending on wheter the file contains an extented
/// attribute or not. Also returns “ ” in case the attributes cannot be read
/// for some reason.
fn attribute_marker(&self) -> ANSIString {
@@ -389,23 +406,22 @@ impl<'a> File<'a> {
/// bits are bold because they're the ones used most often, and executable
/// files are underlined to make them stand out more.
fn permissions_string(&self) -> Cell {
- let bits = self.stat.perm;
- let executable_colour = match self.stat.kind {
- io::FileType::RegularFile => Green.bold().underline(),
- _ => Green.bold(),
- };
+
+ let bits = self.stat.permissions().mode();
+ let executable_colour = if self.is_file() { Green.bold().underline() }
+ else { Green.bold() };
let string = ANSIStrings(&[
self.type_char(),
- File::permission_bit(&bits, io::USER_READ, "r", Yellow.bold()),
- File::permission_bit(&bits, io::USER_WRITE, "w", Red.bold()),
- File::permission_bit(&bits, io::USER_EXECUTE, "x", executable_colour),
- File::permission_bit(&bits, io::GROUP_READ, "r", Yellow.normal()),
- File::permission_bit(&bits, io::GROUP_WRITE, "w", Red.normal()),
- File::permission_bit(&bits, io::GROUP_EXECUTE, "x", Green.normal()),
- File::permission_bit(&bits, io::OTHER_READ, "r", Yellow.normal()),
- File::permission_bit(&bits, io::OTHER_WRITE, "w", Red.normal()),
- File::permission_bit(&bits, io::OTHER_EXECUTE, "x", Green.normal()),
+ File::permission_bit(bits, Permission::UserRead, "r", Yellow.bold()),
+ File::permission_bit(bits, Permission::UserWrite, "w", Red.bold()),
+ File::permission_bit(bits, Permission::UserExecute, "x", executable_colour),
+ File::permission_bit(bits, Permission::GroupRead, "r", Yellow.normal()),
+ File::permission_bit(bits, Permission::GroupWrite, "w", Red.normal()),
+ File::permission_bit(bits, Permission::GroupExecute, "x", Green.normal()),
+ File::permission_bit(bits, Permission::OtherRead, "r", Yellow.normal()),
+ File::permission_bit(bits, Permission::OtherWrite, "w", Red.normal()),
+ File::permission_bit(bits, Permission::OtherExecute, "x", Green.normal()),
self.attribute_marker()
]).to_string();
@@ -413,8 +429,9 @@ impl<'a> File<'a> {
}
/// Helper method for the permissions string.
- fn permission_bit(bits: &io::FilePermission, bit: io::FilePermission, character: &'static str, style: Style) -> ANSIString<'static> {
- if bits.contains(bit) {
+ fn permission_bit(bits: i32, bit: Permission, character: &'static str, style: Style) -> ANSIString<'static> {
+ let bi32 = bit as i32;
+ if bits & bi32 == bi32 {
style.paint(character)
}
else {
@@ -430,7 +447,7 @@ impl<'a> File<'a> {
/// dangerous to highlight *all* compiled, so the paths in this vector
/// are checked for existence first: for example, `foo.js` is perfectly
/// valid without `foo.coffee`.
- pub fn get_source_files(&self) -> Vec<Path> {
+ pub fn get_source_files(&self) -> Vec<PathBuf> {
if let Some(ref ext) = self.ext {
match &ext[..] {
"class" => vec![self.path.with_extension("java")], // Java
@@ -458,15 +475,12 @@ impl<'a> File<'a> {
}
fn git_status(&self) -> Cell {
- use std::os::unix::ffi::OsStrExt;
- use std::ffi::AsOsStr;
-
let status = match self.dir {
None => GREY.paint("--").to_string(),
Some(d) => {
let cwd = match current_dir() {
Err(_) => Path::new(".").join(&self.path),
- Ok(dir) => Path::new(dir.as_os_str().as_bytes()).join(&self.path),
+ Ok(dir) => dir.join(&self.path),
};
d.git_status(&cwd, self.is_directory())
@@ -484,12 +498,10 @@ impl<'a> File<'a> {
/// the path has no components for `.`, `..`, and `/`, so in these
/// cases, the entire path is used.
fn path_filename(path: &Path) -> String {
- let bytes = match path.components().last() {
- Some(b) => b,
- None => path.as_vec(),
- };
-
- String::from_utf8_lossy(bytes).to_string()
+ match path.iter().last() {
+ Some(os_str) => os_str.to_string_lossy().to_string(),
+ None => ".".to_string(), // can this even be reached?
+ }
}
/// Extract an extension from a string, if one is present, in lowercase.
@@ -504,10 +516,21 @@ fn ext<'a>(name: &'a str) -> Option<String> {
name.rfind('.').map(|p| name[p+1..].to_ascii_lowercase())
}
+enum Permission {
+ UserRead = 0o400,
+ UserWrite = 0o200,
+ UserExecute = 0o100,
+ GroupRead = 0o040,
+ GroupWrite = 0o020,
+ GroupExecute = 0o010,
+ OtherRead = 0o004,
+ OtherWrite = 0o002,
+ OtherExecute = 0o001,
+}
+
#[cfg(test)]
pub mod test {
pub use super::*;
- use super::path_filename;
pub use column::{Cell, Column};
pub use std::old_io as io;
@@ -522,18 +545,6 @@ pub mod test {
pub use ansi_term::Colour::Yellow;
#[test]
- fn current_filename() {
- let filename = path_filename(&Path::new("."));
- assert_eq!(&filename[..], ".")
- }
-
- #[test]
- fn parent_filename() {
- let filename = path_filename(&Path::new(".."));
- assert_eq!(&filename[..], "..")
- }
-
- #[test]
fn extension() {
assert_eq!(Some("dat".to_string()), super::ext("fester.dat"))
}