1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
|
use std::{
fs::Metadata,
time::{Duration, SystemTime},
};
use camino::{Utf8Path, Utf8PathBuf};
fn main() {
// currently only accepts one argument, the path to the directory it will
// recurse through and make the csv for.
// spits the csv on stdout
let path: Utf8PathBuf = std::env::args().nth(1).unwrap().parse().unwrap();
process(&path, &path)
}
/// Loop through the provided directory printing CSV rows. The `root_path` is
/// used to make printed paths relative to it.
///
/// Does two passes: First pass prints files. Second pass recurs, printing directories.
fn process(root_path: &Utf8Path, path: &Utf8Path) {
//TODO: do not panic, please
let this_meta = std::fs::metadata(&path).unwrap();
let this_times = Times::metadata(&this_meta);
row(root_path, &path, &this_times);
for entry in path.read_dir_utf8().unwrap() {
let entry = entry.unwrap();
match entry.file_type() {
Err(_) => panic!(),
Ok(ft) if ft.is_file() => {
let meta = entry.metadata().unwrap();
let times = Times::metadata(&meta);
row(root_path, entry.path(), ×)
}
Ok(_) => {}
}
}
for entry in path.read_dir_utf8().unwrap() {
let entry = entry.unwrap();
match entry.file_type() {
Err(_) => panic!(),
Ok(ft) if ft.is_dir() => process(root_path, entry.path()),
Ok(_) => {}
}
}
}
fn row<P: AsRef<Utf8Path>>(root: &Utf8Path, path: P, times: &Times) {
let relative = path
.as_ref()
.strip_prefix(root)
.expect("row wasn't relative to the root; what happened?");
/// Returns an owned string with the duration formatted as seconds, or a
/// borrowed empty string
fn time_string(time: Option<Duration>) -> String {
time.map(|d| d.as_secs().to_string()).unwrap_or_default()
}
println!(
"{},{},{},{}",
escape(relative),
time_string(times.created()),
time_string(times.modified()),
time_string(times.accessed())
);
}
/// Blackslash-escape comma and backslash
fn escape<S: AsRef<str>>(raw: S) -> String {
let raw = raw.as_ref();
let mut escaped = String::with_capacity(raw.len());
for c in raw.chars() {
match c {
',' | '\\' => {
escaped.push('\\');
escaped.push(c);
}
_ => escaped.push(c),
}
}
escaped
}
struct Times {
created: Option<SystemTime>,
modified: Option<SystemTime>,
accessed: Option<SystemTime>,
}
impl Times {
/// Get the btime, mtime, atime from the Metadata. If a time cannot
/// be attained, leave a None in it's place
pub fn metadata(meta: &Metadata) -> Self {
Self {
created: meta.created().ok(),
modified: meta.modified().ok(),
accessed: meta.accessed().ok(),
}
}
/// The time since the Unix Epoch the file was created
pub fn created(&self) -> Option<Duration> {
Self::from_epoch(self.created.as_ref())
}
/// The time since the Unix Epoch the file was last modified
pub fn modified(&self) -> Option<Duration> {
Self::from_epoch(self.modified.as_ref())
}
/// The time since the Unix Epoch the file was last accessed
pub fn accessed(&self) -> Option<Duration> {
Self::from_epoch(self.accessed.as_ref())
}
fn from_epoch(maybe_time: Option<&SystemTime>) -> Option<Duration> {
maybe_time.map(|st| st.duration_since(SystemTime::UNIX_EPOCH).unwrap())
}
}
|