1use std::ffi::OsStr;
2use std::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 #[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_all_lines(&self) -> bool {
289 self.all_lines
290 }
291
292 pub fn set_all_lines(&mut self, v: bool) -> &mut Self {
300 self.all_lines = v;
301 self
302 }
303
304 pub fn all_lines(mut self, v: bool) -> Self {
312 self.all_lines = v;
313 self
314 }
315
316 pub fn get_changelist_number(&self) -> bool {
324 self.changelist_number
325 }
326
327 pub fn set_changelist_number(&mut self, v: bool) -> &mut Self {
335 self.changelist_number = v;
336 self
337 }
338
339 pub fn changelist_number(mut self, v: bool) -> Self {
347 self.changelist_number = v;
348 self
349 }
350
351 #[cfg_attr(feature = "lt2014_2", doc = "-d flag")]
354 #[cfg_attr(not(feature = "lt2014_2"), doc = "-d options")]
355 #[cfg_attr(
358 feature = "lt2014_2",
359 doc = "flags. See the Usage Notes below for a listing of these",
360 doc = "flags."
361 )]
362 #[cfg_attr(
363 all(feature = "lt2015_1", not(feature = "lt2014_2")),
364 doc = "options. See Usage Notes below for a listing of these",
365 doc = "options."
366 )]
367 #[cfg_attr(
368 all(feature = "lt2024_1", not(feature = "lt2015_1")),
369 doc = "options. See Usage Notes for a listing of these options."
370 )]
371 #[cfg_attr(
372 not(feature = "lt2024_1"),
373 doc = "options. See Usage notes for a listing of these options."
374 )]
375 pub fn get_diff_options(&self) -> Option<&DiffOptions> {
376 self.diff_options.as_ref()
377 }
378
379 #[cfg_attr(feature = "lt2014_2", doc = "-d flag")]
382 #[cfg_attr(not(feature = "lt2014_2"), doc = "-d options")]
383 #[cfg_attr(
386 feature = "lt2014_2",
387 doc = "flags. See the Usage Notes below for a listing of these",
388 doc = "flags."
389 )]
390 #[cfg_attr(
391 all(feature = "lt2015_1", not(feature = "lt2014_2")),
392 doc = "options. See Usage Notes below for a listing of these",
393 doc = "options."
394 )]
395 #[cfg_attr(
396 all(feature = "lt2024_1", not(feature = "lt2015_1")),
397 doc = "options. See Usage Notes for a listing of these options."
398 )]
399 #[cfg_attr(
400 not(feature = "lt2024_1"),
401 doc = "options. See Usage notes for a listing of these options."
402 )]
403 pub fn set_diff_options(&mut self, v: impl Into<DiffOptions>) -> &mut Self {
404 self.diff_options = Some(v.into());
405 self
406 }
407
408 #[cfg_attr(feature = "lt2014_2", doc = "-d flag")]
411 #[cfg_attr(not(feature = "lt2014_2"), doc = "-d options")]
412 #[cfg_attr(
415 feature = "lt2014_2",
416 doc = "flags. See the Usage Notes below for a listing of these",
417 doc = "flags."
418 )]
419 #[cfg_attr(
420 all(feature = "lt2015_1", not(feature = "lt2014_2")),
421 doc = "options. See Usage Notes below for a listing of these",
422 doc = "options."
423 )]
424 #[cfg_attr(
425 all(feature = "lt2024_1", not(feature = "lt2015_1")),
426 doc = "options. See Usage Notes for a listing of these options."
427 )]
428 #[cfg_attr(
429 not(feature = "lt2024_1"),
430 doc = "options. See Usage notes for a listing of these options."
431 )]
432 pub fn diff_options(mut self, v: impl Into<DiffOptions>) -> Self {
433 self.diff_options = Some(v.into());
434 self
435 }
436
437 #[cfg_attr(
442 feature = "lt2017_2",
443 doc = "Quiet mode; suppress the one-line header for each file."
444 )]
445 #[cfg_attr(
446 not(feature = "lt2017_2"),
447 doc = "Quiet mode, which suppresses the one-line header for each file."
448 )]
449 pub fn get_quiet_mode(&self) -> bool {
450 self.quiet_mode
451 }
452
453 #[cfg_attr(
458 feature = "lt2017_2",
459 doc = "Quiet mode; suppress the one-line header for each file."
460 )]
461 #[cfg_attr(
462 not(feature = "lt2017_2"),
463 doc = "Quiet mode, which suppresses the one-line header for each file."
464 )]
465 pub fn set_quiet_mode(&mut self, v: bool) -> &mut Self {
466 self.quiet_mode = v;
467 self
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 quiet_mode(mut self, v: bool) -> Self {
483 self.quiet_mode = v;
484 self
485 }
486
487 pub fn get_force_binary(&self) -> bool {
493 self.force_binary
494 }
495
496 pub fn set_force_binary(&mut self, v: bool) -> &mut Self {
502 self.force_binary = v;
503 self
504 }
505
506 pub fn force_binary(mut self, v: bool) -> Self {
512 self.force_binary = v;
513 self
514 }
515
516 #[cfg(not(feature = "lt2015_2"))]
523 pub fn get_user_date(&self) -> bool {
524 self.user_date
525 }
526
527 #[cfg(not(feature = "lt2015_2"))]
534 pub fn set_user_date(&mut self, v: bool) -> &mut Self {
535 self.user_date = v;
536 self
537 }
538
539 #[cfg(not(feature = "lt2015_2"))]
546 pub fn user_date(mut self, v: bool) -> Self {
547 self.user_date = v;
548 self
549 }
550
551 #[cfg(not(feature = "lt2016_1"))]
560 pub fn get_tab(&self) -> Option<u32> {
561 match self.tab {
562 Some(Tab::Default) => Some(8),
563 Some(Tab::Custom(value)) => Some(value),
564 None => None,
565 }
566 }
567
568 #[cfg(not(feature = "lt2016_1"))]
575 pub fn set_tab(&mut self, v: u32) -> &mut Self {
576 self.tab = Some(Tab::Custom(v));
577 self
578 }
579
580 #[cfg(not(feature = "lt2016_1"))]
587 pub fn tab(mut self, v: u32) -> Self {
588 self.tab = Some(Tab::Custom(v));
589 self
590 }
591
592 #[cfg(not(feature = "lt2016_1"))]
599 pub fn set_default_tab(&mut self) -> &mut Self {
600 self.tab = Some(Tab::Default);
601 self
602 }
603
604 #[cfg(not(feature = "lt2016_1"))]
611 pub fn default_tab(mut self) -> Self {
612 self.tab = Some(Tab::Default);
613 self
614 }
615}
616
617impl<F: ExclusiveOption> SubCommand for Annotate<F> {
618 fn name(&self) -> &str {
619 "annotate"
620 }
621
622 fn inject_local_args(&self, command: &mut Command) {
623 if self.all_lines {
624 command.arg("-a");
625 }
626
627 if self.changelist_number {
628 command.arg("-c");
629 }
630
631 if let Some(diff_options) = &self.diff_options {
632 diff_options.inject_arg(command);
633 }
634
635 self.follow.inject_args(command);
636
637 if self.quiet_mode {
638 command.arg("-q");
639 }
640
641 if self.force_binary {
642 command.arg("-t");
643 }
644
645 #[cfg(not(feature = "lt2015_2"))]
646 {
647 if self.user_date {
648 command.arg("-u");
649 }
650 }
651
652 #[cfg(not(feature = "lt2016_1"))]
653 {
654 if let Some(tab) = self.tab {
655 match tab {
656 Tab::Default => {
657 command.arg("-T");
658 }
659 Tab::Custom(value) => {
660 command.arg(format!("--tab={value}"));
661 }
662 }
663 }
664 }
665 }
666
667 fn global_opts(&self) -> Option<&GlobalOpts> {
668 Some(&self.global_opts)
669 }
670}
671
672#[cfg(test)]
673mod tests {
674 use super::*;
675 use crate::cmd::DiffOptionsBuilder;
676 use crate::cmd::args_of;
677
678 #[test]
681 fn without_options() {
682 let annotate = Annotate::new("p4", GlobalOpts::new());
683
684 assert_eq!(args_of(&annotate.setup_command("p4")), ["annotate"]);
685 }
686
687 #[test]
688 fn all_options() {
689 let mut annotate = Annotate::new("p4", GlobalOpts::new());
690 annotate
691 .set_all_lines(true)
692 .set_changelist_number(true)
693 .set_diff_options(DiffOptionsBuilder::raw("Su2"))
694 .set_quiet_mode(true)
695 .set_force_binary(true);
696 #[cfg(not(feature = "lt2015_2"))]
697 annotate.set_user_date(true);
698
699 #[cfg_attr(feature = "lt2016_1", allow(unused_mut))]
702 let mut annotate = annotate.follow_branches();
703
704 #[cfg_attr(feature = "lt2016_1", allow(unused_mut))]
706 let mut expected = vec!["annotate", "-a", "-c", "-dSu2", "-i", "-q", "-t"];
707 #[cfg(not(feature = "lt2015_2"))]
708 expected.push("-u");
709 #[cfg(not(feature = "lt2016_1"))]
710 {
711 annotate.set_default_tab();
712 expected.push("-T");
713 }
714
715 assert_eq!(args_of(&annotate.setup_command("p4")), expected);
716 }
717
718 #[cfg(not(feature = "lt2016_1"))]
719 #[test]
720 fn default_tab_uses_short_flag() {
721 let mut annotate = Annotate::new("p4", GlobalOpts::new());
722 annotate.set_default_tab();
723
724 assert_eq!(annotate.get_tab(), Some(8));
725 assert_eq!(args_of(&annotate.setup_command("p4")), ["annotate", "-T"]);
726 }
727
728 #[cfg(not(feature = "lt2016_1"))]
729 #[test]
730 fn custom_tab_uses_long_flag() {
731 let mut annotate = Annotate::new("p4", GlobalOpts::new());
732 annotate.set_tab(4);
733
734 assert_eq!(annotate.get_tab(), Some(4));
735 assert_eq!(
736 args_of(&annotate.setup_command("p4")),
737 ["annotate", "--tab=4"]
738 );
739 }
740}