1use std::vec;
2
3use error;
4use p4;
5
6#[derive(Debug, Clone)]
30pub struct PrintCommand<'p, 'f> {
31 connection: &'p p4::P4,
32 file: Vec<&'f str>,
33
34 all_revs: bool,
35 keyword_expansion: bool,
36 max_files: Option<usize>,
37}
38
39impl<'p, 'f> PrintCommand<'p, 'f> {
40 pub fn new(connection: &'p p4::P4, file: &'f str) -> Self {
41 Self {
42 connection,
43 file: vec![file],
44 all_revs: false,
45 keyword_expansion: true,
46 max_files: None,
47 }
48 }
49
50 pub fn file(mut self, dir: &'f str) -> Self {
51 self.file.push(dir);
52 self
53 }
54
55 pub fn all_revs(mut self, all_revs: bool) -> Self {
58 self.all_revs = all_revs;
59 self
60 }
61
62 pub fn keyword_expansion(mut self, keyword_expansion: bool) -> Self {
64 self.keyword_expansion = keyword_expansion;
65 self
66 }
67
68 pub fn max_files(mut self, max_files: usize) -> Self {
70 self.max_files = Some(max_files);
71 self
72 }
73
74 pub fn run(self) -> Result<Files, error::P4Error> {
76 let mut cmd = self.connection.connect_with_retries(None);
77 cmd.arg("print");
78 if self.all_revs {
79 cmd.arg("-s");
80 }
81 if !self.keyword_expansion {
82 cmd.arg("-k");
83 }
84 if let Some(max_files) = self.max_files {
85 let max_files = format!("{}", max_files);
86 cmd.args(&["-m", &max_files]);
87 }
88 for file in self.file {
89 cmd.arg(file);
90 }
91 let data = cmd.output().map_err(|e| {
92 error::ErrorKind::SpawnFailed
93 .error()
94 .set_cause(e)
95 .set_context(format!("Command: {:?}", cmd))
96 })?;
97 let (_remains, (mut items, exit)) = files_parser::files(&data.stdout).map_err(|_| {
98 error::ErrorKind::ParseFailed
99 .error()
100 .set_context(format!("Command: {:?}", cmd))
101 })?;
102 items.push(exit);
103 Ok(Files(items))
104 }
105}
106
107pub type FileItem = error::Item<File>;
108
109pub struct Files(Vec<FileItem>);
110
111impl IntoIterator for Files {
112 type Item = FileItem;
113 type IntoIter = FilesIntoIter;
114
115 fn into_iter(self) -> FilesIntoIter {
116 FilesIntoIter(self.0.into_iter())
117 }
118}
119
120#[derive(Debug)]
121pub struct FilesIntoIter(vec::IntoIter<FileItem>);
122
123impl Iterator for FilesIntoIter {
124 type Item = FileItem;
125
126 #[inline]
127 fn next(&mut self) -> Option<FileItem> {
128 self.0.next()
129 }
130
131 #[inline]
132 fn size_hint(&self) -> (usize, Option<usize>) {
133 self.0.size_hint()
134 }
135
136 #[inline]
137 fn count(self) -> usize {
138 self.0.count()
139 }
140}
141
142#[derive(Debug, Clone, PartialEq, Eq)]
143pub enum FileContent {
144 #[doc(hidden)]
145 __Nonexhaustive,
146
147 Text(Vec<String>),
148 Binary(Vec<u8>),
149}
150
151impl FileContent {
152 pub fn as_text(&self) -> Option<&[String]> {
153 match self {
154 FileContent::Text(c) => Some(&c),
155 _ => None,
156 }
157 }
158
159 pub fn as_binary(&self) -> Option<&[u8]> {
160 match self {
161 FileContent::Binary(c) => Some(&c),
162 _ => None,
163 }
164 }
165}
166
167#[derive(Debug, Clone, PartialEq, Eq)]
168pub struct File {
169 pub content: FileContent,
170 pub depot_file: String,
171 pub rev: usize,
172 pub change: usize,
173 pub action: p4::Action,
174 pub file_type: p4::FileType,
175 pub time: p4::Time,
176 pub file_size: usize,
177 non_exhaustive: (),
178}
179
180mod files_parser {
181 use super::*;
182
183 use super::super::parser::*;
184
185 named!(pub file<&[u8], File>,
186 do_parse!(
187 depot_file: depot_file >>
188 rev: rev >>
189 change: change >>
190 action: action >>
191 file_type: file_type >>
192 time: time >>
193 file_size: file_size >>
194 content: alt!(
195 map!(many1!(text), texts_to_content) |
196 map!(take!(file_size.size), slice_to_content)
197 ) >>
198 (
199 File {
200 content: content,
201 depot_file: depot_file.path.to_owned(),
202 rev: rev.rev,
203 change: change.change,
204 action: action.action.parse().expect("`Unknown` to capture all"),
205 file_type: file_type.ft.parse().expect("`Unknown` to capture all"),
206 time: p4::from_timestamp(time.time),
207 file_size: file_size.size,
208 non_exhaustive: (),
209 }
210 )
211 )
212 );
213
214 named!(item<&[u8], FileItem>,
215 alt!(
216 map!(file, data_to_item) |
217 map!(error, error_to_item) |
218 map!(info, info_to_item)
219 )
220 );
221
222 named!(pub files<&[u8], (Vec<FileItem>, FileItem)>,
223 pair!(
224 many0!(item),
225 map!(exit, exit_to_item)
226 )
227 );
228
229 fn texts_to_content(texts: Vec<String>) -> FileContent {
230 FileContent::Text(texts)
231 }
232
233 fn slice_to_content(s: &[u8]) -> FileContent {
234 FileContent::Binary(s.to_vec())
235 }
236}
237
238#[cfg(test)]
239mod test {
240 use super::*;
241
242 #[test]
243 fn print_text_single() {
244 let output: &[u8] = br#"info1: depotFile //depot/dir/file
245info1: rev 3
246info1: change 42
247info1: action edit
248info1: type text
249info1: time 1527128624
250info1: fileSize 494514
251text: Hello
252text: World
253exit: 0
254"#;
255 let (_remains, (items, exit)) = files_parser::files(output).unwrap();
256 let item = items[0].as_data().unwrap();
257 assert_eq!(
258 item.content,
259 FileContent::Text(vec!["Hello".to_owned(), "World".to_owned()])
260 );
261 assert_eq!(exit.as_error(), Some(&error::OperationError::new(0)));
262 }
263
264 #[test]
265 fn print_text_multi() {
266 let output: &[u8] = br#"info1: depotFile //depot/dir/file
267info1: rev 3
268info1: change 42
269info1: action edit
270info1: type text
271info1: time 1527128624
272info1: fileSize 494514
273text: Hello
274text: World
275info1: depotFile //depot/dir/file2
276info1: rev 3
277info1: change 42
278info1: action edit
279info1: type text
280info1: time 1527128624
281info1: fileSize 494514
282text: Goodbye
283text: World
284exit: 0
285"#;
286 let (_remains, (items, exit)) = files_parser::files(output).unwrap();
287 let first = items[0].as_data().unwrap();
288 let last = items[1].as_data().unwrap();
289 assert_eq!(
290 first.content,
291 FileContent::Text(vec!["Hello".to_owned(), "World".to_owned()])
292 );
293 assert_eq!(
294 last.content,
295 FileContent::Text(vec!["Goodbye".to_owned(), "World".to_owned()])
296 );
297 assert_eq!(exit.as_error(), Some(&error::OperationError::new(0)));
298 }
299
300 #[test]
301 fn print_binary_single() {
302 let output: &[u8] = b"info1: depotFile //depot/dir/file
303info1: rev 3
304info1: change 42
305info1: action edit
306info1: type binary
307info1: time 1527128624
308info1: fileSize 5
3091\02\n3exit: 0
310";
311 let (_remains, (items, exit)) = files_parser::files(output).unwrap();
312 assert_eq!(
313 items[0].as_data().unwrap().content,
314 FileContent::Binary(b"1\02\n3".to_vec())
315 );
316 assert_eq!(exit.as_error(), Some(&error::OperationError::new(0)));
317 }
318
319 #[test]
320 fn file_binary() {
321 let output: &[u8] = b"info1: depotFile //depot/dir/file
322info1: rev 3
323info1: change 42
324info1: action edit
325info1: type binary
326info1: time 1527128624
327info1: fileSize 5
3281\02\n3
329";
330 let (_remains, item) = files_parser::file(output).unwrap();
331 assert_eq!(item.content, FileContent::Binary(b"1\02\n3".to_vec()));
332 }
333}