main.rs 6.5 KB
Newer Older
B
Ben S 已提交
1
#![feature(collections, convert, core, exit_status, file_type, fs_ext, fs_mode, fs_time, io, libc, metadata_ext, os, scoped, std_misc, symlink_metadata)]
B
Ben S 已提交
2
#![allow(deprecated)]
3 4

// Other platforms than macos don't need std_misc but you can't
N
nwin 已提交
5
// use #[cfg] on features.
6
#![allow(unused_features)]
7

8
extern crate ansi_term;
9
extern crate datetime;
10
extern crate getopts;
11
extern crate locale;
12
extern crate natord;
B
Benjamin Sago 已提交
13
extern crate num_cpus;
14
extern crate number_prefix;
15
extern crate pad;
16
extern crate users;
B
Ben S 已提交
17 18
extern crate unicode_width;

19

B
Ben S 已提交
20 21 22
#[cfg(feature="git")]
extern crate git2;

B
Ben S 已提交
23
use std::env;
24 25
use std::fs;
use std::path::{Component, Path, PathBuf};
B
bp 已提交
26 27
use std::sync::mpsc::{channel, sync_channel};
use std::thread;
B
Ben S 已提交
28

B
Ben S 已提交
29 30
use dir::Dir;
use file::File;
31
use options::{Options, View};
32
use output::lines_view;
B
Ben S 已提交
33

B
Ben S 已提交
34
pub mod column;
B
Ben S 已提交
35
pub mod dir;
36
pub mod feature;
B
Ben S 已提交
37
pub mod file;
38
pub mod filetype;
B
Ben S 已提交
39
pub mod options;
40
pub mod output;
41
pub mod term;
B
Ben S 已提交
42

B
Ben S 已提交
43
#[cfg(not(test))]
44 45 46
struct Exa<'a> {
    count:   usize,
    options: Options,
47
    dirs:    Vec<PathBuf>,
48 49
    files:   Vec<File<'a>>,
}
B
Benjamin Sago 已提交
50

B
Ben S 已提交
51
#[cfg(not(test))]
52 53 54 55 56 57 58 59
impl<'a> Exa<'a> {
    fn new(options: Options) -> Exa<'a> {
        Exa {
            count: 0,
            options: options,
            dirs: Vec::new(),
            files: Vec::new(),
        }
B
Ben S 已提交
60
    }
B
Ben S 已提交
61

B
bp 已提交
62
    fn load(&mut self, files: &[String]) {
63 64 65
        // Separate the user-supplied paths into directories and files.
        // Files are shown first, and then each directory is expanded
        // and listed second.
B
bp 已提交
66

67
        let is_tree = self.options.dir_action.is_tree() || self.options.dir_action.is_as_file();
B
bp 已提交
68 69 70
        let total_files = files.len();

        // Denotes the maxinum number of concurrent threads
B
Benjamin Sago 已提交
71
        let (thread_capacity_tx, thread_capacity_rs) = sync_channel(8 * num_cpus::get());
B
bp 已提交
72 73 74 75

        // Communication between consumer thread and producer threads
        enum StatResult<'a> {
            File(File<'a>),
76
            Path(PathBuf),
77
            Error
B
bp 已提交
78
        }
79

B
bp 已提交
80 81 82 83 84 85 86 87 88 89 90 91 92
        let (results_tx, results_rx) = channel();

        // Spawn consumer thread
        let _consumer = thread::scoped(move || {
            for _ in 0..total_files {

                // Make room for more producer threads
                let _ = thread_capacity_rs.recv();

                // Receive a producer's result
                match results_rx.recv() {
                    Ok(result) => match result {
                        StatResult::File(file) => self.files.push(file),
93 94
                        StatResult::Path(path) => self.dirs.push(path),
                        StatResult::Error      => ()
B
bp 已提交
95
                    },
96
                    Err(_) => unreachable!(),
B
bp 已提交
97 98 99 100 101 102 103 104 105 106 107 108 109 110
                }
                self.count += 1;
            }
        });

        for file in files.iter() {
            let file = file.clone();
            let results_tx = results_tx.clone();

            // Block until there is room for another thread
            let _ = thread_capacity_tx.send(());

            // Spawn producer thread
            thread::spawn(move || {
111 112
                let path = Path::new(&*file);
                let _ = results_tx.send(match fs::metadata(&path) {
B
bp 已提交
113
                    Ok(stat) => {
114
                        if !stat.is_dir() {
115 116 117 118
                            StatResult::File(File::with_stat(stat, &path, None, false))
                        }
                        else if is_tree {
                            StatResult::File(File::with_stat(stat, &path, None, true))
119 120
                        }
                        else {
121
                            StatResult::Path(path.to_path_buf())
122 123
                        }
                    }
124 125
                    Err(e) => {
                        println!("{}: {}", file, e);
126
                        StatResult::Error
127
                    }
128
                });
B
bp 已提交
129
            });
130
        }
B
Ben S 已提交
131
    }
