mod.rs 9.6 KB
Newer Older
1 2
use std::ffi::OsStr;

B
Benjamin Sago 已提交
3 4 5 6 7 8 9 10 11 12 13 14
use getopts;

use fs::feature::xattr;
use output::{Details, GridDetails};

mod dir_action;
pub use self::dir_action::{DirAction, RecurseOptions};

mod filter;
pub use self::filter::{FileFilter, SortField, SortCase};

mod help;
15
use self::help::HelpString;
B
Benjamin Sago 已提交
16 17 18 19 20 21 22 23 24 25

mod misfire;
pub use self::misfire::Misfire;

mod view;
pub use self::view::View;


/// These **options** represent a parsed, error-checked versions of the
/// user’s command-line options.
B
Ben S 已提交
26
#[derive(PartialEq, Debug, Clone)]
B
Benjamin Sago 已提交
27 28 29 30 31 32 33 34 35 36 37 38 39 40 41
pub struct Options {

    /// The action to perform when encountering a directory rather than a
    /// regular file.
    pub dir_action: DirAction,

    /// How to sort and filter files before outputting them.
    pub filter: FileFilter,

    /// The type of output to use (lines, grid, or details).
    pub view: View,
}

impl Options {

42 43 44 45 46 47
    // Even though the arguments go in as OsStrings, they come out
    // as Strings. Invalid UTF-8 won’t be parsed, but it won’t make
    // exa core dump either.
    //
    // https://github.com/rust-lang-nursery/getopts/pull/29

B
Benjamin Sago 已提交
48 49
    /// Call getopts on the given slice of command-line strings.
    #[allow(unused_results)]
50 51
    pub fn getopts<C>(args: C) -> Result<(Options, Vec<String>), Misfire>
    where C: IntoIterator, C::Item: AsRef<OsStr> {
B
Benjamin Sago 已提交
52 53
        let mut opts = getopts::Options::new();

54
        opts.optflag("v", "version",   "show version of exa");
B
Benjamin Sago 已提交
55 56 57
        opts.optflag("?", "help",      "show list of command-line options");

        // Display options
B
Ben S 已提交
58
        opts.optflag("1", "oneline",      "display one entry per line");
59 60 61
        opts.optflag("l", "long",         "display extended file metadata in a table");
        opts.optflag("G", "grid",         "display entries as a grid (default)");
        opts.optflag("x", "across",       "sort the grid across, rather than downwards");
B
Ben S 已提交
62
        opts.optflag("R", "recurse",      "recurse into directories");
63 64 65 66 67 68
        opts.optflag("T", "tree",         "recurse into directories as a tree");
        opts.optflag("F", "classify",     "display type indicator by file names (one of */=@|)");
        opts.optopt ("",  "color",        "when to use terminal colours", "WHEN");
        opts.optopt ("",  "colour",       "when to use terminal colours", "WHEN");
        opts.optflag("",  "color-scale",  "highlight levels of file sizes distinctly");
        opts.optflag("",  "colour-scale", "highlight levels of file sizes distinctly");
B
Benjamin Sago 已提交
69 70

        // Filtering and sorting options
71 72 73 74 75 76 77
        opts.optflag("",  "group-directories-first", "sort directories before other files");
        opts.optflag("a", "all",         "don't hide hidden and 'dot' files");
        opts.optflag("d", "list-dirs",   "list directories like regular files");
        opts.optopt ("L", "level",       "limit the depth of recursion", "DEPTH");
        opts.optflag("r", "reverse",     "reverse the sert order");
        opts.optopt ("s", "sort",        "which field to sort by", "WORD");
        opts.optopt ("I", "ignore-glob", "ignore files that match these glob patterns", "GLOB1|GLOB2...");
B
Benjamin Sago 已提交
78 79

        // Long view options
80
        opts.optflag("b", "binary",    "list file sizes with binary prefixes");
B
Benjamin Sago 已提交
81
        opts.optflag("B", "bytes",     "list file sizes in bytes, without prefixes");
82 83 84 85 86 87 88 89 90
        opts.optflag("g", "group",     "list each file's group");
        opts.optflag("h", "header",    "add a header row to each column");
        opts.optflag("H", "links",     "list each file's number of hard links");
        opts.optflag("i", "inode",     "list each file's inode number");
        opts.optflag("m", "modified",  "use the modified timestamp field");
        opts.optflag("S", "blocks",    "list each file's number of file system blocks");
        opts.optopt ("t", "time",      "which timestamp field to show", "WORD");
        opts.optflag("u", "accessed",  "use the accessed timestamp field");
        opts.optflag("U", "created",   "use the created timestamp field");
B
Benjamin Sago 已提交
91 92

        if cfg!(feature="git") {
93
            opts.optflag("", "git", "list each file's git status");
B
Benjamin Sago 已提交
94 95 96
        }

        if xattr::ENABLED {
97
            opts.optflag("@", "extended", "list each file's extended attribute keys and sizes");
B
Benjamin Sago 已提交
98 99 100 101 102 103 104 105
        }

        let matches = match opts.parse(args) {
            Ok(m)   => m,
            Err(e)  => return Err(Misfire::InvalidOptions(e)),
        };

        if matches.opt_present("help") {
106 107 108 109 110 111 112
            let help = HelpString {
                only_long: matches.opt_present("long"),
                git: cfg!(feature="git"),
                xattrs: xattr::ENABLED,
            };

            return Err(Misfire::Help(help));
B
Benjamin Sago 已提交
113 114 115 116 117
        }
        else if matches.opt_present("version") {
            return Err(Misfire::Version);
        }

118
        let options = Options::deduce(&matches)?;
B
Benjamin Sago 已提交
119 120 121 122 123 124 125 126
        Ok((options, matches.free))
    }

