Repository: eza-community/eza (language: rust)

Issue:
Windows Support
Right now, Eza does not support windows.

It's not really something I care to implement, specially because I don't own a windows box. 

But if anyone is up for it, you could start by making a PR with the changes to CI from here.

See: https://github.com/cafkafk/eza/pull/19


Discussion / hints:

Fix patch:
diff --git a/src/fs/feature/git.rs b/src/fs/feature/git.rs
index ed14bb457..39c2b8115 100644
--- a/src/fs/feature/git.rs
+++ b/src/fs/feature/git.rs
@@ -331,8 +331,8 @@ fn reorient(path: &Path) -> PathBuf {
 fn reorient(path: &Path) -> PathBuf {
     let unc_path = path.canonicalize().unwrap();
     // On Windows UNC path is returned. We need to strip the prefix for it to work.
-    let normal_path = unc_path.as_os_str().to_str().unwrap().trim_left_matches("\\\\?\\");
-    return PathBuf::from(normal_path);
+    let normal_path = unc_path.as_os_str().to_str().unwrap().trim_start_matches("\\\\?\\");
+    PathBuf::from(normal_path)
 }
 
 /// The character to display if the file has been modified, but not staged.
diff --git a/src/fs/feature/xattr.rs b/src/fs/feature/xattr.rs
index 107e29eda..3312ca92e 100644
--- a/src/fs/feature/xattr.rs
+++ b/src/fs/feature/xattr.rs
@@ -2,7 +2,9 @@
 
 #![allow(trivial_casts)]  // for ARM
 
+#[cfg(any(target_os = "macos", target_os = "linux"))]
 use std::cmp::Ordering;
+#[cfg(any(target_os = "macos", target_os = "linux"))]
 use std::ffi::CString;
 use std::io;
 use std::path::Path;
@@ -94,6 +96,7 @@ fn get_secattr(lister: &lister::Lister, c_path: &std::ffi::CString) -> io::Resul
     }])
 }
 
+#[cfg(any(target_os = "macos", target_os = "linux"))]
 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)
diff --git a/src/fs/fields.rs b/src/fs/fields.rs
index 4ed9f3442..2aa95ae4e 100644
--- a/src/fs/fields.rs
+++ b/src/fs/fields.rs
@@ -134,6 +134,7 @@ pub struct Inode(pub ino_t);
 
 /// A file's size of allocated file system blocks.
 #[derive(Copy, Clone)]
