nuts_tool/cli/archive/
list.rs1use anyhow::Result;
24use clap::{ArgAction, Args};
25use log::debug;
26use nuts_archive::{Entry, Group};
27use std::cmp;
28use std::fmt::{self, Write};
29
30use crate::backend::PluginBackend;
31use crate::cli::archive::open_archive;
32use crate::say;
33use crate::time::TimeFormat;
34
35const SIZE_WIDTH: usize = 9;
36
37struct Type<'a>(&'a Entry<'a, PluginBackend>);
38
39impl<'a> fmt::Display for Type<'a> {
40 fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
41 match self.0 {
42 Entry::File(_) => fmt.write_char('-'),
43 Entry::Directory(_) => fmt.write_char('d'),
44 Entry::Symlink(_) => fmt.write_char('l'),
45 }
46 }
47}
48
49struct Permission<'a>(&'a Entry<'a, PluginBackend>, Group);
50
51impl<'a> fmt::Display for Permission<'a> {
52 fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
53 if self.0.can_read(self.1) {
54 fmt.write_char('r')?;
55 } else {
56 fmt.write_char('-')?;
57 };
58
59 if self.0.can_write(self.1) {
60 fmt.write_char('w')?;
61 } else {
62 fmt.write_char('-')?;
63 };
64
65 if self.0.can_execute(self.1) {
66 fmt.write_char('x')?;
67 } else {
68 fmt.write_char('-')?;
69 };
70
71 Ok(())
72 }
73}
74
75struct Name<'a>(&'a Entry<'a, PluginBackend>);
76
77impl<'a> fmt::Display for Name<'a> {
78 fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
79 if let Some(symlink) = self.0.as_symlink() {
80 write!(fmt, "{} -> {}", self.0.name(), symlink.target())
81 } else {
82 write!(fmt, "{}", self.0.name())
83 }
84 }
85}
86
87#[derive(Debug)]
88struct PrintLongContext {
89 size_width: usize,
90}
91
92impl Default for PrintLongContext {
93 fn default() -> Self {
94 Self {
95 size_width: SIZE_WIDTH,
96 }
97 }
98}
99
100#[derive(Args, Debug)]
101pub struct ArchiveListArgs {
102 #[clap(short, long, action = ArgAction::SetTrue)]
104 long: bool,
105
106 #[clap(short, long, action = ArgAction::SetTrue)]
108 appended: bool,
109
110 #[clap(short = 'r', long, action = ArgAction::SetTrue)]
112 created: bool,
113
114 #[clap(short = 'n', long, action = ArgAction::SetTrue)]
116 changed: bool,
117
118 #[clap(
120 short,
121 long,
122 value_parser,
123 value_name = "FORMAT",
124 default_value = "local"
125 )]
126 time_format: TimeFormat,
127
128 #[clap(long, action = ArgAction::SetTrue)]
130 pub migrate: bool,
131
132 #[clap(short, long, env = "NUTS_CONTAINER")]
134 container: String,
135}
136
137impl ArchiveListArgs {
138 fn print_short(&self, entry: &Entry<PluginBackend>) {
139 say!("{}", entry.name());
140 }
141
142 fn print_long(&self, entry: &Entry<PluginBackend>, ctx: &mut PrintLongContext) {
143 let tstamp = if self.appended {
144 entry.appended()
145 } else if self.created {
146 entry.created()
147 } else if self.changed {
148 entry.changed()
149 } else {
150 entry.modified()
151 };
152
153 let size = entry.size().to_string();
154
155 ctx.size_width = cmp::max(ctx.size_width, size.len());
156
157 say!(
158 "{}{}{}{} {:>size_width$} {} {}",
159 Type(entry),
160 Permission(entry, Group::User),
161 Permission(entry, Group::Group),
162 Permission(entry, Group::Other),
163 size,
164 self.time_format.format(tstamp, "%d %b %H:%M"),
165 Name(entry),
166 size_width = ctx.size_width,
167 );
168 }
169
170 pub fn run(&self) -> Result<()> {
171 debug!("args: {:?}", self);
172
173 let mut archive = open_archive(&self.container, self.migrate)?;
174
175 let mut entry_opt = archive.first();
176 let mut ctx_opt = None;
177
178 loop {
179 match entry_opt {
180 Some(Ok(entry)) => {
181 if self.long {
182 let ctx = ctx_opt.get_or_insert_with(Default::default);
183 self.print_long(&entry, ctx);
184 } else {
185 self.print_short(&entry);
186 }
187
188 entry_opt = entry.next();
189 }
190 Some(Err(err)) => return Err(err.into()),
191 None => break,
192 }
193 }
194
195 Ok(())
196 }
197}