    /// Whether the View specified in this set of options includes a Git
    /// status column. It’s only worth trying to discover a repository if the
    /// results will end up being displayed.
    pub fn should_scan_for_git(&self) -> bool {
        match self.view {
127
            View::Details(Details { columns: Some(cols), .. }) |
B
Benjamin Sago 已提交
128 129 130 131 132 133 134 135
            View::GridDetails(GridDetails { details: Details { columns: Some(cols), .. }, .. }) => cols.should_scan_for_git(),
            _ => false,
        }
    }

    /// Determines the complete set of options based on the given command-line
    /// arguments, after they’ve been parsed.
    fn deduce(matches: &getopts::Matches) -> Result<Options, Misfire> {
D
Daniel Lockyer 已提交
136 137 138
        let dir_action = DirAction::deduce(matches)?;
        let filter = FileFilter::deduce(matches)?;
        let view = View::deduce(matches, filter.clone(), dir_action)?;
B
Benjamin Sago 已提交
139

140
        Ok(Options { dir_action, view, filter })
B
Benjamin Sago 已提交
141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176
    }
}


#[cfg(test)]
mod test {
    use super::{Options, Misfire, SortField, SortCase};
    use fs::feature::xattr;

    fn is_helpful<T>(misfire: Result<T, Misfire>) -> bool {
        match misfire {
            Err(Misfire::Help(_)) => true,
            _                     => false,
        }
    }

    #[test]
    fn help() {
        let opts = Options::getopts(&[ "--help".to_string() ]);
        assert!(is_helpful(opts))
    }

    #[test]
    fn help_with_file() {
        let opts = Options::getopts(&[ "--help".to_string(), "me".to_string() ]);
        assert!(is_helpful(opts))
    }

    #[test]
    fn files() {
        let args = Options::getopts(&[ "this file".to_string(), "that file".to_string() ]).unwrap().1;
        assert_eq!(args, vec![ "this file".to_string(), "that file".to_string() ])
    }

    #[test]
    fn no_args() {
177 178
        let nothing: Vec<String> = Vec::new();
        let args = Options::getopts(&nothing).unwrap().1;
B
Benjamin Sago 已提交
179 180 181 182 183
        assert!(args.is_empty());  // Listing the `.` directory is done in main.rs
    }

    #[test]
    fn file_sizes() {
184
        let opts = Options::getopts(&[ "--long", "--binary", "--bytes" ]);
B
Benjamin Sago 已提交
185 186 187 188 189
        assert_eq!(opts.unwrap_err(), Misfire::Conflict("binary", "bytes"))
    }

