use std::convert::TryFrom;
use std::ops::Range;
use std::path::{Path, PathBuf};
use std::str::FromStr;
use std::time::{Duration, SystemTime};
use chrono::prelude::{NaiveDate, NaiveDateTime, Utc};
use chrono::Datelike;
use lazy_regex::{Lazy, Regex};
use thiserror::Error;
static POSIX_LS_RE: Lazy<Regex> = lazy_regex!(
r#"^([\-ld])([\-rwxsStT]{9})\s+(\d+)\s+([^ ]+)\s+([^ ]+)\s+(\d+)\s+([^ ]+\s+\d{1,2}\s+(?:\d{1,2}:\d{1,2}|\d{4}))\s+(.+)$"#
);
static DOS_LS_RE: Lazy<Regex> =
lazy_regex!(r#"^(\d{2}\-\d{2}\-\d{2}\s+\d{2}:\d{2}\s*[AP]M)\s+(<DIR>)?([\d,]*)\s+(.+)$"#);
#[derive(Debug, Clone, Eq, PartialEq, Hash)]
pub struct File {
name: String,
file_type: FileType,
size: usize,
modified: SystemTime,
uid: Option<u32>,
gid: Option<u32>,
posix_pex: (PosixPex, PosixPex, PosixPex),
}
#[derive(Debug, Clone, Eq, PartialEq, Hash)]
enum FileType {
Directory,
File,
Symlink(PathBuf),
}
#[derive(Debug, Clone, Copy, Eq, PartialEq)]
pub enum PosixPexQuery {
Owner,
Group,
Others,
}
#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)]
struct PosixPex {
read: bool,
write: bool,
execute: bool,
}
#[derive(Debug, Error, Eq, PartialEq)]
pub enum ParseError {
#[error("Syntax error: invalid line")]
SyntaxError,
#[error("Invalid date")]
InvalidDate,
#[error("Bad file size")]
BadSize,
}
impl File {
pub fn name(&self) -> &str {
self.name.as_str()
}
pub fn is_directory(&self) -> bool {
self.file_type.is_directory()
}
pub fn is_file(&self) -> bool {
self.file_type.is_file()
}
pub fn is_symlink(&self) -> bool {
self.file_type.is_symlink()
}
pub fn symlink(&self) -> Option<&Path> {
self.file_type.symlink()
}
pub fn size(&self) -> usize {
self.size
}
pub fn modified(&self) -> SystemTime {
self.modified
}
pub fn uid(&self) -> Option<u32> {
self.uid.to_owned()
}
pub fn gid(&self) -> Option<u32> {
self.gid.to_owned()
}
pub fn can_read(&self, who: PosixPexQuery) -> bool {
self.query_pex(who).can_read()
}
pub fn can_write(&self, who: PosixPexQuery) -> bool {
self.query_pex(who).can_write()
}
pub fn can_execute(&self, who: PosixPexQuery) -> bool {
self.query_pex(who).can_execute()
}
fn query_pex(&self, who: PosixPexQuery) -> &PosixPex {
match who {
PosixPexQuery::Group => &self.posix_pex.1,
PosixPexQuery::Others => &self.posix_pex.2,
PosixPexQuery::Owner => &self.posix_pex.0,
}
}
pub fn from_mlsx_line(line: &str) -> Result<Self, ParseError> {
let tokens = line.split(';').collect::<Vec<&str>>();
if tokens.is_empty() {
return Err(ParseError::SyntaxError);
}
let mut f = File {
name: String::default(),
file_type: FileType::File,
size: 0,
modified: SystemTime::UNIX_EPOCH,
uid: None,
gid: None,
posix_pex: (
PosixPex::from(0o7),
PosixPex::from(0o7),
PosixPex::from(0o7),
),
};
for token in tokens.iter() {
let mut parts = token.split('=');
let key = match parts.next() {
Some(k) => k,
None => continue,
};
let value = match parts.next() {
Some(v) => v,
None => continue,
};
match key.to_lowercase().as_str() {
"type" => {
f.file_type = match value.to_lowercase().as_str() {
"dir" => FileType::Directory,
"file" => FileType::File,
"link" => FileType::Symlink(PathBuf::default()),
_ => return Err(ParseError::SyntaxError),
};
}
"size" => {
f.size = value.parse::<usize>().map_err(|_| ParseError::BadSize)?;
}
"modify" => {
f.modified = Self::parse_mlsx_time(value)?;
}
"unix.uid" => {
f.uid = value.parse::<u32>().ok();
}
"unix.gid" => {
f.gid = value.parse::<u32>().ok();
}
"unix.mode" => {
if value.len() != 3 {
return Err(ParseError::SyntaxError);
}
let chars = value.chars().collect::<Vec<char>>();
let modes = chars
.iter()
.map(|c| c.to_digit(8).unwrap_or(0))
.collect::<Vec<u32>>();
f.posix_pex = (
PosixPex::from(modes[0] as u8),
PosixPex::from(modes[1] as u8),
PosixPex::from(modes[2] as u8),
);
}
_ => continue,
}
}
f.name = tokens.last().unwrap().trim_start().to_string();
Ok(f)
}
pub fn from_posix_line(line: &str) -> Result<Self, ParseError> {
match POSIX_LS_RE.captures(line) {
Some(metadata) => {
trace!("Parsed POSIX line {}", line);
if metadata.len() < 8 {
trace!("Bad syntax for posix line");
return Err(ParseError::SyntaxError);
}
let file_type: FileType = match metadata.get(1).unwrap().as_str() {
"-" => FileType::File,
"d" => FileType::Directory,
"l" => FileType::Symlink(PathBuf::default()),
_ => return Err(ParseError::SyntaxError), };
let pex = |range: Range<usize>| {
let mut count: u8 = 0;
for (i, c) in metadata.get(2).unwrap().as_str()[range].chars().enumerate() {
match c {
'-' | 'S' | 'T' => {}
_ => {
count += match i {
0 => 4,
1 => 2,
2 => 1,
_ => 0,
}
}
}
}
count
};
let posix_pex: (PosixPex, PosixPex, PosixPex) = (
PosixPex::from(pex(0..3)),
PosixPex::from(pex(3..6)),
PosixPex::from(pex(6..9)),
);
let modified: SystemTime = Self::parse_lstime(
metadata.get(7).unwrap().as_str().trim(),
"%b %d %Y",
"%b %d %H:%M",
)?;
let gid: Option<u32> = metadata.get(5).unwrap().as_str().trim().parse::<u32>().ok();
let uid: Option<u32> = metadata.get(4).unwrap().as_str().trim().parse::<u32>().ok();
let size: usize = metadata
.get(6)
.unwrap()
.as_str()
.parse::<usize>()
.map_err(|_| ParseError::BadSize)?;
let (name, symlink_path): (String, Option<PathBuf>) = match file_type.is_symlink() {
true => Self::get_name_and_link(metadata.get(8).unwrap().as_str()),
false => (String::from(metadata.get(8).unwrap().as_str()), None),
};
let file_type: FileType = match symlink_path {
Some(p) => FileType::Symlink(p),
None => file_type,
};
trace!(
"Found file with name {}, type: {:?}, size: {}, uid: {:?}, gid: {:?}, pex: {:?}",
name,
file_type,
size,
uid,
gid,
posix_pex
);
Ok(File {
name,
file_type,
size,
modified,
uid,
gid,
posix_pex,
})
}
None => Err(ParseError::SyntaxError),
}
}
pub fn from_dos_line(line: &str) -> Result<Self, ParseError> {
match DOS_LS_RE.captures(line) {
Some(metadata) => {
trace!("Parsed DOS line {}", line);
if metadata.len() < 5 {
return Err(ParseError::SyntaxError);
}
let modified: SystemTime = Self::parse_dostime(metadata.get(1).unwrap().as_str())?;
let file_type: FileType = match metadata.get(2).is_some() {
true => FileType::Directory,
false => FileType::File,
};
let size: usize = match file_type.is_directory() {
true => 0, false => match metadata.get(3) {
Some(val) => val
.as_str()
.parse::<usize>()
.map_err(|_| ParseError::BadSize)?,
None => 0,
},
};
let name: String = String::from(metadata.get(4).unwrap().as_str());
trace!(
"Found file with name {}, type: {:?}, size: {}",
name,
file_type,
size,
);
Ok(File {
name,
file_type,
size,
modified,
uid: None,
gid: None,
posix_pex: (
PosixPex::default(),
PosixPex::default(),
PosixPex::default(),
),
})
}
None => Err(ParseError::SyntaxError), }
}
fn get_name_and_link(token: &str) -> (String, Option<PathBuf>) {
let tokens: Vec<&str> = token.split(" -> ").collect();
let filename: String = String::from(*tokens.first().unwrap());
let symlink: Option<PathBuf> = tokens.get(1).map(PathBuf::from);
(filename, symlink)
}
fn parse_mlsx_time(tm: &str) -> Result<SystemTime, ParseError> {
NaiveDateTime::parse_from_str(tm, "%Y%m%d%H%M%S")
.map(|dt| {
SystemTime::UNIX_EPOCH
.checked_add(Duration::from_secs(dt.and_utc().timestamp() as u64))
.unwrap_or(SystemTime::UNIX_EPOCH)
})
.map_err(|_| ParseError::InvalidDate)
}
fn parse_lstime(tm: &str, fmt_year: &str, fmt_hours: &str) -> Result<SystemTime, ParseError> {
let datetime: NaiveDateTime = match NaiveDate::parse_from_str(tm, fmt_year) {
Ok(date) => {
date.and_hms_opt(0, 0, 0).unwrap()
}
Err(_) => {
let this_year: i32 = Utc::now().year();
let date_time_str: String = format!("{tm} {this_year}");
NaiveDateTime::parse_from_str(
date_time_str.as_ref(),
format!("{fmt_hours} %Y").as_ref(),
)
.map_err(|_| ParseError::InvalidDate)?
}
};
let sys_time: SystemTime = SystemTime::UNIX_EPOCH;
Ok(sys_time
.checked_add(Duration::from_secs(datetime.and_utc().timestamp() as u64))
.unwrap_or(SystemTime::UNIX_EPOCH))
}
fn parse_dostime(tm: &str) -> Result<SystemTime, ParseError> {
NaiveDateTime::parse_from_str(tm, "%d-%m-%y %I:%M%p")
.map(|dt| {
SystemTime::UNIX_EPOCH
.checked_add(Duration::from_secs(dt.and_utc().timestamp() as u64))
.unwrap_or(SystemTime::UNIX_EPOCH)
})
.map_err(|_| ParseError::InvalidDate)
}
}
impl FromStr for File {
type Err = ParseError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
Self::try_from(s)
}
}
impl TryFrom<&str> for File {
type Error = ParseError;
fn try_from(line: &str) -> Result<Self, Self::Error> {
match Self::from_posix_line(line) {
Ok(entry) => Ok(entry),
Err(_) => match Self::from_dos_line(line) {
Ok(entry) => Ok(entry),
Err(err) => Err(err),
},
}
}
}
impl TryFrom<String> for File {
type Error = ParseError;
fn try_from(line: String) -> Result<Self, Self::Error> {
File::try_from(line.as_str())
}
}
impl FileType {
fn is_directory(&self) -> bool {
matches!(self, &FileType::Directory)
}
fn is_file(&self) -> bool {
matches!(self, &FileType::File)
}
fn is_symlink(&self) -> bool {
matches!(self, &FileType::Symlink(_))
}
fn symlink(&self) -> Option<&Path> {
match self {
FileType::Symlink(p) => Some(p.as_path()),
_ => None,
}
}
}
impl PosixPex {
fn can_read(&self) -> bool {
self.read
}
fn can_write(&self) -> bool {
self.write
}
fn can_execute(&self) -> bool {
self.execute
}
}
impl Default for PosixPex {
fn default() -> Self {
Self {
read: true,
write: true,
execute: true,
}
}
}
impl From<u8> for PosixPex {
fn from(bits: u8) -> Self {
Self {
read: ((bits >> 2) & 0x01) != 0,
write: ((bits >> 1) & 0x01) != 0,
execute: (bits & 0x01) != 0,
}
}
}
#[cfg(test)]
mod test {
use chrono::DateTime;
use pretty_assertions::assert_eq;
use super::*;
#[test]
fn file_getters() {
let file: File = File {
name: String::from("provola.txt"),
file_type: FileType::File,
size: 2048,
modified: SystemTime::UNIX_EPOCH,
gid: Some(0),
uid: Some(0),
posix_pex: (PosixPex::from(7), PosixPex::from(5), PosixPex::from(4)),
};
assert_eq!(file.name(), "provola.txt");
assert_eq!(file.is_directory(), false);
assert_eq!(file.is_file(), true);
assert_eq!(file.is_symlink(), false);
assert_eq!(file.symlink(), None);
assert_eq!(file.size(), 2048);
assert_eq!(file.gid(), Some(0));
assert_eq!(file.uid(), Some(0));
assert_eq!(file.modified(), SystemTime::UNIX_EPOCH);
assert_eq!(file.can_read(PosixPexQuery::Owner), true);
assert_eq!(file.can_write(PosixPexQuery::Owner), true);
assert_eq!(file.can_execute(PosixPexQuery::Owner), true);
assert_eq!(file.can_read(PosixPexQuery::Group), true);
assert_eq!(file.can_write(PosixPexQuery::Group), false);
assert_eq!(file.can_execute(PosixPexQuery::Group), true);
assert_eq!(file.can_read(PosixPexQuery::Others), true);
assert_eq!(file.can_write(PosixPexQuery::Others), false);
assert_eq!(file.can_execute(PosixPexQuery::Others), false);
}
#[test]
fn parse_posix_line() {
let file: File = File::from_str("-rw-rw-r-- 1 0 1 8192 Nov 5 2018 omar.txt")
.ok()
.unwrap();
assert_eq!(file.name(), "omar.txt");
assert_eq!(file.size, 8192);
assert_eq!(file.is_file(), true);
assert_eq!(file.uid, Some(0));
assert_eq!(file.gid, Some(1));
assert_eq!(file.can_read(PosixPexQuery::Owner), true);
assert_eq!(file.can_write(PosixPexQuery::Owner), true);
assert_eq!(file.can_execute(PosixPexQuery::Owner), false);
assert_eq!(file.can_read(PosixPexQuery::Group), true);
assert_eq!(file.can_write(PosixPexQuery::Group), true);
assert_eq!(file.can_execute(PosixPexQuery::Group), false);
assert_eq!(file.can_read(PosixPexQuery::Others), true);
assert_eq!(file.can_write(PosixPexQuery::Others), false);
assert_eq!(file.can_execute(PosixPexQuery::Others), false);
assert_eq!(
file.modified()
.duration_since(SystemTime::UNIX_EPOCH)
.ok()
.unwrap(),
Duration::from_secs(1541376000)
);
let file: File = File::from_str("drwxrwxr-x 1 root dialout 4096 Nov 5 2018 provola")
.ok()
.unwrap();
assert_eq!(file.name(), "provola");
assert_eq!(file.size, 4096);
assert_eq!(file.is_directory(), true);
assert_eq!(file.uid, None);
assert_eq!(file.gid, None);
assert_eq!(file.can_read(PosixPexQuery::Owner), true);
assert_eq!(file.can_write(PosixPexQuery::Owner), true);
assert_eq!(file.can_execute(PosixPexQuery::Owner), true);
assert_eq!(file.can_read(PosixPexQuery::Group), true);
assert_eq!(file.can_write(PosixPexQuery::Group), true);
assert_eq!(file.can_execute(PosixPexQuery::Group), true);
assert_eq!(file.can_read(PosixPexQuery::Others), true);
assert_eq!(file.can_write(PosixPexQuery::Others), false);
assert_eq!(file.can_execute(PosixPexQuery::Others), true);
assert_eq!(
file.modified()
.duration_since(SystemTime::UNIX_EPOCH)
.ok()
.unwrap(),
Duration::from_secs(1541376000)
);
let file: File =
File::from_str("drws------ 2 u-redacted g-redacted 3864 Feb 17 2023 sas")
.ok()
.unwrap();
assert_eq!(file.is_directory(), true);
assert_eq!(file.can_read(PosixPexQuery::Owner), true);
assert_eq!(file.can_write(PosixPexQuery::Owner), true);
assert_eq!(file.can_execute(PosixPexQuery::Owner), true);
assert_eq!(file.can_read(PosixPexQuery::Group), false);
assert_eq!(file.can_write(PosixPexQuery::Group), false);
assert_eq!(file.can_execute(PosixPexQuery::Group), false);
assert_eq!(file.can_read(PosixPexQuery::Others), false);
assert_eq!(file.can_write(PosixPexQuery::Others), false);
assert_eq!(file.can_execute(PosixPexQuery::Others), false);
let file: File =
File::from_str("drwS------ 2 u-redacted g-redacted 3864 Feb 17 2023 sas")
.ok()
.unwrap();
assert_eq!(file.is_directory(), true);
assert_eq!(file.can_read(PosixPexQuery::Owner), true);
assert_eq!(file.can_write(PosixPexQuery::Owner), true);
assert_eq!(file.can_execute(PosixPexQuery::Owner), false);
assert_eq!(file.can_read(PosixPexQuery::Group), false);
assert_eq!(file.can_write(PosixPexQuery::Group), false);
assert_eq!(file.can_execute(PosixPexQuery::Group), false);
assert_eq!(file.can_read(PosixPexQuery::Others), false);
assert_eq!(file.can_write(PosixPexQuery::Others), false);
assert_eq!(file.can_execute(PosixPexQuery::Others), false);
let file: File =
File::from_str("drwx--s--- 2 u-redacted g-redacted 3864 Feb 17 2023 sas")
.ok()
.unwrap();
assert_eq!(file.is_directory(), true);
assert_eq!(file.can_read(PosixPexQuery::Owner), true);
assert_eq!(file.can_write(PosixPexQuery::Owner), true);
assert_eq!(file.can_execute(PosixPexQuery::Owner), true);
assert_eq!(file.can_read(PosixPexQuery::Group), false);
assert_eq!(file.can_write(PosixPexQuery::Group), false);
assert_eq!(file.can_execute(PosixPexQuery::Group), true);
assert_eq!(file.can_read(PosixPexQuery::Others), false);
assert_eq!(file.can_write(PosixPexQuery::Others), false);
assert_eq!(file.can_execute(PosixPexQuery::Others), false);
let file: File =
File::from_str("drwx--S--- 2 u-redacted g-redacted 3864 Feb 17 2023 sas")
.ok()
.unwrap();
assert_eq!(file.is_directory(), true);
assert_eq!(file.can_read(PosixPexQuery::Owner), true);
assert_eq!(file.can_write(PosixPexQuery::Owner), true);
assert_eq!(file.can_execute(PosixPexQuery::Owner), true);
assert_eq!(file.can_read(PosixPexQuery::Group), false);
assert_eq!(file.can_write(PosixPexQuery::Group), false);
assert_eq!(file.can_execute(PosixPexQuery::Group), false);
assert_eq!(file.can_read(PosixPexQuery::Others), false);
assert_eq!(file.can_write(PosixPexQuery::Others), false);
assert_eq!(file.can_execute(PosixPexQuery::Others), false);
let file: File =
File::from_str("drwx-----t 2 u-redacted g-redacted 3864 Feb 17 2023 sas")
.ok()
.unwrap();
assert_eq!(file.is_directory(), true);
assert_eq!(file.can_read(PosixPexQuery::Owner), true);
assert_eq!(file.can_write(PosixPexQuery::Owner), true);
assert_eq!(file.can_execute(PosixPexQuery::Owner), true);
assert_eq!(file.can_read(PosixPexQuery::Group), false);
assert_eq!(file.can_write(PosixPexQuery::Group), false);
assert_eq!(file.can_execute(PosixPexQuery::Group), false);
assert_eq!(file.can_read(PosixPexQuery::Others), false);
assert_eq!(file.can_write(PosixPexQuery::Others), false);
assert_eq!(file.can_execute(PosixPexQuery::Others), true);
let file: File =
File::from_str("drwx--S--T 2 u-redacted g-redacted 3864 Feb 17 2023 sas")
.ok()
.unwrap();
assert_eq!(file.is_directory(), true);
assert_eq!(file.can_read(PosixPexQuery::Owner), true);
assert_eq!(file.can_write(PosixPexQuery::Owner), true);
assert_eq!(file.can_execute(PosixPexQuery::Owner), true);
assert_eq!(file.can_read(PosixPexQuery::Group), false);
assert_eq!(file.can_write(PosixPexQuery::Group), false);
assert_eq!(file.can_execute(PosixPexQuery::Group), false);
assert_eq!(file.can_read(PosixPexQuery::Others), false);
assert_eq!(file.can_write(PosixPexQuery::Others), false);
assert_eq!(file.can_execute(PosixPexQuery::Others), false);
assert_eq!(
File::from_posix_line("drwxrwxr-x 1 0 9 Nov 5 2018 docs")
.err()
.unwrap(),
ParseError::SyntaxError
);
assert_eq!(
File::from_posix_line("drwxrwxr-x 1 root dialout 4096 Nov 31 2018 provola")
.err()
.unwrap(),
ParseError::InvalidDate
);
}
#[test]
fn should_parse_utf8_names_in_ls_output() {
assert!(File::try_from(
"-rw-rw-r-- 1 омар www-data 8192 Nov 5 2018 фообар.txt".to_string()
)
.is_ok());
}
#[test]
fn parse_dos_line() {
let file: File = File::try_from("04-08-14 03:09PM 8192 omar.txt".to_string())
.ok()
.unwrap();
assert_eq!(file.name(), "omar.txt");
assert_eq!(file.size, 8192);
assert!(file.is_file());
assert_eq!(file.gid, None);
assert_eq!(file.uid, None);
assert_eq!(file.can_read(PosixPexQuery::Owner), true);
assert_eq!(file.can_write(PosixPexQuery::Owner), true);
assert_eq!(file.can_execute(PosixPexQuery::Owner), true);
assert_eq!(file.can_read(PosixPexQuery::Group), true);
assert_eq!(file.can_write(PosixPexQuery::Group), true);
assert_eq!(file.can_execute(PosixPexQuery::Group), true);
assert_eq!(file.can_read(PosixPexQuery::Others), true);
assert_eq!(file.can_write(PosixPexQuery::Others), true);
assert_eq!(file.can_execute(PosixPexQuery::Others), true);
assert_eq!(
file.modified
.duration_since(SystemTime::UNIX_EPOCH)
.ok()
.unwrap(),
Duration::from_secs(1407164940)
);
let dir: File = File::try_from("04-08-14 03:09PM <DIR> docs")
.ok()
.unwrap();
assert_eq!(dir.name(), "docs");
assert!(dir.is_directory());
assert_eq!(dir.uid, None);
assert_eq!(dir.gid, None);
assert_eq!(file.can_read(PosixPexQuery::Owner), true);
assert_eq!(file.can_write(PosixPexQuery::Owner), true);
assert_eq!(file.can_execute(PosixPexQuery::Owner), true);
assert_eq!(file.can_read(PosixPexQuery::Group), true);
assert_eq!(file.can_write(PosixPexQuery::Group), true);
assert_eq!(file.can_execute(PosixPexQuery::Group), true);
assert_eq!(file.can_read(PosixPexQuery::Others), true);
assert_eq!(file.can_write(PosixPexQuery::Others), true);
assert_eq!(file.can_execute(PosixPexQuery::Others), true);
assert_eq!(
dir.modified
.duration_since(SystemTime::UNIX_EPOCH)
.ok()
.unwrap(),
Duration::from_secs(1407164940)
);
assert_eq!(
File::from_dos_line("-08-14 03:09PM <DIR> docs")
.err()
.unwrap(),
ParseError::SyntaxError
);
assert_eq!(
File::from_dos_line("34-08-14 03:09PM <DIR> docs")
.err()
.unwrap(),
ParseError::InvalidDate
);
assert_eq!(
File::from_dos_line("04-08-14 03:09PM OMAR docs")
.err()
.unwrap(),
ParseError::BadSize
);
}
#[test]
fn test_should_parse_name_starting_with_tricky_numbers() {
let file = File::from_posix_line(
"-r--r--r-- 1 23 23 1234567 Jan 1 2000 01 1234 foo.mp3",
)
.unwrap();
assert_eq!(file.name(), "01 1234 foo.mp3");
assert_eq!(file.size, 1234567);
assert_eq!(
file.modified
.duration_since(SystemTime::UNIX_EPOCH)
.ok()
.unwrap(),
Duration::from_secs(946684800)
);
}
#[test]
fn get_name_and_link() {
assert_eq!(
File::get_name_and_link("Cargo.toml"),
(String::from("Cargo.toml"), None)
);
assert_eq!(
File::get_name_and_link("Cargo -> Cargo.toml"),
(String::from("Cargo"), Some(PathBuf::from("Cargo.toml")))
);
}
#[test]
fn parse_lstime() {
assert_eq!(
fmt_time(
File::parse_lstime("Nov 5 16:32", "%b %d %Y", "%b %d %H:%M")
.ok()
.unwrap(),
"%m %d %M"
)
.as_str(),
"11 05 32"
);
assert_eq!(
fmt_time(
File::parse_lstime("Dec 2 21:32", "%b %d %Y", "%b %d %H:%M")
.ok()
.unwrap(),
"%m %d %M"
)
.as_str(),
"12 02 32"
);
assert_eq!(
File::parse_lstime("Nov 5 2018", "%b %d %Y", "%b %d %H:%M")
.ok()
.unwrap()
.duration_since(SystemTime::UNIX_EPOCH)
.ok()
.unwrap(),
Duration::from_secs(1541376000)
);
assert_eq!(
File::parse_lstime("Mar 18 2018", "%b %d %Y", "%b %d %H:%M")
.ok()
.unwrap()
.duration_since(SystemTime::UNIX_EPOCH)
.ok()
.unwrap(),
Duration::from_secs(1521331200)
);
assert!(File::parse_lstime("Oma 31 2018", "%b %d %Y", "%b %d %H:%M").is_err());
assert!(File::parse_lstime("Feb 31 2018", "%b %d %Y", "%b %d %H:%M").is_err());
assert!(File::parse_lstime("Feb 15 25:32", "%b %d %Y", "%b %d %H:%M").is_err());
}
#[test]
fn parse_dostime() {
assert_eq!(
File::parse_dostime("04-08-14 03:09PM")
.ok()
.unwrap()
.duration_since(SystemTime::UNIX_EPOCH)
.ok()
.unwrap(),
Duration::from_secs(1407164940)
);
assert!(File::parse_dostime("04-08-14").is_err());
}
#[test]
fn test_parse_mlsx_line() {
let file = File::from_mlsx_line("type=file;size=8192;modify=20181105163248; omar.txt")
.ok()
.unwrap();
assert_eq!(file.name(), "omar.txt");
assert_eq!(file.size, 8192);
assert!(file.is_file());
assert_eq!(file.gid, None);
assert_eq!(file.uid, None);
assert_eq!(file.can_read(PosixPexQuery::Owner), true);
assert_eq!(file.can_write(PosixPexQuery::Owner), true);
assert_eq!(file.can_execute(PosixPexQuery::Owner), true);
assert_eq!(file.can_read(PosixPexQuery::Group), true);
assert_eq!(file.can_write(PosixPexQuery::Group), true);
assert_eq!(file.can_execute(PosixPexQuery::Group), true);
assert_eq!(file.can_read(PosixPexQuery::Others), true);
assert_eq!(file.can_write(PosixPexQuery::Others), true);
assert_eq!(file.can_execute(PosixPexQuery::Others), true);
let file = File::from_mlsx_line("type=dir;size=4096;modify=20181105163248; docs")
.ok()
.unwrap();
assert_eq!(file.name(), "docs");
assert!(file.is_directory());
let file = File::from_mlsx_line(
"type=file;size=4096;modify=20181105163248;unix.mode=644; omar.txt",
)
.ok()
.unwrap();
assert_eq!(
file.posix_pex,
(PosixPex::from(6), PosixPex::from(4), PosixPex::from(4))
);
}
#[test]
fn file_type() {
assert_eq!(FileType::Directory.is_directory(), true);
assert_eq!(FileType::Directory.is_file(), false);
assert_eq!(FileType::Directory.is_symlink(), false);
assert_eq!(FileType::Directory.symlink(), None);
assert_eq!(FileType::File.is_directory(), false);
assert_eq!(FileType::File.is_file(), true);
assert_eq!(FileType::File.is_symlink(), false);
assert_eq!(FileType::File.symlink(), None);
assert_eq!(FileType::Symlink(PathBuf::default()).is_directory(), false);
assert_eq!(FileType::Symlink(PathBuf::default()).is_file(), false);
assert_eq!(FileType::Symlink(PathBuf::default()).is_symlink(), true);
assert_eq!(
FileType::Symlink(PathBuf::default()).symlink(),
Some(PathBuf::default().as_path())
);
}
#[test]
fn posix_pex_from_bits() {
let pex: PosixPex = PosixPex::from(4);
assert_eq!(pex.can_read(), true);
assert_eq!(pex.can_write(), false);
assert_eq!(pex.can_execute(), false);
let pex: PosixPex = PosixPex::from(0);
assert_eq!(pex.can_read(), false);
assert_eq!(pex.can_write(), false);
assert_eq!(pex.can_execute(), false);
let pex: PosixPex = PosixPex::from(3);
assert_eq!(pex.can_read(), false);
assert_eq!(pex.can_write(), true);
assert_eq!(pex.can_execute(), true);
let pex: PosixPex = PosixPex::from(7);
assert_eq!(pex.can_read(), true);
assert_eq!(pex.can_write(), true);
assert_eq!(pex.can_execute(), true);
}
fn fmt_time(time: SystemTime, fmt: &str) -> String {
let datetime: DateTime<Utc> = time.into();
format!("{}", datetime.format(fmt))
}
}