Kat Marchán 5 роки тому
батько
коміт
7f717c3af3

+ 7 - 0
Cargo.lock

@@ -93,6 +93,7 @@ dependencies = [
  "natord 1.0.9 (registry+https://github.com/rust-lang/crates.io-index)",
  "num_cpus 1.10.1 (registry+https://github.com/rust-lang/crates.io-index)",
  "number_prefix 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "os_str_bytes 2.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "scoped_threadpool 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)",
  "term_grid 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)",
  "term_size 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
@@ -272,6 +273,11 @@ dependencies = [
  "vcpkg 0.2.7 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
+[[package]]
+name = "os_str_bytes"
+version = "2.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
 [[package]]
 name = "pad"
 version = "0.1.5"
@@ -506,6 +512,7 @@ dependencies = [
 "checksum number_prefix 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "17b02fc0ff9a9e4b35b3342880f48e896ebf69f2967921fe8646bf5b7125956a"
 "checksum openssl-src 111.3.0+1.1.1c (registry+https://github.com/rust-lang/crates.io-index)" = "53ed5f31d294bdf5f7a4ba0a206c2754b0f60e9a63b7e3076babc5317873c797"
 "checksum openssl-sys 0.9.48 (registry+https://github.com/rust-lang/crates.io-index)" = "b5ba300217253bcc5dc68bed23d782affa45000193866e025329aa8a7a9f05b8"
+"checksum os_str_bytes 2.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ced912e1439b63a8172b943887c33373f226a4a9cfab95d09c0e3c44851d04d4"
 "checksum pad 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "9c9b8de33465981073e32e1d75bb89ade49062bb853e7c97ec2c13439095563a"
 "checksum percent-encoding 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "31010dd2e1ac33d5b46a5b413495239882813e0369f8ed8a5e266f173602f831"
 "checksum pkg-config 0.3.14 (registry+https://github.com/rust-lang/crates.io-index)" = "676e8eb2b1b4c9043511a9b7bea0915320d7e502b0a079fb03f9635a5252b18c"

+ 4 - 1
Cargo.toml

@@ -43,8 +43,11 @@ scoped_threadpool = "0.1.9"
 term_grid = "0.1.7"
 term_size = "0.3.1"
 unicode-width = "0.1.5"
-users = "0.9.1"
 zoneinfo_compiled = "0.4.8"
+os_str_bytes = "2.2.0"
+
+[target.'cfg(unix)'.dependencies]
+users = "0.9.1"
 
 [dependencies.git2]
 version = "0.9.1"

+ 1 - 0
src/fs/fields.rs

@@ -82,6 +82,7 @@ pub struct Permissions {
 /// little more compressed.
 pub struct PermissionsPlus {
     pub file_type:   Type,
+    #[cfg(unix)]
     pub permissions: Permissions,
     pub xattrs:      bool,
 }

+ 137 - 95
src/fs/file.rs

@@ -2,9 +2,10 @@
 
 use std::io::Error as IOError;
 use std::io::Result as IOResult;
-use std::os::unix::fs::{MetadataExt, PermissionsExt, FileTypeExt};
+#[cfg(unix)]
+use std::os::unix::fs::{FileTypeExt, MetadataExt, PermissionsExt};
 use std::path::{Path, PathBuf};
-use std::time::{UNIX_EPOCH, Duration};
+use std::time::{Duration, UNIX_EPOCH};
 
 use log::{debug, error};
 
@@ -19,7 +20,6 @@ use crate::fs::fields as f;
 /// information queried at least once, so it makes sense to do all this at the
 /// start and hold on to all the information.
 pub struct File<'dir> {
-
     /// The filename portion of this file’s path, including the extension.
     ///
     /// This is used to compare against certain filenames (such as checking if
@@ -67,39 +67,61 @@ pub struct File<'dir> {
 
 impl<'dir> File<'dir> {
     pub fn from_args<PD, FN>(path: PathBuf, parent_dir: PD, filename: FN) -> IOResult<File<'dir>>
-    where PD: Into<Option<&'dir Dir>>,
-          FN: Into<Option<String>>
+    where
+        PD: Into<Option<&'dir Dir>>,
+        FN: Into<Option<String>>,
     {
         let parent_dir = parent_dir.into();
-        let name       = filename.into().unwrap_or_else(|| File::filename(&path));
-        let ext        = File::ext(&path);
+        let name = filename.into().unwrap_or_else(|| File::filename(&path));
+        let ext = File::ext(&path);
 
         debug!("Statting file {:?}", &path);
-        let metadata   = std::fs::symlink_metadata(&path)?;
+        let metadata = std::fs::symlink_metadata(&path)?;
         let is_all_all = false;
 
-        Ok(File { path, parent_dir, metadata, ext, name, is_all_all })
+        Ok(File {
+            path,
+            parent_dir,
+            metadata,
+            ext,
+            name,
+            is_all_all,
+        })
     }
 
     pub fn new_aa_current(parent_dir: &'dir Dir) -> IOResult<File<'dir>> {
-        let path       = parent_dir.path.to_path_buf();
-        let ext        = File::ext(&path);
+        let path = parent_dir.path.to_path_buf();
+        let ext = File::ext(&path);
 
         debug!("Statting file {:?}", &path);
-        let metadata   = std::fs::symlink_metadata(&path)?;
+        let metadata = std::fs::symlink_metadata(&path)?;
         let is_all_all = true;
 
-        Ok(File { path, parent_dir: Some(parent_dir), metadata, ext, name: ".".to_string(), is_all_all })
+        Ok(File {
+            path,
+            parent_dir: Some(parent_dir),
+            metadata,
+            ext,
+            name: ".".to_string(),
+            is_all_all,
+        })
     }
 
     pub fn new_aa_parent(path: PathBuf, parent_dir: &'dir Dir) -> IOResult<File<'dir>> {
-        let ext        = File::ext(&path);
+        let ext = File::ext(&path);
 
         debug!("Statting file {:?}", &path);
-        let metadata   = std::fs::symlink_metadata(&path)?;
+        let metadata = std::fs::symlink_metadata(&path)?;
         let is_all_all = true;
 
-        Ok(File { path, parent_dir: Some(parent_dir), metadata, ext, name: "..".to_string(), is_all_all })
+        Ok(File {
+            path,
+            parent_dir: Some(parent_dir),
+            metadata,
+            ext,
+            name: "..".to_string(),
+            is_all_all,
+        })
     }
 
     /// A file’s name is derived from its string. This needs to handle directories
@@ -107,9 +129,12 @@ impl<'dir> File<'dir> {
     /// use the last component as the name.
     pub fn filename(path: &Path) -> String {
         if let Some(back) = path.components().next_back() {
-            back.as_os_str().to_string_lossy().to_string()
-        }
-        else {
+            let name = back.as_os_str().to_string_lossy().to_string();
+            #[cfg(unix)]
+            return name;
+            #[cfg(windows)]
+            return name;
+        } else {
             // use the path as fallback
             error!("Path {:?} has no last component", path);
             path.display().to_string()
@@ -127,7 +152,7 @@ impl<'dir> File<'dir> {
     fn ext(path: &Path) -> Option<String> {
         let name = path.file_name().map(|f| f.to_string_lossy().to_string())?;
 
-        name.rfind('.').map(|p| name[p+1..].to_ascii_lowercase())
+        name.rfind('.').map(|p| name[p + 1..].to_ascii_lowercase())
     }
 
     /// Whether this file is a directory on the filesystem.
@@ -170,6 +195,7 @@ impl<'dir> File<'dir> {
     /// Whether this file is both a regular file *and* executable for the
     /// current user. An executable file has a different purpose from an
     /// executable directory, so they should be highlighted differently.
+    #[cfg(unix)]
     pub fn is_executable_file(&self) -> bool {
         let bit = modes::USER_EXECUTE;
         self.is_file() && (self.metadata.permissions().mode() & bit) == bit
@@ -181,40 +207,40 @@ impl<'dir> File<'dir> {
     }
 
     /// Whether this file is a named pipe on the filesystem.
+    #[cfg(unix)]
     pub fn is_pipe(&self) -> bool {
         self.metadata.file_type().is_fifo()
     }
 
     /// Whether this file is a char device on the filesystem.
+    #[cfg(unix)]
     pub fn is_char_device(&self) -> bool {
         self.metadata.file_type().is_char_device()
     }
 
     /// Whether this file is a block device on the filesystem.
+    #[cfg(unix)]
     pub fn is_block_device(&self) -> bool {
         self.metadata.file_type().is_block_device()
     }
 
     /// Whether this file is a socket on the filesystem.
+    #[cfg(unix)]
     pub fn is_socket(&self) -> bool {
         self.metadata.file_type().is_socket()
     }
 
-
     /// Re-prefixes the path pointed to by this file, if it’s a symlink, to
     /// make it an absolute path that can be accessed from whichever
     /// directory exa is being run from.
     fn reorient_target_path(&self, path: &Path) -> PathBuf {
         if path.is_absolute() {
             path.to_path_buf()
-        }
-        else if let Some(dir) = self.parent_dir {
+        } else if let Some(dir) = self.parent_dir {
             dir.join(&*path)
-        }
-        else if let Some(parent) = self.path.parent() {
+        } else if let Some(parent) = self.path.parent() {
             parent.join(&*path)
-        }
-        else {
+        } else {
             self.path.join(&*path)
         }
     }
@@ -230,15 +256,14 @@ impl<'dir> File<'dir> {
     /// existed. If this file cannot be read at all, returns the error that
     /// we got when we tried to read it.
     pub fn link_target(&self) -> FileTarget<'dir> {
-
         // We need to be careful to treat the path actually pointed to by
         // this file — which could be absolute or relative — to the path
         // we actually look up and turn into a `File` — which needs to be
         // absolute to be accessible from any directory.
         debug!("Reading link {:?}", &self.path);
         let path = match std::fs::read_link(&self.path) {
-            Ok(p)   => p,
-            Err(e)  => return FileTarget::Err(e),
+            Ok(p) => p,
+            Err(e) => return FileTarget::Err(e),
         };
 
         let absolute_path = self.reorient_target_path(&path);
@@ -247,9 +272,16 @@ impl<'dir> File<'dir> {
         // follow links.
         match std::fs::metadata(&absolute_path) {
             Ok(metadata) => {
-                let ext  = File::ext(&path);
+                let ext = File::ext(&path);
                 let name = File::filename(&path);
-                FileTarget::Ok(Box::new(File { parent_dir: None, path, ext, metadata, name, is_all_all: false }))
+                FileTarget::Ok(Box::new(File {
+                    parent_dir: None,
+                    path,
+                    ext,
+                    metadata,
+                    name,
+                    is_all_all: false,
+                }))
             }
             Err(e) => {
                 error!("Error following link {:?}: {:#?}", &path, e);
@@ -265,6 +297,7 @@ impl<'dir> File<'dir> {
     /// is uncommon, while you come across directories and other types
     /// with multiple links much more often. Thus, it should get highlighted
     /// more attentively.
+    #[cfg(unix)]
     pub fn links(&self) -> f::Links {
         let count = self.metadata.nlink();
 
@@ -275,6 +308,7 @@ impl<'dir> File<'dir> {
     }
 
     /// This file's inode.
+    #[cfg(unix)]
     pub fn inode(&self) -> f::Inode {
         f::Inode(self.metadata.ino())
     }
@@ -282,21 +316,23 @@ impl<'dir> File<'dir> {
     /// This file's number of filesystem blocks.
     ///
     /// (Not the size of each block, which we don't actually report on)
+    #[cfg(unix)]
     pub fn blocks(&self) -> f::Blocks {
         if self.is_file() || self.is_link() {
             f::Blocks::Some(self.metadata.blocks())
-        }
-        else {
+        } else {
             f::Blocks::None
         }
     }
 
     /// The ID of the user that own this file.
+    #[cfg(unix)]
     pub fn user(&self) -> f::User {
         f::User(self.metadata.uid())
     }
 
     /// The ID of the group that owns this file.
+    #[cfg(unix)]
     pub fn group(&self) -> f::Group {
         f::Group(self.metadata.gid())
     }
@@ -311,18 +347,19 @@ impl<'dir> File<'dir> {
     /// usually just have a file size of zero.
     pub fn size(&self) -> f::Size {
         if self.is_directory() {
-            f::Size::None
-        }
-        else if self.is_char_device() || self.is_block_device() {
-            let dev = self.metadata.rdev();
-            f::Size::DeviceIDs(f::DeviceIDs {
-                major: (dev / 256) as u8,
-                minor: (dev % 256) as u8,
-            })
-        }
-        else {
-            f::Size::Some(self.metadata.len())
+            return f::Size::None;
+        };
+        #[cfg(unix)]
+        {
+            if self.is_char_device() || self.is_block_device() {
+                let dev = self.metadata.rdev();
+                return f::Size::DeviceIDs(f::DeviceIDs {
+                    major: (dev / 256) as u8,
+                    minor: (dev % 256) as u8,
+                });
+            };
         }
+        return f::Size::Some(self.metadata.len());
     }
 
     /// This file’s last modified timestamp.
@@ -335,8 +372,12 @@ impl<'dir> File<'dir> {
     }
 
     /// This file’s last changed timestamp.
+    #[cfg(unix)]
     pub fn changed_time(&self) -> Duration {
-        Duration::new(self.metadata.ctime() as u64, self.metadata.ctime_nsec() as u32)
+        Duration::new(
+            self.metadata.ctime() as u64,
+            self.metadata.ctime_nsec() as u32,
+        )
     }
 
     /// This file’s last accessed timestamp.
@@ -362,54 +403,59 @@ impl<'dir> File<'dir> {
     /// This is used a the leftmost character of the permissions column.
     /// The file type can usually be guessed from the colour of the file, but
     /// ls puts this character there.
+    #[cfg(windows)]
     pub fn type_char(&self) -> f::Type {
         if self.is_file() {
             f::Type::File
-        }
-        else if self.is_directory() {
+        } else if self.is_directory() {
             f::Type::Directory
+        } else {
+            f::Type::Special
         }
-        else if self.is_pipe() {
+    }
+    #[cfg(unix)]
+    pub fn type_char(&self) -> f::Type {
+        if self.is_file() {
+            f::Type::File
+        } else if self.is_directory() {
+            f::Type::Directory
+        } else if self.is_pipe() {
             f::Type::Pipe
-        }
-        else if self.is_link() {
+        } else if self.is_link() {
             f::Type::Link
-        }
-        else if self.is_char_device() {
+        } else if self.is_char_device() {
             f::Type::CharDevice
-        }
-        else if self.is_block_device() {
+        } else if self.is_block_device() {
             f::Type::BlockDevice
-        }
-        else if self.is_socket() {
+        } else if self.is_socket() {
             f::Type::Socket
-        }
-        else {
+        } else {
             f::Type::Special
         }
     }
 
     /// This file’s permissions, with flags for each bit.
+    #[cfg(unix)]
     pub fn permissions(&self) -> f::Permissions {
         let bits = self.metadata.mode();
-        let has_bit = |bit| { bits & bit == bit };
+        let has_bit = |bit| bits & bit == bit;
 
         f::Permissions {
-            user_read:      has_bit(modes::USER_READ),
-            user_write:     has_bit(modes::USER_WRITE),
-            user_execute:   has_bit(modes::USER_EXECUTE),
+            user_read: has_bit(modes::USER_READ),
+            user_write: has_bit(modes::USER_WRITE),
+            user_execute: has_bit(modes::USER_EXECUTE),
 
-            group_read:     has_bit(modes::GROUP_READ),
-            group_write:    has_bit(modes::GROUP_WRITE),
-            group_execute:  has_bit(modes::GROUP_EXECUTE),
+            group_read: has_bit(modes::GROUP_READ),
+            group_write: has_bit(modes::GROUP_WRITE),
+            group_execute: has_bit(modes::GROUP_EXECUTE),
 
-            other_read:     has_bit(modes::OTHER_READ),
-            other_write:    has_bit(modes::OTHER_WRITE),
-            other_execute:  has_bit(modes::OTHER_EXECUTE),
+            other_read: has_bit(modes::OTHER_READ),
+            other_write: has_bit(modes::OTHER_WRITE),
+            other_execute: has_bit(modes::OTHER_EXECUTE),
 
-            sticky:         has_bit(modes::STICKY),
-            setgid:         has_bit(modes::SETGID),
-            setuid:         has_bit(modes::SETUID),
+            sticky: has_bit(modes::STICKY),
+            setgid: has_bit(modes::SETGID),
+            setuid: has_bit(modes::SETUID),
         }
     }
 
@@ -418,8 +464,8 @@ impl<'dir> File<'dir> {
     /// This will always return `false` if the file has no extension.
     pub fn extension_is_one_of(&self, choices: &[&str]) -> bool {
         match self.ext {
-            Some(ref ext)  => choices.contains(&&ext[..]),
-            None           => false,
+            Some(ref ext) => choices.contains(&&ext[..]),
+            None => false,
         }
     }
 
@@ -430,17 +476,14 @@ impl<'dir> File<'dir> {
     }
 }
 
-
 impl<'a> AsRef<File<'a>> for File<'a> {
     fn as_ref(&self) -> &File<'a> {
         self
     }
 }
 
-
 /// The result of following a symlink.
 pub enum FileTarget<'dir> {
-
     /// The symlink pointed at a file that exists.
     Ok(Box<File<'dir>>),
 
@@ -452,27 +495,25 @@ pub enum FileTarget<'dir> {
     /// file isn’t a link to begin with, but also if, say, we don’t have
     /// permission to follow it.
     Err(IOError),
-
     // Err is its own variant, instead of having the whole thing be inside an
     // `IOResult`, because being unable to follow a symlink is not a serious
     // error -- we just display the error message and move on.
 }
 
 impl<'dir> FileTarget<'dir> {
-
     /// Whether this link doesn’t lead to a file, for whatever reason. This
     /// gets used to determine how to highlight the link in grid views.
     pub fn is_broken(&self) -> bool {
         match *self {
-            FileTarget::Ok(_)                           => false,
-            FileTarget::Broken(_) | FileTarget::Err(_)  => true,
+            FileTarget::Ok(_) => false,
+            FileTarget::Broken(_) | FileTarget::Err(_) => true,
         }
     }
 }
 
-
 /// More readable aliases for the permission bits exposed by libc.
 #[allow(trivial_numeric_casts)]
+#[cfg(unix)]
 mod modes {
     use libc;
 
@@ -480,24 +521,23 @@ mod modes {
     // The `libc::mode_t` type’s actual type varies, but the value returned
     // from `metadata.permissions().mode()` is always `u32`.
 
-    pub const USER_READ: Mode     = libc::S_IRUSR as Mode;
-    pub const USER_WRITE: Mode    = libc::S_IWUSR as Mode;
-    pub const USER_EXECUTE: Mode  = libc::S_IXUSR as Mode;
+    pub const USER_READ: Mode = libc::S_IRUSR as Mode;
+    pub const USER_WRITE: Mode = libc::S_IWUSR as Mode;
+    pub const USER_EXECUTE: Mode = libc::S_IXUSR as Mode;
 
-    pub const GROUP_READ: Mode    = libc::S_IRGRP as Mode;
-    pub const GROUP_WRITE: Mode   = libc::S_IWGRP as Mode;
+    pub const GROUP_READ: Mode = libc::S_IRGRP as Mode;
+    pub const GROUP_WRITE: Mode = libc::S_IWGRP as Mode;
     pub const GROUP_EXECUTE: Mode = libc::S_IXGRP as Mode;
 
-    pub const OTHER_READ: Mode    = libc::S_IROTH as Mode;
-    pub const OTHER_WRITE: Mode   = libc::S_IWOTH as Mode;
+    pub const OTHER_READ: Mode = libc::S_IROTH as Mode;
+    pub const OTHER_WRITE: Mode = libc::S_IWOTH as Mode;
     pub const OTHER_EXECUTE: Mode = libc::S_IXOTH as Mode;
 
-    pub const STICKY: Mode        = libc::S_ISVTX as Mode;
-    pub const SETGID: Mode        = libc::S_ISGID as Mode;
-    pub const SETUID: Mode        = libc::S_ISUID as Mode;
+    pub const STICKY: Mode = libc::S_ISVTX as Mode;
+    pub const SETGID: Mode = libc::S_ISGID as Mode;
+    pub const SETUID: Mode = libc::S_ISUID as Mode;
 }
 
-
 #[cfg(test)]
 mod ext_test {
     use super::File;
@@ -519,7 +559,6 @@ mod ext_test {
     }
 }
 
-
 #[cfg(test)]
 mod filename_test {
     use super::File;
@@ -552,6 +591,9 @@ mod filename_test {
 
     #[test]
     fn topmost() {
-        assert_eq!("/", File::filename(Path::new("/")))
+        #[cfg(unix)]
+        assert_eq!("/", File::filename(Path::new("/")));
+        #[cfg(windows)]
+        assert_eq!("C:\\", File::filename(Path::new("C:\\")));
     }
 }

+ 56 - 55
src/fs/filter.rs

@@ -2,15 +2,15 @@
 
 use std::cmp::Ordering;
 use std::iter::FromIterator;
+#[cfg(unix)]
 use std::os::unix::fs::MetadataExt;
 use std::path::Path;
 
 use glob;
 use natord;
 
-use crate::fs::File;
 use crate::fs::DotFilter;
-
+use crate::fs::File;
 
 /// The **file filter** processes a list of files before displaying them to
 /// the user, by removing files they don’t want to see, and putting the list
@@ -28,7 +28,6 @@ use crate::fs::DotFilter;
 /// performing the comparison.
 #[derive(PartialEq, Debug, Clone)]
 pub struct FileFilter {
-
     /// Whether directories should be listed first, and other types of file
     /// second. Some users prefer it like this.
     pub list_dirs_first: bool,
@@ -91,7 +90,6 @@ pub struct FileFilter {
     pub git_ignore: GitIgnore,
 }
 
-
 impl FileFilter {
     /// Remove every file in the given vector that does *not* pass the
     /// filter predicate for files found inside a directory.
@@ -118,8 +116,9 @@ impl FileFilter {
 
     /// Sort the files in the given vector based on the sort field option.
     pub fn sort_files<'a, F>(&self, files: &mut Vec<F>)
-    where F: AsRef<File<'a>> {
-
+    where
+        F: AsRef<File<'a>>,
+    {
         files.sort_by(|a, b| self.sort_field.compare_files(a.as_ref(), b.as_ref()));
 
         if self.reverse {
@@ -129,18 +128,18 @@ impl FileFilter {
         if self.list_dirs_first {
             // This relies on the fact that `sort_by` is *stable*: it will keep
             // adjacent elements next to each other.
-            files.sort_by(|a, b| {b.as_ref().points_to_directory()
-                .cmp(&a.as_ref().points_to_directory())
+            files.sort_by(|a, b| {
+                b.as_ref()
+                    .points_to_directory()
+                    .cmp(&a.as_ref().points_to_directory())
             });
         }
     }
 }
 
-
 /// User-supplied field to sort by.
 #[derive(PartialEq, Debug, Copy, Clone)]
 pub enum SortField {
-
     /// Don’t apply any sorting. This is usually used as an optimisation in
     /// scripts, where the order doesn’t matter.
     Unsorted,
@@ -156,6 +155,7 @@ pub enum SortField {
 
     /// The file’s inode, which usually corresponds to the order in which
     /// files were created on the filesystem, more or less.
+    #[cfg(unix)]
     FileInode,
 
     /// The time the file was modified (the “mtime”).
@@ -182,6 +182,7 @@ pub enum SortField {
     ///
     /// In original Unix, this was, however, meant as creation time.
     /// https://www.bell-labs.com/usr/dmr/www/cacm.html
+    #[cfg(unix)]
     ChangedDate,
 
     /// The time the file was created (the "btime" or "birthtime").
@@ -220,7 +221,6 @@ pub enum SortField {
 /// effects they have.
 #[derive(PartialEq, Debug, Copy, Clone)]
 pub enum SortCase {
-
     /// Sort files case-sensitively with uppercase first, with ‘A’ coming
     /// before ‘a’.
     ABCabc,
@@ -230,7 +230,6 @@ pub enum SortCase {
 }
 
 impl SortField {
-
     /// Compares two files to determine the order they should be listed in,
     /// depending on the search field.
     ///
@@ -243,42 +242,44 @@ impl SortField {
         use self::SortCase::{ABCabc, AaBbCc};
 
         match self {
-            SortField::Unsorted  => Ordering::Equal,
-
-            SortField::Name(ABCabc)  => natord::compare(&a.name, &b.name),
-            SortField::Name(AaBbCc)  => natord::compare_ignore_case(&a.name, &b.name),
-
-            SortField::Size          => a.metadata.len().cmp(&b.metadata.len()),
-            SortField::FileInode     => a.metadata.ino().cmp(&b.metadata.ino()),
-            SortField::ModifiedDate  => a.modified_time().cmp(&b.modified_time()),
-            SortField::AccessedDate  => a.accessed_time().cmp(&b.accessed_time()),
-            SortField::ChangedDate   => a.changed_time().cmp(&b.changed_time()),
-            SortField::CreatedDate   => a.created_time().cmp(&b.created_time()),
-            SortField::ModifiedAge   => b.modified_time().cmp(&a.modified_time()),  // flip b and a
-
-            SortField::FileType => match a.type_char().cmp(&b.type_char()) { // todo: this recomputes
-                Ordering::Equal  => natord::compare(&*a.name, &*b.name),
-                order            => order,
+            SortField::Unsorted => Ordering::Equal,
+
+            SortField::Name(ABCabc) => natord::compare(&a.name, &b.name),
+            SortField::Name(AaBbCc) => natord::compare_ignore_case(&a.name, &b.name),
+
+            SortField::Size => a.metadata.len().cmp(&b.metadata.len()),
+            #[cfg(unix)]
+            SortField::FileInode => a.metadata.ino().cmp(&b.metadata.ino()),
+            SortField::ModifiedDate => a.modified_time().cmp(&b.modified_time()),
+            SortField::AccessedDate => a.accessed_time().cmp(&b.accessed_time()),
+            #[cfg(unix)]
+            SortField::ChangedDate => a.changed_time().cmp(&b.changed_time()),
+            SortField::CreatedDate => a.created_time().cmp(&b.created_time()),
+            SortField::ModifiedAge => b.modified_time().cmp(&a.modified_time()), // flip b and a
+
+            SortField::FileType => match a.type_char().cmp(&b.type_char()) {
+                // todo: this recomputes
+                Ordering::Equal => natord::compare(&*a.name, &*b.name),
+                order => order,
             },
 
             SortField::Extension(ABCabc) => match a.ext.cmp(&b.ext) {
-                Ordering::Equal  => natord::compare(&*a.name, &*b.name),
-                order            => order,
+                Ordering::Equal => natord::compare(&*a.name, &*b.name),
+                order => order,
             },
 
             SortField::Extension(AaBbCc) => match a.ext.cmp(&b.ext) {
-                Ordering::Equal  => natord::compare_ignore_case(&*a.name, &*b.name),
-                order            => order,
+                Ordering::Equal => natord::compare_ignore_case(&*a.name, &*b.name),
+                order => order,
             },
 
-            SortField::NameMixHidden(ABCabc) => natord::compare(
-                SortField::strip_dot(&a.name),
-                SortField::strip_dot(&b.name)
-            ),
+            SortField::NameMixHidden(ABCabc) => {
+                natord::compare(SortField::strip_dot(&a.name), SortField::strip_dot(&b.name))
+            }
             SortField::NameMixHidden(AaBbCc) => natord::compare_ignore_case(
                 SortField::strip_dot(&a.name),
-                SortField::strip_dot(&b.name)
-            )
+                SortField::strip_dot(&b.name),
+            ),
         }
     }
 
@@ -291,7 +292,6 @@ impl SortField {
     }
 }
 
-
 /// The **ignore patterns** are a list of globs that are tested against
 /// each filename, and if any of them match, that file isn’t displayed.
 /// This lets a user hide, say, text files by ignoring `*.txt`.
@@ -302,23 +302,26 @@ pub struct IgnorePatterns {
 
 impl FromIterator<glob::Pattern> for IgnorePatterns {
     fn from_iter<I: IntoIterator<Item = glob::Pattern>>(iter: I) -> Self {
-        IgnorePatterns { patterns: iter.into_iter().collect() }
+        IgnorePatterns {
+            patterns: iter.into_iter().collect(),
+        }
     }
 }
 
 impl IgnorePatterns {
-
     /// Create a new list from the input glob strings, turning the inputs that
     /// are valid glob patterns into an IgnorePatterns. The inputs that don’t
     /// parse correctly are returned separately.
-    pub fn parse_from_iter<'a, I: IntoIterator<Item = &'a str>>(iter: I) -> (Self, Vec<glob::PatternError>) {
+    pub fn parse_from_iter<'a, I: IntoIterator<Item = &'a str>>(
+        iter: I,
+    ) -> (Self, Vec<glob::PatternError>) {
         let iter = iter.into_iter();
 
         // Almost all glob patterns are valid, so it’s worth pre-allocating
         // the vector with enough space for all of them.
         let mut patterns = match iter.size_hint() {
-            (_, Some(count))  => Vec::with_capacity(count),
-             _                => Vec::new(),
+            (_, Some(count)) => Vec::with_capacity(count),
+            _ => Vec::new(),
         };
 
         // Similarly, assume there won’t be any errors.
@@ -327,7 +330,7 @@ impl IgnorePatterns {
         for input in iter {
             match glob::Pattern::new(input) {
                 Ok(pat) => patterns.push(pat),
-                Err(e)  => errors.push(e),
+                Err(e) => errors.push(e),
             }
         }
 
@@ -336,7 +339,9 @@ impl IgnorePatterns {
 
     /// Create a new empty set of patterns that matches nothing.
     pub fn empty() -> IgnorePatterns {
-        IgnorePatterns { patterns: Vec::new() }
+        IgnorePatterns {
+            patterns: Vec::new(),
+        }
     }
 
     /// Test whether the given file should be hidden from the results.
@@ -353,11 +358,9 @@ impl IgnorePatterns {
     // isn’t probably means it’s in the wrong place
 }
 
-
 /// Whether to ignore or display files that are mentioned in `.gitignore` files.
 #[derive(PartialEq, Debug, Copy, Clone)]
 pub enum GitIgnore {
-
     /// Ignore files that Git would ignore. This means doing a check for a
     /// `.gitignore` file, possibly recursively up the filesystem tree.
     CheckAndIgnore,
@@ -373,8 +376,6 @@ pub enum GitIgnore {
 // > .gitignore, .git/info/exclude and even your global gitignore globs,
 // > usually found in $XDG_CONFIG_HOME/git/ignore.
 
-
-
 #[cfg(test)]
 mod test_ignores {
     use super::*;
@@ -388,23 +389,23 @@ mod test_ignores {
 
     #[test]
     fn ignores_a_glob() {
-        let (pats, fails) = IgnorePatterns::parse_from_iter(vec![ "*.mp3" ]);
+        let (pats, fails) = IgnorePatterns::parse_from_iter(vec!["*.mp3"]);
         assert!(fails.is_empty());
         assert_eq!(false, pats.is_ignored("nothing"));
-        assert_eq!(true,  pats.is_ignored("test.mp3"));
+        assert_eq!(true, pats.is_ignored("test.mp3"));
     }
 
     #[test]
     fn ignores_an_exact_filename() {
-        let (pats, fails) = IgnorePatterns::parse_from_iter(vec![ "nothing" ]);
+        let (pats, fails) = IgnorePatterns::parse_from_iter(vec!["nothing"]);
         assert!(fails.is_empty());
-        assert_eq!(true,  pats.is_ignored("nothing"));
+        assert_eq!(true, pats.is_ignored("nothing"));
         assert_eq!(false, pats.is_ignored("test.mp3"));
     }
 
     #[test]
     fn ignores_both() {
-        let (pats, fails) = IgnorePatterns::parse_from_iter(vec![ "nothing", "*.mp3" ]);
+        let (pats, fails) = IgnorePatterns::parse_from_iter(vec!["nothing", "*.mp3"]);
         assert!(fails.is_empty());
         assert_eq!(true, pats.is_ignored("nothing"));
         assert_eq!(true, pats.is_ignored("test.mp3"));

+ 3 - 0
src/options/filter.rs

@@ -58,9 +58,12 @@ impl SortField {
             // newest files) get sorted at the top, and files with the most
             // age (the oldest) at the bottom.
             "age" | "old" | "oldest" => SortField::ModifiedAge,
+            #[cfg(unix)]
             "ch" | "changed" => SortField::ChangedDate,
             "acc" | "accessed" => SortField::AccessedDate,
+            #[cfg(unix)]
             "cr" | "created" => SortField::CreatedDate,
+            #[cfg(unix)]
             "inode" => SortField::FileInode,
             "type" => SortField::FileType,
             "none" => SortField::Unsorted,

+ 197 - 153
src/options/parser.rs

@@ -27,12 +27,13 @@
 //! command-line options, as all the options and their values (such as
 //! `--sort size`) are guaranteed to just be 8-bit ASCII.
 
-
+use std::borrow::Cow;
 use std::ffi::{OsStr, OsString};
 use std::fmt;
 
-use crate::options::Misfire;
+use os_str_bytes::{OsStrBytes, OsStringBytes};
 
+use crate::options::Misfire;
 
 /// A **short argument** is a single ASCII character.
 pub type ShortArg = u8;
@@ -61,8 +62,8 @@ pub enum Flag {
 impl Flag {
     pub fn matches(&self, arg: &Arg) -> bool {
         match *self {
-            Flag::Short(short)  => arg.short == Some(short),
-            Flag::Long(long)    => arg.long == long,
+            Flag::Short(short) => arg.short == Some(short),
+            Flag::Long(long) => arg.long == long,
         }
     }
 }
@@ -71,7 +72,7 @@ impl fmt::Display for Flag {
     fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
         match *self {
             Flag::Short(short) => write!(f, "-{}", short as char),
-            Flag::Long(long)   => write!(f, "--{}", long),
+            Flag::Long(long) => write!(f, "--{}", long),
         }
     }
 }
@@ -79,7 +80,6 @@ impl fmt::Display for Flag {
 /// Whether redundant arguments should be considered a problem.
 #[derive(PartialEq, Debug, Copy, Clone)]
 pub enum Strictness {
-
     /// Throw an error when an argument doesn’t do anything, either because
     /// it requires another argument to be specified, or because two conflict.
     ComplainAboutRedundantArguments,
@@ -93,7 +93,6 @@ pub enum Strictness {
 /// arguments.
 #[derive(Copy, Clone, PartialEq, Debug)]
 pub enum TakesValue {
-
     /// This flag has to be followed by a value.
     /// If there’s a fixed set of possible values, they can be printed out
     /// with the error text.
@@ -106,11 +105,9 @@ pub enum TakesValue {
     Optional(Option<Values>),
 }
 
-
 /// An **argument** can be matched by one of the user’s input strings.
 #[derive(PartialEq, Debug)]
 pub struct Arg {
-
     /// The short argument that matches it, if any.
     pub short: Option<ShortArg>,
 
@@ -134,18 +131,21 @@ impl fmt::Display for Arg {
     }
 }
 
-
 /// Literally just several args.
 #[derive(PartialEq, Debug)]
 pub struct Args(pub &'static [&'static Arg]);
 
 impl Args {
-
     /// Iterates over the given list of command-line arguments and parses
     /// them into a list of matched flags and free strings.
-    pub fn parse<'args, I>(&self, inputs: I, strictness: Strictness) -> Result<Matches<'args>, ParseError>
-    where I: IntoIterator<Item=&'args OsString> {
-        use std::os::unix::ffi::OsStrExt;
+    pub fn parse<'args, I>(
+        &self,
+        inputs: I,
+        strictness: Strictness,
+    ) -> Result<Matches<'args>, ParseError>
+    where
+        I: IntoIterator<Item = &'args OsString>,
+    {
         use self::TakesValue::*;
 
         let mut parsing = true;
@@ -159,7 +159,7 @@ impl Args {
         // doesn’t have one in its string so it needs the next one.
         let mut inputs = inputs.into_iter();
         while let Some(arg) = inputs.next() {
-            let bytes = arg.as_bytes();
+            let bytes = arg.to_bytes();
 
             // Stop parsing if one of the arguments is the literal string “--”.
             // This allows a file named “--arg” to be specified by passing in
@@ -167,58 +167,52 @@ impl Args {
             // doesn’t exist.
             if !parsing {
                 frees.push(arg)
-            }
-            else if arg == "--" {
+            } else if arg == "--" {
                 parsing = false;
             }
-
             // If the string starts with *two* dashes then it’s a long argument.
             else if bytes.starts_with(b"--") {
-                let long_arg_name = OsStr::from_bytes(&bytes[2..]);
+                let long_arg_name = OsStrBytes::from_bytes(&bytes[2..]).unwrap();
 
                 // If there’s an equals in it, then the string before the
                 // equals will be the flag’s name, and the string after it
                 // will be its value.
-                if let Some((before, after)) = split_on_equals(long_arg_name) {
-                    let arg = self.lookup_long(before)?;
+                if let Some((before, after)) = split_on_equals(&long_arg_name) {
+                    let arg = self.lookup_long(&*before)?;
                     let flag = Flag::Long(arg.long);
                     match arg.takes_value {
-                        Necessary(_)|Optional(_)  => result_flags.push((flag, Some(after))),
-                        Forbidden  => return Err(ParseError::ForbiddenValue { flag })
+                        Necessary(_) | Optional(_) => result_flags.push((flag, Some(after))),
+                        Forbidden => return Err(ParseError::ForbiddenValue { flag }),
                     }
                 }
-
                 // If there’s no equals, then the entire string (apart from
                 // the dashes) is the argument name.
                 else {
-                    let arg = self.lookup_long(long_arg_name)?;
+                    let arg = self.lookup_long(&*long_arg_name)?;
                     let flag = Flag::Long(arg.long);
                     match arg.takes_value {
-                        Forbidden         => result_flags.push((flag, None)),
+                        Forbidden => result_flags.push((flag, None)),
                         Necessary(values) => {
                             if let Some(next_arg) = inputs.next() {
-                                result_flags.push((flag, Some(next_arg)));
-                            }
-                            else {
-                                return Err(ParseError::NeedsValue { flag, values })
+                                result_flags.push((flag, Some(next_arg.clone())));
+                            } else {
+                                return Err(ParseError::NeedsValue { flag, values });
                             }
-                        },
+                        }
                         Optional(_) => {
                             if let Some(next_arg) = inputs.next() {
-                                result_flags.push((flag, Some(next_arg)));
-                            }
-                            else {
+                                result_flags.push((flag, Some(next_arg.clone())));
+                            } else {
                                 result_flags.push((flag, None));
                             }
                         }
                     }
                 }
             }
-
             // If the string starts with *one* dash then it’s one or more
             // short arguments.
             else if bytes.starts_with(b"-") && arg != "-" {
-                let short_arg = OsStr::from_bytes(&bytes[1..]);
+                let short_arg = OsStr::from_bytes(&bytes[1..]).unwrap();
 
                 // If there’s an equals in it, then the argument immediately
                 // before the equals was the one that has the value, with the
@@ -232,16 +226,19 @@ impl Args {
                 // There’s no way to give two values in a cluster like this:
                 // it’s an error if any of the first set of arguments actually
                 // takes a value.
-                if let Some((before, after)) = split_on_equals(short_arg) {
-                    let (arg_with_value, other_args) = before.as_bytes().split_last().unwrap();
+                if let Some((before, after)) = split_on_equals(&short_arg) {
+                    let bytes = before.to_bytes();
+                    let (arg_with_value, other_args) = bytes.split_last().unwrap();
 
                     // Process the characters immediately following the dash...
                     for byte in other_args {
                         let arg = self.lookup_short(*byte)?;
                         let flag = Flag::Short(*byte);
                         match arg.takes_value {
-                            Forbidden|Optional(_)  => result_flags.push((flag, None)),
-                            Necessary(values)  => return Err(ParseError::NeedsValue { flag, values })
+                            Forbidden | Optional(_) => result_flags.push((flag, None)),
+                            Necessary(values) => {
+                                return Err(ParseError::NeedsValue { flag, values })
+                            }
                         }
                     }
 
@@ -249,11 +246,10 @@ impl Args {
                     let arg = self.lookup_short(*arg_with_value)?;
                     let flag = Flag::Short(arg.short.unwrap());
                     match arg.takes_value {
-                        Necessary(_)|Optional(_)  => result_flags.push((flag, Some(after))),
-                        Forbidden  => return Err(ParseError::ForbiddenValue { flag })
+                        Necessary(_) | Optional(_) => result_flags.push((flag, Some(after))),
+                        Forbidden => return Err(ParseError::ForbiddenValue { flag }),
                     }
                 }
-
                 // If there’s no equals, then every character is parsed as
                 // its own short argument. However, if any of the arguments
                 // takes a value, then the *rest* of the string is used as
@@ -271,65 +267,70 @@ impl Args {
                         let arg = self.lookup_short(*byte)?;
                         let flag = Flag::Short(*byte);
                         match arg.takes_value {
-                            Forbidden         => result_flags.push((flag, None)),
-                            Necessary(values)|Optional(values) => {
+                            Forbidden => result_flags.push((flag, None)),
+                            Necessary(values) | Optional(values) => {
                                 if index < bytes.len() - 1 {
-                                    let remnants = &bytes[index+1 ..];
-                                    result_flags.push((flag, Some(OsStr::from_bytes(remnants))));
+                                    let remnants = &bytes[index + 1..];
+                                    result_flags.push((
+                                        flag,
+                                        Some(OsStringBytes::from_bytes(remnants).unwrap()),
+                                    ));
                                     break;
-                                }
-                                else if let Some(next_arg) = inputs.next() {
-                                    result_flags.push((flag, Some(next_arg)));
-                                }
-                                else {
+                                } else if let Some(next_arg) = inputs.next() {
+                                    result_flags.push((flag, Some(next_arg.clone())));
+                                } else {
                                     match arg.takes_value {
                                         Forbidden => assert!(false),
                                         Necessary(_) => {
                                             return Err(ParseError::NeedsValue { flag, values });
-                                        },
+                                        }
                                         Optional(_) => {
                                             result_flags.push((flag, None));
                                         }
                                     }
                                 }
-
                             }
                         }
                     }
                 }
             }
-
             // Otherwise, it’s a free string, usually a file name.
             else {
                 frees.push(arg)
             }
         }
 
-        Ok(Matches { frees, flags: MatchedFlags { flags: result_flags, strictness } })
+        Ok(Matches {
+            frees,
+            flags: MatchedFlags {
+                flags: result_flags,
+                strictness,
+            },
+        })
     }
 
     fn lookup_short(&self, short: ShortArg) -> Result<&Arg, ParseError> {
         match self.0.into_iter().find(|arg| arg.short == Some(short)) {
-            Some(arg)  => Ok(arg),
-            None       => Err(ParseError::UnknownShortArgument { attempt: short })
+            Some(arg) => Ok(arg),
+            None => Err(ParseError::UnknownShortArgument { attempt: short }),
         }
     }
 
     fn lookup_long<'b>(&self, long: &'b OsStr) -> Result<&Arg, ParseError> {
         match self.0.into_iter().find(|arg| arg.long == long) {
-            Some(arg)  => Ok(arg),
-            None       => Err(ParseError::UnknownArgument { attempt: long.to_os_string() })
+            Some(arg) => Ok(arg),
+            None => Err(ParseError::UnknownArgument {
+                attempt: long.to_os_string(),
+            }),
         }
     }
 }
 
-
 /// The **matches** are the result of parsing the user’s command-line strings.
 #[derive(PartialEq, Debug)]
 pub struct Matches<'args> {
-
     /// The flags that were parsed from the user’s input.
-    pub flags: MatchedFlags<'args>,
+    pub flags: MatchedFlags,
 
     /// All the strings that weren’t matched as arguments, as well as anything
     /// after the special "--" string.
@@ -337,27 +338,26 @@ pub struct Matches<'args> {
 }
 
 #[derive(PartialEq, Debug)]
-pub struct MatchedFlags<'args> {
-
+pub struct MatchedFlags {
     /// The individual flags from the user’s input, in the order they were
     /// originally given.
     ///
     /// Long and short arguments need to be kept in the same vector because
     /// we usually want the one nearest the end to count, and to know this,
     /// we need to know where they are in relation to one another.
-    flags: Vec<(Flag, Option<&'args OsStr>)>,
+    flags: Vec<(Flag, Option<OsString>)>,
 
     /// Whether to check for duplicate or redundant arguments.
     strictness: Strictness,
 }
 
-impl<'a> MatchedFlags<'a> {
-
+impl MatchedFlags {
     /// Whether the given argument was specified.
     /// Returns `true` if it was, `false` if it wasn’t, and an error in
     /// strict mode if it was specified more than once.
     pub fn has(&self, arg: &'static Arg) -> Result<bool, Misfire> {
-        self.has_where(|flag| flag.matches(arg)).map(|flag| flag.is_some())
+        self.has_where(|flag| flag.matches(arg))
+            .map(|flag| flag.is_some())
     }
 
     /// Returns the first found argument that satisfies the predicate, or
@@ -366,19 +366,28 @@ impl<'a> MatchedFlags<'a> {
     ///
     /// You’ll have to test the resulting flag to see which argument it was.
     pub fn has_where<P>(&self, predicate: P) -> Result<Option<&Flag>, Misfire>
-    where P: Fn(&Flag) -> bool {
+    where
+        P: Fn(&Flag) -> bool,
+    {
         if self.is_strict() {
-            let all = self.flags.iter()
-                          .filter(|tuple| tuple.1.is_none() && predicate(&tuple.0))
-                          .collect::<Vec<_>>();
-
-            if all.len() < 2 { Ok(all.first().map(|t| &t.0)) }
-                        else { Err(Misfire::Duplicate(all[0].0.clone(), all[1].0.clone())) }
-        }
-        else {
-            let any = self.flags.iter().rev()
-                          .find(|tuple| tuple.1.is_none() && predicate(&tuple.0))
-                          .map(|tuple| &tuple.0);
+            let all = self
+                .flags
+                .iter()
+                .filter(|tuple| tuple.1.is_none() && predicate(&tuple.0))
+                .collect::<Vec<_>>();
+
+            if all.len() < 2 {
+                Ok(all.first().map(|t| &t.0))
+            } else {
+                Err(Misfire::Duplicate(all[0].0.clone(), all[1].0.clone()))
+            }
+        } else {
+            let any = self
+                .flags
+                .iter()
+                .rev()
+                .find(|tuple| tuple.1.is_none() && predicate(&tuple.0))
+                .map(|tuple| &tuple.0);
             Ok(any)
         }
     }
@@ -390,7 +399,7 @@ impl<'a> MatchedFlags<'a> {
     /// Returns the value of the given argument if it was specified, nothing
     /// if it wasn’t, and an error in strict mode if it was specified more
     /// than once.
-    pub fn get(&self, arg: &'static Arg) -> Result<Option<&OsStr>, Misfire> {
+    pub fn get(&self, arg: &'static Arg) -> Result<Option<OsString>, Misfire> {
         self.get_where(|flag| flag.matches(arg))
     }
 
@@ -399,20 +408,29 @@ impl<'a> MatchedFlags<'a> {
     /// multiple arguments matched the predicate.
     ///
     /// It’s not possible to tell which flag the value belonged to from this.
-    pub fn get_where<P>(&self, predicate: P) -> Result<Option<&OsStr>, Misfire>
-    where P: Fn(&Flag) -> bool {
+    pub fn get_where<P>(&self, predicate: P) -> Result<Option<OsString>, Misfire>
+    where
+        P: Fn(&Flag) -> bool,
+    {
         if self.is_strict() {
-            let those = self.flags.iter()
-                            .filter(|tuple| tuple.1.is_some() && predicate(&tuple.0))
-                            .collect::<Vec<_>>();
-
-            if those.len() < 2 { Ok(those.first().cloned().map(|t| t.1.unwrap())) }
-                          else { Err(Misfire::Duplicate(those[0].0.clone(), those[1].0.clone())) }
-        }
-        else {
-            let found = self.flags.iter().rev()
-                            .find(|tuple| tuple.1.is_some() && predicate(&tuple.0))
-                            .map(|tuple| tuple.1.unwrap());
+            let those = self
+                .flags
+                .iter()
+                .filter(|tuple| tuple.1.is_some() && predicate(&tuple.0))
+                .collect::<Vec<_>>();
+
+            if those.len() < 2 {
+                Ok(those.first().cloned().map(|t| t.clone().1.unwrap()))
+            } else {
+                Err(Misfire::Duplicate(those[0].0.clone(), those[1].0.clone()))
+            }
+        } else {
+            let found = self
+                .flags
+                .iter()
+                .rev()
+                .find(|tuple| tuple.1.is_some() && predicate(&tuple.0))
+                .map(|tuple| tuple.clone().1.unwrap());
             Ok(found)
         }
     }
@@ -423,7 +441,8 @@ impl<'a> MatchedFlags<'a> {
     /// Counts the number of occurrences of the given argument, even in
     /// strict mode.
     pub fn count(&self, arg: &Arg) -> usize {
-        self.flags.iter()
+        self.flags
+            .iter()
             .filter(|tuple| tuple.0.matches(arg))
             .count()
     }
@@ -435,12 +454,10 @@ impl<'a> MatchedFlags<'a> {
     }
 }
 
-
 /// A problem with the user’s input that meant it couldn’t be parsed into a
 /// coherent list of arguments.
 #[derive(PartialEq, Debug)]
 pub enum ParseError {
-
     /// A flag that has to take a value was not given one.
     NeedsValue { flag: Flag, values: Option<Values> },
 
@@ -462,26 +479,25 @@ pub enum ParseError {
 // which would give Misfire a lifetime, which gets used everywhere. And this
 // only happens when an error occurs, so it’s not really worth it.
 
-
 /// Splits a string on its `=` character, returning the two substrings on
 /// either side. Returns `None` if there’s no equals or a string is missing.
-fn split_on_equals(input: &OsStr) -> Option<(&OsStr, &OsStr)> {
-    use std::os::unix::ffi::OsStrExt;
-
-    if let Some(index) = input.as_bytes().iter().position(|elem| *elem == b'=') {
-        let (before, after) = input.as_bytes().split_at(index);
+fn split_on_equals<'a>(input: &'a Cow<OsStr>) -> Option<(OsString, OsString)> {
+    if let Some(index) = input.to_bytes().iter().position(|elem| *elem == b'=') {
+        let bytes = input.to_bytes();
+        let (before, after) = bytes.split_at(index);
 
         // The after string contains the = that we need to remove.
         if !before.is_empty() && after.len() >= 2 {
-            return Some((OsStr::from_bytes(before),
-                         OsStr::from_bytes(&after[1..])))
+            return Some((
+                OsStringBytes::from_bytes(before).unwrap(),
+                OsStringBytes::from_bytes(&after[1..]).unwrap(),
+            ));
         }
     }
 
     None
 }
 
-
 /// Creates an `OSString` (used in tests)
 #[cfg(test)]
 fn os(input: &'static str) -> OsString {
@@ -490,25 +506,27 @@ fn os(input: &'static str) -> OsString {
     os
 }
 
-
 #[cfg(test)]
 mod split_test {
-    use super::{split_on_equals, os};
+    use std::borrow::Cow;
+
+    use super::{os, split_on_equals};
 
     macro_rules! test_split {
         ($name:ident: $input:expr => None) => {
             #[test]
             fn $name() {
-                assert_eq!(split_on_equals(&os($input)),
-                           None);
+                assert_eq!(split_on_equals(&Cow::from(os($input))), None);
             }
         };
 
         ($name:ident: $input:expr => $before:expr, $after:expr) => {
             #[test]
             fn $name() {
-                assert_eq!(split_on_equals(&os($input)),
-                           Some((&*os($before), &*os($after))));
+                assert_eq!(
+                    split_on_equals(&Cow::from(os($input))),
+                    Some((os($before), os($after)))
+                );
             }
         };
     }
@@ -525,7 +543,6 @@ mod split_test {
     test_split!(more: "this=that=other" => "this",   "that=other");
 }
 
-
 #[cfg(test)]
 mod parse_test {
     use super::*;
@@ -536,12 +553,10 @@ mod parse_test {
         os
     }
 
-
     macro_rules! test {
         ($name:ident: $inputs:expr => frees: $frees:expr, flags: $flags:expr) => {
             #[test]
             fn $name() {
-
                 // Annoyingly the input &strs need to be converted to OsStrings
                 let inputs: Vec<OsString> = $inputs.as_ref().into_iter().map(|&o| os(o)).collect();
 
@@ -551,9 +566,12 @@ mod parse_test {
 
                 let flags = <[_]>::into_vec(Box::new($flags));
 
-                let strictness = Strictness::UseLastArguments;  // this isn’t even used
+                let strictness = Strictness::UseLastArguments; // this isn’t even used
                 let got = Args(TEST_ARGS).parse(inputs.iter(), strictness);
-                let expected = Ok(Matches { frees, flags: MatchedFlags { flags, strictness } });
+                let expected = Ok(Matches {
+                    frees,
+                    flags: MatchedFlags { flags, strictness },
+                });
                 assert_eq!(got, expected);
             }
         };
@@ -563,8 +581,12 @@ mod parse_test {
             fn $name() {
                 use self::ParseError::*;
 
-                let strictness = Strictness::UseLastArguments;  // this isn’t even used
-                let bits = $inputs.as_ref().into_iter().map(|&o| os(o)).collect::<Vec<OsString>>();
+                let strictness = Strictness::UseLastArguments; // this isn’t even used
+                let bits = $inputs
+                    .as_ref()
+                    .into_iter()
+                    .map(|&o| os(o))
+                    .collect::<Vec<OsString>>();
                 let got = Args(TEST_ARGS).parse(bits.iter(), strictness);
 
                 assert_eq!(got, Err($error));
@@ -572,16 +594,31 @@ mod parse_test {
         };
     }
 
-    const SUGGESTIONS: Values = &[ "example" ];
+    const SUGGESTIONS: Values = &["example"];
 
     static TEST_ARGS: &[&Arg] = &[
-        &Arg { short: Some(b'l'), long: "long",     takes_value: TakesValue::Forbidden },
-        &Arg { short: Some(b'v'), long: "verbose",  takes_value: TakesValue::Forbidden },
-        &Arg { short: Some(b'c'), long: "count",    takes_value: TakesValue::Necessary(None) },
-        &Arg { short: Some(b't'), long: "type",     takes_value: TakesValue::Necessary(Some(SUGGESTIONS)) }
+        &Arg {
+            short: Some(b'l'),
+            long: "long",
+            takes_value: TakesValue::Forbidden,
+        },
+        &Arg {
+            short: Some(b'v'),
+            long: "verbose",
+            takes_value: TakesValue::Forbidden,
+        },
+        &Arg {
+            short: Some(b'c'),
+            long: "count",
+            takes_value: TakesValue::Necessary(None),
+        },
+        &Arg {
+            short: Some(b't'),
+            long: "type",
+            takes_value: TakesValue::Necessary(Some(SUGGESTIONS)),
+        },
     ];
 
-
     // Just filenames
     test!(empty:       []       => frees: [],         flags: []);
     test!(one_arg:     ["exa"]  => frees: [ "exa" ],  flags: []);
@@ -593,7 +630,6 @@ mod parse_test {
     test!(two_arg_l:   ["--", "--long"]  => frees: [ "--long" ],  flags: []);
     test!(two_arg_s:   ["--", "-l"]      => frees: [ "-l" ],      flags: []);
 
-
     // Long args
     test!(long:        ["--long"]               => frees: [],       flags: [ (Flag::Long("long"), None) ]);
     test!(long_then:   ["--long", "4"]          => frees: [ "4" ],  flags: [ (Flag::Long("long"), None) ]);
@@ -602,14 +638,13 @@ mod parse_test {
     // Long args with values
     test!(bad_equals:  ["--long=equals"]  => error ForbiddenValue { flag: Flag::Long("long") });
     test!(no_arg:      ["--count"]        => error NeedsValue     { flag: Flag::Long("count"), values: None });
-    test!(arg_equals:  ["--count=4"]      => frees: [],  flags: [ (Flag::Long("count"), Some(OsStr::new("4"))) ]);
-    test!(arg_then:    ["--count", "4"]   => frees: [],  flags: [ (Flag::Long("count"), Some(OsStr::new("4"))) ]);
+    test!(arg_equals:  ["--count=4"]      => frees: [],  flags: [ (Flag::Long("count"), Some(OsString::from("4"))) ]);
+    test!(arg_then:    ["--count", "4"]   => frees: [],  flags: [ (Flag::Long("count"), Some(OsString::from("4"))) ]);
 
     // Long args with values and suggestions
     test!(no_arg_s:      ["--type"]         => error NeedsValue { flag: Flag::Long("type"), values: Some(SUGGESTIONS) });
-    test!(arg_equals_s:  ["--type=exa"]     => frees: [],  flags: [ (Flag::Long("type"), Some(OsStr::new("exa"))) ]);
-    test!(arg_then_s:    ["--type", "exa"]  => frees: [],  flags: [ (Flag::Long("type"), Some(OsStr::new("exa"))) ]);
-
+    test!(arg_equals_s:  ["--type=exa"]     => frees: [],  flags: [ (Flag::Long("type"), Some(OsString::from("exa"))) ]);
+    test!(arg_then_s:    ["--type", "exa"]  => frees: [],  flags: [ (Flag::Long("type"), Some(OsString::from("exa"))) ]);
 
     // Short args
     test!(short:       ["-l"]            => frees: [],       flags: [ (Flag::Short(b'l'), None) ]);
@@ -620,18 +655,17 @@ mod parse_test {
     // Short args with values
     test!(bad_short:          ["-l=equals"]   => error ForbiddenValue { flag: Flag::Short(b'l') });
     test!(short_none:         ["-c"]          => error NeedsValue     { flag: Flag::Short(b'c'), values: None });
-    test!(short_arg_eq:       ["-c=4"]        => frees: [],  flags: [(Flag::Short(b'c'), Some(OsStr::new("4"))) ]);
-    test!(short_arg_then:     ["-c", "4"]     => frees: [],  flags: [(Flag::Short(b'c'), Some(OsStr::new("4"))) ]);
-    test!(short_two_together: ["-lctwo"]      => frees: [],  flags: [(Flag::Short(b'l'), None), (Flag::Short(b'c'), Some(OsStr::new("two"))) ]);
-    test!(short_two_equals:   ["-lc=two"]     => frees: [],  flags: [(Flag::Short(b'l'), None), (Flag::Short(b'c'), Some(OsStr::new("two"))) ]);
-    test!(short_two_next:     ["-lc", "two"]  => frees: [],  flags: [(Flag::Short(b'l'), None), (Flag::Short(b'c'), Some(OsStr::new("two"))) ]);
+    test!(short_arg_eq:       ["-c=4"]        => frees: [],  flags: [(Flag::Short(b'c'), Some(OsString::from("4"))) ]);
+    test!(short_arg_then:     ["-c", "4"]     => frees: [],  flags: [(Flag::Short(b'c'), Some(OsString::from("4"))) ]);
+    test!(short_two_together: ["-lctwo"]      => frees: [],  flags: [(Flag::Short(b'l'), None), (Flag::Short(b'c'), Some(OsString::from("two"))) ]);
+    test!(short_two_equals:   ["-lc=two"]     => frees: [],  flags: [(Flag::Short(b'l'), None), (Flag::Short(b'c'), Some(OsString::from("two"))) ]);
+    test!(short_two_next:     ["-lc", "two"]  => frees: [],  flags: [(Flag::Short(b'l'), None), (Flag::Short(b'c'), Some(OsString::from("two"))) ]);
 
     // Short args with values and suggestions
     test!(short_none_s:         ["-t"]         => error NeedsValue { flag: Flag::Short(b't'), values: Some(SUGGESTIONS) });
-    test!(short_two_together_s: ["-texa"]      => frees: [],  flags: [(Flag::Short(b't'), Some(OsStr::new("exa"))) ]);
-    test!(short_two_equals_s:   ["-t=exa"]     => frees: [],  flags: [(Flag::Short(b't'), Some(OsStr::new("exa"))) ]);
-    test!(short_two_next_s:     ["-t", "exa"]  => frees: [],  flags: [(Flag::Short(b't'), Some(OsStr::new("exa"))) ]);
-
+    test!(short_two_together_s: ["-texa"]      => frees: [],  flags: [(Flag::Short(b't'), Some(OsString::from("exa"))) ]);
+    test!(short_two_equals_s:   ["-t=exa"]     => frees: [],  flags: [(Flag::Short(b't'), Some(OsString::from("exa"))) ]);
+    test!(short_two_next_s:     ["-t", "exa"]  => frees: [],  flags: [(Flag::Short(b't'), Some(OsString::from("exa"))) ]);
 
     // Unknown args
     test!(unknown_long:          ["--quiet"]      => error UnknownArgument      { attempt: os("quiet") });
@@ -642,7 +676,6 @@ mod parse_test {
     test!(unknown_short_2nd_eq:  ["-lq=shhh"]     => error UnknownShortArgument { attempt: b'q' });
 }
 
-
 #[cfg(test)]
 mod matches_test {
     use super::*;
@@ -661,9 +694,16 @@ mod matches_test {
         };
     }
 
-    static VERBOSE: Arg = Arg { short: Some(b'v'), long: "verbose", takes_value: TakesValue::Forbidden };
-    static COUNT:   Arg = Arg { short: Some(b'c'), long: "count",   takes_value: TakesValue::Necessary(None) };
-
+    static VERBOSE: Arg = Arg {
+        short: Some(b'v'),
+        long: "verbose",
+        takes_value: TakesValue::Forbidden,
+    };
+    static COUNT: Arg = Arg {
+        short: Some(b'c'),
+        long: "count",
+        takes_value: TakesValue::Necessary(None),
+    };
 
     test!(short_never:  [],                                                              has VERBOSE => false);
     test!(short_once:   [(Flag::Short(b'v'), None)],                                     has VERBOSE => true);
@@ -672,36 +712,40 @@ mod matches_test {
     test!(long_twice:   [(Flag::Long("verbose"), None), (Flag::Long("verbose"), None)],  has VERBOSE => true);
     test!(long_mixed:   [(Flag::Long("verbose"), None), (Flag::Short(b'v'), None)],      has VERBOSE => true);
 
-
     #[test]
     fn only_count() {
         let everything = os("everything");
 
         let flags = MatchedFlags {
-            flags: vec![ (Flag::Short(b'c'), Some(&*everything)) ],
+            flags: vec![(Flag::Short(b'c'), Some(everything.clone()))],
             strictness: Strictness::UseLastArguments,
         };
 
-        assert_eq!(flags.get(&COUNT), Ok(Some(&*everything)));
+        assert_eq!(flags.get(&COUNT), Ok(Some(everything)));
     }
 
     #[test]
     fn rightmost_count() {
         let everything = os("everything");
-        let nothing    = os("nothing");
+        let nothing = os("nothing");
 
         let flags = MatchedFlags {
-            flags: vec![ (Flag::Short(b'c'), Some(&*everything)),
-                         (Flag::Short(b'c'), Some(&*nothing)) ],
+            flags: vec![
+                (Flag::Short(b'c'), Some(everything)),
+                (Flag::Short(b'c'), Some(nothing.clone())),
+            ],
             strictness: Strictness::UseLastArguments,
         };
 
-        assert_eq!(flags.get(&COUNT), Ok(Some(&*nothing)));
+        assert_eq!(flags.get(&COUNT), Ok(Some(nothing)));
     }
 
     #[test]
     fn no_count() {
-        let flags = MatchedFlags { flags: Vec::new(), strictness: Strictness::UseLastArguments };
+        let flags = MatchedFlags {
+            flags: Vec::new(),
+            strictness: Strictness::UseLastArguments,
+        };
 
         assert!(!flags.has(&COUNT).unwrap());
     }

+ 4 - 4
src/options/style.rs

@@ -45,13 +45,13 @@ impl TerminalColours {
             None    => return Ok(TerminalColours::default()),
         };
 
-        if word == "always" {
+        if &*word == "always" {
             Ok(TerminalColours::Always)
         }
-        else if word == "auto" || word == "automatic" {
+        else if &*word == "auto" || &*word == "automatic" {
             Ok(TerminalColours::Automatic)
         }
-        else if word == "never" {
+        else if &*word == "never" {
             Ok(TerminalColours::Never)
         }
         else {
@@ -124,7 +124,7 @@ impl Styles {
 /// type mappings or not. The `reset` code needs to be the first one.
 fn parse_color_vars<V: Vars>(vars: &V, colours: &mut Colours) -> (ExtensionMappings, bool) {
     use log::warn;
-    
+
     use crate::options::vars;
     use crate::style::LSColors;
 

+ 4 - 4
src/options/view.rs

@@ -332,16 +332,16 @@ impl TimeTypes {
             else if created {
                 return Err(Misfire::Useless(&flags::CREATED, true, &flags::TIME));
             }
-            else if word == "mod" || word == "modified" {
+            else if &*word == "mod" || &*word == "modified" {
                 TimeTypes { modified: true,  changed: false, accessed: false, created: false }
             }
-            else if word == "ch" || word == "changed" {
+            else if &*word == "ch" || &*word == "changed" {
                 TimeTypes { modified: false, changed: true,  accessed: false, created: false }
             }
-            else if word == "acc" || word == "accessed" {
+            else if &*word == "acc" || &*word == "accessed" {
                 TimeTypes { modified: false, changed: false, accessed: true,  created: false }
             }
-            else if word == "cr" || word == "created" {
+            else if &*word == "cr" || &*word == "created" {
                 TimeTypes { modified: false, changed: false, accessed: false, created: true  }
             }
             else {

+ 16 - 0
src/output/file_name.rs

@@ -198,6 +198,17 @@ impl<'a, 'dir, C: Colours> FileName<'a, 'dir, C> {
 
     /// The character to be displayed after a file when classifying is on, if
     /// the file’s type has one associated with it.
+    #[cfg(windows)]
+    fn classify_char(&self) -> Option<&'static str> {
+        if self.file.is_directory() {
+            Some("/")
+        } else if self.file.is_link() {
+            Some("@")
+        } else {
+            None
+        }
+    }
+    #[cfg(unix)]
     fn classify_char(&self) -> Option<&'static str> {
         if self.file.is_executable_file() {
             Some("*")
@@ -254,11 +265,16 @@ impl<'a, 'dir, C: Colours> FileName<'a, 'dir, C> {
     fn kind_style(&self) -> Option<Style> {
         Some(match self.file {
             f if f.is_directory()        => self.colours.directory(),
+            #[cfg(unix)]
             f if f.is_executable_file()  => self.colours.executable_file(),
             f if f.is_link()             => self.colours.symlink(),
+            #[cfg(unix)]
             f if f.is_pipe()             => self.colours.pipe(),
+            #[cfg(unix)]
             f if f.is_block_device()     => self.colours.block_device(),
+            #[cfg(unix)]
             f if f.is_char_device()      => self.colours.char_device(),
+            #[cfg(unix)]
             f if f.is_socket()           => self.colours.socket(),
             f if !f.is_file()            => self.colours.special(),
             _                            => return None,

+ 4 - 0
src/output/render/mod.rs

@@ -7,7 +7,9 @@ pub use self::filetype::Colours as FiletypeColours;
 mod git;
 pub use self::git::Colours as GitColours;
 
+#[cfg(unix)]
 mod groups;
+#[cfg(unix)]
 pub use self::groups::Colours as GroupColours;
 
 mod inode;
@@ -26,5 +28,7 @@ mod times;
 pub use self::times::Render as TimeRender;
 // times does too
 
+#[cfg(unix)]
 mod users;
+#[cfg(unix)]
 pub use self::users::Colours as UserColours;

+ 1 - 0
src/output/render/permissions.rs

@@ -8,6 +8,7 @@ use crate::output::render::FiletypeColours;
 impl f::PermissionsPlus {
     pub fn render<C: Colours+FiletypeColours>(&self, colours: &C) -> TextCell {
         let mut chars = vec![ self.file_type.render(colours) ];
+        #[cfg(unix)]
         chars.extend(self.permissions.render(colours, self.file_type.is_regular_file()));
 
         if self.xattrs {

+ 138 - 68
src/output/table.rs

@@ -1,6 +1,7 @@
 use std::cmp::max;
 use std::fmt;
 use std::ops::Deref;
+#[cfg(unix)]
 use std::sync::{Mutex, MutexGuard};
 
 use datetime::TimeZone;
@@ -10,15 +11,15 @@ use locale;
 
 use log::debug;
 
+#[cfg(unix)]
 use users::UsersCache;
 
-use crate::style::Colours;
+use crate::fs::feature::git::GitCache;
+use crate::fs::{fields as f, File};
 use crate::output::cell::TextCell;
 use crate::output::render::TimeRender;
 use crate::output::time::TimeFormat;
-use crate::fs::{File, fields as f};
-use crate::fs::feature::git::GitCache;
-
+use crate::style::Colours;
 
 /// Options for displaying a table.
 pub struct Options {
@@ -39,7 +40,6 @@ impl fmt::Debug for Options {
 /// Extra columns to display in the table.
 #[derive(PartialEq, Debug)]
 pub struct Columns {
-
     /// At least one of these timestamps will be shown.
     pub time_types: TimeTypes,
 
@@ -61,14 +61,17 @@ impl Columns {
         let mut columns = Vec::with_capacity(4);
 
         if self.inode {
+            #[cfg(unix)]
             columns.push(Column::Inode);
         }
 
         if self.permissions {
+            #[cfg(unix)]
             columns.push(Column::Permissions);
         }
 
         if self.links {
+            #[cfg(unix)]
             columns.push(Column::HardLinks);
         }
 
@@ -77,34 +80,41 @@ impl Columns {
         }
 
         if self.blocks {
+            #[cfg(unix)]
             columns.push(Column::Blocks);
         }
 
         if self.user {
+            #[cfg(unix)]
             columns.push(Column::User);
         }
 
         if self.group {
+            #[cfg(unix)]
             columns.push(Column::Group);
         }
 
         if self.time_types.modified {
+            #[cfg(unix)]
             columns.push(Column::Timestamp(TimeType::Modified));
         }
 
         if self.time_types.changed {
+            #[cfg(unix)]
             columns.push(Column::Timestamp(TimeType::Changed));
         }
 
         if self.time_types.created {
+            #[cfg(unix)]
             columns.push(Column::Timestamp(TimeType::Created));
         }
 
         if self.time_types.accessed {
+            #[cfg(unix)]
             columns.push(Column::Timestamp(TimeType::Accessed));
         }
 
-        if cfg!(feature="git") && self.git && actually_enable_git {
+        if cfg!(feature = "git") && self.git && actually_enable_git {
             columns.push(Column::GitStatus);
         }
 
@@ -112,17 +122,23 @@ impl Columns {
     }
 }
 
-
 /// A table contains these.
 #[derive(Debug)]
 pub enum Column {
+    #[cfg(unix)]
     Permissions,
     FileSize,
+    #[cfg(unix)]
     Timestamp(TimeType),
+    #[cfg(unix)]
     Blocks,
+    #[cfg(unix)]
     User,
+    #[cfg(unix)]
     Group,
+    #[cfg(unix)]
     HardLinks,
+    #[cfg(unix)]
     Inode,
     GitStatus,
 }
@@ -131,12 +147,13 @@ pub enum Column {
 /// right-aligned, and text is left-aligned.
 #[derive(Copy, Clone)]
 pub enum Alignment {
-    Left, Right,
+    Left,
+    Right,
 }
 
 impl Column {
-
     /// Get the alignment this column should use.
+    #[cfg(unix)]
     pub fn alignment(&self) -> Alignment {
         match *self {
             Column::FileSize
@@ -144,32 +161,45 @@ impl Column {
             | Column::Inode
             | Column::Blocks
             | Column::GitStatus => Alignment::Right,
-            _                   => Alignment::Left,
+            _ => Alignment::Left,
         }
     }
+    #[cfg(windows)]
+    pub fn alignment(&self) -> Alignment {
+        match *self {
+            Column::FileSize | Column::GitStatus => Alignment::Right,
+            _ => Alignment::Left,
+        }
+    }
+    #[cfg(windows)]
 
     /// Get the text that should be printed at the top, when the user elects
     /// to have a header row printed.
     pub fn header(&self) -> &'static str {
         match *self {
-            Column::Permissions   => "Permissions",
-            Column::FileSize      => "Size",
-            Column::Timestamp(t)  => t.header(),
-            Column::Blocks        => "Blocks",
-            Column::User          => "User",
-            Column::Group         => "Group",
-            Column::HardLinks     => "Links",
-            Column::Inode         => "inode",
-            Column::GitStatus     => "Git",
+            #[cfg(unix)]
+            Column::Permissions => "Permissions",
+            Column::FileSize => "Size",
+            #[cfg(unix)]
+            Column::Timestamp(t) => t.header(),
+            #[cfg(unix)]
+            Column::Blocks => "Blocks",
+            #[cfg(unix)]
+            Column::User => "User",
+            #[cfg(unix)]
+            Column::Group => "Group",
+            #[cfg(unix)]
+            Column::HardLinks => "Links",
+            #[cfg(unix)]
+            Column::Inode => "inode",
+            Column::GitStatus => "Git",
         }
     }
 }
 
-
 /// Formatting options for file sizes.
 #[derive(PartialEq, Debug, Copy, Clone)]
 pub enum SizeFormat {
-
     /// Format the file size using **decimal** prefixes, such as “kilo”,
     /// “mega”, or “giga”.
     DecimalBytes,
@@ -188,7 +218,6 @@ impl Default for SizeFormat {
     }
 }
 
-
 /// The types of a file’s time fields. These three fields are standard
 /// across most (all?) operating systems.
 #[derive(PartialEq, Debug, Copy, Clone)]
@@ -207,19 +236,17 @@ pub enum TimeType {
 }
 
 impl TimeType {
-
     /// Returns the text to use for a column’s heading in the columns output.
     pub fn header(self) -> &'static str {
         match self {
-            TimeType::Modified  => "Date Modified",
-            TimeType::Changed   => "Date Changed",
-            TimeType::Accessed  => "Date Accessed",
-            TimeType::Created   => "Date Created",
+            TimeType::Modified => "Date Modified",
+            TimeType::Changed => "Date Changed",
+            TimeType::Accessed => "Date Accessed",
+            TimeType::Created => "Date Created",
         }
     }
 }
 
-
 /// Fields for which of a file’s time fields should be displayed in the
 /// columns output.
 ///
@@ -228,29 +255,29 @@ impl TimeType {
 #[derive(PartialEq, Debug, Copy, Clone)]
 pub struct TimeTypes {
     pub modified: bool,
-    pub changed:  bool,
+    pub changed: bool,
     pub accessed: bool,
-    pub created:  bool,
+    pub created: bool,
 }
 
 impl Default for TimeTypes {
-
     /// By default, display just the ‘modified’ time. This is the most
     /// common option, which is why it has this shorthand.
     fn default() -> TimeTypes {
-        TimeTypes { modified: true, changed: false, accessed: false, created: false }
+        TimeTypes {
+            modified: true,
+            changed: false,
+            accessed: false,
+            created: false,
+        }
     }
 }
 
-
-
-
 /// The **environment** struct contains any data that could change between
 /// running instances of exa, depending on the user's computer's configuration.
 ///
 /// Any environment field should be able to be mocked up for test runs.
 pub struct Environment {
-
     /// Localisation rules for formatting numbers.
     numeric: locale::Numeric,
 
@@ -259,10 +286,12 @@ pub struct Environment {
     tz: Option<TimeZone>,
 
     /// Mapping cache of user IDs to usernames.
+    #[cfg(unix)]
     users: Mutex<UsersCache>,
 }
 
 impl Environment {
+    #[cfg(unix)]
     pub fn lock_users(&self) -> MutexGuard<UsersCache> {
         self.users.lock().unwrap()
     }
@@ -276,12 +305,17 @@ impl Environment {
             }
         };
 
-        let numeric = locale::Numeric::load_user_locale()
-                          .unwrap_or_else(|_| locale::Numeric::english());
-
+        let numeric =
+            locale::Numeric::load_user_locale().unwrap_or_else(|_| locale::Numeric::english());
+        #[cfg(unix)]
         let users = Mutex::new(UsersCache::new());
 
-        Environment { tz, numeric, users }
+        Environment {
+            tz,
+            numeric,
+            #[cfg(unix)]
+            users,
+        }
     }
 }
 
@@ -289,7 +323,6 @@ fn determine_time_zone() -> TZResult<TimeZone> {
     TimeZone::from_file("/etc/localtime")
 }
 
-
 pub struct Table<'a> {
     columns: Vec<Column>,
     colours: &'a Colours,
@@ -311,10 +344,13 @@ impl<'a, 'f> Table<'a> {
         let widths = TableWidths::zero(columns.len());
 
         Table {
-            colours, widths, columns, git,
-            env:         &options.env,
+            colours,
+            widths,
+            columns,
+            git,
+            env: &options.env,
             time_format: &options.time_format,
-            size_format:  options.size_format,
+            size_format: options.size_format,
         }
     }
 
@@ -323,17 +359,21 @@ impl<'a, 'f> Table<'a> {
     }
 
     pub fn header_row(&self) -> Row {
-        let cells = self.columns.iter()
-                        .map(|c| TextCell::paint_str(self.colours.header, c.header()))
-                        .collect();
+        let cells = self
+            .columns
+            .iter()
+            .map(|c| TextCell::paint_str(self.colours.header, c.header()))
+            .collect();
 
         Row { cells }
     }
 
     pub fn row_for_file(&self, file: &File, xattrs: bool) -> Row {
-        let cells = self.columns.iter()
-                        .map(|c| self.display(file, c, xattrs))
-                        .collect();
+        let cells = self
+            .columns
+            .iter()
+            .map(|c| self.display(file, c, xattrs))
+            .collect();
 
         Row { cells }
     }
@@ -345,6 +385,7 @@ impl<'a, 'f> Table<'a> {
     fn permissions_plus(&self, file: &File, xattrs: bool) -> f::PermissionsPlus {
         f::PermissionsPlus {
             file_type: file.type_char(),
+            #[cfg(unix)]
             permissions: file.permissions(),
             xattrs,
         }
@@ -354,19 +395,44 @@ impl<'a, 'f> Table<'a> {
         use crate::output::table::TimeType::*;
 
         match *column {
-            Column::Permissions    => self.permissions_plus(file, xattrs).render(self.colours),
-            Column::FileSize       => file.size().render(self.colours, self.size_format, &self.env.numeric),
-            Column::HardLinks      => file.links().render(self.colours, &self.env.numeric),
-            Column::Inode          => file.inode().render(self.colours.inode),
-            Column::Blocks         => file.blocks().render(self.colours),
-            Column::User           => file.user().render(self.colours, &*self.env.lock_users()),
-            Column::Group          => file.group().render(self.colours, &*self.env.lock_users()),
-            Column::GitStatus      => self.git_status(file).render(self.colours),
-
-            Column::Timestamp(Modified)  => file.modified_time().render(self.colours.date, &self.env.tz, &self.time_format),
-            Column::Timestamp(Changed)   => file.changed_time() .render(self.colours.date, &self.env.tz, &self.time_format),
-            Column::Timestamp(Created)   => file.created_time() .render(self.colours.date, &self.env.tz, &self.time_format),
-            Column::Timestamp(Accessed)  => file.accessed_time().render(self.colours.date, &self.env.tz, &self.time_format),
+            #[cfg(unix)]
+            Column::Permissions => self.permissions_plus(file, xattrs).render(self.colours),
+            Column::FileSize => {
+                file.size()
+                    .render(self.colours, self.size_format, &self.env.numeric)
+            }
+            #[cfg(unix)]
+            Column::HardLinks => file.links().render(self.colours, &self.env.numeric),
+            #[cfg(unix)]
+            Column::Inode => file.inode().render(self.colours.inode),
+            #[cfg(unix)]
+            Column::Blocks => file.blocks().render(self.colours),
+            #[cfg(unix)]
+            Column::User => file.user().render(self.colours, &*self.env.lock_users()),
+            #[cfg(unix)]
+            Column::Group => file.group().render(self.colours, &*self.env.lock_users()),
+            Column::GitStatus => self.git_status(file).render(self.colours),
+
+            #[cfg(unix)]
+            Column::Timestamp(Modified) => {
+                file.modified_time()
+                    .render(self.colours.date, &self.env.tz, &self.time_format)
+            }
+            #[cfg(unix)]
+            Column::Timestamp(Changed) => {
+                file.changed_time()
+                    .render(self.colours.date, &self.env.tz, &self.time_format)
+            }
+            #[cfg(unix)]
+            Column::Timestamp(Created) => {
+                file.created_time()
+                    .render(self.colours.date, &self.env.tz, &self.time_format)
+            }
+            #[cfg(unix)]
+            Column::Timestamp(Accessed) => {
+                file.accessed_time()
+                    .render(self.colours.date, &self.env.tz, &self.time_format)
+            }
         }
     }
 
@@ -384,8 +450,14 @@ impl<'a, 'f> Table<'a> {
             let padding = width - *this_cell.width;
 
             match self.columns[n].alignment() {
-                Alignment::Left  => { cell.append(this_cell); cell.add_spaces(padding); }
-                Alignment::Right => { cell.add_spaces(padding); cell.append(this_cell); }
+                Alignment::Left => {
+                    cell.append(this_cell);
+                    cell.add_spaces(padding);
+                }
+                Alignment::Right => {
+                    cell.add_spaces(padding);
+                    cell.append(this_cell);
+                }
             }
 
             cell.add_spaces(1);
@@ -395,8 +467,6 @@ impl<'a, 'f> Table<'a> {
     }
 }
 
-
-
 pub struct TableWidths(Vec<usize>);
 
 impl Deref for TableWidths {
@@ -409,7 +479,7 @@ impl Deref for TableWidths {
 
 impl TableWidths {
     pub fn zero(count: usize) -> TableWidths {
-        TableWidths(vec![ 0; count ])
+        TableWidths(vec![0; count])
     }
 
     pub fn add_widths(&mut self, row: &Row) {

+ 2 - 0
src/style/colours.rs

@@ -396,6 +396,7 @@ impl render::GitColours for Colours {
     fn ignored(&self)       -> Style { self.git.ignored }
 }
 
+#[cfg(unix)]
 impl render::GroupColours for Colours {
     fn yours(&self)      -> Style { self.users.group_yours }
     fn not_yours(&self)  -> Style { self.users.group_not_yours }
@@ -452,6 +453,7 @@ impl render::SizeColours for Colours {
     fn minor(&self)   -> Style { self.size.minor }
 }
 
+#[cfg(unix)]
 impl render::UserColours for Colours {
     fn you(&self)           -> Style { self.users.user_you }
     fn someone_else(&self)  -> Style { self.users.user_someone_else }