    #[test]
    fn just_binary() {
190
        let opts = Options::getopts(&[ "--binary" ]);
B
Benjamin Sago 已提交
191 192 193 194 195
        assert_eq!(opts.unwrap_err(), Misfire::Useless("binary", false, "long"))
    }

    #[test]
    fn just_bytes() {
196
        let opts = Options::getopts(&[ "--bytes" ]);
B
Benjamin Sago 已提交
197 198 199 200 201
        assert_eq!(opts.unwrap_err(), Misfire::Useless("bytes", false, "long"))
    }

    #[test]
    fn long_across() {
202
        let opts = Options::getopts(&[ "--long", "--across" ]);
B
Benjamin Sago 已提交
203 204 205 206 207
        assert_eq!(opts, Err(Misfire::Useless("across", true, "long")))
    }

    #[test]
    fn oneline_across() {
208
        let opts = Options::getopts(&[ "--oneline", "--across" ]);
B
Benjamin Sago 已提交
209 210 211 212 213
        assert_eq!(opts, Err(Misfire::Useless("across", true, "oneline")))
    }

    #[test]
    fn just_header() {
214
        let opts = Options::getopts(&[ "--header" ]);
B
Benjamin Sago 已提交
215 216 217 218 219
        assert_eq!(opts.unwrap_err(), Misfire::Useless("header", false, "long"))
    }

    #[test]
    fn just_group() {
220
        let opts = Options::getopts(&[ "--group" ]);
B
Benjamin Sago 已提交
221 222 223 224 225
        assert_eq!(opts.unwrap_err(), Misfire::Useless("group", false, "long"))
    }

    #[test]
    fn just_inode() {
226
        let opts = Options::getopts(&[ "--inode" ]);
B
Benjamin Sago 已提交
227 228 229 230 231
        assert_eq!(opts.unwrap_err(), Misfire::Useless("inode", false, "long"))
    }

    #[test]
    fn just_links() {
232
        let opts = Options::getopts(&[ "--links" ]);
B
Benjamin Sago 已提交
233 234 235 236 237
        assert_eq!(opts.unwrap_err(), Misfire::Useless("links", false, "long"))
    }

    #[test]
    fn just_blocks() {
238
        let opts = Options::getopts(&[ "--blocks" ]);
B
Benjamin Sago 已提交
239 240 241 242 243
        assert_eq!(opts.unwrap_err(), Misfire::Useless("blocks", false, "long"))
    }

    #[test]
    fn test_sort_size() {
244
        let opts = Options::getopts(&[ "--sort=size" ]);
B
Benjamin Sago 已提交
245 246 247 248 249
        assert_eq!(opts.unwrap().0.filter.sort_field, SortField::Size);
    }

    #[test]
    fn test_sort_name() {
250
        let opts = Options::getopts(&[ "--sort=name" ]);
B
Benjamin Sago 已提交
251 252 253 254 255
        assert_eq!(opts.unwrap().0.filter.sort_field, SortField::Name(SortCase::Sensitive));
    }

    #[test]
    fn test_sort_name_lowercase() {
256
        let opts = Options::getopts(&[ "--sort=Name" ]);
B
Benjamin Sago 已提交
257 258 259 260 261 262
        assert_eq!(opts.unwrap().0.filter.sort_field, SortField::Name(SortCase::Insensitive));
    }

    #[test]
    #[cfg(feature="git")]
    fn just_git() {
263
        let opts = Options::getopts(&[ "--git" ]);
B
Benjamin Sago 已提交
264 265 266 267 268 269
        assert_eq!(opts.unwrap_err(), Misfire::Useless("git", false, "long"))
    }

    #[test]
    fn extended_without_long() {
        if xattr::ENABLED {
270
            let opts = Options::getopts(&[ "--extended" ]);
B
Benjamin Sago 已提交
271 272 273 274 275 276
            assert_eq!(opts.unwrap_err(), Misfire::Useless("extended", false, "long"))
        }
    }

    #[test]
    fn level_without_recurse_or_tree() {
277
        let opts = Options::getopts(&[ "--level", "69105" ]);
B
Benjamin Sago 已提交
278 279 280
        assert_eq!(opts.unwrap_err(), Misfire::Useless2("level", "recurse", "tree"))
    }
}