mirror of
https://github.com/eza-community/eza
synced 2024-10-04 15:09:49 +00:00
Merge branch 'main' into pr-1177
This commit is contained in:
commit
ec68e17791
|
@ -75,12 +75,13 @@ These options are available when running with `--long` (`-l`):
|
|||
- **-t**, **--time=(field)**: which timestamp field to use
|
||||
- **-u**, **--accessed**: use the accessed timestamp field
|
||||
- **-U**, **--created**: use the created timestamp field
|
||||
- **-Z**, **--context**: list each file’s security context
|
||||
- **-@**, **--extended**: list each file’s extended attributes and sizes
|
||||
- **--changed**: use the changed timestamp field
|
||||
- **--git**: list each file’s Git status, if tracked or ignored
|
||||
- **--time-style**: how to format timestamps
|
||||
- **--no-permissions**: suppress the permissions field
|
||||
- **--octal-permissions**: list each file's permission in octal format
|
||||
- **-o**, **--octal-permissions**: list each file's permission in octal format
|
||||
- **--no-filesize**: suppress the filesize field
|
||||
- **--no-user**: suppress the user field
|
||||
- **--no-time**: suppress the time field
|
||||
|
|
|
@ -62,7 +62,7 @@ complete -c exa -s 'B' -l 'bytes' -d "List file sizes in bytes, without any p
|
|||
complete -c exa -s 'g' -l 'group' -d "List each file's group"
|
||||
complete -c exa -s 'h' -l 'header' -d "Add a header row to each column"
|
||||
complete -c exa -s 'H' -l 'links' -d "List each file's number of hard links"
|
||||
complete -c exa -s 'g' -l 'group' -d "List each file's inode number"
|
||||
complete -c exa -s 'i' -l 'inode' -d "List each file's inode number"
|
||||
complete -c exa -s 'S' -l 'blocks' -d "List each file's number of filesystem blocks"
|
||||
complete -c exa -s 't' -l 'time' -d "Which timestamp field to list" -x -a "
|
||||
modified\t'Display modified time'
|
||||
|
@ -81,12 +81,13 @@ complete -c exa -l 'time-style' -d "How to format timestamps" -x -a "
|
|||
long-iso\t'Display longer ISO timestaps, up to the minute'
|
||||
full-iso\t'Display full ISO timestamps, up to the nanosecond'
|
||||
"
|
||||
complete -c exa -l 'no-permissions' -d "Suppress the permissions field"
|
||||
complete -c exa -l 'octal-permissions' -d "List each file's permission in octal format"
|
||||
complete -c exa -l 'no-filesize' -d "Suppress the filesize field"
|
||||
complete -c exa -l 'no-user' -d "Suppress the user field"
|
||||
complete -c exa -l 'no-time' -d "Suppress the time field"
|
||||
complete -c exa -l 'no-permissions' -d "Suppress the permissions field"
|
||||
complete -c exa -s 'o' -l 'octal-permissions' -d "List each file's permission in octal format"
|
||||
complete -c exa -l 'no-filesize' -d "Suppress the filesize field"
|
||||
complete -c exa -l 'no-user' -d "Suppress the user field"
|
||||
complete -c exa -l 'no-time' -d "Suppress the time field"
|
||||
|
||||
# Optional extras
|
||||
complete -c exa -l 'git' -d "List each file's Git status, if tracked"
|
||||
complete -c exa -s '@' -l 'extended' -d "List each file's extended attributes and sizes"
|
||||
complete -c exa -s 'Z' -l 'context' -d "List each file's security context"
|
||||
|
|
|
@ -46,7 +46,7 @@ __exa() {
|
|||
{-t,--time}="[Which time field to show]:(time field):(accessed changed created modified)" \
|
||||
--time-style="[How to format timestamps]:(time style):(default iso long-iso full-iso)" \
|
||||
--no-permissions"[Suppress the permissions field]" \
|
||||
--octal-permissions"[List each file's permission in octal format]" \
|
||||
{-o, --octal-permissions}"[List each file's permission in octal format]" \
|
||||
--no-filesize"[Suppress the filesize field]" \
|
||||
--no-user"[Suppress the user field]" \
|
||||
--no-time"[Suppress the time field]" \
|
||||
|
@ -54,6 +54,7 @@ __exa() {
|
|||
{-U,--created}"[Use the created timestamp field]" \
|
||||
--git"[List each file's Git status, if tracked]" \
|
||||
{-@,--extended}"[List each file's extended attributes and sizes]" \
|
||||
{-Z,--context}"[List each file's security context]" \
|
||||
'*:filename:_files'
|
||||
}
|
||||
|
||||
|
|
|
@ -171,6 +171,9 @@ These options are available when running with `--long` (`-l`):
|
|||
`--no-permissions`
|
||||
: Suppress the permissions field.
|
||||
|
||||
`-o`, `--octal-permissions`
|
||||
: List each file's permissions in octal format.
|
||||
|
||||
`--no-filesize`
|
||||
: Suppress the file size field.
|
||||
|
||||
|
@ -183,6 +186,9 @@ These options are available when running with `--long` (`-l`):
|
|||
`-@`, `--extended`
|
||||
: List each file’s extended attributes and sizes.
|
||||
|
||||
`-Z`, `--context`
|
||||
: List each file's security context.
|
||||
|
||||
`--git` [if exa was built with git support]
|
||||
: List each file’s Git status, if tracked.
|
||||
|
||||
|
|
|
@ -343,3 +343,52 @@ fn index_status(status: git2::Status) -> f::GitStatus {
|
|||
_ => f::GitStatus::NotModified,
|
||||
}
|
||||
}
|
||||
|
||||
fn current_branch(repo: &git2::Repository) -> Option<String>{
|
||||
let head = match repo.head() {
|
||||
Ok(head) => Some(head),
|
||||
Err(ref e) if e.code() == git2::ErrorCode::UnbornBranch || e.code() == git2::ErrorCode::NotFound => return None,
|
||||
Err(e) => {
|
||||
error!("Error looking up Git branch: {:?}", e);
|
||||
return None
|
||||
}
|
||||
};
|
||||
|
||||
if let Some(h) = head{
|
||||
if let Some(s) = h.shorthand(){
|
||||
let branch_name = s.to_owned();
|
||||
if branch_name.len() > 10 {
|
||||
return Some(branch_name[..8].to_string()+"..");
|
||||
}
|
||||
return Some(branch_name);
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
impl f::SubdirGitRepo{
|
||||
pub fn from_path(dir : &Path, status : bool) -> Self{
|
||||
|
||||
let path = &reorient(&dir);
|
||||
let g = git2::Repository::open(path);
|
||||
if let Ok(repo) = g{
|
||||
|
||||
let branch = current_branch(&repo);
|
||||
if !status{
|
||||
return Self{status : f::SubdirGitRepoStatus::GitUnknown, branch};
|
||||
}
|
||||
match repo.statuses(None) {
|
||||
Ok(es) => {
|
||||
if es.iter().filter(|s| s.status() != git2::Status::IGNORED).any(|_| true){
|
||||
return Self{status : f::SubdirGitRepoStatus::GitDirty, branch};
|
||||
}
|
||||
return Self{status : f::SubdirGitRepoStatus::GitClean, branch};
|
||||
}
|
||||
Err(e) => {
|
||||
error!("Error looking up Git statuses: {:?}", e)
|
||||
}
|
||||
}
|
||||
}
|
||||
Self::default()
|
||||
}
|
||||
}
|
|
@ -30,4 +30,10 @@ pub mod git {
|
|||
unreachable!();
|
||||
}
|
||||
}
|
||||
|
||||
impl f::SubdirGitRepo{
|
||||
pub fn from_path(_dir : &Path, _status : bool) -> Self{
|
||||
panic!("Tried to get subdir Git status, but Git support is disabled")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
#![allow(trivial_casts)] // for ARM
|
||||
|
||||
use std::cmp::Ordering;
|
||||
use std::ffi::CString;
|
||||
use std::io;
|
||||
use std::path::Path;
|
||||
|
||||
|
@ -50,58 +51,98 @@ pub enum FollowSymlinks {
|
|||
#[derive(Debug, Clone)]
|
||||
pub struct Attribute {
|
||||
pub name: String,
|
||||
pub size: usize,
|
||||
pub value: String,
|
||||
}
|
||||
|
||||
|
||||
#[cfg(any(target_os = "macos", target_os = "linux"))]
|
||||
pub fn list_attrs(lister: &lister::Lister, path: &Path) -> io::Result<Vec<Attribute>> {
|
||||
use std::ffi::CString;
|
||||
fn get_secattr(lister: &lister::Lister, c_path: &std::ffi::CString) -> io::Result<Vec<Attribute>> {
|
||||
const SELINUX_XATTR_NAME: &str = "security.selinux";
|
||||
const ENODATA: i32 = 61;
|
||||
|
||||
let c_path = match path.to_str().and_then(|s| CString::new(s).ok()) {
|
||||
Some(cstring) => cstring,
|
||||
None => {
|
||||
return Err(io::Error::new(io::ErrorKind::Other, "Error: path somehow contained a NUL?"));
|
||||
}
|
||||
let c_attr_name = CString::new(SELINUX_XATTR_NAME).map_err(|e| {
|
||||
io::Error::new(io::ErrorKind::Other, e)
|
||||
})?;
|
||||
let size = lister.getxattr_first(c_path, &c_attr_name);
|
||||
|
||||
let size = match size.cmp(&0) {
|
||||
Ordering::Less => {
|
||||
let e = io::Error::last_os_error();
|
||||
|
||||
if e.kind() == io::ErrorKind::Other && e.raw_os_error() == Some(ENODATA) {
|
||||
return Ok(Vec::new())
|
||||
}
|
||||
|
||||
return Err(e)
|
||||
},
|
||||
Ordering::Equal => return Err(io::Error::from(io::ErrorKind::InvalidData)),
|
||||
Ordering::Greater => size as usize,
|
||||
};
|
||||
|
||||
let bufsize = lister.listxattr_first(&c_path);
|
||||
match bufsize.cmp(&0) {
|
||||
Ordering::Less => return Err(io::Error::last_os_error()),
|
||||
Ordering::Equal => return Ok(Vec::new()),
|
||||
Ordering::Greater => {},
|
||||
let mut buf_value = vec![0_u8; size];
|
||||
let size = lister.getxattr_second(c_path, &c_attr_name, &mut buf_value, size);
|
||||
|
||||
match size.cmp(&0) {
|
||||
Ordering::Less => return Err(io::Error::last_os_error()),
|
||||
Ordering::Equal => return Err(io::Error::from(io::ErrorKind::InvalidData)),
|
||||
Ordering::Greater => (),
|
||||
}
|
||||
|
||||
let mut buf = vec![0_u8; bufsize as usize];
|
||||
let err = lister.listxattr_second(&c_path, &mut buf, bufsize);
|
||||
Ok(vec![Attribute {
|
||||
name: String::from(SELINUX_XATTR_NAME),
|
||||
value: lister.translate_attribute_data(&buf_value),
|
||||
}])
|
||||
}
|
||||
|
||||
match err.cmp(&0) {
|
||||
pub fn list_attrs(lister: &lister::Lister, path: &Path) -> io::Result<Vec<Attribute>> {
|
||||
let c_path = CString::new(path.to_str().ok_or(io::Error::new(io::ErrorKind::Other, "Error: path not convertible to string"))?).map_err(|e| {
|
||||
io::Error::new(io::ErrorKind::Other, e)
|
||||
})?;
|
||||
|
||||
let bufsize = lister.listxattr_first(&c_path);
|
||||
let bufsize = match bufsize.cmp(&0) {
|
||||
Ordering::Less => return Err(io::Error::last_os_error()),
|
||||
// Some filesystems, like sysfs, return nothing on listxattr, even though the security
|
||||
// attribute is set.
|
||||
Ordering::Equal => return get_secattr(lister, &c_path),
|
||||
Ordering::Greater => bufsize as usize,
|
||||
};
|
||||
|
||||
let mut buf = vec![0_u8; bufsize];
|
||||
|
||||
match lister.listxattr_second(&c_path, &mut buf, bufsize).cmp(&0) {
|
||||
Ordering::Less => return Err(io::Error::last_os_error()),
|
||||
Ordering::Equal => return Ok(Vec::new()),
|
||||
Ordering::Greater => {},
|
||||
}
|
||||
|
||||
let mut names = Vec::new();
|
||||
if err > 0 {
|
||||
// End indices of the attribute names
|
||||
// the buffer contains 0-terminated c-strings
|
||||
let idx = buf.iter().enumerate().filter_map(|(i, v)|
|
||||
if *v == 0 { Some(i) } else { None }
|
||||
);
|
||||
let mut start = 0;
|
||||
|
||||
for end in idx {
|
||||
let c_end = end + 1; // end of the c-string (including 0)
|
||||
let size = lister.getxattr(&c_path, &buf[start..c_end]);
|
||||
for attr_name in buf.split(|c| c == &0) {
|
||||
if attr_name.is_empty() {
|
||||
continue;
|
||||
}
|
||||
|
||||
if size > 0 {
|
||||
names.push(Attribute {
|
||||
name: lister.translate_attribute_name(&buf[start..end]),
|
||||
size: size as usize,
|
||||
});
|
||||
let c_attr_name = CString::new(attr_name).map_err(|e| {
|
||||
io::Error::new(io::ErrorKind::Other, e)
|
||||
})?;
|
||||
let size = lister.getxattr_first(&c_path, &c_attr_name);
|
||||
|
||||
if size > 0 {
|
||||
let mut buf_value = vec![0_u8; size as usize];
|
||||
if lister.getxattr_second(&c_path, &c_attr_name, &mut buf_value, size as usize) < 0 {
|
||||
return Err(io::Error::last_os_error());
|
||||
}
|
||||
|
||||
start = c_end;
|
||||
names.push(Attribute {
|
||||
name: lister.translate_attribute_data(attr_name),
|
||||
value: lister.translate_attribute_data(&buf_value),
|
||||
});
|
||||
} else {
|
||||
names.push(Attribute {
|
||||
name: lister.translate_attribute_data(attr_name),
|
||||
value: String::new(),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -148,8 +189,8 @@ mod lister {
|
|||
Self { c_flags }
|
||||
}
|
||||
|
||||
pub fn translate_attribute_name(&self, input: &[u8]) -> String {
|
||||
unsafe { std::str::from_utf8_unchecked(input).into() }
|
||||
pub fn translate_attribute_data(&self, input: &[u8]) -> String {
|
||||
unsafe { std::str::from_utf8_unchecked(input).trim_end_matches('\0').into() }
|
||||
}
|
||||
|
||||
pub fn listxattr_first(&self, c_path: &CString) -> ssize_t {
|
||||
|
@ -163,22 +204,22 @@ mod lister {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn listxattr_second(&self, c_path: &CString, buf: &mut Vec<u8>, bufsize: ssize_t) -> ssize_t {
|
||||
pub fn listxattr_second(&self, c_path: &CString, buf: &mut [u8], bufsize: size_t) -> ssize_t {
|
||||
unsafe {
|
||||
listxattr(
|
||||
c_path.as_ptr(),
|
||||
buf.as_mut_ptr().cast::<c_char>(),
|
||||
bufsize as size_t,
|
||||
buf.as_mut_ptr().cast(),
|
||||
bufsize,
|
||||
self.c_flags,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn getxattr(&self, c_path: &CString, buf: &[u8]) -> ssize_t {
|
||||
pub fn getxattr_first(&self, c_path: &CString, c_name: &CString) -> ssize_t {
|
||||
unsafe {
|
||||
getxattr(
|
||||
c_path.as_ptr(),
|
||||
buf.as_ptr().cast::<c_char>(),
|
||||
c_name.as_ptr().cast(),
|
||||
ptr::null_mut(),
|
||||
0,
|
||||
0,
|
||||
|
@ -186,6 +227,19 @@ mod lister {
|
|||
)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn getxattr_second(&self, c_path: &CString, c_name: &CString, buf: &mut [u8], bufsize: size_t) -> ssize_t {
|
||||
unsafe {
|
||||
getxattr(
|
||||
c_path.as_ptr(),
|
||||
c_name.as_ptr().cast(),
|
||||
buf.as_mut_ptr().cast::<libc::c_void>(),
|
||||
bufsize,
|
||||
0,
|
||||
self.c_flags,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -234,8 +288,8 @@ mod lister {
|
|||
Lister { follow_symlinks }
|
||||
}
|
||||
|
||||
pub fn translate_attribute_name(&self, input: &[u8]) -> String {
|
||||
String::from_utf8_lossy(input).into_owned()
|
||||
pub fn translate_attribute_data(&self, input: &[u8]) -> String {
|
||||
String::from_utf8_lossy(input).trim_end_matches('\0').into()
|
||||
}
|
||||
|
||||
pub fn listxattr_first(&self, c_path: &CString) -> ssize_t {
|
||||
|
@ -246,14 +300,14 @@ mod lister {
|
|||
|
||||
unsafe {
|
||||
listxattr(
|
||||
c_path.as_ptr().cast(),
|
||||
c_path.as_ptr(),
|
||||
ptr::null_mut(),
|
||||
0,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn listxattr_second(&self, c_path: &CString, buf: &mut Vec<u8>, bufsize: ssize_t) -> ssize_t {
|
||||
pub fn listxattr_second(&self, c_path: &CString, buf: &mut [u8], bufsize: size_t) -> ssize_t {
|
||||
let listxattr = match self.follow_symlinks {
|
||||
FollowSymlinks::Yes => listxattr,
|
||||
FollowSymlinks::No => llistxattr,
|
||||
|
@ -261,27 +315,43 @@ mod lister {
|
|||
|
||||
unsafe {
|
||||
listxattr(
|
||||
c_path.as_ptr().cast(),
|
||||
c_path.as_ptr(),
|
||||
buf.as_mut_ptr().cast(),
|
||||
bufsize as size_t,
|
||||
bufsize,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn getxattr(&self, c_path: &CString, buf: &[u8]) -> ssize_t {
|
||||
pub fn getxattr_first(&self, c_path: &CString, c_name: &CString) -> ssize_t {
|
||||
let getxattr = match self.follow_symlinks {
|
||||
FollowSymlinks::Yes => getxattr,
|
||||
FollowSymlinks::No => lgetxattr,
|
||||
FollowSymlinks::Yes => getxattr,
|
||||
FollowSymlinks::No => lgetxattr,
|
||||
};
|
||||
|
||||
unsafe {
|
||||
getxattr(
|
||||
c_path.as_ptr().cast(),
|
||||
buf.as_ptr().cast(),
|
||||
c_path.as_ptr(),
|
||||
c_name.as_ptr().cast(),
|
||||
ptr::null_mut(),
|
||||
0,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn getxattr_second(&self, c_path: &CString, c_name: &CString, buf: &mut [u8], bufsize: size_t) -> ssize_t {
|
||||
let getxattr = match self.follow_symlinks {
|
||||
FollowSymlinks::Yes => getxattr,
|
||||
FollowSymlinks::No => lgetxattr,
|
||||
};
|
||||
|
||||
unsafe {
|
||||
getxattr(
|
||||
c_path.as_ptr(),
|
||||
c_name.as_ptr().cast(),
|
||||
buf.as_mut_ptr().cast::<libc::c_void>(),
|
||||
bufsize,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -259,3 +259,36 @@ impl Default for Git {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub enum SecurityContextType<'a> {
|
||||
SELinux(&'a str),
|
||||
None
|
||||
}
|
||||
|
||||
pub struct SecurityContext<'a> {
|
||||
pub context: SecurityContextType<'a>,
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
#[derive(PartialEq, Copy, Clone)]
|
||||
pub enum SubdirGitRepoStatus{
|
||||
NoRepo,
|
||||
GitClean,
|
||||
GitDirty,
|
||||
GitUnknown
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct SubdirGitRepo{
|
||||
pub status : SubdirGitRepoStatus,
|
||||
pub branch : Option<String>
|
||||
}
|
||||
|
||||
impl Default for SubdirGitRepo{
|
||||
fn default() -> Self {
|
||||
Self{
|
||||
status : SubdirGitRepoStatus::NoRepo,
|
||||
branch : None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -11,6 +11,8 @@ use std::time::{Duration, SystemTime, UNIX_EPOCH};
|
|||
use log::*;
|
||||
|
||||
use crate::fs::dir::Dir;
|
||||
use crate::fs::feature::xattr;
|
||||
use crate::fs::feature::xattr::{FileAttributes, Attribute};
|
||||
use crate::fs::fields as f;
|
||||
|
||||
|
||||
|
@ -66,6 +68,9 @@ pub struct File<'dir> {
|
|||
/// directory’s children, and are in fact added specifically by exa; this
|
||||
/// means that they should be skipped when recursing.
|
||||
pub is_all_all: bool,
|
||||
|
||||
/// The extended attributes of this file.
|
||||
pub extended_attributes: Vec<Attribute>,
|
||||
}
|
||||
|
||||
impl<'dir> File<'dir> {
|
||||
|
@ -80,8 +85,9 @@ impl<'dir> File<'dir> {
|
|||
debug!("Statting file {:?}", &path);
|
||||
let metadata = std::fs::symlink_metadata(&path)?;
|
||||
let is_all_all = false;
|
||||
let extended_attributes = File::gather_extended_attributes(&path);
|
||||
|
||||
Ok(File { name, ext, path, metadata, parent_dir, is_all_all })
|
||||
Ok(File { name, ext, path, metadata, parent_dir, is_all_all, extended_attributes })
|
||||
}
|
||||
|
||||
pub fn new_aa_current(parent_dir: &'dir Dir) -> io::Result<File<'dir>> {
|
||||
|
@ -92,8 +98,9 @@ impl<'dir> File<'dir> {
|
|||
let metadata = std::fs::symlink_metadata(&path)?;
|
||||
let is_all_all = true;
|
||||
let parent_dir = Some(parent_dir);
|
||||
let extended_attributes = File::gather_extended_attributes(&path);
|
||||
|
||||
Ok(File { path, parent_dir, metadata, ext, name: ".".into(), is_all_all })
|
||||
Ok(File { path, parent_dir, metadata, ext, name: ".".into(), is_all_all, extended_attributes })
|
||||
}
|
||||
|
||||
pub fn new_aa_parent(path: PathBuf, parent_dir: &'dir Dir) -> io::Result<File<'dir>> {
|
||||
|
@ -103,8 +110,9 @@ impl<'dir> File<'dir> {
|
|||
let metadata = std::fs::symlink_metadata(&path)?;
|
||||
let is_all_all = true;
|
||||
let parent_dir = Some(parent_dir);
|
||||
let extended_attributes = File::gather_extended_attributes(&path);
|
||||
|
||||
Ok(File { path, parent_dir, metadata, ext, name: "..".into(), is_all_all })
|
||||
Ok(File { path, parent_dir, metadata, ext, name: "..".into(), is_all_all, extended_attributes })
|
||||
}
|
||||
|
||||
/// A file’s name is derived from its string. This needs to handle directories
|
||||
|
@ -137,6 +145,21 @@ impl<'dir> File<'dir> {
|
|||
.to_ascii_lowercase())
|
||||
}
|
||||
|
||||
/// Read the extended attributes of a file path.
|
||||
fn gather_extended_attributes(path: &Path) -> Vec<Attribute> {
|
||||
if xattr::ENABLED {
|
||||
match path.symlink_attributes() {
|
||||
Ok(xattrs) => xattrs,
|
||||
Err(e) => {
|
||||
error!("Error looking up extended attributes for {}: {}", path.display(), e);
|
||||
Vec::new()
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Vec::new()
|
||||
}
|
||||
}
|
||||
|
||||
/// Whether this file is a directory on the filesystem.
|
||||
pub fn is_directory(&self) -> bool {
|
||||
self.metadata.is_dir()
|
||||
|
@ -261,7 +284,8 @@ impl<'dir> File<'dir> {
|
|||
Ok(metadata) => {
|
||||
let ext = File::ext(&path);
|
||||
let name = File::filename(&path);
|
||||
let file = File { parent_dir: None, path, ext, metadata, name, is_all_all: false };
|
||||
let extended_attributes = File::gather_extended_attributes(&absolute_path);
|
||||
let file = File { parent_dir: None, path, ext, metadata, name, is_all_all: false, extended_attributes };
|
||||
FileTarget::Ok(Box::new(file))
|
||||
}
|
||||
Err(e) => {
|
||||
|
@ -501,6 +525,16 @@ impl<'dir> File<'dir> {
|
|||
pub fn name_is_one_of(&self, choices: &[&str]) -> bool {
|
||||
choices.contains(&&self.name[..])
|
||||
}
|
||||
|
||||
/// This file’s security context field.
|
||||
pub fn security_context(&self) -> f::SecurityContext<'_> {
|
||||
let context = match &self.extended_attributes.iter().find(|a| a.name == "security.selinux") {
|
||||
Some(attr) => f::SecurityContextType::SELinux(&attr.value),
|
||||
None => f::SecurityContextType::None
|
||||
};
|
||||
|
||||
f::SecurityContext { context }
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -50,7 +50,7 @@ pub enum NumberSource {
|
|||
/// It came... from a command-line argument!
|
||||
Arg(&'static Arg),
|
||||
|
||||
/// It came... from the enviroment!
|
||||
/// It came... from the environment!
|
||||
Env(&'static str),
|
||||
}
|
||||
|
||||
|
|
|
@ -63,9 +63,12 @@ pub static NO_TIME: Arg = Arg { short: None, long: "no-time", takes_value: Takes
|
|||
pub static NO_ICONS: Arg = Arg { short: None, long: "no-icons", takes_value: TakesValue::Forbidden };
|
||||
|
||||
// optional feature options
|
||||
pub static GIT: Arg = Arg { short: None, long: "git", takes_value: TakesValue::Forbidden };
|
||||
pub static EXTENDED: Arg = Arg { short: Some(b'@'), long: "extended", takes_value: TakesValue::Forbidden };
|
||||
pub static OCTAL: Arg = Arg { short: None, long: "octal-permissions", takes_value: TakesValue::Forbidden };
|
||||
pub static GIT: Arg = Arg { short: None, long: "git", takes_value: TakesValue::Forbidden };
|
||||
pub static GIT_REPOS: Arg = Arg { short: None, long: "git-repos", takes_value: TakesValue::Forbidden };
|
||||
pub static GIT_REPOS_NO_STAT: Arg = Arg { short: None, long: "git-repos-no-status", takes_value: TakesValue::Forbidden };
|
||||
pub static EXTENDED: Arg = Arg { short: Some(b'@'), long: "extended", takes_value: TakesValue::Forbidden };
|
||||
pub static OCTAL: Arg = Arg { short: Some(b'o'), long: "octal-permissions", takes_value: TakesValue::Forbidden };
|
||||
pub static SECURITY_CONTEXT: Arg = Arg { short: Some(b'Z'), long: "context", takes_value: TakesValue::Forbidden };
|
||||
|
||||
|
||||
pub static ALL_ARGS: Args = Args(&[
|
||||
|
@ -81,5 +84,5 @@ pub static ALL_ARGS: Args = Args(&[
|
|||
&BLOCKS, &TIME, &ACCESSED, &CREATED, &TIME_STYLE, &HYPERLINK,
|
||||
&NO_PERMISSIONS, &NO_FILESIZE, &NO_USER, &NO_TIME, &NO_ICONS,
|
||||
|
||||
&GIT, &EXTENDED, &OCTAL
|
||||
&GIT, &GIT_REPOS, &GIT_REPOS_NO_STAT, &EXTENDED, &OCTAL, &SECURITY_CONTEXT
|
||||
]);
|
||||
|
|
|
@ -42,30 +42,30 @@ FILTERING AND SORTING OPTIONS
|
|||
date, time, old, and new all refer to modified.
|
||||
|
||||
LONG VIEW OPTIONS
|
||||
-b, --binary list file sizes with binary prefixes
|
||||
-B, --bytes list file sizes in bytes, without any prefixes
|
||||
-g, --group list each file's group
|
||||
-h, --header add a header row to each column
|
||||
-H, --links list each file's number of hard links
|
||||
-i, --inode list each file's inode number
|
||||
-m, --modified use the modified timestamp field
|
||||
-n, --numeric list numeric user and group IDs
|
||||
-S, --blocks show number of file system blocks
|
||||
-t, --time FIELD which timestamp field to list (modified, accessed, created)
|
||||
-u, --accessed use the accessed timestamp field
|
||||
-U, --created use the created timestamp field
|
||||
--changed use the changed timestamp field
|
||||
--time-style how to format timestamps (default, iso, long-iso, full-iso)
|
||||
--no-permissions suppress the permissions field
|
||||
--octal-permissions list each file's permission in octal format
|
||||
--no-filesize suppress the filesize field
|
||||
--no-user suppress the user field
|
||||
--no-time suppress the time field";
|
||||
-b, --binary list file sizes with binary prefixes
|
||||
-B, --bytes list file sizes in bytes, without any prefixes
|
||||
-g, --group list each file's group
|
||||
-h, --header add a header row to each column
|
||||
-H, --links list each file's number of hard links
|
||||
-i, --inode list each file's inode number
|
||||
-m, --modified use the modified timestamp field
|
||||
-n, --numeric list numeric user and group IDs
|
||||
-S, --blocks show number of file system blocks
|
||||
-t, --time FIELD which timestamp field to list (modified, accessed, created)
|
||||
-u, --accessed use the accessed timestamp field
|
||||
-U, --created use the created timestamp field
|
||||
--changed use the changed timestamp field
|
||||
--time-style how to format timestamps (default, iso, long-iso, full-iso)
|
||||
--no-permissions suppress the permissions field
|
||||
-o, --octal-permissions list each file's permission in octal format
|
||||
--no-filesize suppress the filesize field
|
||||
--no-user suppress the user field
|
||||
--no-time suppress the time field";
|
||||
|
||||
static GIT_FILTER_HELP: &str = " --git-ignore ignore files mentioned in '.gitignore'";
|
||||
static GIT_VIEW_HELP: &str = " --git list each file's Git status, if tracked or ignored";
|
||||
static EXTENDED_HELP: &str = " -@, --extended list each file's extended attributes and sizes";
|
||||
|
||||
static SECATTR_HELP: &str = " -Z, --context list each file's security context";
|
||||
|
||||
/// All the information needed to display the help text, which depends
|
||||
/// on which features are enabled and whether the user only wants to
|
||||
|
@ -111,6 +111,7 @@ impl fmt::Display for HelpString {
|
|||
|
||||
if xattr::ENABLED {
|
||||
write!(f, "\n{}", EXTENDED_HELP)?;
|
||||
write!(f, "\n{}", SECATTR_HELP)?;
|
||||
}
|
||||
|
||||
writeln!(f)
|
||||
|
|
|
@ -117,6 +117,7 @@ impl details::Options {
|
|||
table: None,
|
||||
header: false,
|
||||
xattr: xattr::ENABLED && matches.has(&flags::EXTENDED)?,
|
||||
secattr: xattr::ENABLED && matches.has(&flags::SECURITY_CONTEXT)?,
|
||||
};
|
||||
|
||||
Ok(details)
|
||||
|
@ -136,6 +137,7 @@ impl details::Options {
|
|||
table: Some(TableOptions::deduce(matches, vars)?),
|
||||
header: matches.has(&flags::HEADER)?,
|
||||
xattr: xattr::ENABLED && matches.has(&flags::EXTENDED)?,
|
||||
secattr: xattr::ENABLED && matches.has(&flags::SECURITY_CONTEXT)?,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -199,19 +201,23 @@ impl TableOptions {
|
|||
impl Columns {
|
||||
fn deduce(matches: &MatchedFlags<'_>) -> Result<Self, OptionsError> {
|
||||
let time_types = TimeTypes::deduce(matches)?;
|
||||
let git = matches.has(&flags::GIT)?;
|
||||
|
||||
let blocks = matches.has(&flags::BLOCKS)?;
|
||||
let group = matches.has(&flags::GROUP)?;
|
||||
let inode = matches.has(&flags::INODE)?;
|
||||
let links = matches.has(&flags::LINKS)?;
|
||||
let octal = matches.has(&flags::OCTAL)?;
|
||||
let git = matches.has(&flags::GIT)?;
|
||||
let subdir_git_repos = matches.has(&flags::GIT_REPOS)?;
|
||||
let subdir_git_repos_no_stat = !subdir_git_repos && matches.has(&flags::GIT_REPOS_NO_STAT)?;
|
||||
|
||||
let blocks = matches.has(&flags::BLOCKS)?;
|
||||
let group = matches.has(&flags::GROUP)?;
|
||||
let inode = matches.has(&flags::INODE)?;
|
||||
let links = matches.has(&flags::LINKS)?;
|
||||
let octal = matches.has(&flags::OCTAL)?;
|
||||
let security_context = xattr::ENABLED && matches.has(&flags::SECURITY_CONTEXT)?;
|
||||
|
||||
let permissions = ! matches.has(&flags::NO_PERMISSIONS)?;
|
||||
let filesize = ! matches.has(&flags::NO_FILESIZE)?;
|
||||
let user = ! matches.has(&flags::NO_USER)?;
|
||||
|
||||
Ok(Self { time_types, inode, links, blocks, group, git, octal, permissions, filesize, user })
|
||||
Ok(Self { time_types, inode, links, blocks, group, git, subdir_git_repos, subdir_git_repos_no_stat, octal, security_context, permissions, filesize, user })
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -71,7 +71,8 @@ use scoped_threadpool::Pool;
|
|||
use crate::fs::{Dir, File};
|
||||
use crate::fs::dir_action::RecurseOptions;
|
||||
use crate::fs::feature::git::GitCache;
|
||||
use crate::fs::feature::xattr::{Attribute, FileAttributes};
|
||||
use crate::fs::feature::xattr::Attribute;
|
||||
use crate::fs::fields::SecurityContextType;
|
||||
use crate::fs::filter::FileFilter;
|
||||
use crate::output::cell::TextCell;
|
||||
use crate::output::file_name::Options as FileStyle;
|
||||
|
@ -105,6 +106,9 @@ pub struct Options {
|
|||
|
||||
/// Whether to show each file’s extended attributes.
|
||||
pub xattr: bool,
|
||||
|
||||
/// Whether to show each file's security attribute.
|
||||
pub secattr: bool,
|
||||
}
|
||||
|
||||
|
||||
|
@ -132,7 +136,7 @@ pub struct Render<'a> {
|
|||
|
||||
struct Egg<'a> {
|
||||
table_row: Option<TableRow>,
|
||||
xattrs: Vec<Attribute>,
|
||||
xattrs: &'a [Attribute],
|
||||
errors: Vec<(io::Error, Option<PathBuf>)>,
|
||||
dir: Option<Dir>,
|
||||
file: &'a File<'a>,
|
||||
|
@ -189,11 +193,22 @@ impl<'a> Render<'a> {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
/// Whether to show the extended attribute hint
|
||||
pub fn show_xattr_hint(&self, file: &File<'_>) -> bool {
|
||||
// Do not show the hint '@' if the only extended attribute is the security
|
||||
// attribute and the security attribute column is active.
|
||||
let xattr_count = file.extended_attributes.len();
|
||||
let selinux_ctx_shown = self.opts.secattr && match file.security_context().context {
|
||||
SecurityContextType::SELinux(_) => true,
|
||||
SecurityContextType::None => false,
|
||||
};
|
||||
xattr_count > 1 || (xattr_count == 1 && !selinux_ctx_shown)
|
||||
}
|
||||
|
||||
/// Adds files to the table, possibly recursively. This is easily
|
||||
/// parallelisable, and uses a pool of threads.
|
||||
fn add_files_to_table<'dir>(&self, pool: &mut Pool, table: &mut Option<Table<'a>>, rows: &mut Vec<Row>, src: &[File<'dir>], depth: TreeDepth) {
|
||||
use std::sync::{Arc, Mutex};
|
||||
use log::*;
|
||||
use crate::fs::feature::xattr;
|
||||
|
||||
let mut file_eggs = (0..src.len()).map(|_| MaybeUninit::uninit()).collect::<Vec<_>>();
|
||||
|
@ -207,7 +222,6 @@ impl<'a> Render<'a> {
|
|||
|
||||
scoped.execute(move || {
|
||||
let mut errors = Vec::new();
|
||||
let mut xattrs = Vec::new();
|
||||
|
||||
// There are three “levels” of extended attribute support:
|
||||
//
|
||||
|
@ -216,7 +230,7 @@ impl<'a> Render<'a> {
|
|||
// 2. If the feature is enabled and the --extended flag
|
||||
// has been specified, then display an @ in the
|
||||
// permissions column for files with attributes, the
|
||||
// names of all attributes and their lengths, and any
|
||||
// names of all attributes and their values, and any
|
||||
// errors encountered when getting them.
|
||||
// 3. If the --extended flag *hasn’t* been specified, then
|
||||
// display the @, but don’t display anything else.
|
||||
|
@ -231,28 +245,14 @@ impl<'a> Render<'a> {
|
|||
// printed unless the user passes --extended to signify
|
||||
// that they want to see them.
|
||||
|
||||
if xattr::ENABLED {
|
||||
match file.path.attributes() {
|
||||
Ok(xs) => {
|
||||
xattrs.extend(xs);
|
||||
}
|
||||
Err(e) => {
|
||||
if self.opts.xattr {
|
||||
errors.push((e, None));
|
||||
}
|
||||
else {
|
||||
error!("Error looking up xattr for {:?}: {:#?}", file.path, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
let xattrs: &[Attribute] = if xattr::ENABLED && self.opts.xattr {
|
||||
&file.extended_attributes
|
||||
} else {
|
||||
&[]
|
||||
};
|
||||
|
||||
let table_row = table.as_ref()
|
||||
.map(|t| t.row_for_file(file, ! xattrs.is_empty()));
|
||||
|
||||
if ! self.opts.xattr {
|
||||
xattrs.clear();
|
||||
}
|
||||
.map(|t| t.row_for_file(file, self.show_xattr_hint(file)));
|
||||
|
||||
let mut dir = None;
|
||||
if let Some(r) = self.recurse {
|
||||
|
@ -315,7 +315,7 @@ impl<'a> Render<'a> {
|
|||
|
||||
if ! files.is_empty() {
|
||||
for xattr in egg.xattrs {
|
||||
rows.push(self.render_xattr(&xattr, TreeParams::new(depth.deeper(), false)));
|
||||
rows.push(self.render_xattr(xattr, TreeParams::new(depth.deeper(), false)));
|
||||
}
|
||||
|
||||
for (error, path) in errors {
|
||||
|
@ -330,7 +330,7 @@ impl<'a> Render<'a> {
|
|||
let count = egg.xattrs.len();
|
||||
for (index, xattr) in egg.xattrs.into_iter().enumerate() {
|
||||
let params = TreeParams::new(depth.deeper(), errors.is_empty() && index == count - 1);
|
||||
let r = self.render_xattr(&xattr, params);
|
||||
let r = self.render_xattr(xattr, params);
|
||||
rows.push(r);
|
||||
}
|
||||
|
||||
|
@ -367,7 +367,7 @@ impl<'a> Render<'a> {
|
|||
}
|
||||
|
||||
fn render_xattr(&self, xattr: &Attribute, tree: TreeParams) -> Row {
|
||||
let name = TextCell::paint(self.theme.ui.perms.attribute, format!("{} (len {})", xattr.name, xattr.size));
|
||||
let name = TextCell::paint(self.theme.ui.perms.attribute, format!("{}=\"{}\"", xattr.name, xattr.value));
|
||||
Row { cells: None, name, tree }
|
||||
}
|
||||
|
||||
|
|
|
@ -7,7 +7,6 @@ use term_grid as grid;
|
|||
|
||||
use crate::fs::{Dir, File};
|
||||
use crate::fs::feature::git::GitCache;
|
||||
use crate::fs::feature::xattr::FileAttributes;
|
||||
use crate::fs::filter::FileFilter;
|
||||
use crate::output::cell::TextCell;
|
||||
use crate::output::details::{Options as DetailsOptions, Row as DetailsRow, Render as DetailsRender};
|
||||
|
@ -150,7 +149,7 @@ impl<'a> Render<'a> {
|
|||
let (first_table, _) = self.make_table(options, &drender);
|
||||
|
||||
let rows = self.files.iter()
|
||||
.map(|file| first_table.row_for_file(file, file_has_xattrs(file)))
|
||||
.map(|file| first_table.row_for_file(file, drender.show_xattr_hint(file)))
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let file_names = self.files.iter()
|
||||
|
@ -299,11 +298,3 @@ fn divide_rounding_up(a: usize, b: usize) -> usize {
|
|||
|
||||
result
|
||||
}
|
||||
|
||||
|
||||
fn file_has_xattrs(file: &File<'_>) -> bool {
|
||||
match file.path.attributes() {
|
||||
Ok(attrs) => ! attrs.is_empty(),
|
||||
Err(_) => false,
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
use ansi_term::{ANSIString, Style};
|
||||
use ansi_term::{ANSIString, Style, Color};
|
||||
|
||||
use crate::output::cell::{TextCell, DisplayWidth};
|
||||
use crate::fs::fields as f;
|
||||
|
@ -16,6 +16,31 @@ impl f::Git {
|
|||
}
|
||||
}
|
||||
|
||||
impl f::SubdirGitRepo {
|
||||
pub fn render(self) -> TextCell {
|
||||
let style = Style::new();
|
||||
let branch_style = match self.branch.as_deref(){
|
||||
Some("master") => style.fg(Color::Green),
|
||||
Some("main") => style.fg(Color::Green),
|
||||
Some(_) => style.fg(Color::Fixed(208)),
|
||||
_ => style,
|
||||
};
|
||||
|
||||
let branch = branch_style.paint(self.branch.unwrap_or(String::from("-")));
|
||||
|
||||
let s = match self.status {
|
||||
f::SubdirGitRepoStatus::NoRepo => style.paint("- "),
|
||||
f::SubdirGitRepoStatus::GitClean => style.fg(Color::Green).paint("| "),
|
||||
f::SubdirGitRepoStatus::GitDirty => style.bold().fg(Color::Red).paint("- "),
|
||||
f::SubdirGitRepoStatus::GitUnknown => style.paint("- "),
|
||||
};
|
||||
|
||||
TextCell {
|
||||
width: DisplayWidth::from(2 + branch.len()),
|
||||
contents: vec![s,branch].into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl f::GitStatus {
|
||||
fn render(self, colours: &dyn Colours) -> ANSIString<'static> {
|
||||
|
|
|
@ -35,3 +35,6 @@ pub use self::users::Colours as UserColours;
|
|||
|
||||
mod octal;
|
||||
// octal uses just one colour
|
||||
|
||||
mod securityctx;
|
||||
pub use self::securityctx::Colours as SecurityCtxColours;
|
||||
|
|
|
@ -88,6 +88,7 @@ impl f::Permissions {
|
|||
}
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
impl f::Attributes {
|
||||
pub fn render<C: Colours+FiletypeColours>(&self, colours: &C) -> Vec<ANSIString<'static>> {
|
||||
let bit = |bit, chr: &'static str, style: Style| {
|
||||
|
|
45
src/output/render/securityctx.rs
Normal file
45
src/output/render/securityctx.rs
Normal file
|
@ -0,0 +1,45 @@
|
|||
use ansi_term::Style;
|
||||
|
||||
use crate::fs::fields as f;
|
||||
use crate::output::cell::{TextCell, DisplayWidth};
|
||||
|
||||
|
||||
impl f::SecurityContext<'_> {
|
||||
pub fn render<C: Colours>(&self, colours: &C) -> TextCell {
|
||||
match &self.context {
|
||||
f::SecurityContextType::None => {
|
||||
TextCell::paint_str(colours.none(), "?")
|
||||
}
|
||||
f::SecurityContextType::SELinux(context) => {
|
||||
let mut chars = Vec::with_capacity(7);
|
||||
|
||||
for (i, part) in context.split(':').enumerate() {
|
||||
let partcolour = match i {
|
||||
0 => colours.selinux_user(),
|
||||
1 => colours.selinux_role(),
|
||||
2 => colours.selinux_type(),
|
||||
_ => colours.selinux_range()
|
||||
};
|
||||
if i > 0 {
|
||||
chars.push(colours.selinux_colon().paint(":"));
|
||||
}
|
||||
chars.push(partcolour.paint(String::from(part)));
|
||||
}
|
||||
|
||||
TextCell {
|
||||
contents: chars.into(),
|
||||
width: DisplayWidth::from(context.len())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub trait Colours {
|
||||
fn none(&self) -> Style;
|
||||
fn selinux_colon(&self) -> Style;
|
||||
fn selinux_user(&self) -> Style;
|
||||
fn selinux_role(&self) -> Style;
|
||||
fn selinux_type(&self) -> Style;
|
||||
fn selinux_range(&self) -> Style;
|
||||
}
|
|
@ -43,7 +43,10 @@ pub struct Columns {
|
|||
pub blocks: bool,
|
||||
pub group: bool,
|
||||
pub git: bool,
|
||||
pub subdir_git_repos: bool,
|
||||
pub subdir_git_repos_no_stat: bool,
|
||||
pub octal: bool,
|
||||
pub security_context: bool,
|
||||
|
||||
// Defaults to true:
|
||||
pub permissions: bool,
|
||||
|
@ -93,6 +96,10 @@ impl Columns {
|
|||
columns.push(Column::Group);
|
||||
}
|
||||
|
||||
if self.security_context {
|
||||
columns.push(Column::SecurityContext);
|
||||
}
|
||||
|
||||
if self.time_types.modified {
|
||||
columns.push(Column::Timestamp(TimeType::Modified));
|
||||
}
|
||||
|
@ -113,6 +120,14 @@ impl Columns {
|
|||
columns.push(Column::GitStatus);
|
||||
}
|
||||
|
||||
if self.subdir_git_repos {
|
||||
columns.push(Column::SubdirGitRepoStatus);
|
||||
}
|
||||
|
||||
if self.subdir_git_repos_no_stat {
|
||||
columns.push(Column::SubdirGitRepoNoStatus);
|
||||
}
|
||||
|
||||
columns
|
||||
}
|
||||
}
|
||||
|
@ -135,8 +150,12 @@ pub enum Column {
|
|||
#[cfg(unix)]
|
||||
Inode,
|
||||
GitStatus,
|
||||
SubdirGitRepoStatus,
|
||||
SubdirGitRepoNoStatus,
|
||||
#[cfg(unix)]
|
||||
Octal,
|
||||
#[cfg(unix)]
|
||||
SecurityContext,
|
||||
}
|
||||
|
||||
/// Each column can pick its own **Alignment**. Usually, numbers are
|
||||
|
@ -192,8 +211,12 @@ impl Column {
|
|||
#[cfg(unix)]
|
||||
Self::Inode => "inode",
|
||||
Self::GitStatus => "Git",
|
||||
Self::SubdirGitRepoStatus => "Repo",
|
||||
Self::SubdirGitRepoNoStatus => "Repo",
|
||||
#[cfg(unix)]
|
||||
Self::Octal => "Octal",
|
||||
#[cfg(unix)]
|
||||
Self::SecurityContext => "Security Context",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -323,7 +346,7 @@ impl Environment {
|
|||
Some(t)
|
||||
}
|
||||
Err(ref e) => {
|
||||
println!("Unable to determine time zone: {}", e);
|
||||
eprintln!("Unable to determine time zone: {e}");
|
||||
None
|
||||
}
|
||||
};
|
||||
|
@ -493,9 +516,19 @@ impl<'a, 'f> Table<'a> {
|
|||
Column::Group => {
|
||||
file.group().render(self.theme, &*self.env.lock_users(), self.user_format)
|
||||
}
|
||||
#[cfg(unix)]
|
||||
Column::SecurityContext => {
|
||||
file.security_context().render(self.theme)
|
||||
}
|
||||
Column::GitStatus => {
|
||||
self.git_status(file).render(self.theme)
|
||||
}
|
||||
Column::SubdirGitRepoStatus => {
|
||||
self.subdir_git_repo(file, true).render()
|
||||
}
|
||||
Column::SubdirGitRepoNoStatus => {
|
||||
self.subdir_git_repo(file, false).render()
|
||||
}
|
||||
#[cfg(unix)]
|
||||
Column::Octal => {
|
||||
self.octal_permissions(file).render(self.theme.ui.octal)
|
||||
|
@ -524,6 +557,15 @@ impl<'a, 'f> Table<'a> {
|
|||
.unwrap_or_default()
|
||||
}
|
||||
|
||||
fn subdir_git_repo(&self, file: &File<'_>, status : bool) -> f::SubdirGitRepo {
|
||||
debug!("Getting subdir repo status for path {:?}", file.path);
|
||||
|
||||
if file.is_directory(){
|
||||
return f::SubdirGitRepo::from_path(&file.path, status);
|
||||
}
|
||||
f::SubdirGitRepo::default()
|
||||
}
|
||||
|
||||
pub fn render(&self, row: Row) -> TextCell {
|
||||
let mut cell = TextCell::default();
|
||||
|
||||
|
|
|
@ -66,6 +66,17 @@ impl UiStyles {
|
|||
conflicted: Red.normal(),
|
||||
},
|
||||
|
||||
security_context: SecurityContext {
|
||||
none: Style::default(),
|
||||
selinux: SELinuxContext {
|
||||
colon: Style::default().dimmed(),
|
||||
user: Blue.normal(),
|
||||
role: Green.normal(),
|
||||
typ: Yellow.normal(),
|
||||
range: Cyan.normal(),
|
||||
},
|
||||
},
|
||||
|
||||
punctuation: Fixed(244).normal(),
|
||||
date: Blue.normal(),
|
||||
inode: Purple.normal(),
|
||||
|
|
|
@ -308,6 +308,15 @@ impl FileNameColours for Theme {
|
|||
}
|
||||
}
|
||||
|
||||
impl render::SecurityCtxColours for Theme {
|
||||
fn none(&self) -> Style { self.ui.security_context.none }
|
||||
fn selinux_colon(&self) -> Style { self.ui.security_context.selinux.colon }
|
||||
fn selinux_user(&self) -> Style { self.ui.security_context.selinux.user }
|
||||
fn selinux_role(&self) -> Style { self.ui.security_context.selinux.role }
|
||||
fn selinux_type(&self) -> Style { self.ui.security_context.selinux.typ }
|
||||
fn selinux_range(&self) -> Style { self.ui.security_context.selinux.range }
|
||||
}
|
||||
|
||||
|
||||
/// Some of the styles are **overlays**: although they have the same attribute
|
||||
/// set as regular styles (foreground and background colours, bold, underline,
|
||||
|
|
|
@ -7,12 +7,13 @@ use crate::theme::lsc::Pair;
|
|||
pub struct UiStyles {
|
||||
pub colourful: bool,
|
||||
|
||||
pub filekinds: FileKinds,
|
||||
pub perms: Permissions,
|
||||
pub size: Size,
|
||||
pub users: Users,
|
||||
pub links: Links,
|
||||
pub git: Git,
|
||||
pub filekinds: FileKinds,
|
||||
pub perms: Permissions,
|
||||
pub size: Size,
|
||||
pub users: Users,
|
||||
pub links: Links,
|
||||
pub git: Git,
|
||||
pub security_context: SecurityContext,
|
||||
|
||||
pub punctuation: Style,
|
||||
pub date: Style,
|
||||
|
@ -104,6 +105,21 @@ pub struct Git {
|
|||
pub conflicted: Style,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Default, PartialEq)]
|
||||
pub struct SELinuxContext {
|
||||
pub colon: Style,
|
||||
pub user: Style,
|
||||
pub role: Style,
|
||||
pub typ: Style,
|
||||
pub range: Style,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Default, PartialEq)]
|
||||
pub struct SecurityContext {
|
||||
pub none: Style,
|
||||
pub selinux: SELinuxContext,
|
||||
}
|
||||
|
||||
impl UiStyles {
|
||||
pub fn plain() -> Self {
|
||||
Self::default()
|
||||
|
|
|
@ -33,24 +33,24 @@ FILTERING AND SORTING OPTIONS
|
|||
date, time, old, and new all refer to modified.
|
||||
|
||||
LONG VIEW OPTIONS
|
||||
-b, --binary list file sizes with binary prefixes
|
||||
-B, --bytes list file sizes in bytes, without any prefixes
|
||||
-g, --group list each file's group
|
||||
-h, --header add a header row to each column
|
||||
-H, --links list each file's number of hard links
|
||||
-i, --inode list each file's inode number
|
||||
-m, --modified use the modified timestamp field
|
||||
-n, --numeric list numeric user and group IDs
|
||||
-S, --blocks show number of file system blocks
|
||||
-t, --time FIELD which timestamp field to list (modified, accessed, created)
|
||||
-u, --accessed use the accessed timestamp field
|
||||
-U, --created use the created timestamp field
|
||||
--changed use the changed timestamp field
|
||||
--time-style how to format timestamps (default, iso, long-iso, full-iso)
|
||||
--no-permissions suppress the permissions field
|
||||
--octal-permissions list each file's permission in octal format
|
||||
--no-filesize suppress the filesize field
|
||||
--no-user suppress the user field
|
||||
--no-time suppress the time field
|
||||
--git list each file's Git status, if tracked or ignored
|
||||
-@, --extended list each file's extended attributes and sizes
|
||||
-b, --binary list file sizes with binary prefixes
|
||||
-B, --bytes list file sizes in bytes, without any prefixes
|
||||
-g, --group list each file's group
|
||||
-h, --header add a header row to each column
|
||||
-H, --links list each file's number of hard links
|
||||
-i, --inode list each file's inode number
|
||||
-m, --modified use the modified timestamp field
|
||||
-n, --numeric list numeric user and group IDs
|
||||
-S, --blocks show number of file system blocks
|
||||
-t, --time FIELD which timestamp field to list (modified, accessed, created)
|
||||
-u, --accessed use the accessed timestamp field
|
||||
-U, --created use the created timestamp field
|
||||
--changed use the changed timestamp field
|
||||
--time-style how to format timestamps (default, iso, long-iso, full-iso)
|
||||
--no-permissions suppress the permissions field
|
||||
-o, --octal-permissions list each file's permission in octal format
|
||||
--no-filesize suppress the filesize field
|
||||
--no-user suppress the user field
|
||||
--no-time suppress the time field
|
||||
--git list each file's Git status, if tracked or ignored
|
||||
-@, --extended list each file's extended attributes and sizes
|
||||
|
|
Loading…
Reference in a new issue