Просмотр исходного кода

Merge branch 'main' into add-git-repo-colors

MartinFillon 2 лет назад
Родитель
Сommit
2dc01d351a

+ 17 - 0
Justfile

@@ -120,12 +120,24 @@ tar BINARY TARGET:
 zip BINARY TARGET:
     zip -j ./target/"bin-$(convco version)"/{{BINARY}}_{{TARGET}}.zip ./target/{{TARGET}}/release/{{BINARY}}
 
+tar_static BINARY TARGET:
+    tar czvf ./target/"bin-$(convco version)"/{{BINARY}}_{{TARGET}}_static.tar.gz -C ./target/{{TARGET}}/release/ ./{{BINARY}}
+
+zip_static BINARY TARGET:
+    zip -j ./target/"bin-$(convco version)"/{{BINARY}}_{{TARGET}}_static.zip ./target/{{TARGET}}/release/{{BINARY}}
+
 binary BINARY TARGET:
     rustup target add {{TARGET}}
     cross build --release --target {{TARGET}}
     just tar {{BINARY}} {{TARGET}}
     just zip {{BINARY}} {{TARGET}}
 
+binary_static BINARY TARGET:
+    rustup target add {{TARGET}}
+    RUSTFLAGS='-C target-feature=+crt-static' cross build --release --target {{TARGET}}
+    just tar_static {{BINARY}} {{TARGET}}
+    just zip_static {{BINARY}} {{TARGET}}
+
 checksum:
     echo "# Checksums"
     echo "## sha256sum"
@@ -152,13 +164,17 @@ alias c := cross
     ## Linux
     ### x86
     just binary eza x86_64-unknown-linux-gnu
+    just binary_static eza x86_64-unknown-linux-gnu
     just binary eza x86_64-unknown-linux-musl
+    just binary_static eza x86_64-unknown-linux-musl
 
     ### aarch
     just binary eza aarch64-unknown-linux-gnu
+    # BUG: just binary_static eza aarch64-unknown-linux-gnu
 
     ### arm
     just binary eza arm-unknown-linux-gnueabihf
+    just binary_static eza arm-unknown-linux-gnueabihf
 
     ## MacOS
     # TODO: just binary eza x86_64-apple-darwin
@@ -166,6 +182,7 @@ alias c := cross
     ## Windows
     ### x86
     just binary eza.exe x86_64-pc-windows-gnu
+    just binary_static eza.exe x86_64-pc-windows-gnu
     # TODO: just binary eza.exe x86_64-pc-windows-gnullvm
     # TODO: just binary eza.exe x86_64-pc-windows-msvc
 

+ 6 - 6
README.md

@@ -140,6 +140,7 @@ nix-env -i eza
 ```
 
 **Declarative Nix Installations**
+
 - Simple NixOS installation: [rfaulhaber/dotfiles](https://github.com/rfaulhaber/dotfiles/blob/a8d084d178efd0592b7ac02d34a450fb58913aca/nix/modules/programs/eza/default.nix#L15)
 - Using the flake via NixOS: [hallettj/home.nix](https://github.com/hallettj/home.nix/blob/a8388483e5d78e110be73c5af0e7f0e3ca8f8aa3/flake.nix#L19)
 - Using home-manager on NixOS: [Misterio77/nix-config](https://github.com/Misterio77/nix-config/blob/6867d66a2fe7899c608b9c8e5a8f9aee279d188b/home/misterio/features/cli/fish.nix#L6)
@@ -217,7 +218,6 @@ sudo port install eza
 
 [![Windows package](https://repology.org/badge/version-for-repo/winget/eza.svg)](https://repology.org/project/eza/versions)
 
-
 Eza is available on Winget.
 
 To install eza, run:
@@ -246,13 +246,13 @@ scoop install eza
 > Change `~/.zshrc` to your preferred zsh config file.
 
 ##### Clone the repository:
-   
+
 ```sh
 git clone https://github.com/eza-community/eza.git
 ```
 
 ##### Add the completion path to your zsh configuration:
-   
+
 Replace `<path_to_eza>` with the actual path where you cloned the `eza` repository.
 
 ```sh
