Skip to content

Instantly share code, notes, and snippets.

@rust-play
Created July 21, 2019 22:51
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save rust-play/3bb4c40b84858e14f9b1e72032c28704 to your computer and use it in GitHub Desktop.
Save rust-play/3bb4c40b84858e14f9b1e72032c28704 to your computer and use it in GitHub Desktop.
Code shared from the Rust Playground
//! [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