1use std::{
2 ffi::OsStr,
3 path::{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 pub fn get_bin(&self) -> &Path {
206 &self.bin
207 }
208
209 pub fn set_bin(&mut self, bin: impl Into<PathBuf>) -> &mut Self {
211 self.bin = bin.into();
212 self
213 }
214
215 pub fn bin(mut self, bin: impl Into<PathBuf>) -> Self {
217 self.set_bin(bin);
218 self
219 }
220
221 #[cfg_attr(
226 feature = "lt2014_2",
227 doc = "See the [Global Options](GlobalOpts) section."
228 )]
229 #[cfg_attr(
230 all(feature = "lt2015_1", not(feature = "lt2014_2")),
231 doc = "See the [“Global Options”](GlobalOpts) section."
232 )]
233 #[cfg_attr(
234 all(feature = "lt2017_1", not(feature = "lt2015_1")),
235 doc = "See [“Global Options”](GlobalOpts)."
236 )]
237 #[cfg_attr(
238 all(feature = "lt2018_2", not(feature = "lt2017_1")),
239 doc = "See [Global Options](GlobalOpts)."
240 )]
241 #[cfg_attr(not(feature = "lt2018_2"), doc = "See [Global options](GlobalOpts).")]
242 pub fn get_global_opts(&self) -> &GlobalOpts {
243 &self.global_opts
244 }
245
246 #[cfg_attr(
251 feature = "lt2014_2",
252 doc = "See the [Global Options](GlobalOpts) section."
253 )]
254 #[cfg_attr(
255 all(feature = "lt2015_1", not(feature = "lt2014_2")),
256 doc = "See the [“Global Options”](GlobalOpts) section."
257 )]
258 #[cfg_attr(
259 all(feature = "lt2017_1", not(feature = "lt2015_1")),
260 doc = "See [“Global Options”](GlobalOpts)."
261 )]
262 #[cfg_attr(
263 all(feature = "lt2018_2", not(feature = "lt2017_1")),
264 doc = "See [Global Options](GlobalOpts)."
265 )]
266 #[cfg_attr(not(feature = "lt2018_2"), doc = "See [Global options](GlobalOpts).")]
267 pub fn set_global_opts(&mut self, v: GlobalOpts) -> &mut Self {
268 self.global_opts = v;
269 self
270 }
271
272 #[cfg_attr(
277 feature = "lt2014_2",
278 doc = "See the [Global Options](GlobalOpts) section."
279 )]
280 #[cfg_attr(
281 all(feature = "lt2015_1", not(feature = "lt2014_2")),
282 doc = "See the [“Global Options”](GlobalOpts) section."
283 )]
284 #[cfg_attr(
285 all(feature = "lt2017_1", not(feature = "lt2015_1")),
286 doc = "See [“Global Options”](GlobalOpts)."
287 )]
288 #[cfg_attr(
289 all(feature = "lt2018_2", not(feature = "lt2017_1")),
290 doc = "See [Global Options](GlobalOpts)."
291 )]
292 #[cfg_attr(not(feature = "lt2018_2"), doc = "See [Global options](GlobalOpts).")]
293 pub fn global_opts(mut self, v: GlobalOpts) -> Self {
294 self.global_opts = v;
295 self
296 }
297
298 pub fn get_changelist(&self) -> Option<&String> {
304 self.changelist.as_ref()
305 }
306
307 pub fn set_changelist(&mut self, v: impl Into<String>) -> &mut Self {
313 self.changelist = Some(v.into());
314 self
315 }
316
317 pub fn changelist(mut self, v: impl Into<String>) -> Self {
323 self.changelist = Some(v.into());
324 self
325 }
326
327 pub fn get_follow_branches(&self) -> bool {
333 self.follow_branches
334 }
335
336 pub fn set_follow_branches(&mut self, v: bool) -> &mut Self {
342 self.follow_branches = v;
343 self
344 }
345
346 pub fn follow_branches(mut self, v: bool) -> Self {
352 self.follow_branches = v;
353 self
354 }
355
356 pub fn get_limit(&self) -> Option<u64> {
362 self.limit
363 }
364
365 pub fn set_limit(&mut self, v: u64) -> &mut Self {
371 self.limit = Some(v);
372 self
373 }
374
375 pub fn limit(mut self, v: u64) -> Self {
381 self.limit = Some(v);
382 self
383 }
384
385 pub fn get_ignore_non_contributory(&self) -> bool {
392 self.ignore_non_contributory
393 }
394
395 pub fn set_ignore_non_contributory(&mut self, v: bool) -> &mut Self {
402 self.ignore_non_contributory = v;
403 self
404 }
405
406 pub fn ignore_non_contributory(mut self, v: bool) -> Self {
413 self.ignore_non_contributory = v;
414 self
415 }
416
417 pub fn get_include_time(&self) -> bool {
423 self.include_time
424 }
425
426 pub fn set_include_time(&mut self, v: bool) -> &mut Self {
432 self.include_time = v;
433 self
434 }
435
436 pub fn include_time(mut self, v: bool) -> Self {
442 self.include_time = v;
443 self
444 }
445}
446
447impl<L: ExclusiveOption> FileLog<L, DisplayContentHistory> {
448 pub fn get_skip_promoted_tasks(&self) -> bool {
455 self.content_history.skip_promoted_tasks
456 }
457
458 pub fn set_skip_promoted_tasks(&mut self, v: bool) -> &mut Self {
465 self.content_history.skip_promoted_tasks = v;
466 self
467 }
468
469 pub fn skip_promoted_tasks(mut self, v: bool) -> Self {
476 self.content_history.skip_promoted_tasks = v;
477 self
478 }
479}
480
481impl<L: ExclusiveOption, H: ExclusiveOption> SubCommand for FileLog<L, H> {
482 fn name(&self) -> &str {
483 "filelog"
484 }
485
486 fn inject_local_args(&self, command: &mut Command) {
487 if let Some(changelist) = &self.changelist {
488 command.arg("-c").arg(changelist);
489 }
490
491 self.content_history.inject_args(command);
492
493 if self.follow_branches {
494 command.arg("-i");
495 }
496
497 self.long_output.inject_args(command);
498
499 if let Some(max) = self.limit {
500 command.arg("-m").arg(max.to_string());
501 }
502
503 if self.ignore_non_contributory {
504 command.arg("-s");
505 }
506
507 if self.include_time {
508 command.arg("-t");
509 }
510 }
511
512 fn global_opts(&self) -> Option<&GlobalOpts> {
513 Some(&self.global_opts)
514 }
515}
516
517#[cfg(test)]
518mod tests {
519 use super::*;
520 use crate::cmd::args_of;
521
522 #[test]
523 fn bin_accessors_update_executable_path() {
524 let mut command = FileLog::new("p4", GlobalOpts::new());
525 assert_eq!(command.get_bin(), Path::new("p4"));
526
527 command.set_bin("/usr/local/bin/p4");
528 assert_eq!(command.get_bin(), Path::new("/usr/local/bin/p4"));
529
530 let command = command.bin("p4.exe");
531 assert_eq!(command.get_bin(), Path::new("p4.exe"));
532 }
533
534 #[test]
535 fn with_files() {
536 let filelog = FileLog::new("p4", GlobalOpts::default());
537 let mut cmd = filelog.setup_command("p4");
538 cmd.arg("//depot/project/...");
539 assert_eq!(args_of(&cmd), vec!["filelog", "//depot/project/..."]);
540 }
541
542 #[test]
543 fn changelist() {
544 let filelog = FileLog::new("p4", GlobalOpts::default()).changelist("100");
545 let cmd = filelog.setup_command("p4");
546 assert_eq!(args_of(&cmd), vec!["filelog", "-c", "100"]);
547 }
548
549 #[test]
550 fn content_history() {
551 let filelog = FileLog::new("p4", GlobalOpts::default()).content_history();
552 let cmd = filelog.setup_command("p4");
553 assert_eq!(args_of(&cmd), vec!["filelog", "-h"]);
554 }
555
556 #[test]
557 fn follow_branches() {
558 let filelog = FileLog::new("p4", GlobalOpts::default()).follow_branches(true);
559 let cmd = filelog.setup_command("p4");
560 assert_eq!(args_of(&cmd), vec!["filelog", "-i"]);
561 }
562
563 #[test]
564 fn full_description() {
565 let filelog = FileLog::new("p4", GlobalOpts::default()).full_description();
566 let cmd = filelog.setup_command("p4");
567 assert_eq!(args_of(&cmd), vec!["filelog", "-l"]);
568 }
569
570 #[test]
571 fn truncated_description() {
572 let filelog = FileLog::new("p4", GlobalOpts::default()).truncated_description();
573 let cmd = filelog.setup_command("p4");
574 assert_eq!(args_of(&cmd), vec!["filelog", "-L"]);
575 }
576
577 #[test]
578 fn limit() {
579 let filelog = FileLog::new("p4", GlobalOpts::default()).limit(5);
580 let cmd = filelog.setup_command("p4");
581 assert_eq!(args_of(&cmd), vec!["filelog", "-m", "5"]);
582 }
583
584 #[test]
585 fn skip_promoted_tasks() {
586 let filelog = FileLog::new("p4", GlobalOpts::default())
587 .content_history()
588 .skip_promoted_tasks(true);
589 let cmd = filelog.setup_command("p4");
590 assert_eq!(args_of(&cmd), vec!["filelog", "-h", "-p"]);
591 }
592
593 #[test]
594 fn skip_promoted_tasks_accessors() {
595 let mut filelog = FileLog::new("p4", GlobalOpts::default())
596 .full_description()
597 .content_history();
598 filelog.set_skip_promoted_tasks(true);
599
600 assert!(filelog.get_skip_promoted_tasks());
601 assert_eq!(
602 args_of(&filelog.setup_command("p4")),
603 ["filelog", "-h", "-p", "-l"]
604 );
605 }
606
607 #[test]
608 fn ignore_non_contributory() {
609 let filelog = FileLog::new("p4", GlobalOpts::default()).ignore_non_contributory(true);
610 let cmd = filelog.setup_command("p4");
611 assert_eq!(args_of(&cmd), vec!["filelog", "-s"]);
612 }
613
614 #[test]
615 fn include_time() {
616 let filelog = FileLog::new("p4", GlobalOpts::default()).include_time(true);
617 let cmd = filelog.setup_command("p4");
618 assert_eq!(args_of(&cmd), vec!["filelog", "-t"]);
619 }
620
621 #[test]
622 fn all_options_order() {
623 let filelog = FileLog::new("p4", GlobalOpts::default())
624 .changelist("100")
625 .content_history()
626 .follow_branches(true)
627 .full_description()
628 .limit(5)
629 .skip_promoted_tasks(true)
630 .ignore_non_contributory(true)
631 .include_time(true);
632 let cmd = filelog.setup_command("p4");
633 assert_eq!(
634 args_of(&cmd),
635 vec![
636 "filelog", "-c", "100", "-h", "-p", "-i", "-l", "-m", "5", "-s", "-t",
637 ]
638 );
639 }
640}