1use std::{
2 ffi::OsStr,
3 path::PathBuf,
4 process::{Child, Command, Stdio},
5};
6
7use super::{ExclusiveOption, SubCommand, Unselected};
8
9use crate::global::GlobalOpts;
10use crate::spawn::ParameterizedSpawn;
11
12#[derive(Debug, Clone, Copy, Default)]
17pub struct FullDescription;
18
19impl ExclusiveOption for FullDescription {
20 fn inject_args(&self, command: &mut Command) {
21 command.arg("-l");
22 }
23}
24
25#[derive(Debug, Clone, Copy, Default)]
31pub struct TruncatedDescription;
32
33impl ExclusiveOption for TruncatedDescription {
34 fn inject_args(&self, command: &mut Command) {
35 command.arg("-L");
36 }
37}
38
39#[derive(Debug, Clone, Copy, Default)]
46pub struct DisplayContentHistory {
47 skip_promoted_tasks: bool,
48}
49
50impl ExclusiveOption for DisplayContentHistory {
51 fn inject_args(&self, command: &mut Command) {
52 command.arg("-h");
53
54 if self.skip_promoted_tasks {
55 command.arg("-p");
56 }
57 }
58}
59
60#[derive(Debug, Clone, Default)]
71pub struct FileLog<L = Unselected, H = Unselected> {
72 bin: PathBuf,
73
74 global_opts: GlobalOpts,
75
76 changelist: Option<String>,
77
78 content_history: H,
79
80 follow_branches: bool,
81
82 long_output: L,
83
84 limit: Option<u64>,
85
86 ignore_non_contributory: bool,
87
88 include_time: bool,
89}
90
91impl FileLog<Unselected, Unselected> {
92 pub fn new(bin: impl Into<PathBuf>, global_opts: GlobalOpts) -> Self {
96 Self {
97 bin: bin.into(),
98 global_opts,
99 changelist: None,
100 content_history: Unselected,
101 follow_branches: false,
102 long_output: Unselected,
103 limit: None,
104 ignore_non_contributory: false,
105 include_time: false,
106 }
107 }
108}
109
110impl<H: ExclusiveOption> FileLog<Unselected, H> {
111 pub fn full_description(self) -> FileLog<FullDescription, H> {
119 FileLog {
120 bin: self.bin,
121 global_opts: self.global_opts,
122 changelist: self.changelist,
123 content_history: self.content_history,
124 follow_branches: self.follow_branches,
125 long_output: FullDescription,
126 limit: self.limit,
127 ignore_non_contributory: self.ignore_non_contributory,
128 include_time: self.include_time,
129 }
130 }
131
132 pub fn truncated_description(self) -> FileLog<TruncatedDescription, H> {
141 FileLog {
142 bin: self.bin,
143 global_opts: self.global_opts,
144 changelist: self.changelist,
145 content_history: self.content_history,
146 follow_branches: self.follow_branches,
147 long_output: TruncatedDescription,
148 limit: self.limit,
149 ignore_non_contributory: self.ignore_non_contributory,
150 include_time: self.include_time,
151 }
152 }
153}
154
155impl<L: ExclusiveOption> FileLog<L, Unselected> {
156 pub fn content_history(self) -> FileLog<L, DisplayContentHistory> {
165 FileLog {
166 bin: self.bin,
167 global_opts: self.global_opts,
168 changelist: self.changelist,
169 content_history: DisplayContentHistory {
170 skip_promoted_tasks: false,
171 },
172 follow_branches: self.follow_branches,
173 long_output: self.long_output,
174 limit: self.limit,
175 ignore_non_contributory: self.ignore_non_contributory,
176 include_time: self.include_time,
177 }
178 }
179}
180
181impl<L: ExclusiveOption, H: ExclusiveOption, S, I> ParameterizedSpawn<(S,)> for FileLog<L, H>
182where
183 S: IntoIterator<Item = I>,
184 I: AsRef<OsStr>,
185{
186 type Output = Child;
187 type Error = std::io::Error;
188
189 fn spawn_with(&mut self, (files,): (S,)) -> Result<Self::Output, Self::Error> {
195 self.setup_command(&self.bin)
196 .args(files)
197 .stdout(Stdio::piped())
198 .stderr(Stdio::piped())
199 .spawn()
200 }
201}
202
203impl<L: ExclusiveOption, H: ExclusiveOption> FileLog<L, H> {
204 #[cfg_attr(
209 feature = "lt2014_2",
210 doc = "See the [Global Options](GlobalOpts) section."
211 )]
212 #[cfg_attr(
213 all(feature = "lt2015_1", not(feature = "lt2014_2")),
214 doc = "See the [“Global Options”](GlobalOpts) section."
215 )]
216 #[cfg_attr(
217 all(feature = "lt2017_1", not(feature = "lt2015_1")),
218 doc = "See [“Global Options”](GlobalOpts)."
219 )]
220 #[cfg_attr(
221 all(feature = "lt2018_2", not(feature = "lt2017_1")),
222 doc = "See [Global Options](GlobalOpts)."
223 )]
224 #[cfg_attr(not(feature = "lt2018_2"), doc = "See [Global options](GlobalOpts).")]
225 pub fn get_global_opts(&self) -> &GlobalOpts {
226 &self.global_opts
227 }
228
229 #[cfg_attr(
234 feature = "lt2014_2",
235 doc = "See the [Global Options](GlobalOpts) section."
236 )]
237 #[cfg_attr(
238 all(feature = "lt2015_1", not(feature = "lt2014_2")),
239 doc = "See the [“Global Options”](GlobalOpts) section."
240 )]
241 #[cfg_attr(
242 all(feature = "lt2017_1", not(feature = "lt2015_1")),
243 doc = "See [“Global Options”](GlobalOpts)."
244 )]
245 #[cfg_attr(
246 all(feature = "lt2018_2", not(feature = "lt2017_1")),
247 doc = "See [Global Options](GlobalOpts)."
248 )]
249 #[cfg_attr(not(feature = "lt2018_2"), doc = "See [Global options](GlobalOpts).")]
250 pub fn set_global_opts(&mut self, v: GlobalOpts) -> &mut Self {
251 self.global_opts = v;
252 self
253 }
254
255 #[cfg_attr(
260 feature = "lt2014_2",
261 doc = "See the [Global Options](GlobalOpts) section."
262 )]
263 #[cfg_attr(
264 all(feature = "lt2015_1", not(feature = "lt2014_2")),
265 doc = "See the [“Global Options”](GlobalOpts) section."
266 )]
267 #[cfg_attr(
268 all(feature = "lt2017_1", not(feature = "lt2015_1")),
269 doc = "See [“Global Options”](GlobalOpts)."
270 )]
271 #[cfg_attr(
272 all(feature = "lt2018_2", not(feature = "lt2017_1")),
273 doc = "See [Global Options](GlobalOpts)."
274 )]
275 #[cfg_attr(not(feature = "lt2018_2"), doc = "See [Global options](GlobalOpts).")]
276 pub fn global_opts(mut self, v: GlobalOpts) -> Self {
277 self.global_opts = v;
278 self
279 }
280
281 pub fn get_changelist(&self) -> Option<&String> {
287 self.changelist.as_ref()
288 }
289
290 pub fn set_changelist(&mut self, v: impl Into<String>) -> &mut Self {
296 self.changelist = Some(v.into());
297 self
298 }
299
300 pub fn changelist(mut self, v: impl Into<String>) -> Self {
306 self.changelist = Some(v.into());
307 self
308 }
309
310 pub fn get_follow_branches(&self) -> bool {
316 self.follow_branches
317 }
318
319 pub fn set_follow_branches(&mut self, v: bool) -> &mut Self {
325 self.follow_branches = v;
326 self
327 }
328
329 pub fn follow_branches(mut self, v: bool) -> Self {
335 self.follow_branches = v;
336 self
337 }
338
339 pub fn get_limit(&self) -> Option<u64> {
345 self.limit
346 }
347
348 pub fn set_limit(&mut self, v: u64) -> &mut Self {
354 self.limit = Some(v);
355 self
356 }
357
358 pub fn limit(mut self, v: u64) -> Self {
364 self.limit = Some(v);
365 self
366 }
367
368 pub fn get_ignore_non_contributory(&self) -> bool {
375 self.ignore_non_contributory
376 }
377
378 pub fn set_ignore_non_contributory(&mut self, v: bool) -> &mut Self {
385 self.ignore_non_contributory = v;
386 self
387 }
388
389 pub fn ignore_non_contributory(mut self, v: bool) -> Self {
396 self.ignore_non_contributory = v;
397 self
398 }
399
400 pub fn get_include_time(&self) -> bool {
406 self.include_time
407 }
408
409 pub fn set_include_time(&mut self, v: bool) -> &mut Self {
415 self.include_time = v;
416 self
417 }
418
419 pub fn include_time(mut self, v: bool) -> Self {
425 self.include_time = v;
426 self
427 }
428}
429
430impl<L: ExclusiveOption> FileLog<L, DisplayContentHistory> {
431 pub fn get_skip_promoted_tasks(&self) -> bool {
438 self.content_history.skip_promoted_tasks
439 }
440
441 pub fn set_skip_promoted_tasks(&mut self, v: bool) -> &mut Self {
448 self.content_history.skip_promoted_tasks = v;
449 self
450 }
451
452 pub fn skip_promoted_tasks(mut self, v: bool) -> Self {
459 self.content_history.skip_promoted_tasks = v;
460 self
461 }
462}
463
464impl<L: ExclusiveOption, H: ExclusiveOption> SubCommand for FileLog<L, H> {
465 fn name(&self) -> &str {
466 "filelog"
467 }
468
469 fn inject_local_args(&self, command: &mut Command) {
470 if let Some(changelist) = &self.changelist {
471 command.arg("-c").arg(changelist);
472 }
473
474 self.content_history.inject_args(command);
475
476 if self.follow_branches {
477 command.arg("-i");
478 }
479
480 self.long_output.inject_args(command);
481
482 if let Some(max) = self.limit {
483 command.arg("-m").arg(max.to_string());
484 }
485
486 if self.ignore_non_contributory {
487 command.arg("-s");
488 }
489
490 if self.include_time {
491 command.arg("-t");
492 }
493 }
494
495 fn global_opts(&self) -> Option<&GlobalOpts> {
496 Some(&self.global_opts)
497 }
498}
499
500#[cfg(test)]
501mod tests {
502 use super::*;
503 use crate::cmd::args_of;
504
505 #[test]
506 fn with_files() {
507 let filelog = FileLog::new("p4", GlobalOpts::default());
508 let mut cmd = filelog.setup_command("p4");
509 cmd.arg("//depot/project/...");
510 assert_eq!(args_of(&cmd), vec!["filelog", "//depot/project/..."]);
511 }
512
513 #[test]
514 fn changelist() {
515 let filelog = FileLog::new("p4", GlobalOpts::default()).changelist("100");
516 let cmd = filelog.setup_command("p4");
517 assert_eq!(args_of(&cmd), vec!["filelog", "-c", "100"]);
518 }
519
520 #[test]
521 fn content_history() {
522 let filelog = FileLog::new("p4", GlobalOpts::default()).content_history();
523 let cmd = filelog.setup_command("p4");
524 assert_eq!(args_of(&cmd), vec!["filelog", "-h"]);
525 }
526
527 #[test]
528 fn follow_branches() {
529 let filelog = FileLog::new("p4", GlobalOpts::default()).follow_branches(true);
530 let cmd = filelog.setup_command("p4");
531 assert_eq!(args_of(&cmd), vec!["filelog", "-i"]);
532 }
533
534 #[test]
535 fn full_description() {
536 let filelog = FileLog::new("p4", GlobalOpts::default()).full_description();
537 let cmd = filelog.setup_command("p4");
538 assert_eq!(args_of(&cmd), vec!["filelog", "-l"]);
539 }
540
541 #[test]
542 fn truncated_description() {
543 let filelog = FileLog::new("p4", GlobalOpts::default()).truncated_description();
544 let cmd = filelog.setup_command("p4");
545 assert_eq!(args_of(&cmd), vec!["filelog", "-L"]);
546 }
547
548 #[test]
549 fn limit() {
550 let filelog = FileLog::new("p4", GlobalOpts::default()).limit(5);
551 let cmd = filelog.setup_command("p4");
552 assert_eq!(args_of(&cmd), vec!["filelog", "-m", "5"]);
553 }
554
555 #[test]
556 fn skip_promoted_tasks() {
557 let filelog = FileLog::new("p4", GlobalOpts::default())
558 .content_history()
559 .skip_promoted_tasks(true);
560 let cmd = filelog.setup_command("p4");
561 assert_eq!(args_of(&cmd), vec!["filelog", "-h", "-p"]);
562 }
563
564 #[test]
565 fn skip_promoted_tasks_accessors() {
566 let mut filelog = FileLog::new("p4", GlobalOpts::default())
567 .full_description()
568 .content_history();
569 filelog.set_skip_promoted_tasks(true);
570
571 assert!(filelog.get_skip_promoted_tasks());
572 assert_eq!(
573 args_of(&filelog.setup_command("p4")),
574 ["filelog", "-h", "-p", "-l"]
575 );
576 }
577
578 #[test]
579 fn ignore_non_contributory() {
580 let filelog = FileLog::new("p4", GlobalOpts::default()).ignore_non_contributory(true);
581 let cmd = filelog.setup_command("p4");
582 assert_eq!(args_of(&cmd), vec!["filelog", "-s"]);
583 }
584
585 #[test]
586 fn include_time() {
587 let filelog = FileLog::new("p4", GlobalOpts::default()).include_time(true);
588 let cmd = filelog.setup_command("p4");
589 assert_eq!(args_of(&cmd), vec!["filelog", "-t"]);
590 }
591
592 #[test]
593 fn all_options_order() {
594 let filelog = FileLog::new("p4", GlobalOpts::default())
595 .changelist("100")
596 .content_history()
597 .follow_branches(true)
598 .full_description()
599 .limit(5)
600 .skip_promoted_tasks(true)
601 .ignore_non_contributory(true)
602 .include_time(true);
603 let cmd = filelog.setup_command("p4");
604 assert_eq!(
605 args_of(&cmd),
606 vec![
607 "filelog", "-c", "100", "-h", "-p", "-i", "-l", "-m", "5", "-s", "-t",
608 ]
609 );
610 }
611}