1use std::ffi::OsStr;
2use std::path::{Path, PathBuf};
3use std::process::{Child, Command, Stdio};
4
5use super::{DiffOptions, ExclusiveOption, SubCommand, Unselected};
6
7use crate::global::GlobalOpts;
8use crate::spawn::ParameterizedSpawn;
9
10#[cfg(not(feature = "lt2016_1"))]
12#[derive(Debug, Clone, Copy)]
13enum Tab {
14 Default,
16 Custom(u32),
18}
19
20pub mod follow {
23 #[derive(Debug, Clone, Copy, Default)]
25 pub struct Branches;
26
27 #[derive(Debug, Clone, Copy, Default)]
29 pub struct Integrations;
30}
31
32impl ExclusiveOption for follow::Branches {
33 fn inject_args(&self, command: &mut Command) {
34 command.arg("-i");
35 }
36}
37
38impl ExclusiveOption for follow::Integrations {
39 fn inject_args(&self, command: &mut Command) {
40 command.arg("-I");
41 }
42}
43
44#[cfg_attr(
45 feature = "lt2014_2",
46 doc = "`p4 [g-opts] annotate [-a -c -i -I -q -t -d flag] file[revRange] ...`: print file lines along with their revisions."
47)]
48#[cfg_attr(
49 all(feature = "lt2015_1", not(feature = "lt2014_2")),
50 doc = "`p4 [g-opts] annotate [-a -c -i -I -q -t -d options] file[revRange] ...`: print file lines along with their revisions."
51)]
52#[cfg_attr(
53 all(feature = "lt2015_2", not(feature = "lt2015_1")),
54 doc = "`p4 [g-opts] annotate [-a -c -i -I -q -t] [-d options] file[revRange] ...`: print file lines along with their revisions."
55)]
56#[cfg_attr(
57 all(feature = "lt2016_1", not(feature = "lt2015_2")),
58 doc = "`p4 [g-opts] annotate [-a -c -i -I -q -t -u] [-d options] file[revRange] ...`: print file lines along with their revisions."
59)]
60#[cfg_attr(
61 all(feature = "lt2018_1", not(feature = "lt2016_1")),
62 doc = "`p4 [g-opts] annotate [-a -c -i -I -q -t -u -T] [-d options] file[revRange] ...`: print file lines along with their revisions."
63)]
64#[cfg_attr(
65 all(feature = "lt2019_1", not(feature = "lt2018_1")),
66 doc = "`p4 [g-opts] annotate [-a -c -i -I -q -t -u -T] [-d options] File Spec[revSpec]`: print file lines along with their revisions."
67)]
68#[cfg_attr(
69 all(feature = "lt2022_2", not(feature = "lt2019_1")),
70 doc = "`p4 [g-opts] annotate [-a -c -i -I -q -t -T -u] [-d options] File Spec[revSpec]`: print file lines along with their revisions."
71)]
72#[cfg_attr(
73 not(feature = "lt2022_2"),
74 doc = "`p4 [g-opts] annotate [-a -c -i -I -q -t -T -u] [-d options] FileSpec[revSpec]`: print file lines along with their revisions."
75)]
76#[derive(Debug, Clone, Default)]
77pub struct Annotate<F = Unselected> {
78 bin: PathBuf,
79
80 global_opts: GlobalOpts,
81
82 all_lines: bool,
83
84 changelist_number: bool,
85
86 diff_options: Option<DiffOptions>,
87
88 follow: F,
89
90 quiet_mode: bool,
91
92 force_binary: bool,
93
94 #[cfg(not(feature = "lt2015_2"))]
95 user_date: bool,
96
97 #[cfg(not(feature = "lt2016_1"))]
98 tab: Option<Tab>,
99}
100
101impl Annotate<Unselected> {
102 pub fn new(bin: impl Into<PathBuf>, global_opts: GlobalOpts) -> Self {
106 Self {
107 bin: bin.into(),
108 global_opts,
109 ..Default::default()
110 }
111 }
112
113 #[cfg_attr(
120 feature = "lt2017_2",
121 doc = "Perforce includes revisions up to the branch point."
122 )]
123 #[cfg_attr(
124 not(feature = "lt2017_2"),
125 doc = "the output includes revisions up to the branch point."
126 )]
127 pub fn follow_branches(self) -> Annotate<follow::Branches> {
130 Annotate {
131 bin: self.bin,
132 global_opts: self.global_opts,
133 all_lines: self.all_lines,
134 changelist_number: self.changelist_number,
135 diff_options: self.diff_options,
136 follow: follow::Branches,
137 quiet_mode: self.quiet_mode,
138 force_binary: self.force_binary,
139 #[cfg(not(feature = "lt2015_2"))]
140 user_date: self.user_date,
141 #[cfg(not(feature = "lt2016_1"))]
142 tab: self.tab,
143 }
144 }
145
146 #[cfg_attr(
154 feature = "lt2017_2",
155 doc = "If that source was itself the result of an integration, that",
156 doc = "source will be used instead, and so on. The use of the -I option",
157 doc = "implies the -c option. The -I option cannot be combined with -i."
158 )]
159 #[cfg_attr(
160 not(feature = "lt2017_2"),
161 doc = "If that source was itself the result of an integration, that",
162 doc = "source will be used instead. The use of the -I option implies the",
163 doc = "-c option. The -I option cannot be combined with -i."
164 )]
165 pub fn follow_integrations(self) -> Annotate<follow::Integrations> {
166 Annotate {
167 bin: self.bin,
168 global_opts: self.global_opts,
169 all_lines: self.all_lines,
170 changelist_number: self.changelist_number,
171 diff_options: self.diff_options,
172 follow: follow::Integrations,
173 quiet_mode: self.quiet_mode,
174 force_binary: self.force_binary,
175 #[cfg(not(feature = "lt2015_2"))]
176 user_date: self.user_date,
177 #[cfg(not(feature = "lt2016_1"))]
178 tab: self.tab,
179 }
180 }
181}
182
183impl<F: ExclusiveOption, S, I> ParameterizedSpawn<(S,)> for Annotate<F>
184where
185 S: IntoIterator<Item = I>,
186 I: AsRef<OsStr>,
187{
188 type Output = Child;
189 type Error = std::io::Error;
190
191 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<F: ExclusiveOption> Annotate<F> {
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_all_lines(&self) -> bool {
306 self.all_lines
307 }
308
309 pub fn set_all_lines(&mut self, v: bool) -> &mut Self {
317 self.all_lines = v;
318 self
319 }
320
321 pub fn all_lines(mut self, v: bool) -> Self {
329 self.all_lines = v;
330 self
331 }
332
333 pub fn get_changelist_number(&self) -> bool {
341 self.changelist_number
342 }
343
344 pub fn set_changelist_number(&mut self, v: bool) -> &mut Self {
352 self.changelist_number = v;
353 self
354 }
355
356 pub fn changelist_number(mut self, v: bool) -> Self {
364 self.changelist_number = v;
365 self
366 }
367
368 #[cfg_attr(feature = "lt2014_2", doc = "-d flag")]
371 #[cfg_attr(not(feature = "lt2014_2"), doc = "-d options")]
372 #[cfg_attr(
375 feature = "lt2014_2",
376 doc = "flags. See the Usage Notes below for a listing of these",
377 doc = "flags."
378 )]
379 #[cfg_attr(
380 all(feature = "lt2015_1", not(feature = "lt2014_2")),
381 doc = "options. See Usage Notes below for a listing of these",
382 doc = "options."
383 )]
384 #[cfg_attr(
385 all(feature = "lt2024_1", not(feature = "lt2015_1")),
386 doc = "options. See Usage Notes for a listing of these options."
387 )]
388 #[cfg_attr(
389 not(feature = "lt2024_1"),
390 doc = "options. See Usage notes for a listing of these options."
391 )]
392 pub fn get_diff_options(&self) -> Option<&DiffOptions> {
393 self.diff_options.as_ref()
394 }
395
396 #[cfg_attr(feature = "lt2014_2", doc = "-d flag")]
399 #[cfg_attr(not(feature = "lt2014_2"), doc = "-d options")]
400 #[cfg_attr(
403 feature = "lt2014_2",
404 doc = "flags. See the Usage Notes below for a listing of these",
405 doc = "flags."
406 )]
407 #[cfg_attr(
408 all(feature = "lt2015_1", not(feature = "lt2014_2")),
409 doc = "options. See Usage Notes below for a listing of these",
410 doc = "options."
411 )]
412 #[cfg_attr(
413 all(feature = "lt2024_1", not(feature = "lt2015_1")),
414 doc = "options. See Usage Notes for a listing of these options."
415 )]
416 #[cfg_attr(
417 not(feature = "lt2024_1"),
418 doc = "options. See Usage notes for a listing of these options."
419 )]
420 pub fn set_diff_options(&mut self, v: impl Into<DiffOptions>) -> &mut Self {
421 self.diff_options = Some(v.into());
422 self
423 }
424
425 #[cfg_attr(feature = "lt2014_2", doc = "-d flag")]
428 #[cfg_attr(not(feature = "lt2014_2"), doc = "-d options")]
429 #[cfg_attr(
432 feature = "lt2014_2",
433 doc = "flags. See the Usage Notes below for a listing of these",
434 doc = "flags."
435 )]
436 #[cfg_attr(
437 all(feature = "lt2015_1", not(feature = "lt2014_2")),
438 doc = "options. See Usage Notes below for a listing of these",
439 doc = "options."
440 )]
441 #[cfg_attr(
442 all(feature = "lt2024_1", not(feature = "lt2015_1")),
443 doc = "options. See Usage Notes for a listing of these options."
444 )]
445 #[cfg_attr(
446 not(feature = "lt2024_1"),
447 doc = "options. See Usage notes for a listing of these options."
448 )]
449 pub fn diff_options(mut self, v: impl Into<DiffOptions>) -> Self {
450 self.diff_options = Some(v.into());
451 self
452 }
453
454 #[cfg_attr(
459 feature = "lt2017_2",
460 doc = "Quiet mode; suppress the one-line header for each file."
461 )]
462 #[cfg_attr(
463 not(feature = "lt2017_2"),
464 doc = "Quiet mode, which suppresses the one-line header for each file."
465 )]
466 pub fn get_quiet_mode(&self) -> bool {
467 self.quiet_mode
468 }
469
470 #[cfg_attr(
475 feature = "lt2017_2",
476 doc = "Quiet mode; suppress the one-line header for each file."
477 )]
478 #[cfg_attr(
479 not(feature = "lt2017_2"),
480 doc = "Quiet mode, which suppresses the one-line header for each file."
481 )]
482 pub fn set_quiet_mode(&mut self, v: bool) -> &mut Self {
483 self.quiet_mode = v;
484 self
485 }
486
487 #[cfg_attr(
492 feature = "lt2017_2",
493 doc = "Quiet mode; suppress the one-line header for each file."
494 )]
495 #[cfg_attr(
496 not(feature = "lt2017_2"),
497 doc = "Quiet mode, which suppresses the one-line header for each file."
498 )]
499 pub fn quiet_mode(mut self, v: bool) -> Self {
500 self.quiet_mode = v;
501 self
502 }
503
504 pub fn get_force_binary(&self) -> bool {
510 self.force_binary
511 }
512
513 pub fn set_force_binary(&mut self, v: bool) -> &mut Self {
519 self.force_binary = v;
520 self
521 }
522
523 pub fn force_binary(mut self, v: bool) -> Self {
529 self.force_binary = v;
530 self
531 }
532
533 #[cfg(not(feature = "lt2015_2"))]
540 pub fn get_user_date(&self) -> bool {
541 self.user_date
542 }
543
544 #[cfg(not(feature = "lt2015_2"))]
551 pub fn set_user_date(&mut self, v: bool) -> &mut Self {
552 self.user_date = v;
553 self
554 }
555
556 #[cfg(not(feature = "lt2015_2"))]
563 pub fn user_date(mut self, v: bool) -> Self {
564 self.user_date = v;
565 self
566 }
567
568 #[cfg(not(feature = "lt2016_1"))]
577 pub fn get_tab(&self) -> Option<u32> {
578 match self.tab {
579 Some(Tab::Default) => Some(8),
580 Some(Tab::Custom(value)) => Some(value),
581 None => None,
582 }
583 }
584
585 #[cfg(not(feature = "lt2016_1"))]
592 pub fn set_tab(&mut self, v: u32) -> &mut Self {
593 self.tab = Some(Tab::Custom(v));
594 self
595 }
596
597 #[cfg(not(feature = "lt2016_1"))]
604 pub fn tab(mut self, v: u32) -> Self {
605 self.tab = Some(Tab::Custom(v));
606 self
607 }
608
609 #[cfg(not(feature = "lt2016_1"))]
616 pub fn set_default_tab(&mut self) -> &mut Self {
617 self.tab = Some(Tab::Default);
618 self
619 }
620
621 #[cfg(not(feature = "lt2016_1"))]
628 pub fn default_tab(mut self) -> Self {
629 self.tab = Some(Tab::Default);
630 self
631 }
632}
633
634impl<F: ExclusiveOption> SubCommand for Annotate<F> {
635 fn name(&self) -> &str {
636 "annotate"
637 }
638
639 fn inject_local_args(&self, command: &mut Command) {
640 if self.all_lines {
641 command.arg("-a");
642 }
643
644 if self.changelist_number {
645 command.arg("-c");
646 }
647
648 if let Some(diff_options) = &self.diff_options {
649 diff_options.inject_arg(command);
650 }
651
652 self.follow.inject_args(command);
653
654 if self.quiet_mode {
655 command.arg("-q");
656 }
657
658 if self.force_binary {
659 command.arg("-t");
660 }
661
662 #[cfg(not(feature = "lt2015_2"))]
663 {
664 if self.user_date {
665 command.arg("-u");
666 }
667 }
668
669 #[cfg(not(feature = "lt2016_1"))]
670 {
671 if let Some(tab) = self.tab {
672 match tab {
673 Tab::Default => {
674 command.arg("-T");
675 }
676 Tab::Custom(value) => {
677 command.arg(format!("--tab={value}"));
678 }
679 }
680 }
681 }
682 }
683
684 fn global_opts(&self) -> Option<&GlobalOpts> {
685 Some(&self.global_opts)
686 }
687}
688
689#[cfg(test)]
690mod tests {
691 use super::*;
692 use crate::cmd::DiffOptionsBuilder;
693 use crate::cmd::args_of;
694
695 #[test]
696 fn bin_accessors_update_executable_path() {
697 let mut command = Annotate::new("p4", GlobalOpts::new());
698 assert_eq!(command.get_bin(), Path::new("p4"));
699
700 command.set_bin("/usr/local/bin/p4");
701 assert_eq!(command.get_bin(), Path::new("/usr/local/bin/p4"));
702
703 let command = command.bin("p4.exe");
704 assert_eq!(command.get_bin(), Path::new("p4.exe"));
705 }
706
707 #[test]
710 fn without_options() {
711 let annotate = Annotate::new("p4", GlobalOpts::new());
712
713 assert_eq!(args_of(&annotate.setup_command("p4")), ["annotate"]);
714 }
715
716 #[test]
717 fn all_options() {
718 let mut annotate = Annotate::new("p4", GlobalOpts::new());
719 annotate
720 .set_all_lines(true)
721 .set_changelist_number(true)
722 .set_diff_options(DiffOptionsBuilder::raw("Su2"))
723 .set_quiet_mode(true)
724 .set_force_binary(true);
725 #[cfg(not(feature = "lt2015_2"))]
726 annotate.set_user_date(true);
727
728 #[cfg_attr(feature = "lt2016_1", allow(unused_mut))]
731 let mut annotate = annotate.follow_branches();
732
733 #[cfg_attr(feature = "lt2016_1", allow(unused_mut))]
735 let mut expected = vec!["annotate", "-a", "-c", "-dSu2", "-i", "-q", "-t"];
736 #[cfg(not(feature = "lt2015_2"))]
737 expected.push("-u");
738 #[cfg(not(feature = "lt2016_1"))]
739 {
740 annotate.set_default_tab();
741 expected.push("-T");
742 }
743
744 assert_eq!(args_of(&annotate.setup_command("p4")), expected);
745 }
746
747 #[cfg(not(feature = "lt2016_1"))]
748 #[test]
749 fn default_tab_uses_short_flag() {
750 let mut annotate = Annotate::new("p4", GlobalOpts::new());
751 annotate.set_default_tab();
752
753 assert_eq!(annotate.get_tab(), Some(8));
754 assert_eq!(args_of(&annotate.setup_command("p4")), ["annotate", "-T"]);
755 }
756
757 #[cfg(not(feature = "lt2016_1"))]
758 #[test]
759 fn custom_tab_uses_long_flag() {
760 let mut annotate = Annotate::new("p4", GlobalOpts::new());
761 annotate.set_tab(4);
762
763 assert_eq!(annotate.get_tab(), Some(4));
764 assert_eq!(
765 args_of(&annotate.setup_command("p4")),
766 ["annotate", "--tab=4"]
767 );
768 }
769}