B
Ben S 已提交
132

133 134
    fn print_files(&self) {
        if !self.files.is_empty() {
B
Ben S 已提交
135
            self.print(None, &self.files[..]);
B
Ben S 已提交
136
        }
137
    }
138

139 140 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
    fn print_dirs(&mut self) {
        let mut first = self.files.is_empty();

        // Directories are put on a stack rather than just being iterated through,
        // as the vector can change as more directories are added.
        loop {
            let dir_path = match self.dirs.pop() {
                None => break,
                Some(f) => f,
            };

            // Put a gap between directories, or between the list of files and the
            // first directory.
            if first {
                first = false;
            }
            else {
                print!("\n");
            }

            match Dir::readdir(&dir_path) {
                Ok(ref dir) => {
                    let mut files = dir.files(false);
                    self.options.transform_files(&mut files);

                    // When recursing, add any directories to the dirs stack
                    // backwards: the *last* element of the stack is used each
                    // time, so by inserting them backwards, they get displayed in
                    // the correct sort order.
168
                    if let Some(recurse_opts) = self.options.dir_action.recurse_options() {
169
                        let depth = dir_path.components().filter(|&c| c != Component::CurDir).count() + 1;
170
                        if !recurse_opts.tree && !recurse_opts.is_too_deep(depth) {
171
                            for dir in files.iter().filter(|f| f.is_directory()).rev() {
172 173
                                self.dirs.push(dir.path.clone());
                            }
174
                        }
B
Ben S 已提交
175 176
                    }

177 178 179 180 181
                    if self.count > 1 {
                        println!("{}:", dir_path.display());
                    }
                    self.count += 1;

B
Ben S 已提交
182
                    self.print(Some(dir), &files[..]);
183 184 185 186
                }
                Err(e) => {
                    println!("{}: {}", dir_path.display(), e);
                    return;
187
                }
188 189 190
            };
        }
    }
191

192 193 194 195 196 197
    fn print(&self, dir: Option<&Dir>, files: &[File]) {
        match self.options.view {
            View::Grid(g)     => g.view(files),
            View::Details(d)  => d.view(dir, files),
            View::Lines       => lines_view(files),
        }
198
    }
199
}
200

B
Ben S 已提交
201
#[cfg(not(test))]
202
fn main() {
B
Ben S 已提交
203
    let args: Vec<String> = env::args().collect();
204

205
    match Options::getopts(args.tail()) {
206 207
        Ok((options, paths)) => {
            let mut exa = Exa::new(options);
B
bp 已提交
208
            exa.load(&paths);
209 210 211
            exa.print_files();
            exa.print_dirs();
        },
212
        Err(e) => {
213
            println!("{}", e);
B
Ben S 已提交
214
            env::set_exit_status(e.error_code());
215
        },
216 217
    };
}