Created
July 21, 2019 22:51
-
-
Save rust-play/3bb4c40b84858e14f9b1e72032c28704 to your computer and use it in GitHub Desktop.
Code shared from the Rust Playground
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
//! [dependencies] | |
//! chrono = "0.4.7" | |
//! iter-python = "0.9.2" | |
//! users = "0.9.1" | |
#[cfg(not(unix))] | |
compile_error!("This utility only supports Unix"); | |
use ::std::{*, | |
collections::{ | |
BTreeMap as Map, // keeps entries sorted | |
}, | |
fs::{ | |
Metadata, | |
}, | |
ops::Not, | |
os::unix::fs::{ | |
MetadataExt, | |
PermissionsExt, | |
}, | |
}; | |
use ::chrono::{ | |
offset::{ | |
Local, | |
TimeZone, | |
}, | |
}; | |
use ::users::{ | |
Groups, | |
Users, | |
UsersCache, | |
}; | |
fn main () | |
{ | |
let path: borrow::Cow<str> = | |
env::args() | |
.nth(1) | |
.map(Into::into) | |
.unwrap_or_else(|| ".".into()) | |
; | |
let mut map: Map<String, FileStats> = Map::new(); | |
let cache = UsersCache::new(); | |
read_file(&mut map, &*path, &cache, false) | |
.unwrap_or_else(|err| { | |
eprintln!("Error: {}", err); | |
process::exit(1); | |
}) | |
; | |
print_results(&map); | |
} | |
pub | |
struct FileStats { | |
is_dir: bool, | |
permissions: String, | |
user: String, | |
group: String, | |
timestamp: String, | |
size: String, | |
} | |
impl FileStats { | |
pub | |
fn format_with ( | |
self: &'_ Self, | |
user_pad: usize, | |
group_pad: usize, | |
size_pad: usize, | |
) -> impl fmt::Display + '_ | |
{ | |
::iter_python::f!( | |
concat!( | |
"{perms}", | |
" ", | |
"{user: >0user_pad$}", | |
" ", | |
"{group: <0group_pad$}", | |
" ", | |
"{size: >0size_pad$}", | |
" ", | |
"{time}", | |
), | |
perms=self.permissions, | |
user=self.user, | |
group=self.group, | |
size=self.size, | |
time=self.timestamp, | |
user_pad=user_pad, | |
group_pad=group_pad, | |
size_pad=size_pad, | |
) | |
} | |
} | |
fn read_file ( | |
map: &'_ mut Map<String, FileStats>, | |
path: &'_ (impl AsRef<path::Path> + ?Sized), | |
cache: &'_ UsersCache, | |
no_recursion: bool, | |
) -> io::Result<()> | |
{ | |
fn read_dir ( | |
map: &'_ mut Map<String, FileStats>, | |
path: &'_ (impl AsRef<path::Path> + ?Sized), | |
cache: &'_ UsersCache, | |
) -> io::Result<()> | |
{ | |
let path = path.as_ref(); | |
for p in fs::read_dir(path)? { | |
let sub_path = p?.path(); | |
read_file(map, &*sub_path, cache, true)?; | |
} | |
Ok(()) | |
} | |
let path = path.as_ref(); | |
let meta = fs::metadata(path)?; | |
let stats = create_file_stats(&meta, cache)?; | |
let mut relative_path = String::new(); | |
let path = &*path.to_string_lossy() as &str; | |
if path.starts_with(".").not() && path.starts_with("/").not() { | |
relative_path.push_str("./"); | |
}; | |
let is_dir = meta.is_dir(); | |
relative_path.push_str(path); | |
if is_dir && path.ends_with("/").not() { | |
relative_path.push('/'); | |
} | |
map.insert(relative_path, stats); | |
if no_recursion.not() && is_dir { | |
read_dir(map, path, cache)?; | |
} | |
Ok(()) | |
} | |
fn print_results (map: &'_ collections::BTreeMap<String, FileStats>) | |
{ | |
let (mut user_pad, mut group_pad, mut size_pad) = (0, 0, 0); | |
map .values() | |
.for_each(|file_stats| { | |
user_pad = cmp::max(user_pad, file_stats.user.len()); | |
group_pad = cmp::max(user_pad, file_stats.group.len()); | |
size_pad = cmp::max(user_pad, file_stats.size.len()); | |
}); | |
eprintln!("{} entries:", map.len()); | |
// no need to sort the entries of a BTreeMap | |
for (path, file_stats) in map.iter() { | |
if file_stats.is_dir { | |
println!( | |
"{} \x1B[36m{}\x1B[0m", | |
file_stats.format_with(user_pad, group_pad, size_pad), | |
path, | |
); | |
} else { | |
println!( | |
"{} {}", | |
file_stats.format_with(user_pad, group_pad, size_pad), | |
path, | |
); | |
} | |
} | |
} | |
fn create_file_stats ( | |
meta: &'_ Metadata, | |
cache: &'_ UsersCache, | |
) -> io::Result<FileStats> | |
{ | |
fn human_size (size: u64) -> String | |
{ | |
const FACTOR: f64 = 1024.; | |
if size < (FACTOR as u64) { | |
return size.to_string(); | |
} | |
let mut size = size as f64; | |
let suffix = | |
["", "K", "M", "G"] | |
.iter().copied() | |
.find(|_| { | |
let next_size = size / FACTOR; | |
if next_size >= 1. { | |
size = next_size; | |
false | |
} else { | |
true | |
} | |
}) | |
.unwrap_or("T") | |
; | |
format!("{:.1}{}", (size * 10.).round() / 10., suffix) | |
} | |
fn build_permissions (is_dir: bool, mode: u32) -> String | |
{ | |
#[allow(non_snake_case)] | |
mod Perms { | |
pub const DIR : char = 'd'; | |
pub const NONE : char = '-'; | |
pub const READ : char = 'r'; | |
pub const WRITE : char = 'w'; | |
pub const EXECUTE: char = 'x'; | |
} | |
let mut mask = 1 << 9; | |
iter::once( | |
if is_dir { Perms::DIR } else { Perms::NONE } | |
).chain({ | |
const RWX: [char; 3] = | |
[Perms::READ, Perms::WRITE, Perms::EXECUTE] | |
; | |
(0 .. 9).map(|i| { | |
mask >>= 1; | |
if mode & mask != 0 { | |
RWX[i % 3] | |
} else { | |
Perms::NONE | |
} | |
}) | |
}).collect() | |
} | |
let is_dir = meta.is_dir(); | |
let size = human_size(meta.len()); | |
let timestamp = Local.timestamp(meta.mtime(), meta.mtime_nsec() as _); | |
let timestamp = format!("{}", timestamp.format("%b %_d %H:%M")).into(); | |
let permissions = meta.permissions(); | |
let mode = permissions.mode(); | |
let permissions = build_permissions(is_dir, mode).into(); | |
let user = | |
cache | |
.get_user_by_uid(meta.uid()) | |
.map(|user| { | |
user.name() | |
.to_os_string() | |
.into_string() | |
.unwrap_or_else(|err| err.to_string_lossy().into_owned()) | |
}) | |
.unwrap_or_else(|| meta.uid().to_string()) | |
; | |
let group = | |
cache | |
.get_group_by_gid(meta.gid()) | |
.map(|group| { | |
group | |
.name() | |
.to_os_string() | |
.into_string() | |
.unwrap_or_else(|err| err.to_string_lossy().into_owned()) | |
}) | |
.unwrap_or_else(|| meta.gid().to_string()) | |
; | |
Ok(FileStats { | |
is_dir, | |
permissions, | |
user, | |
group, | |
timestamp, | |
size, | |
}) | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment