Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ls: Add options --summary and --long #763

Merged
merged 1 commit into from
Jul 20, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion changelog/new.txt
Original file line number Diff line number Diff line change
Expand Up @@ -16,4 +16,5 @@ New features:
- fix: wait for password-command to successfully exit, allowing to input something into the command, and read password from stdout.
- repoinfo: Added new options --json, --only-files, --only-index
- Creation of new keys now enforces confirmation of entered key. This helps to prevent mistype of passwords during the initial entry
- Check: Add check if time is set for packs-to-delete
- Check: Add check if time is set for packs-to-delete
- ls: Options --long (-l) and --summary (-s) have been added.
121 changes: 118 additions & 3 deletions src/commands/ls.rs
Original file line number Diff line number Diff line change
@@ -1,13 +1,31 @@
//! `ls` subcommand

use std::path::Path;

/// App-local prelude includes `app_reader()`/`app_writer()`/`app_config()`
/// accessors along with logging macros. Customize as you see fit.
use crate::{commands::open_repository, status_err, Application, RUSTIC_APP};

use abscissa_core::{Command, Runnable, Shutdown};
use anyhow::Result;

use rustic_core::TreeStreamerOptions;
use rustic_core::{Node, NodeType, TreeStreamerOptions};

mod constants {
// constants from man page inode(7)
pub(super) const S_IRUSR: u32 = 0o400; // owner has read permission
pub(super) const S_IWUSR: u32 = 0o200; // owner has write permission
pub(super) const S_IXUSR: u32 = 0o100; // owner has execute permission

pub(super) const S_IRGRP: u32 = 0o040; // group has read permission
pub(super) const S_IWGRP: u32 = 0o020; // group has write permission
pub(super) const S_IXGRP: u32 = 0o010; // group has execute permission

pub(super) const S_IROTH: u32 = 0o004; // others have read permission
pub(super) const S_IWOTH: u32 = 0o002; // others have write permission
pub(super) const S_IXOTH: u32 = 0o001; // others have execute permission
}
use constants::{S_IRGRP, S_IROTH, S_IRUSR, S_IWGRP, S_IWOTH, S_IWUSR, S_IXGRP, S_IXOTH, S_IXUSR};

/// `ls` subcommand
#[derive(clap::Parser, Command, Debug)]
Expand All @@ -20,6 +38,14 @@ pub(crate) struct LsCmd {
#[clap(long)]
recursive: bool,

/// show summary
#[clap(long, short = 's')]
summary: bool,

/// show long listing
#[clap(long, short = 'l')]
long: bool,

#[clap(flatten)]
streamer_opts: TreeStreamerOptions,
}
Expand All @@ -33,6 +59,25 @@ impl Runnable for LsCmd {
}
}

#[derive(Default)]
struct Summary {
files: usize,
size: u64,
dirs: usize,
}

impl Summary {
fn update(&mut self, node: &Node) {
if node.is_dir() {
self.dirs += 1;
}
if node.is_file() {
self.files += 1;
self.size += node.meta.size;
}
}
}

impl LsCmd {
fn inner_run(&self) -> Result<()> {
let config = RUSTIC_APP.config();
Expand All @@ -42,13 +87,83 @@ impl LsCmd {
let node =
repo.node_from_snapshot_path(&self.snap, |sn| config.snapshot_filter.matches(sn))?;

// recursive if standard if we specify a snapshot without dirs. In other cases, use the parameter `recursive`
let recursive = !self.snap.contains(':') || self.recursive;

let mut summary = Summary::default();

for item in repo.ls(&node, &self.streamer_opts, recursive)? {
let (path, _) = item?;
println!("{path:?} ");
let (path, node) = item?;
summary.update(&node);
if self.long {
print_node(&node, &path);
} else {
println!("{path:?} ");
}
}

if self.summary {
println!(
"total: {} dirs, {} files, {} bytes",
summary.dirs, summary.files, summary.size
);
}

Ok(())
}
}

// print node in format similar to unix `ls`
fn print_node(node: &Node, path: &Path) {
println!(
"{:>1}{:>9} {:>8} {:>8} {:>9} {:>12} {path:?} {}",
match node.node_type {
NodeType::Dir => 'd',
NodeType::Symlink { .. } => 'l',
NodeType::Chardev { .. } => 'c',
NodeType::Dev { .. } => 'b',
NodeType::Fifo { .. } => 'p',
NodeType::Socket => 's',
_ => '-',
},
node.meta
.mode
.map(parse_permissions)
.unwrap_or_else(|| "?????????".to_string()),
node.meta.user.clone().unwrap_or_else(|| "?".to_string()),
node.meta.group.clone().unwrap_or_else(|| "?".to_string()),
node.meta.size,
node.meta
.mtime
.map(|t| t.format("%_d %b %H:%M").to_string())
.unwrap_or_else(|| "?".to_string()),
if let NodeType::Symlink { linktarget } = &node.node_type {
["->", linktarget].join(" ")
} else {
String::new()
}
);
}

// helper fn to put permissions in readable format
fn parse_permissions(mode: u32) -> String {
let user = triplet(mode, S_IRUSR, S_IWUSR, S_IXUSR);
let group = triplet(mode, S_IRGRP, S_IWGRP, S_IXGRP);
let other = triplet(mode, S_IROTH, S_IWOTH, S_IXOTH);
[user, group, other].join("")
}

// helper fn to put permissions in readable format
fn triplet(mode: u32, read: u32, write: u32, execute: u32) -> String {
match (mode & read, mode & write, mode & execute) {
(0, 0, 0) => "---",
(_, 0, 0) => "r--",
(0, _, 0) => "-w-",
(0, 0, _) => "--x",
(_, 0, _) => "r-x",
(_, _, 0) => "rw-",
(0, _, _) => "-wx",
(_, _, _) => "rwx",
}
.to_string()
}
Loading