@@ -260,7 +260,7 @@ echo 'export FPATH="<path_to_eza>/completions/zsh:$FPATH"' >> ~/.zshrc
 ```
 
 ##### Reload your zsh configuration:
-   
+
 ```sh
 source ~/.zshrc
 ```
@@ -331,9 +331,9 @@ These options are available when running with `--long` (`-l`):
 - **--changed**: use the changed timestamp field
 - **--git**: list each file’s Git status, if tracked or ignored
 - **--git-repos**: list each directory’s Git status, if tracked
-- **--git-repos-no-status**:  list whether a directory is a Git repository, but not its status (faster)
+- **--git-repos-no-status**: list whether a directory is a Git repository, but not its status (faster)
 - **--no-git**: suppress Git status (always overrides `--git`, `--git-repos`, `--git-repos-no-status`)
-- **--time-style**: how to format timestamps
+- **--time-style**: how to format timestamps. valid timestamp styles are ‘`default`’, ‘`iso`’, ‘`long-iso`’, ‘`full-iso`’, ‘`relative`', or you can use a `custom` style with '`+`' as prefix. (Ex: "`+%Y/%m/%d, %H:%M`" => "`2023/9/30, 12:00`"). [more about format syntax](https://docs.rs/chrono/latest/chrono/format/strftime/index.html).
 - **--no-permissions**: suppress the permissions field
 - **-o**, **--octal-permissions**: list each file's permission in octal format
 - **--no-filesize**: suppress the filesize field

+ 1 - 1
man/eza.1.md

@@ -172,7 +172,7 @@ These options are available when running with `--long` (`-l`):
 `--time-style=STYLE`
 : How to format timestamps.
 
-: Valid timestamp styles are ‘`default`’, ‘`iso`’, ‘`long-iso`’, ‘`full-iso`’, and ‘`relative`’.
+: Valid timestamp styles are ‘`default`’, ‘`iso`’, ‘`long-iso`’, ‘`full-iso`’, ‘`relative`', or you can use a `custom` style with '`+`' as prefix. (Ex: "`+%Y/%m/%d, %H:%M`" => "`2023/9/30, 12:00`"). for more details about format syntax, please read: https://docs.rs/chrono/latest/chrono/format/strftime/index.html
 
 `-u`, `--accessed`
 : Use the accessed timestamp field.

+ 6 - 0
man/eza_colors.5.md

@@ -168,12 +168,18 @@ LIST OF CODES
 `uu`
 : a user that’s you
 
+`uR`
+: a user that's root
+
 `un`
 : a user that’s someone else
 
 `gu`
 : a group that you belong to
 
+`gR`
+: a group realted to root
+
 `gn`
 : a group you aren’t a member of
 

+ 1 - 0
src/info/filetype.rs

@@ -241,6 +241,7 @@ const EXTENSION_TYPES: Map<&'static str, FileType> = phf_map! {
     "bk"         => FileType::Temp,
     "bkp"        => FileType::Temp,
     "download"   => FileType::Temp,
+    "fdmdownload"=> FileType::Temp,
     "swn"        => FileType::Temp,
     "swo"        => FileType::Temp,
     "swp"        => FileType::Temp,

+ 1 - 1
src/options/help.rs

@@ -61,7 +61,7 @@ LONG VIEW OPTIONS
   -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, relative)
+  --time-style             how to format timestamps (default, iso, long-iso, full-iso, relative, or a custom style with '+' as prefix. Ex: '+%Y/%m/%d')
   --no-permissions         suppress the permissions field
   -o, --octal-permissions  list each file's permission in octal format
   --no-filesize            suppress the filesize field

+ 10 - 1
src/options/mod.rs

@@ -209,7 +209,16 @@ impl Options {
     }
 }
 
-/// The result of the `Options::getopts` function.
+/// The result of the `Options::parse` function.
+///
+/// NOTE: We disallow the `large_enum_variant` lint here, because we're not
+/// overly concerned about variant fragmentation. We can do this because we are
+/// reasonably sure that the error variant will be rare, and only on faulty
+/// program execution and thus boxing the large variant will be a waste of
+/// resources, but should we come to use it more, we should reconsider.
+///
+/// See <https://github.com/eza-community/eza/pull/437#issuecomment-1738470254>
+#[allow(clippy::large_enum_variant)]
 #[derive(Debug)]
 pub enum OptionsResult<'args> {
     /// The options were parsed successfully.

+ 10 - 5
src/options/view.rs

@@ -319,6 +319,9 @@ impl TimeFormat {
             "iso" => Ok(Self::ISOFormat),
             "long-iso" => Ok(Self::LongISO),
             "full-iso" => Ok(Self::FullISO),
+            fmt if fmt.starts_with('+') => Ok(Self::Custom {
+                fmt: fmt[1..].to_owned(),
+            }),
             _ => Err(OptionsError::BadArgument(&flags::TIME_STYLE, word)),
         }
     }
@@ -533,11 +536,13 @@ mod test {
         test!(empty:     TimeFormat <- [], None;                            Both => like Ok(TimeFormat::DefaultFormat));
 
         // Individual settings
-        test!(default:   TimeFormat <- ["--time-style=default"], None;      Both => like Ok(TimeFormat::DefaultFormat));
-        test!(iso:       TimeFormat <- ["--time-style", "iso"], None;       Both => like Ok(TimeFormat::ISOFormat));
-        test!(relative:  TimeFormat <- ["--time-style", "relative"], None;  Both => like Ok(TimeFormat::Relative));
-        test!(long_iso:  TimeFormat <- ["--time-style=long-iso"], None;     Both => like Ok(TimeFormat::LongISO));
-        test!(full_iso:  TimeFormat <- ["--time-style", "full-iso"], None;  Both => like Ok(TimeFormat::FullISO));
+        test!(default:          TimeFormat <- ["--time-style=default"], None;      Both => like Ok(TimeFormat::DefaultFormat));
+        test!(iso:              TimeFormat <- ["--time-style", "iso"], None;       Both => like Ok(TimeFormat::ISOFormat));
+        test!(relative:         TimeFormat <- ["--time-style", "relative"], None;  Both => like Ok(TimeFormat::Relative));
+        test!(long_iso:         TimeFormat <- ["--time-style=long-iso"], None;     Both => like Ok(TimeFormat::LongISO));
+        test!(full_iso:         TimeFormat <- ["--time-style", "full-iso"], None;  Both => like Ok(TimeFormat::FullISO));
+        test!(custom_style:     TimeFormat <- ["--time-style", "+%Y/%m/%d"], None; Both => like Ok(TimeFormat::Custom { .. }));
+        test!(bad_custom_style: TimeFormat <- ["--time-style", "%Y/%m/%d"], None;  Both => err OptionsError::BadArgument(&flags::TIME_STYLE, OsString::from("%Y/%m/%d")));
 
         // Overriding
         test!(actually:  TimeFormat <- ["--time-style=default", "--time-style", "iso"], None;  Last => like Ok(TimeFormat::ISOFormat));

+ 27 - 16
src/output/icons.rs

@@ -21,6 +21,7 @@ impl Icons {
     const DISK_IMAGE: char      = '\u{e271}';  // 
     const DOCKER: char          = '\u{e650}';  // 
     const DOCUMENT: char        = '\u{f1c2}';  // 
+    const DOWNLOAD: char        = '\u{f01da}'; // 󰇚
     const EMACS: char           = '\u{e632}';  // 
     const FILE: char            = '\u{f15b}';  // 
     const FILE_OUTLINE: char    = '\u{f016}';  // 
@@ -59,14 +60,15 @@ impl Icons {
     const LANG_PERL: char       = '\u{e67e}';  // 
     const LANG_PHP: char        = '\u{e73d}';  // 
     const LANG_PYTHON: char     = '\u{e606}';  // 
-    const LANG_R: char          = '\u{f25d}';  // 
+    const LANG_R: char          = '\u{e68a}';  // 
     const LANG_RUBY: char       = '\u{e21e}';  // 
     const LANG_RUBYRAILS: char  = '\u{e73b}';  // 
-    const LANG_RUST: char       = '\u{e7a8}';  // 
+    const LANG_RUST: char       = '\u{e68b}';  // 
     const LANG_SASS: char       = '\u{e603}';  // 
     const LANG_STYLUS: char     = '\u{e600}';  // 
     const LANG_TEX: char        = '\u{e69b}';  // 
     const LANG_TYPESCRIPT: char = '\u{e628}';  // 
+    const LIBRARY: char         = '\u{eb9c}';  // 
     const LICENSE: char         = '\u{f02d}';  // 
     const LOCK: char            = '\u{f023}';  // 
     const MAKE: char            = '\u{e673}';  // 
@@ -85,6 +87,7 @@ impl Icons {
     const PUBLIC_KEY: char      = '\u{f0dd6}'; // 󰷖
     const RAZOR: char           = '\u{f1fa}';  // 
     const REACT: char           = '\u{e7ba}';  // 
+    const README: char          = '\u{f00ba}'; // 󰂺
     const SHEET: char           = '\u{f1c3}';  // 
     const SHELL_CMD: char       = '\u{f489}';  // 
     const SHELL: char           = '\u{f1183}'; // 󱆃
@@ -116,6 +119,7 @@ const DIRECTORY_ICONS: Map<&'static str, char> = phf_map! {
     ".npm"                => Icons::FOLDER_NPM,     // 
     ".ssh"                => Icons::FOLDER_KEY,     // 󰢬
     ".Trash"              => '\u{f1f8}',            // 
+    "Contacts"            => '\u{f024c}',           // 󰉌
     "cron.d"              => Icons::FOLDER_CONFIG,  // 
     "cron.daily"          => Icons::FOLDER_CONFIG,  // 
     "cron.hourly"         => Icons::FOLDER_CONFIG,  // 
@@ -125,7 +129,9 @@ const DIRECTORY_ICONS: Map<&'static str, char> = phf_map! {
     "Downloads"           => '\u{f024d}',           // 󰉍
     "config"              => Icons::FOLDER_CONFIG,  // 
     "etc"                 => Icons::FOLDER_CONFIG,  // 
+    "Favorites"           => '\u{f069d}',           // 󰚝
     "hidden"              => Icons::FOLDER_HIDDEN,  // 󱞞
+    "home"                => '\u{f10b5}',           // 󱂵
     "include"             => Icons::FOLDER_CONFIG,  // 
     "Mail"                => '\u{f01f0}',           // 󰇰
     "Movies"              => '\u{f0fce}',           // 󰿎
@@ -173,7 +179,7 @@ const FILENAME_ICONS: Map<&'static str, char> = phf_map! {
     ".npmrc"              => Icons::NPM,            // 
     ".profile"            => Icons::SHELL,          // 󱆃
     ".python_history"     => Icons::LANG_PYTHON,    // 
-    ".rustfmt.toml"       => Icons::LANG_RUST,      // 
+    ".rustfmt.toml"       => Icons::LANG_RUST,      // 
     ".rvm"                => Icons::LANG_RUBY,      // 
     ".rvmrc"              => Icons::LANG_RUBY,      // 
     ".tcshrc"             => Icons::SHELL,          // 󱆃
@@ -196,8 +202,8 @@ const FILENAME_ICONS: Map<&'static str, char> = phf_map! {
     "bashrc"              => Icons::SHELL,          // 󱆃
     "bspwmrc"             => Icons::CONFIG,         // 
     "build.gradle.kts"    => Icons::GRADLE,         // 
-    "Cargo.lock"          => Icons::LANG_RUST,      // 
-    "Cargo.toml"          => Icons::LANG_RUST,      // 
+    "Cargo.lock"          => Icons::LANG_RUST,      // 
+    "Cargo.toml"          => Icons::LANG_RUST,      // 
     "CMakeLists.txt"      => Icons::MAKE,           // 
     "composer.json"       => Icons::LANG_PHP,       // 
     "composer.lock"       => Icons::LANG_PHP,       // 
@@ -272,7 +278,8 @@ const FILENAME_ICONS: Map<&'static str, char> = phf_map! {
     "profile"             => Icons::SHELL,          // 󱆃
     "pyproject.toml"      => Icons::LANG_PYTHON,    // 
     "Rakefile"            => Icons::LANG_RUBY,      // 
-    "release.toml"        => Icons::LANG_RUST,      // 
+    "README"              => Icons::README,         // 󰂺
+    "release.toml"        => Icons::LANG_RUST,      // 
     "requirements.txt"    => Icons::LANG_PYTHON,    // 
     "robots.txt"          => '\u{f06a9}',           // 󰚩
     "rubydoc"             => Icons::LANG_RUBYRAILS, // 
@@ -318,6 +325,7 @@ const EXTENSION_ICONS: Map<&'static str, char> = phf_map! {
     "bash"           => Icons::SHELL_CMD,        // 
     "bat"            => Icons::OS_WINDOWS_CMD,   // 
     "bats"           => Icons::SHELL_CMD,        // 
+    "bdf"            => Icons::FONT,             // 
     "bib"            => Icons::LANG_TEX,         // 
     "bin"            => Icons::BINARY,           // 
     "bmp"            => Icons::IMAGE,            // 
@@ -370,12 +378,12 @@ const EXTENSION_ICONS: Map<&'static str, char> = phf_map! {
     "diff"           => Icons::DIFF,             // 
     "djv"            => Icons::DOCUMENT,         // 
     "djvu"           => Icons::DOCUMENT,         // 
-    "dll"            => Icons::OS_WINDOWS,       // 
+    "dll"            => Icons::LIBRARY,          // 
     "dmg"            => Icons::DISK_IMAGE,       // 
     "doc"            => Icons::DOCUMENT,         // 
     "docx"           => Icons::DOCUMENT,         // 
     "dot"            => '\u{f1049}',             // 󱁉
-    "download"       => '\u{f01da}',             // 󰇚
+    "download"       => Icons::DOWNLOAD,         // 󰇚
     "drawio"         => '\u{ebba}',              // 
     "dump"           => Icons::DATABASE,         // 
     "dvi"            => Icons::IMAGE,            // 
@@ -397,6 +405,7 @@ const EXTENSION_ICONS: Map<&'static str, char> = phf_map! {
     "ex"             => Icons::LANG_ELIXIR,      // 
     "exe"            => Icons::OS_WINDOWS_CMD,   // 
     "exs"            => Icons::LANG_ELIXIR,      // 
+    "fdmdownload"    => Icons::DOWNLOAD,         // 󰇚
     "fish"           => Icons::SHELL_CMD,        // 
     "flac"           => Icons::AUDIO,            // 
     "flv"            => Icons::VIDEO,            // 
@@ -475,6 +484,7 @@ const EXTENSION_ICONS: Map<&'static str, char> = phf_map! {
     "lhs"            => Icons::LANG_HASKELL,     // 
     "license"        => Icons::LICENSE,          // 
     "lisp"           => '\u{f0172}',             // 󰅲
+    "lib"            => Icons::LIBRARY,          // 
     "localized"      => Icons::OS_APPLE,         // 
     "lock"           => Icons::LOCK,             // 
     "log"            => '\u{f18d}',              // 
@@ -561,6 +571,7 @@ const EXTENSION_ICONS: Map<&'static str, char> = phf_map! {
     "psd"            => '\u{e7b8}',              // 
     "psd1"           => Icons::POWERSHELL,       // 
     "psm1"           => Icons::POWERSHELL,       // 
+    "psf"            => Icons::FONT,             // 
     "pub"            => Icons::PUBLIC_KEY,       // 󰷖
     "pxm"            => Icons::IMAGE,            // 
     "py"             => Icons::LANG_PYTHON,      // 
@@ -570,21 +581,21 @@ const EXTENSION_ICONS: Map<&'static str, char> = phf_map! {
     "pyo"            => Icons::LANG_PYTHON,      // 
     "qcow"           => Icons::DISK_IMAGE,       // 
     "qcow2"          => Icons::DISK_IMAGE,       // 
-    "r"              => Icons::LANG_R,           // 
+    "r"              => Icons::LANG_R,           // 
     "rar"            => Icons::COMPRESSED,       // 
     "raw"            => Icons::IMAGE,            // 
     "razor"          => Icons::RAZOR,            // 
     "rb"             => Icons::LANG_RUBY,        // 
-    "rdata"          => Icons::LANG_R,           // 
+    "rdata"          => Icons::LANG_R,           // 
     "rdb"            => '\u{e76d}',              // 
     "rdoc"           => Icons::MARKDOWN,         // 
-    "rds"            => Icons::LANG_R,           // 
-    "readme"         => Icons::MARKDOWN,         // 
-    "rlib"           => Icons::LANG_RUST,        // 
-    "rmd"            => Icons::MARKDOWN,         // 
-    "rmeta"          => Icons::LANG_RUST,        // 
+    "rds"            => Icons::LANG_R,           // 
+    "readme"         => Icons::README,           // 󰂺
+    "rlib"           => Icons::LANG_RUST,        // 
+    "rmd"            => Icons::MARKDOWN,         // 
+    "rmeta"          => Icons::LANG_RUST,        // 
     "rpm"            => '\u{e7bb}',              // 
-    "rs"             => Icons::LANG_RUST,        // 
+    "rs"             => Icons::LANG_RUST,        // 
     "rspec"          => Icons::LANG_RUBY,        // 
     "rspec_parallel" => Icons::LANG_RUBY,        // 
     "rspec_status"   => Icons::LANG_RUBY,        // 

+ 6 - 0
src/output/render/groups.rs

@@ -42,6 +42,10 @@ impl Render for Option<f::Group> {
             }
         }
 
+        if group.gid() == 0 {
+            style = colours.root_group();
+        }
+
         let group_name = match format {
             UserFormat::Name => group.name().to_string_lossy().into(),
             UserFormat::Numeric => group.gid().to_string(),
@@ -55,6 +59,7 @@ pub trait Colours {
     fn yours(&self) -> Style;
     fn not_yours(&self) -> Style;
     fn no_group(&self) -> Style;
+    fn root_group(&self) -> Style;
 }
 
 #[cfg(test)]
@@ -78,6 +83,7 @@ pub mod test {
         fn yours(&self)     -> Style { Fixed(80).normal() }
         fn not_yours(&self) -> Style { Fixed(81).normal() }
         fn no_group(&self)   -> Style { Black.italic() }
+        fn root_group(&self) -> Style { Fixed(82).normal() }
     }
 
     #[test]

+ 7 - 3
src/output/render/users.rs

@@ -25,8 +25,10 @@ impl Render for Option<f::User> {
 
         let style = if users.get_current_uid() == uid {
             colours.you()
+        } else if uid == 0 {
+            colours.root()
         } else {
-            colours.someone_else()
+            colours.other()
         };
         TextCell::paint(style, user_name)
     }
@@ -34,7 +36,8 @@ impl Render for Option<f::User> {
 
 pub trait Colours {
     fn you(&self) -> Style;
-    fn someone_else(&self) -> Style;
+    fn other(&self) -> Style;
+    fn root(&self) -> Style;
     fn no_user(&self) -> Style;
 }
 
@@ -56,7 +59,8 @@ pub mod test {
     #[rustfmt::skip]
     impl Colours for TestColours {
         fn you(&self)          -> Style { Red.bold() }
-        fn someone_else(&self) -> Style { Blue.underline() }
+        fn other(&self) -> Style { Blue.underline() }
+        fn root(&self)         -> Style { Blue.underline() }
         fn no_user(&self)      -> Style { Black.italic() }
     }
 

+ 5 - 5
src/output/table.rs

@@ -375,7 +375,7 @@ impl<'a> Table<'a> {
             columns,
             git,
             env,
-            time_format: options.time_format,
+            time_format: options.time_format.clone(),
             size_format: options.size_format,
             #[cfg(unix)]
             user_format: options.user_format,
@@ -471,22 +471,22 @@ impl<'a> Table<'a> {
             Column::Timestamp(TimeType::Modified) => file.modified_time().render(
                 self.theme.ui.date,
                 self.env.time_offset,
-                self.time_format,
+                self.time_format.clone(),
             ),
             Column::Timestamp(TimeType::Changed) => file.changed_time().render(
                 self.theme.ui.date,
                 self.env.time_offset,
-                self.time_format,
+                self.time_format.clone(),
             ),
             Column::Timestamp(TimeType::Created) => file.created_time().render(
                 self.theme.ui.date,
                 self.env.time_offset,
-                self.time_format,
+                self.time_format.clone(),
             ),
             Column::Timestamp(TimeType::Accessed) => file.accessed_time().render(
                 self.theme.ui.date,
                 self.env.time_offset,
-                self.time_format,
+                self.time_format.clone(),
             ),
         }
     }

+ 10 - 2
src/output/time.rs

@@ -20,9 +20,9 @@ use unicode_width::UnicodeWidthStr;
 /// own enum variants. It’s not worth looking the locale up if the formatter
 /// prints month names as numbers.
 ///
-/// Currently exa does not support *custom* styles, where the user enters a
+/// Also, eza supports *custom* styles, where the user enters a
 /// format string in an environment variable or something. Just these four.
-#[derive(PartialEq, Eq, Debug, Copy, Clone)]
+#[derive(PartialEq, Eq, Debug, Clone)]
 pub enum TimeFormat {
     /// The **default format** uses the user’s locale to print month names,
     /// and specifies the timestamp down to the minute for recent times, and
@@ -45,6 +45,9 @@ pub enum TimeFormat {
 
     /// Use a relative but fixed width representation.
     Relative,
+
+    /// Use a custom format
+    Custom { fmt: String },
 }
 
 impl TimeFormat {
@@ -56,6 +59,7 @@ impl TimeFormat {
             Self::LongISO        => long(time),
             Self::FullISO        => full(time),
             Self::Relative       => relative(time),
+            Self::Custom { fmt } => custom(time, &fmt),
         };
     }
 }
@@ -111,6 +115,10 @@ fn full(time: &DateTime<FixedOffset>) -> String {
     time.format("%Y-%m-%d %H:%M:%S.%f %z").to_string()
 }
 
+fn custom(time: &DateTime<FixedOffset>, fmt: &str) -> String {
+    time.format(fmt).to_string()
+}
+
 lazy_static! {
 
     static ref CURRENT_YEAR: i32 = Local::now().year();

+ 6 - 4
src/theme/default_theme.rs

@@ -48,10 +48,12 @@ impl UiStyles {
 
             #[rustfmt::skip]
             users: Users {
-                user_you:           Yellow.bold(),
-                user_someone_else:  Style::default(),
-                group_yours:        Yellow.bold(),
-                group_not_yours:    Style::default(),
+                user_you:                       Yellow.bold(),
+                user_other:                     Style::default(),
+                user_root:                      Style::default(),
+                group_yours:                    Yellow.bold(),
+                group_other:                    Style::default(),
+                group_root:                     Style::default(),
             },
 
             #[rustfmt::skip]

+ 64 - 62
src/theme/mod.rs

@@ -292,7 +292,8 @@ impl render::GitRepoColours for Theme {
 #[cfg(unix)]
 impl render::GroupColours for Theme {
     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_other }
+    fn root_group(&self) -> Style { self.ui.users.group_root }
     fn no_group(&self)   -> Style { self.ui.punctuation }
 }
 
@@ -361,7 +362,8 @@ impl render::SizeColours for Theme {
 #[cfg(unix)]
 impl render::UserColours for Theme {
     fn you(&self)           -> Style { self.ui.users.user_you }
-    fn someone_else(&self)  -> Style { self.ui.users.user_someone_else }
+    fn other(&self)         -> Style { self.ui.users.user_other }
+    fn root(&self)          -> Style { self.ui.users.user_root }
     fn no_user(&self)       -> Style { self.ui.punctuation }
 }
 
@@ -546,66 +548,66 @@ mod customs_test {
         c.size.unit_huge = Fixed(114).normal();
     });
 
-    test!(exa_nb:  ls "", exa "nb=38;5;115"  =>  colours c -> { c.size.number_byte          = Fixed(115).normal(); });
-    test!(exa_nk:  ls "", exa "nk=38;5;116"  =>  colours c -> { c.size.number_kilo          = Fixed(116).normal(); });
-    test!(exa_nm:  ls "", exa "nm=38;5;117"  =>  colours c -> { c.size.number_mega          = Fixed(117).normal(); });
-    test!(exa_ng:  ls "", exa "ng=38;5;118"  =>  colours c -> { c.size.number_giga          = Fixed(118).normal(); });
-    test!(exa_nt:  ls "", exa "nt=38;5;119"  =>  colours c -> { c.size.number_huge          = Fixed(119).normal(); });
-
-    test!(exa_ub:  ls "", exa "ub=38;5;115"  =>  colours c -> { c.size.unit_byte            = Fixed(115).normal(); });
-    test!(exa_uk:  ls "", exa "uk=38;5;116"  =>  colours c -> { c.size.unit_kilo            = Fixed(116).normal(); });
-    test!(exa_um:  ls "", exa "um=38;5;117"  =>  colours c -> { c.size.unit_mega            = Fixed(117).normal(); });
-    test!(exa_ug:  ls "", exa "ug=38;5;118"  =>  colours c -> { c.size.unit_giga            = Fixed(118).normal(); });
-    test!(exa_ut:  ls "", exa "ut=38;5;119"  =>  colours c -> { c.size.unit_huge            = Fixed(119).normal(); });
-
-    test!(exa_df:  ls "", exa "df=38;5;115"  =>  colours c -> { c.size.major                = Fixed(115).normal(); });
-    test!(exa_ds:  ls "", exa "ds=38;5;116"  =>  colours c -> { c.size.minor                = Fixed(116).normal(); });
-
-    test!(exa_uu:  ls "", exa "uu=38;5;117"  =>  colours c -> { c.users.user_you            = Fixed(117).normal(); });
-    test!(exa_un:  ls "", exa "un=38;5;118"  =>  colours c -> { c.users.user_someone_else   = Fixed(118).normal(); });
-    test!(exa_gu:  ls "", exa "gu=38;5;119"  =>  colours c -> { c.users.group_yours         = Fixed(119).normal(); });
-    test!(exa_gn:  ls "", exa "gn=38;5;120"  =>  colours c -> { c.users.group_not_yours     = Fixed(120).normal(); });
-
-    test!(exa_lc:  ls "", exa "lc=38;5;121"  =>  colours c -> { c.links.normal              = Fixed(121).normal(); });
-    test!(exa_lm:  ls "", exa "lm=38;5;122"  =>  colours c -> { c.links.multi_link_file     = Fixed(122).normal(); });
-
-    test!(exa_ga:  ls "", exa "ga=38;5;123"  =>  colours c -> { c.git.new                   = Fixed(123).normal(); });
-    test!(exa_gm:  ls "", exa "gm=38;5;124"  =>  colours c -> { c.git.modified              = Fixed(124).normal(); });
-    test!(exa_gd:  ls "", exa "gd=38;5;125"  =>  colours c -> { c.git.deleted               = Fixed(125).normal(); });
-    test!(exa_gv:  ls "", exa "gv=38;5;126"  =>  colours c -> { c.git.renamed               = Fixed(126).normal(); });
-    test!(exa_gt:  ls "", exa "gt=38;5;127"  =>  colours c -> { c.git.typechange            = Fixed(127).normal(); });
-    test!(exa_gi:  ls "", exa "gi=38;5;128"  =>  colours c -> { c.git.ignored               = Fixed(128).normal(); });
-    test!(exa_gc:  ls "", exa "gc=38;5;129"  =>  colours c -> { c.git.conflicted            = Fixed(129).normal(); });
-
-    test!(exa_xx:  ls "", exa "xx=38;5;128"  =>  colours c -> { c.punctuation               = Fixed(128).normal(); });
-    test!(exa_da:  ls "", exa "da=38;5;129"  =>  colours c -> { c.date                      = Fixed(129).normal(); });
-    test!(exa_in:  ls "", exa "in=38;5;130"  =>  colours c -> { c.inode                     = Fixed(130).normal(); });
-    test!(exa_bl:  ls "", exa "bl=38;5;131"  =>  colours c -> { c.blocks                    = Fixed(131).normal(); });
-    test!(exa_hd:  ls "", exa "hd=38;5;132"  =>  colours c -> { c.header                    = Fixed(132).normal(); });
-    test!(exa_lp:  ls "", exa "lp=38;5;133"  =>  colours c -> { c.symlink_path              = Fixed(133).normal(); });
-    test!(exa_cc:  ls "", exa "cc=38;5;134"  =>  colours c -> { c.control_char              = Fixed(134).normal(); });
-    test!(exa_oc:  ls "", exa "oc=38;5;135"  =>  colours c -> { c.octal                     = Fixed(135).normal(); });
-    test!(exa_bo:  ls "", exa "bO=4"         =>  colours c -> { c.broken_path_overlay       = Style::default().underline(); });
-
-    test!(exa_mp:  ls "", exa "mp=1;34;4"    =>  colours c -> { c.filekinds.mount_point     = Blue.bold().underline(); });
-    test!(exa_sp:  ls "", exa "sp=1;35;4"    =>  colours c -> { c.filekinds.special         = Purple.bold().underline(); });
-
-    test!(exa_im:  ls "", exa "im=38;5;128"  =>  colours c -> { c.file_type.image           = Fixed(128).normal(); });
-    test!(exa_vi:  ls "", exa "vi=38;5;129"  =>  colours c -> { c.file_type.video           = Fixed(129).normal(); });
-    test!(exa_mu:  ls "", exa "mu=38;5;130"  =>  colours c -> { c.file_type.music           = Fixed(130).normal(); });
-    test!(exa_lo:  ls "", exa "lo=38;5;131"  =>  colours c -> { c.file_type.lossless        = Fixed(131).normal(); });
-    test!(exa_cr:  ls "", exa "cr=38;5;132"  =>  colours c -> { c.file_type.crypto          = Fixed(132).normal(); });
-    test!(exa_do:  ls "", exa "do=38;5;133"  =>  colours c -> { c.file_type.document        = Fixed(133).normal(); });
-    test!(exa_co:  ls "", exa "co=38;5;134"  =>  colours c -> { c.file_type.compressed      = Fixed(134).normal(); });
-    test!(exa_tm:  ls "", exa "tm=38;5;135"  =>  colours c -> { c.file_type.temp            = Fixed(135).normal(); });
-    test!(exa_cm:  ls "", exa "cm=38;5;136"  =>  colours c -> { c.file_type.compiled        = Fixed(136).normal(); });
-    test!(exa_ie:  ls "", exa "bu=38;5;137"  =>  colours c -> { c.file_type.build           = Fixed(137).normal(); });
-
-    test!(exa_Sn:  ls "", exa "Sn=38;5;128"  =>  colours c -> { c.security_context.none          = Fixed(128).normal(); });
-    test!(exa_Su:  ls "", exa "Su=38;5;129"  =>  colours c -> { c.security_context.selinux.user  = Fixed(129).normal(); });
-    test!(exa_Sr:  ls "", exa "Sr=38;5;130"  =>  colours c -> { c.security_context.selinux.role  = Fixed(130).normal(); });
-    test!(exa_St:  ls "", exa "St=38;5;131"  =>  colours c -> { c.security_context.selinux.typ   = Fixed(131).normal(); });
-    test!(exa_Sl:  ls "", exa "Sl=38;5;132"  =>  colours c -> { c.security_context.selinux.range = Fixed(132).normal(); });
+    test!(exa_nb:  ls "", exa "nb=38;5;115"  =>  colours c -> { c.size.number_byte                      = Fixed(115).normal(); });
+    test!(exa_nk:  ls "", exa "nk=38;5;116"  =>  colours c -> { c.size.number_kilo                      = Fixed(116).normal(); });
+    test!(exa_nm:  ls "", exa "nm=38;5;117"  =>  colours c -> { c.size.number_mega                      = Fixed(117).normal(); });
+    test!(exa_ng:  ls "", exa "ng=38;5;118"  =>  colours c -> { c.size.number_giga                      = Fixed(118).normal(); });
+    test!(exa_nt:  ls "", exa "nt=38;5;119"  =>  colours c -> { c.size.number_huge                      = Fixed(119).normal(); });
+
+    test!(exa_ub:  ls "", exa "ub=38;5;115"  =>  colours c -> { c.size.unit_byte                        = Fixed(115).normal(); });
+    test!(exa_uk:  ls "", exa "uk=38;5;116"  =>  colours c -> { c.size.unit_kilo                        = Fixed(116).normal(); });
+    test!(exa_um:  ls "", exa "um=38;5;117"  =>  colours c -> { c.size.unit_mega                        = Fixed(117).normal(); });
+    test!(exa_ug:  ls "", exa "ug=38;5;118"  =>  colours c -> { c.size.unit_giga                        = Fixed(118).normal(); });
+    test!(exa_ut:  ls "", exa "ut=38;5;119"  =>  colours c -> { c.size.unit_huge                        = Fixed(119).normal(); });
+
+    test!(exa_df:  ls "", exa "df=38;5;115"  =>  colours c -> { c.size.major                            = Fixed(115).normal(); });
+    test!(exa_ds:  ls "", exa "ds=38;5;116"  =>  colours c -> { c.size.minor                            = Fixed(116).normal(); });
+
+    test!(exa_uu:  ls "", exa "uu=38;5;117"  =>  colours c -> { c.users.user_you                        = Fixed(117).normal(); });
+    test!(exa_un:  ls "", exa "un=38;5;118"  =>  colours c -> { c.users.user_other                      = Fixed(118).normal(); });
+    test!(exa_gu:  ls "", exa "gu=38;5;119"  =>  colours c -> { c.users.group_yours                     = Fixed(119).normal(); });
+    test!(exa_gn:  ls "", exa "gn=38;5;120"  =>  colours c -> { c.users.group_other                     = Fixed(120).normal(); });
+
+    test!(exa_lc:  ls "", exa "lc=38;5;121"  =>  colours c -> { c.links.normal                          = Fixed(121).normal(); });
+    test!(exa_lm:  ls "", exa "lm=38;5;122"  =>  colours c -> { c.links.multi_link_file                 = Fixed(122).normal(); });
+
+    test!(exa_ga:  ls "", exa "ga=38;5;123"  =>  colours c -> { c.git.new                               = Fixed(123).normal(); });
+    test!(exa_gm:  ls "", exa "gm=38;5;124"  =>  colours c -> { c.git.modified                          = Fixed(124).normal(); });
+    test!(exa_gd:  ls "", exa "gd=38;5;125"  =>  colours c -> { c.git.deleted                           = Fixed(125).normal(); });
+    test!(exa_gv:  ls "", exa "gv=38;5;126"  =>  colours c -> { c.git.renamed                           = Fixed(126).normal(); });
+    test!(exa_gt:  ls "", exa "gt=38;5;127"  =>  colours c -> { c.git.typechange                        = Fixed(127).normal(); });
+    test!(exa_gi:  ls "", exa "gi=38;5;128"  =>  colours c -> { c.git.ignored                           = Fixed(128).normal(); });
+    test!(exa_gc:  ls "", exa "gc=38;5;129"  =>  colours c -> { c.git.conflicted                        = Fixed(129).normal(); });
+
+    test!(exa_xx:  ls "", exa "xx=38;5;128"  =>  colours c -> { c.punctuation                           = Fixed(128).normal(); });
+    test!(exa_da:  ls "", exa "da=38;5;129"  =>  colours c -> { c.date                                  = Fixed(129).normal(); });
+    test!(exa_in:  ls "", exa "in=38;5;130"  =>  colours c -> { c.inode                                 = Fixed(130).normal(); });
+    test!(exa_bl:  ls "", exa "bl=38;5;131"  =>  colours c -> { c.blocks                                = Fixed(131).normal(); });
+    test!(exa_hd:  ls "", exa "hd=38;5;132"  =>  colours c -> { c.header                                = Fixed(132).normal(); });
+    test!(exa_lp:  ls "", exa "lp=38;5;133"  =>  colours c -> { c.symlink_path                          = Fixed(133).normal(); });
+    test!(exa_cc:  ls "", exa "cc=38;5;134"  =>  colours c -> { c.control_char                          = Fixed(134).normal(); });
+    test!(exa_oc:  ls "", exa "oc=38;5;135"  =>  colours c -> { c.octal                                 = Fixed(135).normal(); });
+    test!(exa_bo:  ls "", exa "bO=4"         =>  colours c -> { c.broken_path_overlay                   = Style::default().underline(); });
+
+    test!(exa_mp:  ls "", exa "mp=1;34;4"    =>  colours c -> { c.filekinds.mount_point                 = Blue.bold().underline(); });
+    test!(exa_sp:  ls "", exa "sp=1;35;4"    =>  colours c -> { c.filekinds.special                     = Purple.bold().underline(); });
+
+    test!(exa_im:  ls "", exa "im=38;5;128"  =>  colours c -> { c.file_type.image                       = Fixed(128).normal(); });
+    test!(exa_vi:  ls "", exa "vi=38;5;129"  =>  colours c -> { c.file_type.video                       = Fixed(129).normal(); });
+    test!(exa_mu:  ls "", exa "mu=38;5;130"  =>  colours c -> { c.file_type.music                       = Fixed(130).normal(); });
+    test!(exa_lo:  ls "", exa "lo=38;5;131"  =>  colours c -> { c.file_type.lossless                    = Fixed(131).normal(); });
+    test!(exa_cr:  ls "", exa "cr=38;5;132"  =>  colours c -> { c.file_type.crypto                      = Fixed(132).normal(); });
+    test!(exa_do:  ls "", exa "do=38;5;133"  =>  colours c -> { c.file_type.document                    = Fixed(133).normal(); });
+    test!(exa_co:  ls "", exa "co=38;5;134"  =>  colours c -> { c.file_type.compressed                  = Fixed(134).normal(); });
+    test!(exa_tm:  ls "", exa "tm=38;5;135"  =>  colours c -> { c.file_type.temp                        = Fixed(135).normal(); });
+    test!(exa_cm:  ls "", exa "cm=38;5;136"  =>  colours c -> { c.file_type.compiled                    = Fixed(136).normal(); });
+    test!(exa_ie:  ls "", exa "bu=38;5;137"  =>  colours c -> { c.file_type.build                       = Fixed(137).normal(); });
+
+    test!(exa_Sn:  ls "", exa "Sn=38;5;128"  =>  colours c -> { c.security_context.none                 = Fixed(128).normal(); });
+    test!(exa_Su:  ls "", exa "Su=38;5;129"  =>  colours c -> { c.security_context.selinux.user         = Fixed(129).normal(); });
+    test!(exa_Sr:  ls "", exa "Sr=38;5;130"  =>  colours c -> { c.security_context.selinux.role         = Fixed(130).normal(); });
+    test!(exa_St:  ls "", exa "St=38;5;131"  =>  colours c -> { c.security_context.selinux.typ          = Fixed(131).normal(); });
+    test!(exa_Sl:  ls "", exa "Sl=38;5;132"  =>  colours c -> { c.security_context.selinux.range        = Fixed(132).normal(); });
 
     // All the while, LS_COLORS treats them as filenames:
     test!(ls_uu:   ls "uu=38;5;117", exa ""  =>  exts [ ("uu", Fixed(117).normal()) ]);

+ 8 - 4
src/theme/ui_styles.rs

@@ -90,9 +90,11 @@ pub struct Size {
 #[derive(Clone, Copy, Debug, Default, PartialEq)]
 pub struct Users {
     pub user_you: Style,           // uu
-    pub user_someone_else: Style,  // un
+    pub user_root: Style,          // uR
+    pub user_other: Style,         // un
     pub group_yours: Style,        // gu
-    pub group_not_yours: Style,    // gn
+    pub group_other: Style,        // gn
+    pub group_root: Style,         // gR
 }
 
 #[rustfmt::skip]
@@ -222,9 +224,11 @@ impl UiStyles {
             "ds" => self.size.minor                     = pair.to_style(),
 
             "uu" => self.users.user_you                 = pair.to_style(),
-            "un" => self.users.user_someone_else        = pair.to_style(),
+            "un" => self.users.user_other               = pair.to_style(),
+            "uR" => self.users.user_root                = pair.to_style(),
             "gu" => self.users.group_yours              = pair.to_style(),
-            "gn" => self.users.group_not_yours          = pair.to_style(),
+            "gn" => self.users.group_other              = pair.to_style(),
+            "gR" => self.users.group_root               = pair.to_style(),
 
             "lc" => self.links.normal                   = pair.to_style(),
             "lm" => self.links.multi_link_file          = pair.to_style(),