+#[cfg(unix)]
 pub enum Blocksize {
 
     /// This file has the given number of blocks.
diff --git a/src/fs/file.rs b/src/fs/file.rs
index a73a064bc..025fa3604 100644
--- a/src/fs/file.rs
+++ b/src/fs/file.rs
@@ -6,7 +6,9 @@ use std::os::unix::fs::{FileTypeExt, MetadataExt, PermissionsExt};
 #[cfg(windows)]
 use std::os::windows::fs::MetadataExt;
 use std::path::{Path, PathBuf};
-use std::time::{Duration, SystemTime, UNIX_EPOCH};
+use std::time::SystemTime;
+#[cfg(unix)]
+use std::time::{Duration, UNIX_EPOCH};
 
 use log::*;
 
@@ -361,6 +363,7 @@ impl<'dir> File<'dir> {
 
     /// The ID of the user that own this file. If dereferencing links, the links
     /// may be broken, in which case `None` will be returned.
+    #[cfg(unix)]
     pub fn user(&self) -> Option<f::User> {
         if self.is_link() && self.deref_links {
             match self.link_target_recurse() {
@@ -372,6 +375,7 @@ impl<'dir> File<'dir> {
     }
 
     /// The ID of the group that owns this file.
+    #[cfg(unix)]
     pub fn group(&self) -> Option<f::Group> {
         if self.is_link() && self.deref_links {
             match self.link_target_recurse() {
@@ -539,7 +543,7 @@ impl<'dir> File<'dir> {
 
     #[cfg(windows)]
     pub fn changed_time(&self) -> Option<SystemTime> {
-        return self.modified_time()
+        self.modified_time()
     }
 
     /// This file’s last accessed timestamp, if available on this platform.
diff --git a/src/options/parser.rs b/src/options/parser.rs
index 618130e1e..e7722014a 100644
--- a/src/options/parser.rs
+++ b/src/options/parser.rs
@@ -508,12 +508,12 @@ fn bytes_to_os_str(b: &[u8]) -> &OsStr{
 }
 
 #[cfg(windows)]
-fn os_str_to_bytes<'b>(s: &'b OsStr) ->  &'b [u8]{
+fn os_str_to_bytes(s: &OsStr) ->  &[u8]{
     return s.to_str().unwrap().as_bytes()
 }
 
 #[cfg(windows)]
-fn bytes_to_os_str<'b>(b:  &'b [u8]) ->  &'b OsStr{
+fn bytes_to_os_str(b:  &[u8]) ->  &OsStr{
     use std::str;
 
     return OsStr::new(str::from_utf8(b).unwrap());
diff --git a/src/options/theme.rs b/src/options/theme.rs
index 9f3dbcad4..4aae7ba1d 100644
--- a/src/options/theme.rs
+++ b/src/options/theme.rs
@@ -146,13 +146,13 @@ mod terminal_test {
     impl Vars for MockVars {
         fn get(&self, name: &'static str) -> Option<OsString> {
             if name == vars::LS_COLORS && ! self.ls.is_empty() {
-                Some(OsString::from(self.ls.clone()))
+                Some(OsString::from(self.ls))
             }
             else if name == vars::EXA_COLORS && ! self.exa.is_empty() {
-                Some(OsString::from(self.exa.clone()))
+                Some(OsString::from(self.exa))
             }
             else if name == vars::NO_COLOR && ! self.no_color.is_empty() {
-                Some(OsString::from(self.no_color.clone()))
+                Some(OsString::from(self.no_color))
             }
             else {
                 None
diff --git a/src/output/render/links.rs b/src/output/render/links.rs
index 612a5d36d..1ec620813 100644
--- a/src/output/render/links.rs
+++ b/src/output/render/links.rs
@@ -1,10 +1,13 @@
 use ansi_term::Style;
+#[cfg(unix)]
 use locale::Numeric as NumericLocale;
 
+#[cfg(unix)]
 use crate::fs::fields as f;
+#[cfg(unix)]
 use crate::output::cell::TextCell;
 
-
+#[cfg(unix)]
 impl f::Links {
     pub fn render<C: Colours>(&self, colours: &C, numeric: &NumericLocale) -> TextCell {
         let style = if self.multiple { colours.multi_link_file() }
@@ -24,11 +27,14 @@ pub trait Colours {
 #[cfg(test)]
 pub mod test {
     use super::Colours;
+    #[cfg(unix)]   
     use crate::output::cell::{TextCell, DisplayWidth};
+    #[cfg(unix)]
     use crate::fs::fields as f;
 
     use ansi_term::Colour::*;
     use ansi_term::Style;
+    #[cfg(unix)]
     use locale;
 
 
@@ -41,6 +47,7 @@ pub mod test {
 
 
     #[test]
+    #[cfg(unix)]
     fn regular_file() {
         let stati = f::Links {
             count:    1,
@@ -56,6 +63,7 @@ pub mod test {
     }
 
     #[test]
+    #[cfg(unix)]
     fn regular_directory() {
         let stati = f::Links {
             count:    3005,
@@ -71,6 +79,7 @@ pub mod test {
     }
 
     #[test]
+    #[cfg(unix)]
     fn popular_file() {
         let stati = f::Links {
             count:    3005,
diff --git a/src/output/render/mod.rs b/src/output/render/mod.rs
index ac77a1321..9cc7bd3e9 100644
--- a/src/output/render/mod.rs
+++ b/src/output/render/mod.rs
@@ -1,4 +1,6 @@
+#[cfg(unix)]
 mod blocks;
+#[cfg(unix)]
 pub use self::blocks::Colours as BlocksColours;
 
 mod filetype;
@@ -12,6 +14,7 @@ mod groups;
 #[cfg(unix)]
 pub use self::groups::{Colours as GroupColours, Render as GroupRender};
 
+#[cfg(unix)]
 mod inode;
 // inode uses just one colour
 
@@ -32,6 +35,7 @@ pub use self::times::Render as TimeRender;
 mod users;
 #[cfg(unix)]
 pub use self::users::Colours as UserColours;
+#[cfg(unix)]
 pub use self::users::Render as UserRender;
 
 mod octal;
diff --git a/src/output/render/permissions.rs b/src/output/render/permissions.rs
index 4626ad5ad..8c7a44e48 100644
--- a/src/output/render/permissions.rs
+++ b/src/output/render/permissions.rs
@@ -10,8 +10,8 @@ pub trait PermissionsPlusRender {
     fn render<C: Colours+FiletypeColours>(&self, colours: &C) -> TextCell;
 }
 
-#[cfg(unix)]
 impl PermissionsPlusRender for Option<f::PermissionsPlus> {
+    #[cfg(unix)]
     fn render<C: Colours+FiletypeColours>(&self, colours: &C) -> TextCell {
         match self {
             Some(p) => {
@@ -42,13 +42,23 @@ impl PermissionsPlusRender for Option<f::PermissionsPlus> {
     }
 
     #[cfg(windows)]
-    pub fn render<C: Colours+FiletypeColours>(&self, colours: &C) -> TextCell {
-        let mut chars = vec![ self.attributes.render_type(colours) ];
-        chars.extend(self.attributes.render(colours));
+    fn render<C: Colours+FiletypeColours>(&self, colours: &C) -> TextCell {
+        match self {
+            Some(p) => {
+                let mut chars = vec![ p.attributes.render_type(colours) ];
+                chars.extend(p.attributes.render(colours));
 
-        TextCell {
-            width:    DisplayWidth::from(chars.len()),
-            contents: chars.into(),
+                TextCell {
+                    width:    DisplayWidth::from(chars.len()),
+                    contents: chars.into(),
+                }        
+            },
+            None => {
+                TextCell {
+                    width:    DisplayWidth::from(0),
+                    contents: vec![].into(),
+                }        
+            }
         }
     }
 }
@@ -118,7 +128,7 @@ impl f::Permissions {
 
 #[cfg(windows)]
 impl f::Attributes {
-    pub fn render<C: Colours+FiletypeColours>(&self, colours: &C) -> Vec<ANSIString<'static>> {
+    pub fn render<C: Colours+FiletypeColours>(self, colours: &C) -> Vec<ANSIString<'static>> {
         let bit = |bit, chr: &'static str, style: Style| {
             if bit { style.paint(chr) }
               else { colours.dash().paint("-") }
@@ -132,16 +142,14 @@ impl f::Attributes {
         ]
     }
 
-    pub fn render_type<C: Colours+FiletypeColours>(&self, colours: &C) -> ANSIString<'static> {
+    pub fn render_type<C: Colours+FiletypeColours>(self, colours: &C) -> ANSIString<'static> {
         if self.reparse_point {
             return colours.pipe().paint("l")
         }
         else if self.directory {
             return colours.directory().paint("d")
         }
-        else {
-            return colours.dash().paint("-")
-        }
+        colours.dash().paint("-")
     }
 }
 
diff --git a/src/output/table.rs b/src/output/table.rs
index 04bbba41e..9cd5e7a5a 100644
--- a/src/output/table.rs
+++ b/src/output/table.rs
@@ -1,11 +1,14 @@
 use std::cmp::max;
+#[cfg(unix)]
 use std::env;
 use std::ops::Deref;
 #[cfg(unix)]
 use std::sync::{Mutex, MutexGuard};
 
 use datetime::TimeZone;
-use zoneinfo_compiled::{CompiledData, Result as TZResult};
+#[cfg(unix)]
+use zoneinfo_compiled::CompiledData;
+use zoneinfo_compiled::Result as TZResult;
 
 use lazy_static::lazy_static;
 use log::*;
@@ -15,11 +18,11 @@ use uzers::UsersCache;
 use crate::fs::{File, fields as f};
 use crate::fs::feature::git::GitCache;
 use crate::output::cell::TextCell;
+use crate::output::render::{PermissionsPlusRender, TimeRender};
+#[cfg(unix)]
 use crate::output::render::{
     GroupRender,
     OctalPermissionsRender,
-    PermissionsPlusRender,
-    TimeRender,
     UserRender
 };
 use crate::output::time::TimeFormat;
@@ -104,6 +107,7 @@ impl Columns {
             columns.push(Column::Group);
         }
 
+        #[cfg(target_os = "linux")]
         if self.security_context {
             columns.push(Column::SecurityContext);
         }
@@ -192,7 +196,7 @@ impl Column {
     }
 
     #[cfg(windows)]
-    pub fn alignment(&self) -> Alignment {
+    pub fn alignment(self) -> Alignment {
         match self {
             Self::FileSize   |
             Self::GitStatus  => Alignment::Right,
@@ -392,6 +396,7 @@ fn determine_time_zone() -> TZResult<TimeZone> {
     }
 }
 
+#[allow(clippy::unnecessary_wraps)] // Needs to match Unix function
 #[cfg(windows)]
 fn determine_time_zone() -> TZResult<TimeZone> {
     use datetime::zone::{FixedTimespan, FixedTimespanSet, StaticTimeZone, TimeZoneSource};
@@ -406,7 +411,7 @@ fn determine_time_zone() -> TZResult<TimeZone> {
                 name: Cow::Borrowed("ZONE_A"),
             },
             rest: &[(
-                1206838800,
+                1_206_838_800, // Sun Mar 30 2008 01:00:00 GMT+0000
                 FixedTimespan {
                     offset: 3600,
                     is_dst: false,
@@ -429,6 +434,7 @@ pub struct Table<'a> {
     widths: TableWidths,
     time_format: TimeFormat,
     size_format: SizeFormat,
+    #[cfg(unix)]
     user_format: UserFormat,
     git: Option<&'a GitCache>,
 }
@@ -452,6 +458,7 @@ impl<'a> Table<'a> {
             env,
             time_format: options.time_format,
             size_format: options.size_format,
+            #[cfg(unix)]
             user_format: options.user_format,
         }
     }
@@ -480,14 +487,23 @@ impl<'a> Table<'a> {
         self.widths.add_widths(row);
     }
 
+    #[cfg(unix)]
     fn permissions_plus(&self, file: &File<'_>, xattrs: bool) -> Option<f::PermissionsPlus> {
         file.permissions().map(|p| f::PermissionsPlus {
             file_type: file.type_char(),
-            #[cfg(unix)]
             permissions: p,
+            xattrs
+        })
+    }
+
+    #[allow(clippy::unnecessary_wraps)] // Needs to match Unix function
+    #[cfg(windows)]
+    fn permissions_plus(&self, file: &File<'_>, xattrs: bool) -> Option<f::PermissionsPlus> {
+        Some(f::PermissionsPlus {
+            file_type: file.type_char(),
             #[cfg(windows)]
             attributes: file.attributes(),
-            xattrs
+            xattrs,
         })
     }
 
diff --git a/src/theme/mod.rs b/src/theme/mod.rs
index f72030b76..ae8d62979 100644
--- a/src/theme/mod.rs
+++ b/src/theme/mod.rs
@@ -200,6 +200,7 @@ impl ExtensionMappings {
 
 
 
+#[cfg(unix)]
 impl render::BlocksColours for Theme {
     fn blocksize(&self, prefix: Option<number_prefix::Prefix>) -> Style {
         use number_prefix::Prefix::*;
@@ -373,6 +374,7 @@ fn apply_overlay(mut base: Style, overlay: Style) -> Style {
 
 
 #[cfg(test)]
+#[cfg(unix)]
 mod customs_test {
     use super::*;
     use crate::theme::ui_styles::UiStyles;


Test patch:
diff --git a/.github/workflows/unit-tests.yml b/.github/workflows/unit-tests.yml
index a4eb8d0da..d6f5d1b12 100644
--- a/.github/workflows/unit-tests.yml
+++ b/.github/workflows/unit-tests.yml
@@ -27,7 +27,7 @@ jobs:
 
     strategy:
       matrix:
-        os: [ubuntu-latest, macos-latest]
+        os: [ubuntu-latest, macos-latest, windows-latest]
         rust: [1.71.0, stable, beta, nightly]
 
     steps: