mirror of
https://github.com/TeFiLeDo/tree-owners.git
synced 2024-11-21 20:06:17 +01:00
redo output
This commit is contained in:
parent
a806fb4de2
commit
02166a07ef
28
src/id.rs
28
src/id.rs
@ -1,28 +0,0 @@
|
||||
use std::{collections::BTreeSet, fmt::Display};
|
||||
|
||||
use serde::Serialize;
|
||||
|
||||
/// Unique `uid`s and `gid`s.
|
||||
#[derive(Debug, Default, Serialize)]
|
||||
pub struct Ids {
|
||||
pub users: BTreeSet<u32>,
|
||||
pub groups: BTreeSet<u32>,
|
||||
}
|
||||
|
||||
impl Display for Ids {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
writeln!(f, "users:")?;
|
||||
for user in &self.users {
|
||||
writeln!(f, " {user}")?;
|
||||
}
|
||||
|
||||
writeln!(f)?;
|
||||
|
||||
writeln!(f, "groups:")?;
|
||||
for group in &self.groups {
|
||||
writeln!(f, " {group}")?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
43
src/main.rs
43
src/main.rs
@ -1,31 +1,42 @@
|
||||
use std::{fs::read_dir, os::linux::fs::MetadataExt, path::Path};
|
||||
|
||||
use anyhow::{ensure, Context, Result};
|
||||
use anyhow::{anyhow, ensure, Context, Result};
|
||||
use clap::Parser;
|
||||
|
||||
use crate::{cli::Args, id::Ids, name::Names, output::Output};
|
||||
use crate::{cli::Args, summary::Summary};
|
||||
|
||||
mod cli;
|
||||
mod id;
|
||||
mod name;
|
||||
mod output;
|
||||
mod summary;
|
||||
|
||||
fn main() -> Result<()> {
|
||||
human_panic::setup_panic!();
|
||||
let args = Args::parse();
|
||||
|
||||
let mut ids = Ids::default();
|
||||
let mut summary = Summary::default();
|
||||
for root in args.roots {
|
||||
fs_entry(&root, &mut ids)?;
|
||||
fs_entry(&root, &mut summary)?;
|
||||
}
|
||||
|
||||
let output: Box<dyn Output> = match args.raw {
|
||||
false => Box::new(Names::try_from(ids).context("failed to get names")?),
|
||||
true => Box::new(ids),
|
||||
};
|
||||
if !args.raw {
|
||||
let (uf, gf) = summary.lookup_names();
|
||||
|
||||
for (uid, e) in uf {
|
||||
eprintln!(
|
||||
"{:#}",
|
||||
anyhow!(e).context(format!("failed to get name for user {uid}"))
|
||||
);
|
||||
}
|
||||
|
||||
for (gid, e) in gf {
|
||||
eprintln!(
|
||||
"{:#}",
|
||||
anyhow!(e).context(format!("failed to get name for group {gid}"))
|
||||
);
|
||||
}
|
||||
}
|
||||
let output = match args.json {
|
||||
false => output.human_readable(),
|
||||
true => output.json().context("failed json serialization")?,
|
||||
false => summary.to_string(),
|
||||
true => serde_json::to_string_pretty(&summary).context("json serialization failed")?,
|
||||
};
|
||||
println!("{output}");
|
||||
|
||||
@ -33,7 +44,7 @@ fn main() -> Result<()> {
|
||||
}
|
||||
|
||||
/// Perform gid & uid gathering for a file, or a directory and its children.
|
||||
fn fs_entry(entry: &Path, summary: &mut Ids) -> Result<()> {
|
||||
fn fs_entry(entry: &Path, summary: &mut Summary) -> Result<()> {
|
||||
let display = entry.display();
|
||||
ensure!(
|
||||
entry.is_symlink() || entry.exists(),
|
||||
@ -43,8 +54,8 @@ fn fs_entry(entry: &Path, summary: &mut Ids) -> Result<()> {
|
||||
let meta = entry
|
||||
.symlink_metadata()
|
||||
.context(format!("failed to get metadata for {}", display))?;
|
||||
summary.users.insert(meta.st_uid());
|
||||
summary.groups.insert(meta.st_gid());
|
||||
summary.add_user(meta.st_uid());
|
||||
summary.add_group(meta.st_gid());
|
||||
|
||||
if entry.is_dir() {
|
||||
let children = read_dir(entry).context(format!("failed to read dir {}", display))?;
|
||||
|
59
src/name.rs
59
src/name.rs
@ -1,59 +0,0 @@
|
||||
use std::{collections::BTreeSet, fmt::Display};
|
||||
|
||||
use anyhow::{anyhow, Error, Result};
|
||||
use file_owner::{Group, Owner};
|
||||
use serde::Serialize;
|
||||
|
||||
use crate::id::Ids;
|
||||
|
||||
/// Unique user and group names.
|
||||
#[derive(Debug, Default, Serialize)]
|
||||
pub struct Names {
|
||||
pub users: BTreeSet<String>,
|
||||
pub groups: BTreeSet<String>,
|
||||
}
|
||||
|
||||
impl Display for Names {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
writeln!(f, "users:")?;
|
||||
for user in &self.users {
|
||||
writeln!(f, " {user}")?;
|
||||
}
|
||||
|
||||
writeln!(f)?;
|
||||
|
||||
writeln!(f, "groups:")?;
|
||||
for group in &self.groups {
|
||||
writeln!(f, " {group}")?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<Ids> for Names {
|
||||
type Error = Error;
|
||||
|
||||
fn try_from(value: Ids) -> Result<Self> {
|
||||
Ok(Self {
|
||||
users: value
|
||||
.users
|
||||
.into_iter()
|
||||
.map(|u| match Owner::from_uid(u).name() {
|
||||
Ok(Some(name)) => Ok(name),
|
||||
Ok(None) => Err(anyhow!("no name for user {u}")),
|
||||
Err(_) => Err(anyhow!("failed to get name for user {u}")),
|
||||
})
|
||||
.collect::<Result<_>>()?,
|
||||
groups: value
|
||||
.groups
|
||||
.into_iter()
|
||||
.map(|g| match Group::from_gid(g).name() {
|
||||
Ok(Some(name)) => Ok(name),
|
||||
Ok(None) => Err(anyhow!("no name for group {g}")),
|
||||
Err(_) => Err(anyhow!("failed to get name for group {g}")),
|
||||
})
|
||||
.collect::<Result<_>>()?,
|
||||
})
|
||||
}
|
||||
}
|
@ -1,22 +0,0 @@
|
||||
use serde::Serialize;
|
||||
|
||||
/// Data that can be printed to the console.
|
||||
pub trait Output {
|
||||
/// A human readable representation of the data.
|
||||
fn human_readable(&self) -> String;
|
||||
/// A `json` representation of the data.
|
||||
fn json(&self) -> Result<String, serde_json::Error>;
|
||||
}
|
||||
|
||||
impl<T> Output for T
|
||||
where
|
||||
T: ToString + Serialize,
|
||||
{
|
||||
fn human_readable(&self) -> String {
|
||||
self.to_string()
|
||||
}
|
||||
|
||||
fn json(&self) -> Result<String, serde_json::Error> {
|
||||
serde_json::to_string_pretty(&self)
|
||||
}
|
||||
}
|
78
src/summary.rs
Normal file
78
src/summary.rs
Normal file
@ -0,0 +1,78 @@
|
||||
use std::{collections::BTreeMap, fmt::Display};
|
||||
|
||||
use file_owner::{FileOwnerError, Group, Owner};
|
||||
use serde::Serialize;
|
||||
|
||||
/// Lists of unique [User]s and [Group]s.
|
||||
#[derive(Debug, Default, Serialize)]
|
||||
pub struct Summary {
|
||||
users: BTreeMap<u32, Option<String>>,
|
||||
groups: BTreeMap<u32, Option<String>>,
|
||||
}
|
||||
|
||||
impl Summary {
|
||||
/// Add a new [User] to the [Summary].
|
||||
pub fn add_user(&mut self, uid: u32) {
|
||||
self.users.entry(uid).or_default();
|
||||
}
|
||||
|
||||
/// Add a new [Group] to the [Summary].
|
||||
pub fn add_group(&mut self, gid: u32) {
|
||||
self.groups.entry(gid).or_default();
|
||||
}
|
||||
|
||||
/// Look up the names of all users and groups.
|
||||
pub fn lookup_names(&mut self) -> (Vec<(u32, FileOwnerError)>, Vec<(u32, FileOwnerError)>) {
|
||||
let mut user_failures = vec![];
|
||||
for (uid, name) in &mut self.users {
|
||||
if name.is_some() {
|
||||
continue;
|
||||
}
|
||||
|
||||
match Owner::from_uid(*uid).name() {
|
||||
Ok(n) => *name = n,
|
||||
Err(e) => user_failures.push((*uid, e)),
|
||||
}
|
||||
}
|
||||
|
||||
let mut group_failures = vec![];
|
||||
for (gid, name) in &mut self.groups {
|
||||
if name.is_some() {
|
||||
continue;
|
||||
}
|
||||
|
||||
match Group::from_gid(*gid).name() {
|
||||
Ok(n) => *name = n,
|
||||
Err(e) => group_failures.push((*gid, e)),
|
||||
}
|
||||
}
|
||||
|
||||
(user_failures, group_failures)
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for Summary {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
let Self { users, groups } = self;
|
||||
|
||||
writeln!(f, "users:")?;
|
||||
for (uid, name) in users {
|
||||
match name {
|
||||
None => writeln!(f, " {uid}")?,
|
||||
Some(name) => writeln!(f, " {name} ({uid})")?,
|
||||
}
|
||||
}
|
||||
|
||||
writeln!(f)?;
|
||||
|
||||
writeln!(f, "groups:")?;
|
||||
for (gid, name) in groups {
|
||||
match name {
|
||||
None => writeln!(f, " {gid}")?,
|
||||
Some(name) => writeln!(f, " {name} ({gid})")?,
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user