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> ParameterizedSpawn for FileLog<L, H> {
182 type Input<'a> = &'a [&'a OsStr];
183 type Output<'a> = Child;
184 type Error = std::io::Error;
185
186 fn spawn_with<'a>(&mut self, files: Self::Input<'a>) -> Result<Self::Output<'a>, Self::Error> {
192 self.setup_command(&self.bin)
193 .args(files)
194 .stdout(Stdio::piped())
195 .stderr(Stdio::piped())
196 .spawn()
197 }
198}
199
200impl<L: ExclusiveOption, H: ExclusiveOption> FileLog<L, H> {
201 #[cfg_attr(
206 feature = "lt2014_2",
207 doc = "See the [Global Options](GlobalOpts) section."
208 )]
209 #[cfg_attr(
210 all(feature = "lt2015_1", not(feature = "lt2014_2")),
211 doc = "See the [“Global Options”](GlobalOpts) section."
212 )]
213 #[cfg_attr(
214 all(feature = "lt2017_1", not(feature = "lt2015_1")),
215 doc = "See [“Global Options”](GlobalOpts)."
216 )]
217 #[cfg_attr(
218 all(feature = "lt2018_2", not(feature = "lt2017_1")),
219 doc = "See [Global Options](GlobalOpts)."
220 )]
221 #[cfg_attr(not(feature = "lt2018_2"), doc = "See [Global options](GlobalOpts).")]
222 pub fn get_global_opts(&self) -> &GlobalOpts {
223 &self.global_opts
224 }
225
226 #[cfg_attr(
231 feature = "lt2014_2",
232 doc = "See the [Global Options](GlobalOpts) section."
233 )]
234 #[cfg_attr(
235 all(feature = "lt2015_1", not(feature = "lt2014_2")),
236 doc = "See the [“Global Options”](GlobalOpts) section."
237 )]
238 #[cfg_attr(
239 all(feature = "lt2017_1", not(feature = "lt2015_1")),
240 doc = "See [“Global Options”](GlobalOpts)."
241 )]
242 #[cfg_attr(
243 all(feature = "lt2018_2", not(feature = "lt2017_1")),
244 doc = "See [Global Options](GlobalOpts)."
245 )]
246 #[cfg_attr(not(feature = "lt2018_2"), doc = "See [Global options](GlobalOpts).")]
247 pub fn set_global_opts(&mut self, v: GlobalOpts) -> &mut Self {
248 self.global_opts = v;
249 self
250 }
251
252 #[cfg_attr(
257 feature = "lt2014_2",
258 doc = "See the [Global Options](GlobalOpts) section."
259 )]
260 #[cfg_attr(
261 all(feature = "lt2015_1", not(feature = "lt2014_2")),
262 doc = "See the [“Global Options”](GlobalOpts) section."
263 )]
264 #[cfg_attr(
265 all(feature = "lt2017_1", not(feature = "lt2015_1")),
266 doc = "See [“Global Options”](GlobalOpts)."
267 )]
268 #[cfg_attr(
269 all(feature = "lt2018_2", not(feature = "lt2017_1")),
270 doc = "See [Global Options](GlobalOpts)."
271 )]
272 #[cfg_attr(not(feature = "lt2018_2"), doc = "See [Global options](GlobalOpts).")]
273 pub fn global_opts(mut self, v: GlobalOpts) -> Self {
274 self.global_opts = v;
275 self
276 }
277
278 pub fn get_changelist(&self) -> Option<&String> {
284 self.changelist.as_ref()
285 }
286
287 pub fn set_changelist(&mut self, v: impl Into<String>) -> &mut Self {
293 self.changelist = Some(v.into());
294 self
295 }
296
297 pub fn changelist(mut self, v: impl Into<String>) -> Self {
303 self.changelist = Some(v.into());
304 self
305 }
306
307 pub fn get_follow_branches(&self) -> bool {
313 self.follow_branches
314 }
315
316 pub fn set_follow_branches(&mut self, v: bool) -> &mut Self {
322 self.follow_branches = v;
323 self
324 }
325
326 pub fn follow_branches(mut self, v: bool) -> Self {
332 self.follow_branches = v;
333 self
334 }
335
336 pub fn get_limit(&self) -> Option<u64> {
342 self.limit
343 }
344
345 pub fn set_limit(&mut self, v: u64) -> &mut Self {
351 self.limit = Some(v);
352 self
353 }
354
355 pub fn limit(mut self, v: u64) -> Self {
361 self.limit = Some(v);
362 self
363 }
364
365 pub fn get_ignore_non_contributory(&self) -> bool {
372 self.ignore_non_contributory
373 }
374
375 pub fn set_ignore_non_contributory(&mut self, v: bool) -> &mut Self {
382 self.ignore_non_contributory = v;
383 self
384 }
385
386 pub fn ignore_non_contributory(mut self, v: bool) -> Self {
393 self.ignore_non_contributory = v;
394 self
395 }
396
397 pub fn get_include_time(&self) -> bool {
403 self.include_time
404 }
405
406 pub fn set_include_time(&mut self, v: bool) -> &mut Self {
412 self.include_time = v;
413 self
414 }
415
416 pub fn include_time(mut self, v: bool) -> Self {
422 self.include_time = v;
423 self
424 }
425}
426
427impl<L: ExclusiveOption> FileLog<L, DisplayContentHistory> {
428 pub fn get_skip_promoted_tasks(&self) -> bool {
435 self.content_history.skip_promoted_tasks
436 }
437
438 pub fn set_skip_promoted_tasks(&mut self, v: bool) -> &mut Self {
445 self.content_history.skip_promoted_tasks = v;
446 self
447 }
448
449 pub fn skip_promoted_tasks(mut self, v: bool) -> Self {
456 self.content_history.skip_promoted_tasks = v;
457 self
458 }
459}
460
461impl<L: ExclusiveOption, H: ExclusiveOption> SubCommand for FileLog<L, H> {
462 fn name(&self) -> &str {
463 "filelog"
464 }
465
466 fn inject_local_args(&self, command: &mut Command) {
467 if let Some(changelist) = &self.changelist {
468 command.arg("-c").arg(changelist);
469 }
470
471 self.content_history.inject_args(command);
472
473 if self.follow_branches {
474 command.arg("-i");
475 }
476
477 self.long_output.inject_args(command);
478
479 if let Some(max) = self.limit {
480 command.arg("-m").arg(max.to_string());
481 }
482
483 if self.ignore_non_contributory {
484 command.arg("-s");
485 }
486
487 if self.include_time {
488 command.arg("-t");
489 }
490 }
491
492 fn global_opts(&self) -> Option<&GlobalOpts> {
493 Some(&self.global_opts)
494 }
495}
496
497#[cfg(test)]
498mod tests {
499 use super::*;
500 use crate::cmd::args_of;
501
502 #[test]
503 fn with_files() {
504 let filelog = FileLog::new("p4", GlobalOpts::default());
505 let mut cmd = filelog.setup_command("p4");
506 cmd.arg("//depot/project/...");
507 assert_eq!(args_of(&cmd), vec!["filelog", "//depot/project/..."]);
508 }
509
510 #[test]
511 fn changelist() {
512 let filelog = FileLog::new("p4", GlobalOpts::default()).changelist("100");
513 let cmd = filelog.setup_command("p4");
514 assert_eq!(args_of(&cmd), vec!["filelog", "-c", "100"]);
515 }
516
517 #[test]
518 fn content_history() {
519 let filelog = FileLog::new("p4", GlobalOpts::default()).content_history();
520 let cmd = filelog.setup_command("p4");
521 assert_eq!(args_of(&cmd), vec!["filelog", "-h"]);
522 }
523
524 #[test]
525 fn follow_branches() {
526 let filelog = FileLog::new("p4", GlobalOpts::default()).follow_branches(true);
527 let cmd = filelog.setup_command("p4");
528 assert_eq!(args_of(&cmd), vec!["filelog", "-i"]);
529 }
530
531 #[test]
532 fn full_description() {
533 let filelog = FileLog::new("p4", GlobalOpts::default()).full_description();
534 let cmd = filelog.setup_command("p4");
535 assert_eq!(args_of(&cmd), vec!["filelog", "-l"]);
536 }
537
538 #[test]
539 fn truncated_description() {
540 let filelog = FileLog::new("p4", GlobalOpts::default()).truncated_description();
541 let cmd = filelog.setup_command("p4");
542 assert_eq!(args_of(&cmd), vec!["filelog", "-L"]);
543 }
544
545 #[test]
546 fn limit() {
547 let filelog = FileLog::new("p4", GlobalOpts::default()).limit(5);
548 let cmd = filelog.setup_command("p4");
549 assert_eq!(args_of(&cmd), vec!["filelog", "-m", "5"]);
550 }
551
552 #[test]
553 fn skip_promoted_tasks() {
554 let filelog = FileLog::new("p4", GlobalOpts::default())
555 .content_history()
556 .skip_promoted_tasks(true);
557 let cmd = filelog.setup_command("p4");
558 assert_eq!(args_of(&cmd), vec!["filelog", "-h", "-p"]);
559 }
560
561 #[test]
562 fn skip_promoted_tasks_accessors() {
563 let mut filelog = FileLog::new("p4", GlobalOpts::default())
564 .full_description()
565 .content_history();
566 filelog.set_skip_promoted_tasks(true);
567
568 assert!(filelog.get_skip_promoted_tasks());
569 assert_eq!(
570 args_of(&filelog.setup_command("p4")),
571 ["filelog", "-h", "-p", "-l"]
572 );
573 }
574
575 #[test]
576 fn ignore_non_contributory() {
577 let filelog = FileLog::new("p4", GlobalOpts::default()).ignore_non_contributory(true);
578 let cmd = filelog.setup_command("p4");
579 assert_eq!(args_of(&cmd), vec!["filelog", "-s"]);
580 }
581
582 #[test]
583 fn include_time() {
584 let filelog = FileLog::new("p4", GlobalOpts::default()).include_time(true);
585 let cmd = filelog.setup_command("p4");
586 assert_eq!(args_of(&cmd), vec!["filelog", "-t"]);
587 }
588
589 #[test]
590 fn all_options_order() {
591 let filelog = FileLog::new("p4", GlobalOpts::default())
592 .changelist("100")
593 .content_history()
594 .follow_branches(true)
595 .full_description()
596 .limit(5)
597 .skip_promoted_tasks(true)
598 .ignore_non_contributory(true)
599 .include_time(true);
600 let cmd = filelog.setup_command("p4");
601 assert_eq!(
602 args_of(&cmd),
603 vec![
604 "filelog", "-c", "100", "-h", "-p", "-i", "-l", "-m", "5", "-s", "-t",
605 ]
606 );
607 }
608}