mirror of
https://github.com/Llewellynvdm/exa.git
synced 2024-11-25 13:27:33 +00:00
merge
This commit is contained in:
commit
6a642d0f32
7
Cargo.lock
generated
7
Cargo.lock
generated
@ -72,6 +72,7 @@ dependencies = [
|
|||||||
"natord",
|
"natord",
|
||||||
"num_cpus",
|
"num_cpus",
|
||||||
"number_prefix",
|
"number_prefix",
|
||||||
|
"os_str_bytes",
|
||||||
"scoped_threadpool",
|
"scoped_threadpool",
|
||||||
"term_grid",
|
"term_grid",
|
||||||
"term_size",
|
"term_size",
|
||||||
@ -268,6 +269,12 @@ dependencies = [
|
|||||||
"vcpkg",
|
"vcpkg",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "os_str_bytes"
|
||||||
|
version = "3.0.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e293568965aea261bdf010db17df7030e3c9a275c415d51d6112f7cf9b7af012"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pad"
|
name = "pad"
|
||||||
version = "0.1.6"
|
version = "0.1.6"
|
||||||
|
@ -36,9 +36,14 @@ scoped_threadpool = "0.1"
|
|||||||
term_grid = "0.1"
|
term_grid = "0.1"
|
||||||
term_size = "0.3"
|
term_size = "0.3"
|
||||||
unicode-width = "0.1"
|
unicode-width = "0.1"
|
||||||
users = "0.11"
|
|
||||||
zoneinfo_compiled = "0.5"
|
zoneinfo_compiled = "0.5"
|
||||||
|
|
||||||
|
[target.'cfg(unix)'.dependencies]
|
||||||
|
users = "0.11"
|
||||||
|
|
||||||
|
[target.'cfg(windows)'.dependencies]
|
||||||
|
os_str_bytes = "3.0"
|
||||||
|
|
||||||
[dependencies.git2]
|
[dependencies.git2]
|
||||||
version = "0.13"
|
version = "0.13"
|
||||||
optional = true
|
optional = true
|
||||||
|
@ -88,6 +88,7 @@ pub struct Permissions {
|
|||||||
#[derive(Copy, Clone)]
|
#[derive(Copy, Clone)]
|
||||||
pub struct PermissionsPlus {
|
pub struct PermissionsPlus {
|
||||||
pub file_type: Type,
|
pub file_type: Type,
|
||||||
|
#[cfg(unix)]
|
||||||
pub permissions: Permissions,
|
pub permissions: Permissions,
|
||||||
pub xattrs: bool,
|
pub xattrs: bool,
|
||||||
}
|
}
|
||||||
|
172
src/fs/file.rs
172
src/fs/file.rs
@ -1,6 +1,7 @@
|
|||||||
//! Files, and methods and fields to access their metadata.
|
//! Files, and methods and fields to access their metadata.
|
||||||
|
|
||||||
use std::io;
|
use std::io;
|
||||||
|
#[cfg(unix)]
|
||||||
use std::os::unix::fs::{FileTypeExt, MetadataExt, PermissionsExt};
|
use std::os::unix::fs::{FileTypeExt, MetadataExt, PermissionsExt};
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
use std::time::{Duration, SystemTime, UNIX_EPOCH};
|
use std::time::{Duration, SystemTime, UNIX_EPOCH};
|
||||||
@ -19,7 +20,6 @@ use crate::fs::fields as f;
|
|||||||
/// information queried at least once, so it makes sense to do all this at the
|
/// information queried at least once, so it makes sense to do all this at the
|
||||||
/// start and hold on to all the information.
|
/// start and hold on to all the information.
|
||||||
pub struct File<'dir> {
|
pub struct File<'dir> {
|
||||||
|
|
||||||
/// The filename portion of this file’s path, including the extension.
|
/// The filename portion of this file’s path, including the extension.
|
||||||
///
|
///
|
||||||
/// This is used to compare against certain filenames (such as checking if
|
/// This is used to compare against certain filenames (such as checking if
|
||||||
@ -71,14 +71,21 @@ impl<'dir> File<'dir> {
|
|||||||
FN: Into<Option<String>>
|
FN: Into<Option<String>>
|
||||||
{
|
{
|
||||||
let parent_dir = parent_dir.into();
|
let parent_dir = parent_dir.into();
|
||||||
let name = filename.into().unwrap_or_else(|| File::filename(&path));
|
let name = filename.into().unwrap_or_else(|| File::filename(&path));
|
||||||
let ext = File::ext(&path);
|
let ext = File::ext(&path);
|
||||||
|
|
||||||
debug!("Statting file {:?}", &path);
|
debug!("Statting file {:?}", &path);
|
||||||
let metadata = std::fs::symlink_metadata(&path)?;
|
let metadata = std::fs::symlink_metadata(&path)?;
|
||||||
let is_all_all = false;
|
let is_all_all = false;
|
||||||
|
|
||||||
Ok(File { path, parent_dir, metadata, ext, name, is_all_all })
|
Ok(File {
|
||||||
|
path,
|
||||||
|
parent_dir,
|
||||||
|
metadata,
|
||||||
|
ext,
|
||||||
|
name,
|
||||||
|
is_all_all,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn new_aa_current(parent_dir: &'dir Dir) -> io::Result<File<'dir>> {
|
pub fn new_aa_current(parent_dir: &'dir Dir) -> io::Result<File<'dir>> {
|
||||||
@ -86,7 +93,7 @@ impl<'dir> File<'dir> {
|
|||||||
let ext = File::ext(&path);
|
let ext = File::ext(&path);
|
||||||
|
|
||||||
debug!("Statting file {:?}", &path);
|
debug!("Statting file {:?}", &path);
|
||||||
let metadata = std::fs::symlink_metadata(&path)?;
|
let metadata = std::fs::symlink_metadata(&path)?;
|
||||||
let is_all_all = true;
|
let is_all_all = true;
|
||||||
let parent_dir = Some(parent_dir);
|
let parent_dir = Some(parent_dir);
|
||||||
|
|
||||||
@ -97,7 +104,7 @@ impl<'dir> File<'dir> {
|
|||||||
let ext = File::ext(&path);
|
let ext = File::ext(&path);
|
||||||
|
|
||||||
debug!("Statting file {:?}", &path);
|
debug!("Statting file {:?}", &path);
|
||||||
let metadata = std::fs::symlink_metadata(&path)?;
|
let metadata = std::fs::symlink_metadata(&path)?;
|
||||||
let is_all_all = true;
|
let is_all_all = true;
|
||||||
let parent_dir = Some(parent_dir);
|
let parent_dir = Some(parent_dir);
|
||||||
|
|
||||||
@ -109,9 +116,12 @@ impl<'dir> File<'dir> {
|
|||||||
/// use the last component as the name.
|
/// use the last component as the name.
|
||||||
pub fn filename(path: &Path) -> String {
|
pub fn filename(path: &Path) -> String {
|
||||||
if let Some(back) = path.components().next_back() {
|
if let Some(back) = path.components().next_back() {
|
||||||
back.as_os_str().to_string_lossy().to_string()
|
let name = back.as_os_str().to_string_lossy().to_string();
|
||||||
}
|
#[cfg(unix)]
|
||||||
else {
|
return name;
|
||||||
|
#[cfg(windows)]
|
||||||
|
return name;
|
||||||
|
} else {
|
||||||
// use the path as fallback
|
// use the path as fallback
|
||||||
error!("Path {:?} has no last component", path);
|
error!("Path {:?} has no last component", path);
|
||||||
path.display().to_string()
|
path.display().to_string()
|
||||||
@ -174,6 +184,7 @@ impl<'dir> File<'dir> {
|
|||||||
/// Whether this file is both a regular file *and* executable for the
|
/// Whether this file is both a regular file *and* executable for the
|
||||||
/// current user. An executable file has a different purpose from an
|
/// current user. An executable file has a different purpose from an
|
||||||
/// executable directory, so they should be highlighted differently.
|
/// executable directory, so they should be highlighted differently.
|
||||||
|
#[cfg(unix)]
|
||||||
pub fn is_executable_file(&self) -> bool {
|
pub fn is_executable_file(&self) -> bool {
|
||||||
let bit = modes::USER_EXECUTE;
|
let bit = modes::USER_EXECUTE;
|
||||||
self.is_file() && (self.metadata.permissions().mode() & bit) == bit
|
self.is_file() && (self.metadata.permissions().mode() & bit) == bit
|
||||||
@ -185,40 +196,40 @@ impl<'dir> File<'dir> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Whether this file is a named pipe on the filesystem.
|
/// Whether this file is a named pipe on the filesystem.
|
||||||
|
#[cfg(unix)]
|
||||||
pub fn is_pipe(&self) -> bool {
|
pub fn is_pipe(&self) -> bool {
|
||||||
self.metadata.file_type().is_fifo()
|
self.metadata.file_type().is_fifo()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Whether this file is a char device on the filesystem.
|
/// Whether this file is a char device on the filesystem.
|
||||||
|
#[cfg(unix)]
|
||||||
pub fn is_char_device(&self) -> bool {
|
pub fn is_char_device(&self) -> bool {
|
||||||
self.metadata.file_type().is_char_device()
|
self.metadata.file_type().is_char_device()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Whether this file is a block device on the filesystem.
|
/// Whether this file is a block device on the filesystem.
|
||||||
|
#[cfg(unix)]
|
||||||
pub fn is_block_device(&self) -> bool {
|
pub fn is_block_device(&self) -> bool {
|
||||||
self.metadata.file_type().is_block_device()
|
self.metadata.file_type().is_block_device()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Whether this file is a socket on the filesystem.
|
/// Whether this file is a socket on the filesystem.
|
||||||
|
#[cfg(unix)]
|
||||||
pub fn is_socket(&self) -> bool {
|
pub fn is_socket(&self) -> bool {
|
||||||
self.metadata.file_type().is_socket()
|
self.metadata.file_type().is_socket()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/// Re-prefixes the path pointed to by this file, if it’s a symlink, to
|
/// Re-prefixes the path pointed to by this file, if it’s a symlink, to
|
||||||
/// make it an absolute path that can be accessed from whichever
|
/// make it an absolute path that can be accessed from whichever
|
||||||
/// directory exa is being run from.
|
/// directory exa is being run from.
|
||||||
fn reorient_target_path(&self, path: &Path) -> PathBuf {
|
fn reorient_target_path(&self, path: &Path) -> PathBuf {
|
||||||
if path.is_absolute() {
|
if path.is_absolute() {
|
||||||
path.to_path_buf()
|
path.to_path_buf()
|
||||||
}
|
} else if let Some(dir) = self.parent_dir {
|
||||||
else if let Some(dir) = self.parent_dir {
|
|
||||||
dir.join(&*path)
|
dir.join(&*path)
|
||||||
}
|
} else if let Some(parent) = self.path.parent() {
|
||||||
else if let Some(parent) = self.path.parent() {
|
|
||||||
parent.join(&*path)
|
parent.join(&*path)
|
||||||
}
|
} else {
|
||||||
else {
|
|
||||||
self.path.join(&*path)
|
self.path.join(&*path)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -234,15 +245,14 @@ impl<'dir> File<'dir> {
|
|||||||
/// existed. If this file cannot be read at all, returns the error that
|
/// existed. If this file cannot be read at all, returns the error that
|
||||||
/// we got when we tried to read it.
|
/// we got when we tried to read it.
|
||||||
pub fn link_target(&self) -> FileTarget<'dir> {
|
pub fn link_target(&self) -> FileTarget<'dir> {
|
||||||
|
|
||||||
// We need to be careful to treat the path actually pointed to by
|
// We need to be careful to treat the path actually pointed to by
|
||||||
// this file — which could be absolute or relative — to the path
|
// this file — which could be absolute or relative — to the path
|
||||||
// we actually look up and turn into a `File` — which needs to be
|
// we actually look up and turn into a `File` — which needs to be
|
||||||
// absolute to be accessible from any directory.
|
// absolute to be accessible from any directory.
|
||||||
debug!("Reading link {:?}", &self.path);
|
debug!("Reading link {:?}", &self.path);
|
||||||
let path = match std::fs::read_link(&self.path) {
|
let path = match std::fs::read_link(&self.path) {
|
||||||
Ok(p) => p,
|
Ok(p) => p,
|
||||||
Err(e) => return FileTarget::Err(e),
|
Err(e) => return FileTarget::Err(e),
|
||||||
};
|
};
|
||||||
|
|
||||||
let absolute_path = self.reorient_target_path(&path);
|
let absolute_path = self.reorient_target_path(&path);
|
||||||
@ -251,7 +261,7 @@ impl<'dir> File<'dir> {
|
|||||||
// follow links.
|
// follow links.
|
||||||
match std::fs::metadata(&absolute_path) {
|
match std::fs::metadata(&absolute_path) {
|
||||||
Ok(metadata) => {
|
Ok(metadata) => {
|
||||||
let ext = File::ext(&path);
|
let ext = File::ext(&path);
|
||||||
let name = File::filename(&path);
|
let name = File::filename(&path);
|
||||||
let file = File { parent_dir: None, path, ext, metadata, name, is_all_all: false };
|
let file = File { parent_dir: None, path, ext, metadata, name, is_all_all: false };
|
||||||
FileTarget::Ok(Box::new(file))
|
FileTarget::Ok(Box::new(file))
|
||||||
@ -270,6 +280,7 @@ impl<'dir> File<'dir> {
|
|||||||
/// is uncommon, while you come across directories and other types
|
/// is uncommon, while you come across directories and other types
|
||||||
/// with multiple links much more often. Thus, it should get highlighted
|
/// with multiple links much more often. Thus, it should get highlighted
|
||||||
/// more attentively.
|
/// more attentively.
|
||||||
|
#[cfg(unix)]
|
||||||
pub fn links(&self) -> f::Links {
|
pub fn links(&self) -> f::Links {
|
||||||
let count = self.metadata.nlink();
|
let count = self.metadata.nlink();
|
||||||
|
|
||||||
@ -280,6 +291,7 @@ impl<'dir> File<'dir> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// This file’s inode.
|
/// This file’s inode.
|
||||||
|
#[cfg(unix)]
|
||||||
pub fn inode(&self) -> f::Inode {
|
pub fn inode(&self) -> f::Inode {
|
||||||
f::Inode(self.metadata.ino())
|
f::Inode(self.metadata.ino())
|
||||||
}
|
}
|
||||||
@ -287,21 +299,23 @@ impl<'dir> File<'dir> {
|
|||||||
/// This file’s number of filesystem blocks.
|
/// This file’s number of filesystem blocks.
|
||||||
///
|
///
|
||||||
/// (Not the size of each block, which we don’t actually report on)
|
/// (Not the size of each block, which we don’t actually report on)
|
||||||
|
#[cfg(unix)]
|
||||||
pub fn blocks(&self) -> f::Blocks {
|
pub fn blocks(&self) -> f::Blocks {
|
||||||
if self.is_file() || self.is_link() {
|
if self.is_file() || self.is_link() {
|
||||||
f::Blocks::Some(self.metadata.blocks())
|
f::Blocks::Some(self.metadata.blocks())
|
||||||
}
|
} else {
|
||||||
else {
|
|
||||||
f::Blocks::None
|
f::Blocks::None
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The ID of the user that own this file.
|
/// The ID of the user that own this file.
|
||||||
|
#[cfg(unix)]
|
||||||
pub fn user(&self) -> f::User {
|
pub fn user(&self) -> f::User {
|
||||||
f::User(self.metadata.uid())
|
f::User(self.metadata.uid())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The ID of the group that owns this file.
|
/// The ID of the group that owns this file.
|
||||||
|
#[cfg(unix)]
|
||||||
pub fn group(&self) -> f::Group {
|
pub fn group(&self) -> f::Group {
|
||||||
f::Group(self.metadata.gid())
|
f::Group(self.metadata.gid())
|
||||||
}
|
}
|
||||||
@ -316,18 +330,19 @@ impl<'dir> File<'dir> {
|
|||||||
/// usually just have a file size of zero.
|
/// usually just have a file size of zero.
|
||||||
pub fn size(&self) -> f::Size {
|
pub fn size(&self) -> f::Size {
|
||||||
if self.is_directory() {
|
if self.is_directory() {
|
||||||
f::Size::None
|
return f::Size::None;
|
||||||
}
|
};
|
||||||
else if self.is_char_device() || self.is_block_device() {
|
#[cfg(unix)]
|
||||||
let dev = self.metadata.rdev();
|
{
|
||||||
f::Size::DeviceIDs(f::DeviceIDs {
|
if self.is_char_device() || self.is_block_device() {
|
||||||
major: (dev / 256) as u8,
|
let dev = self.metadata.rdev();
|
||||||
minor: (dev % 256) as u8,
|
return f::Size::DeviceIDs(f::DeviceIDs {
|
||||||
})
|
major: (dev / 256) as u8,
|
||||||
}
|
minor: (dev % 256) as u8,
|
||||||
else {
|
});
|
||||||
f::Size::Some(self.metadata.len())
|
};
|
||||||
}
|
}
|
||||||
|
return f::Size::Some(self.metadata.len());
|
||||||
}
|
}
|
||||||
|
|
||||||
/// This file’s last modified timestamp, if available on this platform.
|
/// This file’s last modified timestamp, if available on this platform.
|
||||||
@ -336,6 +351,7 @@ impl<'dir> File<'dir> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// This file’s last changed timestamp, if available on this platform.
|
/// This file’s last changed timestamp, if available on this platform.
|
||||||
|
#[cfg(unix)]
|
||||||
pub fn changed_time(&self) -> Option<SystemTime> {
|
pub fn changed_time(&self) -> Option<SystemTime> {
|
||||||
let (mut sec, mut nanosec) = (self.metadata.ctime(), self.metadata.ctime_nsec());
|
let (mut sec, mut nanosec) = (self.metadata.ctime(), self.metadata.ctime_nsec());
|
||||||
|
|
||||||
@ -369,54 +385,59 @@ impl<'dir> File<'dir> {
|
|||||||
/// This is used a the leftmost character of the permissions column.
|
/// This is used a the leftmost character of the permissions column.
|
||||||
/// The file type can usually be guessed from the colour of the file, but
|
/// The file type can usually be guessed from the colour of the file, but
|
||||||
/// ls puts this character there.
|
/// ls puts this character there.
|
||||||
|
#[cfg(windows)]
|
||||||
pub fn type_char(&self) -> f::Type {
|
pub fn type_char(&self) -> f::Type {
|
||||||
if self.is_file() {
|
if self.is_file() {
|
||||||
f::Type::File
|
f::Type::File
|
||||||
}
|
} else if self.is_directory() {
|
||||||
else if self.is_directory() {
|
|
||||||
f::Type::Directory
|
f::Type::Directory
|
||||||
|
} else {
|
||||||
|
f::Type::Special
|
||||||
}
|
}
|
||||||
else if self.is_pipe() {
|
}
|
||||||
|
#[cfg(unix)]
|
||||||
|
pub fn type_char(&self) -> f::Type {
|
||||||
|
if self.is_file() {
|
||||||
|
f::Type::File
|
||||||
|
} else if self.is_directory() {
|
||||||
|
f::Type::Directory
|
||||||
|
} else if self.is_pipe() {
|
||||||
f::Type::Pipe
|
f::Type::Pipe
|
||||||
}
|
} else if self.is_link() {
|
||||||
else if self.is_link() {
|
|
||||||
f::Type::Link
|
f::Type::Link
|
||||||
}
|
} else if self.is_char_device() {
|
||||||
else if self.is_char_device() {
|
|
||||||
f::Type::CharDevice
|
f::Type::CharDevice
|
||||||
}
|
} else if self.is_block_device() {
|
||||||
else if self.is_block_device() {
|
|
||||||
f::Type::BlockDevice
|
f::Type::BlockDevice
|
||||||
}
|
} else if self.is_socket() {
|
||||||
else if self.is_socket() {
|
|
||||||
f::Type::Socket
|
f::Type::Socket
|
||||||
}
|
} else {
|
||||||
else {
|
|
||||||
f::Type::Special
|
f::Type::Special
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// This file’s permissions, with flags for each bit.
|
/// This file’s permissions, with flags for each bit.
|
||||||
|
#[cfg(unix)]
|
||||||
pub fn permissions(&self) -> f::Permissions {
|
pub fn permissions(&self) -> f::Permissions {
|
||||||
let bits = self.metadata.mode();
|
let bits = self.metadata.mode();
|
||||||
let has_bit = |bit| bits & bit == bit;
|
let has_bit = |bit| bits & bit == bit;
|
||||||
|
|
||||||
f::Permissions {
|
f::Permissions {
|
||||||
user_read: has_bit(modes::USER_READ),
|
user_read: has_bit(modes::USER_READ),
|
||||||
user_write: has_bit(modes::USER_WRITE),
|
user_write: has_bit(modes::USER_WRITE),
|
||||||
user_execute: has_bit(modes::USER_EXECUTE),
|
user_execute: has_bit(modes::USER_EXECUTE),
|
||||||
|
|
||||||
group_read: has_bit(modes::GROUP_READ),
|
group_read: has_bit(modes::GROUP_READ),
|
||||||
group_write: has_bit(modes::GROUP_WRITE),
|
group_write: has_bit(modes::GROUP_WRITE),
|
||||||
group_execute: has_bit(modes::GROUP_EXECUTE),
|
group_execute: has_bit(modes::GROUP_EXECUTE),
|
||||||
|
|
||||||
other_read: has_bit(modes::OTHER_READ),
|
other_read: has_bit(modes::OTHER_READ),
|
||||||
other_write: has_bit(modes::OTHER_WRITE),
|
other_write: has_bit(modes::OTHER_WRITE),
|
||||||
other_execute: has_bit(modes::OTHER_EXECUTE),
|
other_execute: has_bit(modes::OTHER_EXECUTE),
|
||||||
|
|
||||||
sticky: has_bit(modes::STICKY),
|
sticky: has_bit(modes::STICKY),
|
||||||
setgid: has_bit(modes::SETGID),
|
setgid: has_bit(modes::SETGID),
|
||||||
setuid: has_bit(modes::SETUID),
|
setuid: has_bit(modes::SETUID),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -437,17 +458,14 @@ impl<'dir> File<'dir> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
impl<'a> AsRef<File<'a>> for File<'a> {
|
impl<'a> AsRef<File<'a>> for File<'a> {
|
||||||
fn as_ref(&self) -> &File<'a> {
|
fn as_ref(&self) -> &File<'a> {
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/// The result of following a symlink.
|
/// The result of following a symlink.
|
||||||
pub enum FileTarget<'dir> {
|
pub enum FileTarget<'dir> {
|
||||||
|
|
||||||
/// The symlink pointed at a file that exists.
|
/// The symlink pointed at a file that exists.
|
||||||
Ok(Box<File<'dir>>),
|
Ok(Box<File<'dir>>),
|
||||||
|
|
||||||
@ -466,7 +484,6 @@ pub enum FileTarget<'dir> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl<'dir> FileTarget<'dir> {
|
impl<'dir> FileTarget<'dir> {
|
||||||
|
|
||||||
/// Whether this link doesn’t lead to a file, for whatever reason. This
|
/// Whether this link doesn’t lead to a file, for whatever reason. This
|
||||||
/// gets used to determine how to highlight the link in grid views.
|
/// gets used to determine how to highlight the link in grid views.
|
||||||
pub fn is_broken(&self) -> bool {
|
pub fn is_broken(&self) -> bool {
|
||||||
@ -474,33 +491,32 @@ impl<'dir> FileTarget<'dir> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/// More readable aliases for the permission bits exposed by libc.
|
/// More readable aliases for the permission bits exposed by libc.
|
||||||
#[allow(trivial_numeric_casts)]
|
#[allow(trivial_numeric_casts)]
|
||||||
|
#[cfg(unix)]
|
||||||
mod modes {
|
mod modes {
|
||||||
|
|
||||||
// The `libc::mode_t` type’s actual type varies, but the value returned
|
// The `libc::mode_t` type’s actual type varies, but the value returned
|
||||||
// from `metadata.permissions().mode()` is always `u32`.
|
// from `metadata.permissions().mode()` is always `u32`.
|
||||||
pub type Mode = u32;
|
pub type Mode = u32;
|
||||||
|
|
||||||
pub const USER_READ: Mode = libc::S_IRUSR as Mode;
|
pub const USER_READ: Mode = libc::S_IRUSR as Mode;
|
||||||
pub const USER_WRITE: Mode = libc::S_IWUSR as Mode;
|
pub const USER_WRITE: Mode = libc::S_IWUSR as Mode;
|
||||||
pub const USER_EXECUTE: Mode = libc::S_IXUSR as Mode;
|
pub const USER_EXECUTE: Mode = libc::S_IXUSR as Mode;
|
||||||
|
|
||||||
pub const GROUP_READ: Mode = libc::S_IRGRP as Mode;
|
pub const GROUP_READ: Mode = libc::S_IRGRP as Mode;
|
||||||
pub const GROUP_WRITE: Mode = libc::S_IWGRP as Mode;
|
pub const GROUP_WRITE: Mode = libc::S_IWGRP as Mode;
|
||||||
pub const GROUP_EXECUTE: Mode = libc::S_IXGRP as Mode;
|
pub const GROUP_EXECUTE: Mode = libc::S_IXGRP as Mode;
|
||||||
|
|
||||||
pub const OTHER_READ: Mode = libc::S_IROTH as Mode;
|
pub const OTHER_READ: Mode = libc::S_IROTH as Mode;
|
||||||
pub const OTHER_WRITE: Mode = libc::S_IWOTH as Mode;
|
pub const OTHER_WRITE: Mode = libc::S_IWOTH as Mode;
|
||||||
pub const OTHER_EXECUTE: Mode = libc::S_IXOTH as Mode;
|
pub const OTHER_EXECUTE: Mode = libc::S_IXOTH as Mode;
|
||||||
|
|
||||||
pub const STICKY: Mode = libc::S_ISVTX as Mode;
|
pub const STICKY: Mode = libc::S_ISVTX as Mode;
|
||||||
pub const SETGID: Mode = libc::S_ISGID as Mode;
|
pub const SETGID: Mode = libc::S_ISGID as Mode;
|
||||||
pub const SETUID: Mode = libc::S_ISUID as Mode;
|
pub const SETUID: Mode = libc::S_ISUID as Mode;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod ext_test {
|
mod ext_test {
|
||||||
use super::File;
|
use super::File;
|
||||||
@ -522,7 +538,6 @@ mod ext_test {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod filename_test {
|
mod filename_test {
|
||||||
use super::File;
|
use super::File;
|
||||||
@ -555,6 +570,9 @@ mod filename_test {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn topmost() {
|
fn topmost() {
|
||||||
assert_eq!("/", File::filename(Path::new("/")))
|
#[cfg(unix)]
|
||||||
|
assert_eq!("/", File::filename(Path::new("/")));
|
||||||
|
#[cfg(windows)]
|
||||||
|
assert_eq!("C:\\", File::filename(Path::new("C:\\")));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
use std::cmp::Ordering;
|
use std::cmp::Ordering;
|
||||||
use std::iter::FromIterator;
|
use std::iter::FromIterator;
|
||||||
|
#[cfg(unix)]
|
||||||
use std::os::unix::fs::MetadataExt;
|
use std::os::unix::fs::MetadataExt;
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
|
|
||||||
@ -25,7 +26,6 @@ use crate::fs::File;
|
|||||||
/// performing the comparison.
|
/// performing the comparison.
|
||||||
#[derive(PartialEq, Debug, Clone)]
|
#[derive(PartialEq, Debug, Clone)]
|
||||||
pub struct FileFilter {
|
pub struct FileFilter {
|
||||||
|
|
||||||
/// Whether directories should be listed first, and other types of file
|
/// Whether directories should be listed first, and other types of file
|
||||||
/// second. Some users prefer it like this.
|
/// second. Some users prefer it like this.
|
||||||
pub list_dirs_first: bool,
|
pub list_dirs_first: bool,
|
||||||
@ -137,11 +137,9 @@ impl FileFilter {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/// User-supplied field to sort by.
|
/// User-supplied field to sort by.
|
||||||
#[derive(PartialEq, Debug, Copy, Clone)]
|
#[derive(PartialEq, Debug, Copy, Clone)]
|
||||||
pub enum SortField {
|
pub enum SortField {
|
||||||
|
|
||||||
/// Don’t apply any sorting. This is usually used as an optimisation in
|
/// Don’t apply any sorting. This is usually used as an optimisation in
|
||||||
/// scripts, where the order doesn’t matter.
|
/// scripts, where the order doesn’t matter.
|
||||||
Unsorted,
|
Unsorted,
|
||||||
@ -157,6 +155,7 @@ pub enum SortField {
|
|||||||
|
|
||||||
/// The file’s inode, which usually corresponds to the order in which
|
/// The file’s inode, which usually corresponds to the order in which
|
||||||
/// files were created on the filesystem, more or less.
|
/// files were created on the filesystem, more or less.
|
||||||
|
#[cfg(unix)]
|
||||||
FileInode,
|
FileInode,
|
||||||
|
|
||||||
/// The time the file was modified (the “mtime”).
|
/// The time the file was modified (the “mtime”).
|
||||||
@ -183,6 +182,7 @@ pub enum SortField {
|
|||||||
///
|
///
|
||||||
/// In original Unix, this was, however, meant as creation time.
|
/// In original Unix, this was, however, meant as creation time.
|
||||||
/// https://www.bell-labs.com/usr/dmr/www/cacm.html
|
/// https://www.bell-labs.com/usr/dmr/www/cacm.html
|
||||||
|
#[cfg(unix)]
|
||||||
ChangedDate,
|
ChangedDate,
|
||||||
|
|
||||||
/// The time the file was created (the “btime” or “birthtime”).
|
/// The time the file was created (the “btime” or “birthtime”).
|
||||||
@ -221,7 +221,6 @@ pub enum SortField {
|
|||||||
/// effects they have.
|
/// effects they have.
|
||||||
#[derive(PartialEq, Debug, Copy, Clone)]
|
#[derive(PartialEq, Debug, Copy, Clone)]
|
||||||
pub enum SortCase {
|
pub enum SortCase {
|
||||||
|
|
||||||
/// Sort files case-sensitively with uppercase first, with ‘A’ coming
|
/// Sort files case-sensitively with uppercase first, with ‘A’ coming
|
||||||
/// before ‘a’.
|
/// before ‘a’.
|
||||||
ABCabc,
|
ABCabc,
|
||||||
@ -231,7 +230,6 @@ pub enum SortCase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl SortField {
|
impl SortField {
|
||||||
|
|
||||||
/// Compares two files to determine the order they should be listed in,
|
/// Compares two files to determine the order they should be listed in,
|
||||||
/// depending on the search field.
|
/// depending on the search field.
|
||||||
///
|
///
|
||||||
@ -250,9 +248,11 @@ impl SortField {
|
|||||||
Self::Name(AaBbCc) => natord::compare_ignore_case(&a.name, &b.name),
|
Self::Name(AaBbCc) => natord::compare_ignore_case(&a.name, &b.name),
|
||||||
|
|
||||||
Self::Size => a.metadata.len().cmp(&b.metadata.len()),
|
Self::Size => a.metadata.len().cmp(&b.metadata.len()),
|
||||||
|
#[cfg(unix)]
|
||||||
Self::FileInode => a.metadata.ino().cmp(&b.metadata.ino()),
|
Self::FileInode => a.metadata.ino().cmp(&b.metadata.ino()),
|
||||||
Self::ModifiedDate => a.modified_time().cmp(&b.modified_time()),
|
Self::ModifiedDate => a.modified_time().cmp(&b.modified_time()),
|
||||||
Self::AccessedDate => a.accessed_time().cmp(&b.accessed_time()),
|
Self::AccessedDate => a.accessed_time().cmp(&b.accessed_time()),
|
||||||
|
#[cfg(unix)]
|
||||||
Self::ChangedDate => a.changed_time().cmp(&b.changed_time()),
|
Self::ChangedDate => a.changed_time().cmp(&b.changed_time()),
|
||||||
Self::CreatedDate => a.created_time().cmp(&b.created_time()),
|
Self::CreatedDate => a.created_time().cmp(&b.created_time()),
|
||||||
Self::ModifiedAge => b.modified_time().cmp(&a.modified_time()), // flip b and a
|
Self::ModifiedAge => b.modified_time().cmp(&a.modified_time()), // flip b and a
|
||||||
@ -289,7 +289,6 @@ impl SortField {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/// The **ignore patterns** are a list of globs that are tested against
|
/// The **ignore patterns** are a list of globs that are tested against
|
||||||
/// each filename, and if any of them match, that file isn’t displayed.
|
/// each filename, and if any of them match, that file isn’t displayed.
|
||||||
/// This lets a user hide, say, text files by ignoring `*.txt`.
|
/// This lets a user hide, say, text files by ignoring `*.txt`.
|
||||||
@ -309,7 +308,6 @@ impl FromIterator<glob::Pattern> for IgnorePatterns {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl IgnorePatterns {
|
impl IgnorePatterns {
|
||||||
|
|
||||||
/// Create a new list from the input glob strings, turning the inputs that
|
/// Create a new list from the input glob strings, turning the inputs that
|
||||||
/// are valid glob patterns into an `IgnorePatterns`. The inputs that
|
/// are valid glob patterns into an `IgnorePatterns`. The inputs that
|
||||||
/// don’t parse correctly are returned separately.
|
/// don’t parse correctly are returned separately.
|
||||||
@ -319,8 +317,8 @@ impl IgnorePatterns {
|
|||||||
// Almost all glob patterns are valid, so it’s worth pre-allocating
|
// Almost all glob patterns are valid, so it’s worth pre-allocating
|
||||||
// the vector with enough space for all of them.
|
// the vector with enough space for all of them.
|
||||||
let mut patterns = match iter.size_hint() {
|
let mut patterns = match iter.size_hint() {
|
||||||
(_, Some(count)) => Vec::with_capacity(count),
|
(_, Some(count)) => Vec::with_capacity(count),
|
||||||
_ => Vec::new(),
|
_ => Vec::new(),
|
||||||
};
|
};
|
||||||
|
|
||||||
// Similarly, assume there won’t be any errors.
|
// Similarly, assume there won’t be any errors.
|
||||||
@ -329,7 +327,7 @@ impl IgnorePatterns {
|
|||||||
for input in iter {
|
for input in iter {
|
||||||
match glob::Pattern::new(input) {
|
match glob::Pattern::new(input) {
|
||||||
Ok(pat) => patterns.push(pat),
|
Ok(pat) => patterns.push(pat),
|
||||||
Err(e) => errors.push(e),
|
Err(e) => errors.push(e),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -355,11 +353,9 @@ impl IgnorePatterns {
|
|||||||
// isn’t probably means it’s in the wrong place
|
// isn’t probably means it’s in the wrong place
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/// Whether to ignore or display files that are mentioned in `.gitignore` files.
|
/// Whether to ignore or display files that are mentioned in `.gitignore` files.
|
||||||
#[derive(PartialEq, Debug, Copy, Clone)]
|
#[derive(PartialEq, Debug, Copy, Clone)]
|
||||||
pub enum GitIgnore {
|
pub enum GitIgnore {
|
||||||
|
|
||||||
/// Ignore files that Git would ignore. This means doing a check for a
|
/// Ignore files that Git would ignore. This means doing a check for a
|
||||||
/// `.gitignore` file, possibly recursively up the filesystem tree.
|
/// `.gitignore` file, possibly recursively up the filesystem tree.
|
||||||
CheckAndIgnore,
|
CheckAndIgnore,
|
||||||
@ -375,7 +371,6 @@ pub enum GitIgnore {
|
|||||||
// > .gitignore, .git/info/exclude and even your global gitignore globs,
|
// > .gitignore, .git/info/exclude and even your global gitignore globs,
|
||||||
// > usually found in $XDG_CONFIG_HOME/git/ignore.
|
// > usually found in $XDG_CONFIG_HOME/git/ignore.
|
||||||
|
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod test_ignores {
|
mod test_ignores {
|
||||||
use super::*;
|
use super::*;
|
||||||
@ -389,23 +384,23 @@ mod test_ignores {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn ignores_a_glob() {
|
fn ignores_a_glob() {
|
||||||
let (pats, fails) = IgnorePatterns::parse_from_iter(vec![ "*.mp3" ]);
|
let (pats, fails) = IgnorePatterns::parse_from_iter(vec!["*.mp3"]);
|
||||||
assert!(fails.is_empty());
|
assert!(fails.is_empty());
|
||||||
assert_eq!(false, pats.is_ignored("nothing"));
|
assert_eq!(false, pats.is_ignored("nothing"));
|
||||||
assert_eq!(true, pats.is_ignored("test.mp3"));
|
assert_eq!(true, pats.is_ignored("test.mp3"));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn ignores_an_exact_filename() {
|
fn ignores_an_exact_filename() {
|
||||||
let (pats, fails) = IgnorePatterns::parse_from_iter(vec![ "nothing" ]);
|
let (pats, fails) = IgnorePatterns::parse_from_iter(vec!["nothing"]);
|
||||||
assert!(fails.is_empty());
|
assert!(fails.is_empty());
|
||||||
assert_eq!(true, pats.is_ignored("nothing"));
|
assert_eq!(true, pats.is_ignored("nothing"));
|
||||||
assert_eq!(false, pats.is_ignored("test.mp3"));
|
assert_eq!(false, pats.is_ignored("test.mp3"));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn ignores_both() {
|
fn ignores_both() {
|
||||||
let (pats, fails) = IgnorePatterns::parse_from_iter(vec![ "nothing", "*.mp3" ]);
|
let (pats, fails) = IgnorePatterns::parse_from_iter(vec!["nothing", "*.mp3"]);
|
||||||
assert!(fails.is_empty());
|
assert!(fails.is_empty());
|
||||||
assert_eq!(true, pats.is_ignored("nothing"));
|
assert_eq!(true, pats.is_ignored("nothing"));
|
||||||
assert_eq!(true, pats.is_ignored("test.mp3"));
|
assert_eq!(true, pats.is_ignored("test.mp3"));
|
||||||
|
@ -78,16 +78,19 @@ impl SortField {
|
|||||||
"age" | "old" | "oldest" => {
|
"age" | "old" | "oldest" => {
|
||||||
Self::ModifiedAge
|
Self::ModifiedAge
|
||||||
}
|
}
|
||||||
|
#[cfg(unix)]
|
||||||
"ch" | "changed" => {
|
"ch" | "changed" => {
|
||||||
Self::ChangedDate
|
Self::ChangedDate
|
||||||
}
|
}
|
||||||
|
#[cfg(unix)]
|
||||||
"acc" | "accessed" => {
|
"acc" | "accessed" => {
|
||||||
Self::AccessedDate
|
Self::AccessedDate
|
||||||
}
|
}
|
||||||
|
#[cfg(unix)]
|
||||||
"cr" | "created" => {
|
"cr" | "created" => {
|
||||||
Self::CreatedDate
|
Self::CreatedDate
|
||||||
}
|
}
|
||||||
|
#[cfg(unix)]
|
||||||
"inode" => {
|
"inode" => {
|
||||||
Self::FileInode
|
Self::FileInode
|
||||||
}
|
}
|
||||||
|
@ -27,7 +27,7 @@
|
|||||||
//! command-line options, as all the options and their values (such as
|
//! command-line options, as all the options and their values (such as
|
||||||
//! `--sort size`) are guaranteed to just be 8-bit ASCII.
|
//! `--sort size`) are guaranteed to just be 8-bit ASCII.
|
||||||
|
|
||||||
|
use std::borrow::Cow;
|
||||||
use std::ffi::{OsStr, OsString};
|
use std::ffi::{OsStr, OsString};
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
|
|
||||||
@ -79,7 +79,6 @@ impl fmt::Display for Flag {
|
|||||||
/// Whether redundant arguments should be considered a problem.
|
/// Whether redundant arguments should be considered a problem.
|
||||||
#[derive(PartialEq, Debug, Copy, Clone)]
|
#[derive(PartialEq, Debug, Copy, Clone)]
|
||||||
pub enum Strictness {
|
pub enum Strictness {
|
||||||
|
|
||||||
/// Throw an error when an argument doesn’t do anything, either because
|
/// Throw an error when an argument doesn’t do anything, either because
|
||||||
/// it requires another argument to be specified, or because two conflict.
|
/// it requires another argument to be specified, or because two conflict.
|
||||||
ComplainAboutRedundantArguments,
|
ComplainAboutRedundantArguments,
|
||||||
@ -93,7 +92,6 @@ pub enum Strictness {
|
|||||||
/// arguments.
|
/// arguments.
|
||||||
#[derive(Copy, Clone, PartialEq, Debug)]
|
#[derive(Copy, Clone, PartialEq, Debug)]
|
||||||
pub enum TakesValue {
|
pub enum TakesValue {
|
||||||
|
|
||||||
/// This flag has to be followed by a value.
|
/// This flag has to be followed by a value.
|
||||||
/// If there’s a fixed set of possible values, they can be printed out
|
/// If there’s a fixed set of possible values, they can be printed out
|
||||||
/// with the error text.
|
/// with the error text.
|
||||||
@ -106,11 +104,9 @@ pub enum TakesValue {
|
|||||||
Optional(Option<Values>),
|
Optional(Option<Values>),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/// An **argument** can be matched by one of the user’s input strings.
|
/// An **argument** can be matched by one of the user’s input strings.
|
||||||
#[derive(PartialEq, Debug, Copy, Clone)]
|
#[derive(PartialEq, Debug, Copy, Clone)]
|
||||||
pub struct Arg {
|
pub struct Arg {
|
||||||
|
|
||||||
/// The short argument that matches it, if any.
|
/// The short argument that matches it, if any.
|
||||||
pub short: Option<ShortArg>,
|
pub short: Option<ShortArg>,
|
||||||
|
|
||||||
@ -134,19 +130,20 @@ impl fmt::Display for Arg {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/// Literally just several args.
|
/// Literally just several args.
|
||||||
#[derive(PartialEq, Debug)]
|
#[derive(PartialEq, Debug)]
|
||||||
pub struct Args(pub &'static [&'static Arg]);
|
pub struct Args(pub &'static [&'static Arg]);
|
||||||
|
|
||||||
impl Args {
|
impl Args {
|
||||||
|
|
||||||
/// Iterates over the given list of command-line arguments and parses
|
/// Iterates over the given list of command-line arguments and parses
|
||||||
/// them into a list of matched flags and free strings.
|
/// them into a list of matched flags and free strings.
|
||||||
pub fn parse<'args, I>(&self, inputs: I, strictness: Strictness) -> Result<Matches<'args>, ParseError>
|
pub fn parse<'args, I>(&self, inputs: I, strictness: Strictness) -> Result<Matches<'args>, ParseError>
|
||||||
where I: IntoIterator<Item = &'args OsStr>
|
where I: IntoIterator<Item = &'args OsStr>
|
||||||
{
|
{
|
||||||
|
#[cfg(unix)]
|
||||||
use std::os::unix::ffi::OsStrExt;
|
use std::os::unix::ffi::OsStrExt;
|
||||||
|
#[cfg(windows)]
|
||||||
|
use os_str_bytes::{OsStrBytes, OsStringBytes};
|
||||||
|
|
||||||
let mut parsing = true;
|
let mut parsing = true;
|
||||||
|
|
||||||
@ -159,7 +156,7 @@ impl Args {
|
|||||||
// doesn’t have one in its string so it needs the next one.
|
// doesn’t have one in its string so it needs the next one.
|
||||||
let mut inputs = inputs.into_iter();
|
let mut inputs = inputs.into_iter();
|
||||||
while let Some(arg) = inputs.next() {
|
while let Some(arg) = inputs.next() {
|
||||||
let bytes = arg.as_bytes();
|
let bytes = arg.to_bytes();
|
||||||
|
|
||||||
// Stop parsing if one of the arguments is the literal string “--”.
|
// Stop parsing if one of the arguments is the literal string “--”.
|
||||||
// This allows a file named “--arg” to be specified by passing in
|
// This allows a file named “--arg” to be specified by passing in
|
||||||
@ -167,20 +164,18 @@ impl Args {
|
|||||||
// doesn’t exist.
|
// doesn’t exist.
|
||||||
if ! parsing {
|
if ! parsing {
|
||||||
frees.push(arg)
|
frees.push(arg)
|
||||||
}
|
} else if arg == "--" {
|
||||||
else if arg == "--" {
|
|
||||||
parsing = false;
|
parsing = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// If the string starts with *two* dashes then it’s a long argument.
|
// If the string starts with *two* dashes then it’s a long argument.
|
||||||
else if bytes.starts_with(b"--") {
|
else if bytes.starts_with(b"--") {
|
||||||
let long_arg_name = OsStr::from_bytes(&bytes[2..]);
|
let long_arg_name = OsStrBytes::from_bytes(&bytes[2..]).unwrap();
|
||||||
|
|
||||||
// If there’s an equals in it, then the string before the
|
// If there’s an equals in it, then the string before the
|
||||||
// equals will be the flag’s name, and the string after it
|
// equals will be the flag’s name, and the string after it
|
||||||
// will be its value.
|
// will be its value.
|
||||||
if let Some((before, after)) = split_on_equals(long_arg_name) {
|
if let Some((before, after)) = split_on_equals(&long_arg_name) {
|
||||||
let arg = self.lookup_long(before)?;
|
let arg = self.lookup_long(&*before)?;
|
||||||
let flag = Flag::Long(arg.long);
|
let flag = Flag::Long(arg.long);
|
||||||
match arg.takes_value {
|
match arg.takes_value {
|
||||||
TakesValue::Necessary(_) |
|
TakesValue::Necessary(_) |
|
||||||
@ -188,11 +183,10 @@ impl Args {
|
|||||||
TakesValue::Forbidden => return Err(ParseError::ForbiddenValue { flag }),
|
TakesValue::Forbidden => return Err(ParseError::ForbiddenValue { flag }),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// If there’s no equals, then the entire string (apart from
|
// If there’s no equals, then the entire string (apart from
|
||||||
// the dashes) is the argument name.
|
// the dashes) is the argument name.
|
||||||
else {
|
else {
|
||||||
let arg = self.lookup_long(long_arg_name)?;
|
let arg = self.lookup_long(&*long_arg_name)?;
|
||||||
let flag = Flag::Long(arg.long);
|
let flag = Flag::Long(arg.long);
|
||||||
match arg.takes_value {
|
match arg.takes_value {
|
||||||
TakesValue::Forbidden => {
|
TakesValue::Forbidden => {
|
||||||
@ -200,28 +194,25 @@ impl Args {
|
|||||||
}
|
}
|
||||||
TakesValue::Necessary(values) => {
|
TakesValue::Necessary(values) => {
|
||||||
if let Some(next_arg) = inputs.next() {
|
if let Some(next_arg) = inputs.next() {
|
||||||
result_flags.push((flag, Some(next_arg)));
|
result_flags.push((flag, Some(next_arg.clone())));
|
||||||
}
|
} else {
|
||||||
else {
|
return Err(ParseError::NeedsValue { flag, values });
|
||||||
return Err(ParseError::NeedsValue { flag, values })
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
TakesValue::Optional(_) => {
|
TakesValue::Optional(_) => {
|
||||||
if let Some(next_arg) = inputs.next() {
|
if let Some(next_arg) = inputs.next() {
|
||||||
result_flags.push((flag, Some(next_arg)));
|
result_flags.push((flag, Some(next_arg.clone())));
|
||||||
}
|
} else {
|
||||||
else {
|
|
||||||
result_flags.push((flag, None));
|
result_flags.push((flag, None));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// If the string starts with *one* dash then it’s one or more
|
// If the string starts with *one* dash then it’s one or more
|
||||||
// short arguments.
|
// short arguments.
|
||||||
else if bytes.starts_with(b"-") && arg != "-" {
|
else if bytes.starts_with(b"-") && arg != "-" {
|
||||||
let short_arg = OsStr::from_bytes(&bytes[1..]);
|
let short_arg = OsStr::from_bytes(&bytes[1..]).unwrap();
|
||||||
|
|
||||||
// If there’s an equals in it, then the argument immediately
|
// If there’s an equals in it, then the argument immediately
|
||||||
// before the equals was the one that has the value, with the
|
// before the equals was the one that has the value, with the
|
||||||
@ -235,8 +226,9 @@ impl Args {
|
|||||||
// There’s no way to give two values in a cluster like this:
|
// There’s no way to give two values in a cluster like this:
|
||||||
// it’s an error if any of the first set of arguments actually
|
// it’s an error if any of the first set of arguments actually
|
||||||
// takes a value.
|
// takes a value.
|
||||||
if let Some((before, after)) = split_on_equals(short_arg) {
|
if let Some((before, after)) = split_on_equals(&short_arg) {
|
||||||
let (arg_with_value, other_args) = before.as_bytes().split_last().unwrap();
|
let bytes = before.to_bytes();
|
||||||
|
let (arg_with_value, other_args) = bytes.split_last().unwrap();
|
||||||
|
|
||||||
// Process the characters immediately following the dash...
|
// Process the characters immediately following the dash...
|
||||||
for byte in other_args {
|
for byte in other_args {
|
||||||
@ -266,7 +258,6 @@ impl Args {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// If there’s no equals, then every character is parsed as
|
// If there’s no equals, then every character is parsed as
|
||||||
// its own short argument. However, if any of the arguments
|
// its own short argument. However, if any of the arguments
|
||||||
// takes a value, then the *rest* of the string is used as
|
// takes a value, then the *rest* of the string is used as
|
||||||
@ -290,14 +281,15 @@ impl Args {
|
|||||||
TakesValue::Necessary(values) |
|
TakesValue::Necessary(values) |
|
||||||
TakesValue::Optional(values) => {
|
TakesValue::Optional(values) => {
|
||||||
if index < bytes.len() - 1 {
|
if index < bytes.len() - 1 {
|
||||||
let remnants = &bytes[index+1 ..];
|
let remnants = &bytes[index + 1..];
|
||||||
result_flags.push((flag, Some(OsStr::from_bytes(remnants))));
|
result_flags.push((
|
||||||
|
flag,
|
||||||
|
Some(OsStringBytes::from_bytes(remnants).unwrap()),
|
||||||
|
));
|
||||||
break;
|
break;
|
||||||
}
|
} else if let Some(next_arg) = inputs.next() {
|
||||||
else if let Some(next_arg) = inputs.next() {
|
result_flags.push((flag, Some(next_arg.clone())));
|
||||||
result_flags.push((flag, Some(next_arg)));
|
} else {
|
||||||
}
|
|
||||||
else {
|
|
||||||
match arg.takes_value {
|
match arg.takes_value {
|
||||||
TakesValue::Forbidden => {
|
TakesValue::Forbidden => {
|
||||||
unreachable!()
|
unreachable!()
|
||||||
@ -315,14 +307,19 @@ impl Args {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Otherwise, it’s a free string, usually a file name.
|
// Otherwise, it’s a free string, usually a file name.
|
||||||
else {
|
else {
|
||||||
frees.push(arg)
|
frees.push(arg)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(Matches { frees, flags: MatchedFlags { flags: result_flags, strictness } })
|
Ok(Matches {
|
||||||
|
frees,
|
||||||
|
flags: MatchedFlags {
|
||||||
|
flags: result_flags,
|
||||||
|
strictness,
|
||||||
|
},
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn lookup_short(&self, short: ShortArg) -> Result<&Arg, ParseError> {
|
fn lookup_short(&self, short: ShortArg) -> Result<&Arg, ParseError> {
|
||||||
@ -340,22 +337,19 @@ impl Args {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/// The **matches** are the result of parsing the user’s command-line strings.
|
/// The **matches** are the result of parsing the user’s command-line strings.
|
||||||
#[derive(PartialEq, Debug)]
|
#[derive(PartialEq, Debug)]
|
||||||
pub struct Matches<'args> {
|
pub struct Matches<'args> {
|
||||||
|
|
||||||
/// The flags that were parsed from the user’s input.
|
/// The flags that were parsed from the user’s input.
|
||||||
pub flags: MatchedFlags<'args>,
|
pub flags: MatchedFlags<'args> ,
|
||||||
|
|
||||||
/// All the strings that weren’t matched as arguments, as well as anything
|
/// All the strings that weren’t matched as arguments, as well as anything
|
||||||
/// after the special “--” string.
|
/// after the special "--" string.
|
||||||
pub frees: Vec<&'args OsStr>,
|
pub frees: Vec<&'args OsStr>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(PartialEq, Debug)]
|
#[derive(PartialEq, Debug)]
|
||||||
pub struct MatchedFlags<'args> {
|
pub struct MatchedFlags {
|
||||||
|
|
||||||
/// The individual flags from the user’s input, in the order they were
|
/// The individual flags from the user’s input, in the order they were
|
||||||
/// originally given.
|
/// originally given.
|
||||||
///
|
///
|
||||||
@ -368,8 +362,7 @@ pub struct MatchedFlags<'args> {
|
|||||||
strictness: Strictness,
|
strictness: Strictness,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> MatchedFlags<'a> {
|
impl MatchedFlags {
|
||||||
|
|
||||||
/// Whether the given argument was specified.
|
/// Whether the given argument was specified.
|
||||||
/// Returns `true` if it was, `false` if it wasn’t, and an error in
|
/// Returns `true` if it was, `false` if it wasn’t, and an error in
|
||||||
/// strict mode if it was specified more than once.
|
/// strict mode if it was specified more than once.
|
||||||
@ -449,7 +442,8 @@ impl<'a> MatchedFlags<'a> {
|
|||||||
/// Counts the number of occurrences of the given argument, even in
|
/// Counts the number of occurrences of the given argument, even in
|
||||||
/// strict mode.
|
/// strict mode.
|
||||||
pub fn count(&self, arg: &Arg) -> usize {
|
pub fn count(&self, arg: &Arg) -> usize {
|
||||||
self.flags.iter()
|
self.flags
|
||||||
|
.iter()
|
||||||
.filter(|tuple| tuple.0.matches(arg))
|
.filter(|tuple| tuple.0.matches(arg))
|
||||||
.count()
|
.count()
|
||||||
}
|
}
|
||||||
@ -461,12 +455,10 @@ impl<'a> MatchedFlags<'a> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/// A problem with the user’s input that meant it couldn’t be parsed into a
|
/// A problem with the user’s input that meant it couldn’t be parsed into a
|
||||||
/// coherent list of arguments.
|
/// coherent list of arguments.
|
||||||
#[derive(PartialEq, Debug)]
|
#[derive(PartialEq, Debug)]
|
||||||
pub enum ParseError {
|
pub enum ParseError {
|
||||||
|
|
||||||
/// A flag that has to take a value was not given one.
|
/// A flag that has to take a value was not given one.
|
||||||
NeedsValue { flag: Flag, values: Option<Values> },
|
NeedsValue { flag: Flag, values: Option<Values> },
|
||||||
|
|
||||||
@ -495,14 +487,12 @@ impl fmt::Display for ParseError {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/// Splits a string on its `=` character, returning the two substrings on
|
/// Splits a string on its `=` character, returning the two substrings on
|
||||||
/// either side. Returns `None` if there’s no equals or a string is missing.
|
/// either side. Returns `None` if there’s no equals or a string is missing.
|
||||||
fn split_on_equals(input: &OsStr) -> Option<(&OsStr, &OsStr)> {
|
fn split_on_equals<'a>(input: &'a Cow<OsStr>) -> Option<(OsString, OsString)> {
|
||||||
use std::os::unix::ffi::OsStrExt;
|
if let Some(index) = input.to_bytes().iter().position(|elem| *elem == b'=') {
|
||||||
|
let bytes = input.to_bytes();
|
||||||
if let Some(index) = input.as_bytes().iter().position(|elem| *elem == b'=') {
|
let (before, after) = bytes.split_at(index);
|
||||||
let (before, after) = input.as_bytes().split_at(index);
|
|
||||||
|
|
||||||
// The after string contains the = that we need to remove.
|
// The after string contains the = that we need to remove.
|
||||||
if ! before.is_empty() && after.len() >= 2 {
|
if ! before.is_empty() && after.len() >= 2 {
|
||||||
@ -550,7 +540,6 @@ mod split_test {
|
|||||||
test_split!(more: "this=that=other" => "this", "that=other");
|
test_split!(more: "this=that=other" => "this", "that=other");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod parse_test {
|
mod parse_test {
|
||||||
use super::*;
|
use super::*;
|
||||||
@ -591,16 +580,31 @@ mod parse_test {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
const SUGGESTIONS: Values = &[ "example" ];
|
const SUGGESTIONS: Values = &["example"];
|
||||||
|
|
||||||
static TEST_ARGS: &[&Arg] = &[
|
static TEST_ARGS: &[&Arg] = &[
|
||||||
&Arg { short: Some(b'l'), long: "long", takes_value: TakesValue::Forbidden },
|
&Arg {
|
||||||
&Arg { short: Some(b'v'), long: "verbose", takes_value: TakesValue::Forbidden },
|
short: Some(b'l'),
|
||||||
&Arg { short: Some(b'c'), long: "count", takes_value: TakesValue::Necessary(None) },
|
long: "long",
|
||||||
&Arg { short: Some(b't'), long: "type", takes_value: TakesValue::Necessary(Some(SUGGESTIONS)) }
|
takes_value: TakesValue::Forbidden,
|
||||||
|
},
|
||||||
|
&Arg {
|
||||||
|
short: Some(b'v'),
|
||||||
|
long: "verbose",
|
||||||
|
takes_value: TakesValue::Forbidden,
|
||||||
|
},
|
||||||
|
&Arg {
|
||||||
|
short: Some(b'c'),
|
||||||
|
long: "count",
|
||||||
|
takes_value: TakesValue::Necessary(None),
|
||||||
|
},
|
||||||
|
&Arg {
|
||||||
|
short: Some(b't'),
|
||||||
|
long: "type",
|
||||||
|
takes_value: TakesValue::Necessary(Some(SUGGESTIONS)),
|
||||||
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|
||||||
// Just filenames
|
// Just filenames
|
||||||
test!(empty: [] => frees: [], flags: []);
|
test!(empty: [] => frees: [], flags: []);
|
||||||
test!(one_arg: ["exa"] => frees: [ "exa" ], flags: []);
|
test!(one_arg: ["exa"] => frees: [ "exa" ], flags: []);
|
||||||
@ -612,7 +616,6 @@ mod parse_test {
|
|||||||
test!(two_arg_l: ["--", "--long"] => frees: [ "--long" ], flags: []);
|
test!(two_arg_l: ["--", "--long"] => frees: [ "--long" ], flags: []);
|
||||||
test!(two_arg_s: ["--", "-l"] => frees: [ "-l" ], flags: []);
|
test!(two_arg_s: ["--", "-l"] => frees: [ "-l" ], flags: []);
|
||||||
|
|
||||||
|
|
||||||
// Long args
|
// Long args
|
||||||
test!(long: ["--long"] => frees: [], flags: [ (Flag::Long("long"), None) ]);
|
test!(long: ["--long"] => frees: [], flags: [ (Flag::Long("long"), None) ]);
|
||||||
test!(long_then: ["--long", "4"] => frees: [ "4" ], flags: [ (Flag::Long("long"), None) ]);
|
test!(long_then: ["--long", "4"] => frees: [ "4" ], flags: [ (Flag::Long("long"), None) ]);
|
||||||
@ -621,14 +624,13 @@ mod parse_test {
|
|||||||
// Long args with values
|
// Long args with values
|
||||||
test!(bad_equals: ["--long=equals"] => error ForbiddenValue { flag: Flag::Long("long") });
|
test!(bad_equals: ["--long=equals"] => error ForbiddenValue { flag: Flag::Long("long") });
|
||||||
test!(no_arg: ["--count"] => error NeedsValue { flag: Flag::Long("count"), values: None });
|
test!(no_arg: ["--count"] => error NeedsValue { flag: Flag::Long("count"), values: None });
|
||||||
test!(arg_equals: ["--count=4"] => frees: [], flags: [ (Flag::Long("count"), Some(OsStr::new("4"))) ]);
|
test!(arg_equals: ["--count=4"] => frees: [], flags: [ (Flag::Long("count"), Some(OsString::from("4"))) ]);
|
||||||
test!(arg_then: ["--count", "4"] => frees: [], flags: [ (Flag::Long("count"), Some(OsStr::new("4"))) ]);
|
test!(arg_then: ["--count", "4"] => frees: [], flags: [ (Flag::Long("count"), Some(OsString::from("4"))) ]);
|
||||||
|
|
||||||
// Long args with values and suggestions
|
// Long args with values and suggestions
|
||||||
test!(no_arg_s: ["--type"] => error NeedsValue { flag: Flag::Long("type"), values: Some(SUGGESTIONS) });
|
test!(no_arg_s: ["--type"] => error NeedsValue { flag: Flag::Long("type"), values: Some(SUGGESTIONS) });
|
||||||
test!(arg_equals_s: ["--type=exa"] => frees: [], flags: [ (Flag::Long("type"), Some(OsStr::new("exa"))) ]);
|
test!(arg_equals_s: ["--type=exa"] => frees: [], flags: [ (Flag::Long("type"), Some(OsString::from("exa"))) ]);
|
||||||
test!(arg_then_s: ["--type", "exa"] => frees: [], flags: [ (Flag::Long("type"), Some(OsStr::new("exa"))) ]);
|
test!(arg_then_s: ["--type", "exa"] => frees: [], flags: [ (Flag::Long("type"), Some(OsString::from("exa"))) ]);
|
||||||
|
|
||||||
|
|
||||||
// Short args
|
// Short args
|
||||||
test!(short: ["-l"] => frees: [], flags: [ (Flag::Short(b'l'), None) ]);
|
test!(short: ["-l"] => frees: [], flags: [ (Flag::Short(b'l'), None) ]);
|
||||||
@ -639,18 +641,17 @@ mod parse_test {
|
|||||||
// Short args with values
|
// Short args with values
|
||||||
test!(bad_short: ["-l=equals"] => error ForbiddenValue { flag: Flag::Short(b'l') });
|
test!(bad_short: ["-l=equals"] => error ForbiddenValue { flag: Flag::Short(b'l') });
|
||||||
test!(short_none: ["-c"] => error NeedsValue { flag: Flag::Short(b'c'), values: None });
|
test!(short_none: ["-c"] => error NeedsValue { flag: Flag::Short(b'c'), values: None });
|
||||||
test!(short_arg_eq: ["-c=4"] => frees: [], flags: [(Flag::Short(b'c'), Some(OsStr::new("4"))) ]);
|
test!(short_arg_eq: ["-c=4"] => frees: [], flags: [(Flag::Short(b'c'), Some(OsString::from("4"))) ]);
|
||||||
test!(short_arg_then: ["-c", "4"] => frees: [], flags: [(Flag::Short(b'c'), Some(OsStr::new("4"))) ]);
|
test!(short_arg_then: ["-c", "4"] => frees: [], flags: [(Flag::Short(b'c'), Some(OsString::from("4"))) ]);
|
||||||
test!(short_two_together: ["-lctwo"] => frees: [], flags: [(Flag::Short(b'l'), None), (Flag::Short(b'c'), Some(OsStr::new("two"))) ]);
|
test!(short_two_together: ["-lctwo"] => frees: [], flags: [(Flag::Short(b'l'), None), (Flag::Short(b'c'), Some(OsString::from("two"))) ]);
|
||||||
test!(short_two_equals: ["-lc=two"] => frees: [], flags: [(Flag::Short(b'l'), None), (Flag::Short(b'c'), Some(OsStr::new("two"))) ]);
|
test!(short_two_equals: ["-lc=two"] => frees: [], flags: [(Flag::Short(b'l'), None), (Flag::Short(b'c'), Some(OsString::from("two"))) ]);
|
||||||
test!(short_two_next: ["-lc", "two"] => frees: [], flags: [(Flag::Short(b'l'), None), (Flag::Short(b'c'), Some(OsStr::new("two"))) ]);
|
test!(short_two_next: ["-lc", "two"] => frees: [], flags: [(Flag::Short(b'l'), None), (Flag::Short(b'c'), Some(OsString::from("two"))) ]);
|
||||||
|
|
||||||
// Short args with values and suggestions
|
// Short args with values and suggestions
|
||||||
test!(short_none_s: ["-t"] => error NeedsValue { flag: Flag::Short(b't'), values: Some(SUGGESTIONS) });
|
test!(short_none_s: ["-t"] => error NeedsValue { flag: Flag::Short(b't'), values: Some(SUGGESTIONS) });
|
||||||
test!(short_two_together_s: ["-texa"] => frees: [], flags: [(Flag::Short(b't'), Some(OsStr::new("exa"))) ]);
|
test!(short_two_together_s: ["-texa"] => frees: [], flags: [(Flag::Short(b't'), Some(OsString::from("exa"))) ]);
|
||||||
test!(short_two_equals_s: ["-t=exa"] => frees: [], flags: [(Flag::Short(b't'), Some(OsStr::new("exa"))) ]);
|
test!(short_two_equals_s: ["-t=exa"] => frees: [], flags: [(Flag::Short(b't'), Some(OsString::from("exa"))) ]);
|
||||||
test!(short_two_next_s: ["-t", "exa"] => frees: [], flags: [(Flag::Short(b't'), Some(OsStr::new("exa"))) ]);
|
test!(short_two_next_s: ["-t", "exa"] => frees: [], flags: [(Flag::Short(b't'), Some(OsString::from("exa"))) ]);
|
||||||
|
|
||||||
|
|
||||||
// Unknown args
|
// Unknown args
|
||||||
test!(unknown_long: ["--quiet"] => error UnknownArgument { attempt: OsString::from("quiet") });
|
test!(unknown_long: ["--quiet"] => error UnknownArgument { attempt: OsString::from("quiet") });
|
||||||
@ -661,7 +662,6 @@ mod parse_test {
|
|||||||
test!(unknown_short_2nd_eq: ["-lq=shhh"] => error UnknownShortArgument { attempt: b'q' });
|
test!(unknown_short_2nd_eq: ["-lq=shhh"] => error UnknownShortArgument { attempt: b'q' });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod matches_test {
|
mod matches_test {
|
||||||
use super::*;
|
use super::*;
|
||||||
@ -680,9 +680,16 @@ mod matches_test {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
static VERBOSE: Arg = Arg { short: Some(b'v'), long: "verbose", takes_value: TakesValue::Forbidden };
|
static VERBOSE: Arg = Arg {
|
||||||
static COUNT: Arg = Arg { short: Some(b'c'), long: "count", takes_value: TakesValue::Necessary(None) };
|
short: Some(b'v'),
|
||||||
|
long: "verbose",
|
||||||
|
takes_value: TakesValue::Forbidden,
|
||||||
|
};
|
||||||
|
static COUNT: Arg = Arg {
|
||||||
|
short: Some(b'c'),
|
||||||
|
long: "count",
|
||||||
|
takes_value: TakesValue::Necessary(None),
|
||||||
|
};
|
||||||
|
|
||||||
test!(short_never: [], has VERBOSE => false);
|
test!(short_never: [], has VERBOSE => false);
|
||||||
test!(short_once: [(Flag::Short(b'v'), None)], has VERBOSE => true);
|
test!(short_once: [(Flag::Short(b'v'), None)], has VERBOSE => true);
|
||||||
@ -691,17 +698,16 @@ mod matches_test {
|
|||||||
test!(long_twice: [(Flag::Long("verbose"), None), (Flag::Long("verbose"), None)], has VERBOSE => true);
|
test!(long_twice: [(Flag::Long("verbose"), None), (Flag::Long("verbose"), None)], has VERBOSE => true);
|
||||||
test!(long_mixed: [(Flag::Long("verbose"), None), (Flag::Short(b'v'), None)], has VERBOSE => true);
|
test!(long_mixed: [(Flag::Long("verbose"), None), (Flag::Short(b'v'), None)], has VERBOSE => true);
|
||||||
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn only_count() {
|
fn only_count() {
|
||||||
let everything = OsString::from("everything");
|
let everything = OsString::from("everything");
|
||||||
|
|
||||||
let flags = MatchedFlags {
|
let flags = MatchedFlags {
|
||||||
flags: vec![ (Flag::Short(b'c'), Some(&*everything)) ],
|
flags: vec![(Flag::Short(b'c'), Some(everything.clone()))],
|
||||||
strictness: Strictness::UseLastArguments,
|
strictness: Strictness::UseLastArguments,
|
||||||
};
|
};
|
||||||
|
|
||||||
assert_eq!(flags.get(&COUNT), Ok(Some(&*everything)));
|
assert_eq!(flags.get(&COUNT), Ok(Some(everything)));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@ -710,17 +716,22 @@ mod matches_test {
|
|||||||
let nothing = OsString::from("nothing");
|
let nothing = OsString::from("nothing");
|
||||||
|
|
||||||
let flags = MatchedFlags {
|
let flags = MatchedFlags {
|
||||||
flags: vec![ (Flag::Short(b'c'), Some(&*everything)),
|
flags: vec![
|
||||||
(Flag::Short(b'c'), Some(&*nothing)) ],
|
(Flag::Short(b'c'), Some(everything)),
|
||||||
|
(Flag::Short(b'c'), Some(nothing.clone())),
|
||||||
|
],
|
||||||
strictness: Strictness::UseLastArguments,
|
strictness: Strictness::UseLastArguments,
|
||||||
};
|
};
|
||||||
|
|
||||||
assert_eq!(flags.get(&COUNT), Ok(Some(&*nothing)));
|
assert_eq!(flags.get(&COUNT), Ok(Some(nothing)));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn no_count() {
|
fn no_count() {
|
||||||
let flags = MatchedFlags { flags: Vec::new(), strictness: Strictness::UseLastArguments };
|
let flags = MatchedFlags {
|
||||||
|
flags: Vec::new(),
|
||||||
|
strictness: Strictness::UseLastArguments,
|
||||||
|
};
|
||||||
|
|
||||||
assert_eq!(flags.has(&COUNT).unwrap(), false);
|
assert_eq!(flags.has(&COUNT).unwrap(), false);
|
||||||
}
|
}
|
||||||
|
@ -235,6 +235,17 @@ impl<'a, 'dir, C: Colours> FileName<'a, 'dir, C> {
|
|||||||
|
|
||||||
/// The character to be displayed after a file when classifying is on, if
|
/// The character to be displayed after a file when classifying is on, if
|
||||||
/// the file’s type has one associated with it.
|
/// the file’s type has one associated with it.
|
||||||
|
#[cfg(windows)]
|
||||||
|
fn classify_char(&self) -> Option<&'static str> {
|
||||||
|
if self.file.is_directory() {
|
||||||
|
Some("/")
|
||||||
|
} else if self.file.is_link() {
|
||||||
|
Some("@")
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#[cfg(unix)]
|
||||||
fn classify_char(&self) -> Option<&'static str> {
|
fn classify_char(&self) -> Option<&'static str> {
|
||||||
if self.file.is_executable_file() {
|
if self.file.is_executable_file() {
|
||||||
Some("*")
|
Some("*")
|
||||||
@ -295,11 +306,16 @@ impl<'a, 'dir, C: Colours> FileName<'a, 'dir, C> {
|
|||||||
|
|
||||||
match self.file {
|
match self.file {
|
||||||
f if f.is_directory() => self.colours.directory(),
|
f if f.is_directory() => self.colours.directory(),
|
||||||
|
#[cfg(unix)]
|
||||||
f if f.is_executable_file() => self.colours.executable_file(),
|
f if f.is_executable_file() => self.colours.executable_file(),
|
||||||
f if f.is_link() => self.colours.symlink(),
|
f if f.is_link() => self.colours.symlink(),
|
||||||
|
#[cfg(unix)]
|
||||||
f if f.is_pipe() => self.colours.pipe(),
|
f if f.is_pipe() => self.colours.pipe(),
|
||||||
|
#[cfg(unix)]
|
||||||
f if f.is_block_device() => self.colours.block_device(),
|
f if f.is_block_device() => self.colours.block_device(),
|
||||||
|
#[cfg(unix)]
|
||||||
f if f.is_char_device() => self.colours.char_device(),
|
f if f.is_char_device() => self.colours.char_device(),
|
||||||
|
#[cfg(unix)]
|
||||||
f if f.is_socket() => self.colours.socket(),
|
f if f.is_socket() => self.colours.socket(),
|
||||||
f if ! f.is_file() => self.colours.special(),
|
f if ! f.is_file() => self.colours.special(),
|
||||||
_ => self.colours.colour_file(self.file),
|
_ => self.colours.colour_file(self.file),
|
||||||
|
@ -7,7 +7,9 @@ pub use self::filetype::Colours as FiletypeColours;
|
|||||||
mod git;
|
mod git;
|
||||||
pub use self::git::Colours as GitColours;
|
pub use self::git::Colours as GitColours;
|
||||||
|
|
||||||
|
#[cfg(unix)]
|
||||||
mod groups;
|
mod groups;
|
||||||
|
#[cfg(unix)]
|
||||||
pub use self::groups::Colours as GroupColours;
|
pub use self::groups::Colours as GroupColours;
|
||||||
|
|
||||||
mod inode;
|
mod inode;
|
||||||
@ -26,7 +28,9 @@ mod times;
|
|||||||
pub use self::times::Render as TimeRender;
|
pub use self::times::Render as TimeRender;
|
||||||
// times does too
|
// times does too
|
||||||
|
|
||||||
|
#[cfg(unix)]
|
||||||
mod users;
|
mod users;
|
||||||
|
#[cfg(unix)]
|
||||||
pub use self::users::Colours as UserColours;
|
pub use self::users::Colours as UserColours;
|
||||||
|
|
||||||
mod octal;
|
mod octal;
|
||||||
|
@ -8,6 +8,7 @@ use crate::output::render::FiletypeColours;
|
|||||||
impl f::PermissionsPlus {
|
impl f::PermissionsPlus {
|
||||||
pub fn render<C: Colours+FiletypeColours>(&self, colours: &C) -> TextCell {
|
pub fn render<C: Colours+FiletypeColours>(&self, colours: &C) -> TextCell {
|
||||||
let mut chars = vec![ self.file_type.render(colours) ];
|
let mut chars = vec![ self.file_type.render(colours) ];
|
||||||
|
#[cfg(unix)]
|
||||||
chars.extend(self.permissions.render(colours, self.file_type.is_regular_file()));
|
chars.extend(self.permissions.render(colours, self.file_type.is_regular_file()));
|
||||||
|
|
||||||
if self.xattrs {
|
if self.xattrs {
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
use std::cmp::max;
|
use std::cmp::max;
|
||||||
use std::env;
|
use std::env;
|
||||||
use std::ops::Deref;
|
use std::ops::Deref;
|
||||||
|
#[cfg(unix)]
|
||||||
use std::sync::{Mutex, MutexGuard};
|
use std::sync::{Mutex, MutexGuard};
|
||||||
|
|
||||||
use datetime::TimeZone;
|
use datetime::TimeZone;
|
||||||
@ -8,6 +9,7 @@ use zoneinfo_compiled::{CompiledData, Result as TZResult};
|
|||||||
|
|
||||||
use lazy_static::lazy_static;
|
use lazy_static::lazy_static;
|
||||||
use log::*;
|
use log::*;
|
||||||
|
#[cfg(unix)]
|
||||||
use users::UsersCache;
|
use users::UsersCache;
|
||||||
|
|
||||||
use crate::fs::{File, fields as f};
|
use crate::fs::{File, fields as f};
|
||||||
@ -29,7 +31,6 @@ pub struct Options {
|
|||||||
/// Extra columns to display in the table.
|
/// Extra columns to display in the table.
|
||||||
#[derive(PartialEq, Debug, Copy, Clone)]
|
#[derive(PartialEq, Debug, Copy, Clone)]
|
||||||
pub struct Columns {
|
pub struct Columns {
|
||||||
|
|
||||||
/// At least one of these timestamps will be shown.
|
/// At least one of these timestamps will be shown.
|
||||||
pub time_types: TimeTypes,
|
pub time_types: TimeTypes,
|
||||||
|
|
||||||
@ -52,6 +53,7 @@ impl Columns {
|
|||||||
let mut columns = Vec::with_capacity(4);
|
let mut columns = Vec::with_capacity(4);
|
||||||
|
|
||||||
if self.inode {
|
if self.inode {
|
||||||
|
#[cfg(unix)]
|
||||||
columns.push(Column::Inode);
|
columns.push(Column::Inode);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -60,10 +62,12 @@ impl Columns {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if self.permissions {
|
if self.permissions {
|
||||||
|
#[cfg(unix)]
|
||||||
columns.push(Column::Permissions);
|
columns.push(Column::Permissions);
|
||||||
}
|
}
|
||||||
|
|
||||||
if self.links {
|
if self.links {
|
||||||
|
#[cfg(unix)]
|
||||||
columns.push(Column::HardLinks);
|
columns.push(Column::HardLinks);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -72,30 +76,37 @@ impl Columns {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if self.blocks {
|
if self.blocks {
|
||||||
|
#[cfg(unix)]
|
||||||
columns.push(Column::Blocks);
|
columns.push(Column::Blocks);
|
||||||
}
|
}
|
||||||
|
|
||||||
if self.user {
|
if self.user {
|
||||||
|
#[cfg(unix)]
|
||||||
columns.push(Column::User);
|
columns.push(Column::User);
|
||||||
}
|
}
|
||||||
|
|
||||||
if self.group {
|
if self.group {
|
||||||
|
#[cfg(unix)]
|
||||||
columns.push(Column::Group);
|
columns.push(Column::Group);
|
||||||
}
|
}
|
||||||
|
|
||||||
if self.time_types.modified {
|
if self.time_types.modified {
|
||||||
|
#[cfg(unix)]
|
||||||
columns.push(Column::Timestamp(TimeType::Modified));
|
columns.push(Column::Timestamp(TimeType::Modified));
|
||||||
}
|
}
|
||||||
|
|
||||||
if self.time_types.changed {
|
if self.time_types.changed {
|
||||||
|
#[cfg(unix)]
|
||||||
columns.push(Column::Timestamp(TimeType::Changed));
|
columns.push(Column::Timestamp(TimeType::Changed));
|
||||||
}
|
}
|
||||||
|
|
||||||
if self.time_types.created {
|
if self.time_types.created {
|
||||||
|
#[cfg(unix)]
|
||||||
columns.push(Column::Timestamp(TimeType::Created));
|
columns.push(Column::Timestamp(TimeType::Created));
|
||||||
}
|
}
|
||||||
|
|
||||||
if self.time_types.accessed {
|
if self.time_types.accessed {
|
||||||
|
#[cfg(unix)]
|
||||||
columns.push(Column::Timestamp(TimeType::Accessed));
|
columns.push(Column::Timestamp(TimeType::Accessed));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -107,17 +118,23 @@ impl Columns {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/// A table contains these.
|
/// A table contains these.
|
||||||
#[derive(Debug, Copy, Clone)]
|
#[derive(Debug, Copy, Clone)]
|
||||||
pub enum Column {
|
pub enum Column {
|
||||||
|
#[cfg(unix)]
|
||||||
Permissions,
|
Permissions,
|
||||||
FileSize,
|
FileSize,
|
||||||
|
#[cfg(unix)]
|
||||||
Timestamp(TimeType),
|
Timestamp(TimeType),
|
||||||
|
#[cfg(unix)]
|
||||||
Blocks,
|
Blocks,
|
||||||
|
#[cfg(unix)]
|
||||||
User,
|
User,
|
||||||
|
#[cfg(unix)]
|
||||||
Group,
|
Group,
|
||||||
|
#[cfg(unix)]
|
||||||
HardLinks,
|
HardLinks,
|
||||||
|
#[cfg(unix)]
|
||||||
Inode,
|
Inode,
|
||||||
GitStatus,
|
GitStatus,
|
||||||
Octal,
|
Octal,
|
||||||
@ -132,8 +149,8 @@ pub enum Alignment {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Column {
|
impl Column {
|
||||||
|
|
||||||
/// Get the alignment this column should use.
|
/// Get the alignment this column should use.
|
||||||
|
#[cfg(unix)]
|
||||||
pub fn alignment(self) -> Alignment {
|
pub fn alignment(self) -> Alignment {
|
||||||
match self {
|
match self {
|
||||||
Self::FileSize |
|
Self::FileSize |
|
||||||
@ -144,18 +161,33 @@ impl Column {
|
|||||||
_ => Alignment::Left,
|
_ => Alignment::Left,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
#[cfg(windows)]
|
||||||
|
pub fn alignment(&self) -> Alignment {
|
||||||
|
match *self {
|
||||||
|
Column::FileSize | Column::GitStatus => Alignment::Right,
|
||||||
|
_ => Alignment::Left,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#[cfg(windows)]
|
||||||
|
|
||||||
/// Get the text that should be printed at the top, when the user elects
|
/// Get the text that should be printed at the top, when the user elects
|
||||||
/// to have a header row printed.
|
/// to have a header row printed.
|
||||||
pub fn header(self) -> &'static str {
|
pub fn header(self) -> &'static str {
|
||||||
match self {
|
match self {
|
||||||
|
#[cfg(unix)]
|
||||||
Self::Permissions => "Permissions",
|
Self::Permissions => "Permissions",
|
||||||
Self::FileSize => "Size",
|
Self::FileSize => "Size",
|
||||||
|
#[cfg(unix)]
|
||||||
Self::Timestamp(t) => t.header(),
|
Self::Timestamp(t) => t.header(),
|
||||||
|
#[cfg(unix)]
|
||||||
Self::Blocks => "Blocks",
|
Self::Blocks => "Blocks",
|
||||||
|
#[cfg(unix)]
|
||||||
Self::User => "User",
|
Self::User => "User",
|
||||||
|
#[cfg(unix)]
|
||||||
Self::Group => "Group",
|
Self::Group => "Group",
|
||||||
|
#[cfg(unix)]
|
||||||
Self::HardLinks => "Links",
|
Self::HardLinks => "Links",
|
||||||
|
#[cfg(unix)]
|
||||||
Self::Inode => "inode",
|
Self::Inode => "inode",
|
||||||
Self::GitStatus => "Git",
|
Self::GitStatus => "Git",
|
||||||
Self::Octal => "Octal",
|
Self::Octal => "Octal",
|
||||||
@ -163,11 +195,9 @@ impl Column {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/// Formatting options for file sizes.
|
/// Formatting options for file sizes.
|
||||||
#[derive(PartialEq, Debug, Copy, Clone)]
|
#[derive(PartialEq, Debug, Copy, Clone)]
|
||||||
pub enum SizeFormat {
|
pub enum SizeFormat {
|
||||||
|
|
||||||
/// Format the file size using **decimal** prefixes, such as “kilo”,
|
/// Format the file size using **decimal** prefixes, such as “kilo”,
|
||||||
/// “mega”, or “giga”.
|
/// “mega”, or “giga”.
|
||||||
DecimalBytes,
|
DecimalBytes,
|
||||||
@ -186,7 +216,6 @@ impl Default for SizeFormat {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/// The types of a file’s time fields. These three fields are standard
|
/// The types of a file’s time fields. These three fields are standard
|
||||||
/// across most (all?) operating systems.
|
/// across most (all?) operating systems.
|
||||||
#[derive(PartialEq, Debug, Copy, Clone)]
|
#[derive(PartialEq, Debug, Copy, Clone)]
|
||||||
@ -206,7 +235,6 @@ pub enum TimeType {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl TimeType {
|
impl TimeType {
|
||||||
|
|
||||||
/// Returns the text to use for a column’s heading in the columns output.
|
/// Returns the text to use for a column’s heading in the columns output.
|
||||||
pub fn header(self) -> &'static str {
|
pub fn header(self) -> &'static str {
|
||||||
match self {
|
match self {
|
||||||
@ -218,7 +246,6 @@ impl TimeType {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/// Fields for which of a file’s time fields should be displayed in the
|
/// Fields for which of a file’s time fields should be displayed in the
|
||||||
/// columns output.
|
/// columns output.
|
||||||
///
|
///
|
||||||
@ -228,13 +255,12 @@ impl TimeType {
|
|||||||
#[allow(clippy::struct_excessive_bools)]
|
#[allow(clippy::struct_excessive_bools)]
|
||||||
pub struct TimeTypes {
|
pub struct TimeTypes {
|
||||||
pub modified: bool,
|
pub modified: bool,
|
||||||
pub changed: bool,
|
pub changed: bool,
|
||||||
pub accessed: bool,
|
pub accessed: bool,
|
||||||
pub created: bool,
|
pub created: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for TimeTypes {
|
impl Default for TimeTypes {
|
||||||
|
|
||||||
/// By default, display just the ‘modified’ time. This is the most
|
/// By default, display just the ‘modified’ time. This is the most
|
||||||
/// common option, which is why it has this shorthand.
|
/// common option, which is why it has this shorthand.
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
@ -247,13 +273,11 @@ impl Default for TimeTypes {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/// The **environment** struct contains any data that could change between
|
/// The **environment** struct contains any data that could change between
|
||||||
/// running instances of exa, depending on the user’s computer’s configuration.
|
/// running instances of exa, depending on the user’s computer’s configuration.
|
||||||
///
|
///
|
||||||
/// Any environment field should be able to be mocked up for test runs.
|
/// Any environment field should be able to be mocked up for test runs.
|
||||||
pub struct Environment {
|
pub struct Environment {
|
||||||
|
|
||||||
/// Localisation rules for formatting numbers.
|
/// Localisation rules for formatting numbers.
|
||||||
numeric: locale::Numeric,
|
numeric: locale::Numeric,
|
||||||
|
|
||||||
@ -262,10 +286,12 @@ pub struct Environment {
|
|||||||
tz: Option<TimeZone>,
|
tz: Option<TimeZone>,
|
||||||
|
|
||||||
/// Mapping cache of user IDs to usernames.
|
/// Mapping cache of user IDs to usernames.
|
||||||
|
#[cfg(unix)]
|
||||||
users: Mutex<UsersCache>,
|
users: Mutex<UsersCache>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Environment {
|
impl Environment {
|
||||||
|
#[cfg(unix)]
|
||||||
pub fn lock_users(&self) -> MutexGuard<'_, UsersCache> {
|
pub fn lock_users(&self) -> MutexGuard<'_, UsersCache> {
|
||||||
self.users.lock().unwrap()
|
self.users.lock().unwrap()
|
||||||
}
|
}
|
||||||
@ -284,9 +310,10 @@ impl Environment {
|
|||||||
let numeric = locale::Numeric::load_user_locale()
|
let numeric = locale::Numeric::load_user_locale()
|
||||||
.unwrap_or_else(|_| locale::Numeric::english());
|
.unwrap_or_else(|_| locale::Numeric::english());
|
||||||
|
|
||||||
|
#[cfg(unix)]
|
||||||
let users = Mutex::new(UsersCache::new());
|
let users = Mutex::new(UsersCache::new());
|
||||||
|
|
||||||
Self { tz, numeric, users }
|
Self { tz, numeric, #[cfg(unix)] users }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -303,7 +330,6 @@ lazy_static! {
|
|||||||
static ref ENVIRONMENT: Environment = Environment::load_all();
|
static ref ENVIRONMENT: Environment = Environment::load_all();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
pub struct Table<'a> {
|
pub struct Table<'a> {
|
||||||
columns: Vec<Column>,
|
columns: Vec<Column>,
|
||||||
theme: &'a Theme,
|
theme: &'a Theme,
|
||||||
@ -363,6 +389,7 @@ impl<'a, 'f> Table<'a> {
|
|||||||
fn permissions_plus(&self, file: &File<'_>, xattrs: bool) -> f::PermissionsPlus {
|
fn permissions_plus(&self, file: &File<'_>, xattrs: bool) -> f::PermissionsPlus {
|
||||||
f::PermissionsPlus {
|
f::PermissionsPlus {
|
||||||
file_type: file.type_char(),
|
file_type: file.type_char(),
|
||||||
|
#[cfg(unix)]
|
||||||
permissions: file.permissions(),
|
permissions: file.permissions(),
|
||||||
xattrs,
|
xattrs,
|
||||||
}
|
}
|
||||||
@ -376,24 +403,30 @@ impl<'a, 'f> Table<'a> {
|
|||||||
|
|
||||||
fn display(&self, file: &File<'_>, column: Column, xattrs: bool) -> TextCell {
|
fn display(&self, file: &File<'_>, column: Column, xattrs: bool) -> TextCell {
|
||||||
match column {
|
match column {
|
||||||
|
#[cfg(unix)]
|
||||||
Column::Permissions => {
|
Column::Permissions => {
|
||||||
self.permissions_plus(file, xattrs).render(self.theme)
|
self.permissions_plus(file, xattrs).render(self.theme)
|
||||||
}
|
}
|
||||||
Column::FileSize => {
|
Column::FileSize => {
|
||||||
file.size().render(self.theme, self.size_format, &self.env.numeric)
|
file.size().render(self.theme, self.size_format, &self.env.numeric)
|
||||||
}
|
}
|
||||||
|
#[cfg(unix)]
|
||||||
Column::HardLinks => {
|
Column::HardLinks => {
|
||||||
file.links().render(self.theme, &self.env.numeric)
|
file.links().render(self.theme, &self.env.numeric)
|
||||||
}
|
}
|
||||||
|
#[cfg(unix)]
|
||||||
Column::Inode => {
|
Column::Inode => {
|
||||||
file.inode().render(self.theme.ui.inode)
|
file.inode().render(self.theme.ui.inode)
|
||||||
}
|
}
|
||||||
|
#[cfg(unix)]
|
||||||
Column::Blocks => {
|
Column::Blocks => {
|
||||||
file.blocks().render(self.theme)
|
file.blocks().render(self.theme)
|
||||||
}
|
}
|
||||||
|
#[cfg(unix)]
|
||||||
Column::User => {
|
Column::User => {
|
||||||
file.user().render(self.theme, &*self.env.lock_users())
|
file.user().render(self.theme, &*self.env.lock_users())
|
||||||
}
|
}
|
||||||
|
#[cfg(unix)]
|
||||||
Column::Group => {
|
Column::Group => {
|
||||||
file.group().render(self.theme, &*self.env.lock_users())
|
file.group().render(self.theme, &*self.env.lock_users())
|
||||||
}
|
}
|
||||||
@ -455,7 +488,6 @@ impl<'a, 'f> Table<'a> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
pub struct TableWidths(Vec<usize>);
|
pub struct TableWidths(Vec<usize>);
|
||||||
|
|
||||||
impl Deref for TableWidths {
|
impl Deref for TableWidths {
|
||||||
|
@ -229,6 +229,7 @@ impl render::GitColours for Theme {
|
|||||||
fn conflicted(&self) -> Style { self.ui.git.conflicted }
|
fn conflicted(&self) -> Style { self.ui.git.conflicted }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(unix)]
|
||||||
impl render::GroupColours for Theme {
|
impl render::GroupColours for Theme {
|
||||||
fn yours(&self) -> Style { self.ui.users.group_yours }
|
fn yours(&self) -> Style { self.ui.users.group_yours }
|
||||||
fn not_yours(&self) -> Style { self.ui.users.group_not_yours }
|
fn not_yours(&self) -> Style { self.ui.users.group_not_yours }
|
||||||
@ -287,6 +288,7 @@ impl render::SizeColours for Theme {
|
|||||||
fn minor(&self) -> Style { self.ui.size.minor }
|
fn minor(&self) -> Style { self.ui.size.minor }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(unix)]
|
||||||
impl render::UserColours for Theme {
|
impl render::UserColours for Theme {
|
||||||
fn you(&self) -> Style { self.ui.users.user_you }
|
fn you(&self) -> Style { self.ui.users.user_you }
|
||||||
fn someone_else(&self) -> Style { self.ui.users.user_someone_else }
|
fn someone_else(&self) -> Style { self.ui.users.user_someone_else }
|
||||||
|
Loading…
Reference in New Issue
Block a user