p4_cmd/
print.rs

1use std::vec;
2
3use error;
4use p4;
5
6/// Write a depot file to standard output
7///
8/// Retrieve the contents of a depot file to the client's standard output.
9/// The file is not synced.  If file is specified using client syntax,
10/// Perforce uses the client view to determine the corresponding depot
11/// file.
12///
13/// By default, the head revision is printed.  If the file argument
14/// includes a revision, the specified revision is printed.  If the
15/// file argument has a revision range,  then only files selected by
16/// that revision range are printed, and the highest revision in the
17/// range is printed. For details about revision specifiers, see 'p4
18/// help revisions'.
19///
20/// # Examples
21///
22/// ```rust,no_run
23/// let p4 = p4_cmd::P4::new();
24/// let files = p4.print("//depot/dir/file").run().unwrap();
25/// for file in files {
26///     println!("{:?}", file);
27/// }
28/// ```
29#[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    /// The -a flag prints all revisions within the specified range, rather
56    /// than just the highest revision in the range.
57    pub fn all_revs(mut self, all_revs: bool) -> Self {
58        self.all_revs = all_revs;
59        self
60    }
61
62    /// The -k flag suppresses keyword expansion.
63    pub fn keyword_expansion(mut self, keyword_expansion: bool) -> Self {
64        self.keyword_expansion = keyword_expansion;
65        self
66    }
67
68    /// The -m flag limits print to the first 'max' number of files.
69    pub fn max_files(mut self, max_files: usize) -> Self {
70        self.max_files = Some(max_files);
71        self
72    }
73
74    /// Run the `print` command.
75    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}