fileinput/
lib.rs

1//! Read from multiple input streams.
2//!
3//! A `FileInput` implements the `std::io::Read` trait and reads the contents of each file
4//! specified (`-` means standard input), or standard input if none are given.
5//!
6//! An example that prints out all the lines in each of the two files specified:
7//!
8//! ```
9//! use std::io::{BufRead,BufReader};
10//! use fileinput::FileInput;
11//!
12//! let filenames = vec!["testdata/1", "testdata/2"];
13//! let fileinput = FileInput::new(&filenames);
14//! let mut reader = BufReader::new(fileinput);
15//!
16//! for line in reader.lines() {
17//!     println!("{}", line.unwrap());
18//! }
19//! ```
20use std::fs::File;
21use std::io;
22use std::io::{Read,stdin};
23use std::borrow::Borrow;
24
25
26/// A file source.
27#[derive(Debug, Eq, PartialEq, Clone)]
28pub enum Source {
29    /// Read from the process's standard in.
30    Stdin,
31    /// Read from the specified file.
32    File(String),
33}
34
35fn make_source_vec<T>(filenames: &[T]) -> Vec<Source> where T: Borrow<str> {
36    if filenames.is_empty() {
37        return vec![Source::Stdin];
38    }
39
40    let mut sources = Vec::with_capacity(filenames.len());
41    for filename in filenames {
42        sources.push(match filename.borrow() {
43            "-" => Source::Stdin,
44            filename => Source::File(filename.to_string()),
45        });
46    }
47    sources
48}
49
50struct State {
51    source: Source,
52    reader: Box<Read>,
53}
54
55/// A wrapper which reads from multiple streams.
56pub struct FileInput {
57    sources: Vec<Source>,
58    state: Option<State>,
59}
60
61impl FileInput {
62    /// Constructs a new `FileInput` that will read from the files specified.
63    pub fn new<T>(paths: &[T]) -> Self where T: Borrow<str> {
64        FileInput {
65            sources: make_source_vec(paths),
66            state: None,
67        }
68    }
69
70    /// Returns the current source being read from.
71    ///
72    /// This function will return `None` if no reading has been done yet or all the inputs have
73    /// been drained.
74    pub fn source(&self) -> Option<Source> {
75        self.state.as_ref().map(|s| s.source.clone())
76    }
77
78    fn open_next_file(&mut self) -> io::Result<()> {
79        let next_source = self.sources.remove(0);
80        let reader: Box<Read> = match &next_source {
81            &Source::Stdin => Box::new(stdin()),
82            &Source::File(ref path) => Box::new(try!(File::open(path))),
83        };
84
85        self.state = Some(State {
86            source: next_source,
87            reader: reader,
88        });
89
90        Ok(())
91    }
92}
93
94impl Read for FileInput {
95    fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
96        loop {
97            if self.state.is_none() {
98                if self.sources.is_empty() {
99                    return Ok(0);
100                }
101
102                try!(self.open_next_file());
103            }
104
105            let bytes_read = try!(self.state.as_mut().unwrap().reader.read(buf));
106
107            if bytes_read == 0 {
108                self.state = None;
109                continue;
110            }
111
112            return Ok(bytes_read);
113        }
114    }
115}
116
117#[cfg(test)]
118mod test {
119    mod source_vec {
120        use super::super::{make_source_vec,Source};
121
122        #[test]
123        fn empty_list_makes_stdin() {
124            let names: Vec<String> = vec![];
125            let paths = make_source_vec(&names);
126            assert_eq!(paths, [Source::Stdin]);
127        }
128
129        #[test]
130        fn dash_makes_stdin() {
131            let names = vec!["-"];
132            let paths = make_source_vec(&names);
133            assert_eq!(paths, [Source::Stdin]);
134        }
135
136        #[test]
137        fn filename_makes_path() {
138            let names = vec!["example-file"];
139            let paths = make_source_vec(&names);
140            assert_eq!(paths, [Source::File("example-file".to_string())]);
141        }
142
143        #[test]
144        fn mixed() {
145            let names = vec!["one", "two", "-", "three"];
146            let paths = make_source_vec(&names);
147            assert_eq!(paths, [Source::File("one".to_string()), Source::File("two".to_string()), Source::Stdin, Source::File("three".to_string())]);
148        }
149    }
150
151    mod fileinput {
152        use super::super::*;
153        use std::io::{Read,ErrorKind,BufRead,BufReader};
154
155        #[test]
156        fn read_files() {
157            let paths = vec!["testdata/1", "testdata/2"];
158            let mut fileinput = FileInput::new(&paths);
159            let mut buffer = String::new();
160
161            fileinput.read_to_string(&mut buffer).unwrap();
162
163            assert_eq!(buffer, "One.\nTwo.\nTwo.\n");
164        }
165
166        #[test]
167        fn skip_empty_file() {
168            let paths = vec!["testdata/1", "testdata/empty", "testdata/2"];
169            let mut fileinput = FileInput::new(&paths);
170            let mut buffer = String::new();
171
172            fileinput.read_to_string(&mut buffer).unwrap();
173
174            assert_eq!(buffer, "One.\nTwo.\nTwo.\n");
175        }
176
177        #[test]
178        fn get_source() {
179            let paths = vec!["testdata/1", "testdata/2"];
180            let fileinput = FileInput::new(&paths);
181            let mut reader = BufReader::new(fileinput);
182            let mut buffer = String::new();
183
184            assert_eq!(reader.get_ref().source(), None);
185            reader.read_line(&mut buffer).unwrap();
186            assert_eq!(reader.get_ref().source(), Some(Source::File("testdata/1".to_string())));
187            reader.read_line(&mut buffer).unwrap();
188            assert_eq!(reader.get_ref().source(), Some(Source::File("testdata/2".to_string())));
189            reader.read_line(&mut buffer).unwrap();
190            reader.read_line(&mut buffer).unwrap();
191            assert_eq!(reader.get_ref().source(), None);
192        }
193
194        #[test]
195        fn error_on_nonexistent_file() {
196            let paths = vec!["testdata/NOPE"];
197            let mut fileinput = FileInput::new(&paths);
198            let mut buffer = String::new();
199            let result = fileinput.read_to_string(&mut buffer);
200
201            assert_eq!(result.unwrap_err().kind(), ErrorKind::NotFound);
202        }
203    }
204}