1use std::{
2 ffi::OsStr,
3 path::{Path, PathBuf},
4 process::{Child, Stdio},
5};
6
7use super::SubCommand;
8
9use crate::{global::GlobalOpts, spawn::ParameterizedSpawn};
10
11#[derive(Debug, Clone, Default)]
15pub struct Add {
16 bin: PathBuf,
17
18 global_opts: GlobalOpts,
19
20 change_list: Option<String>,
21
22 downgrade: bool,
23
24 force_literal_filenames: bool,
25
26 skip_ignore: bool,
27
28 preview: bool,
29
30 filetype: Option<String>,
31}
32
33impl SubCommand for Add {
34 fn name(&self) -> &str {
35 "add"
36 }
37
38 fn inject_local_args(&self, command: &mut std::process::Command) {
39 if let Some(change_list) = self.change_list.as_ref() {
40 command.arg("-c").arg(change_list);
41 }
42 if self.downgrade {
43 command.arg("-d");
44 }
45 if self.force_literal_filenames {
46 command.arg("-f");
47 }
48 if self.skip_ignore {
49 command.arg("-I");
50 }
51 if self.preview {
52 command.arg("-n");
53 }
54 if let Some(filetype) = self.filetype.as_ref() {
55 command.arg("-t").arg(filetype);
56 }
57 }
58
59 fn global_opts(&self) -> Option<&GlobalOpts> {
60 Some(&self.global_opts)
61 }
62}
63
64impl<S, I> ParameterizedSpawn<(S,)> for Add
65where
66 S: IntoIterator<Item = I>,
67 I: AsRef<OsStr>,
68{
69 type Output = Child;
70 type Error = std::io::Error;
71
72 fn spawn_with(&mut self, (input,): (S,)) -> Result<Self::Output, Self::Error> {
78 self.setup_command(&self.bin)
79 .args(input)
80 .stdout(Stdio::piped())
81 .stderr(Stdio::piped())
82 .spawn()
83 }
84}
85
86impl Add {
87 pub fn get_bin(&self) -> &Path {
89 &self.bin
90 }
91
92 pub fn set_bin(&mut self, bin: impl Into<PathBuf>) -> &mut Self {
94 self.bin = bin.into();
95 self
96 }
97
98 pub fn bin(mut self, bin: impl Into<PathBuf>) -> Self {
100 self.set_bin(bin);
101 self
102 }
103
104 pub fn new(bin: impl Into<PathBuf>, global_opts: GlobalOpts) -> Self {
108 Self {
109 bin: bin.into(),
110 global_opts,
111 ..Default::default()
112 }
113 }
114
115 #[cfg_attr(
120 feature = "lt2014_2",
121 doc = "See the [Global Options](GlobalOpts) section."
122 )]
123 #[cfg_attr(
124 all(feature = "lt2015_1", not(feature = "lt2014_2")),
125 doc = "See the [“Global Options”](GlobalOpts) section."
126 )]
127 #[cfg_attr(
128 all(feature = "lt2017_1", not(feature = "lt2015_1")),
129 doc = "See [“Global Options”](GlobalOpts)."
130 )]
131 #[cfg_attr(
132 all(feature = "lt2018_2", not(feature = "lt2017_1")),
133 doc = "See [Global Options](GlobalOpts)."
134 )]
135 #[cfg_attr(not(feature = "lt2018_2"), doc = "See [Global options](GlobalOpts).")]
136 pub fn get_global_opts(&self) -> &GlobalOpts {
137 &self.global_opts
138 }
139
140 #[cfg_attr(
145 feature = "lt2014_2",
146 doc = "See the [Global Options](GlobalOpts) section."
147 )]
148 #[cfg_attr(
149 all(feature = "lt2015_1", not(feature = "lt2014_2")),
150 doc = "See the [“Global Options”](GlobalOpts) section."
151 )]
152 #[cfg_attr(
153 all(feature = "lt2017_1", not(feature = "lt2015_1")),
154 doc = "See [“Global Options”](GlobalOpts)."
155 )]
156 #[cfg_attr(
157 all(feature = "lt2018_2", not(feature = "lt2017_1")),
158 doc = "See [Global Options](GlobalOpts)."
159 )]
160 #[cfg_attr(not(feature = "lt2018_2"), doc = "See [Global options](GlobalOpts).")]
161 pub fn set_global_opts(&mut self, v: GlobalOpts) -> &mut Self {
162 self.global_opts = v;
163 self
164 }
165
166 #[cfg_attr(
171 feature = "lt2014_2",
172 doc = "See the [Global Options](GlobalOpts) section."
173 )]
174 #[cfg_attr(
175 all(feature = "lt2015_1", not(feature = "lt2014_2")),
176 doc = "See the [“Global Options”](GlobalOpts) section."
177 )]
178 #[cfg_attr(
179 all(feature = "lt2017_1", not(feature = "lt2015_1")),
180 doc = "See [“Global Options”](GlobalOpts)."
181 )]
182 #[cfg_attr(
183 all(feature = "lt2018_2", not(feature = "lt2017_1")),
184 doc = "See [Global Options](GlobalOpts)."
185 )]
186 #[cfg_attr(not(feature = "lt2018_2"), doc = "See [Global options](GlobalOpts).")]
187 pub fn global_opts(mut self, v: GlobalOpts) -> Self {
188 self.global_opts = v;
189 self
190 }
191
192 #[cfg_attr(feature = "lt2014_2", doc = "flag is")]
198 #[cfg_attr(not(feature = "lt2014_2"), doc = "option is")]
199 pub fn get_change_list(&self) -> Option<&String> {
201 self.change_list.as_ref()
202 }
203
204 #[cfg_attr(feature = "lt2014_2", doc = "flag is")]
210 #[cfg_attr(not(feature = "lt2014_2"), doc = "option is")]
211 pub fn set_change_list(&mut self, v: impl Into<String>) -> &mut Self {
213 self.change_list = Some(v.into());
214 self
215 }
216
217 #[cfg_attr(feature = "lt2014_2", doc = "flag is")]
223 #[cfg_attr(not(feature = "lt2014_2"), doc = "option is")]
224 pub fn change_list(mut self, v: impl Into<String>) -> Self {
226 self.change_list = Some(v.into());
227 self
228 }
229
230 pub fn get_downgrade(&self) -> bool {
236 self.downgrade
237 }
238 pub fn set_downgrade(&mut self, v: bool) -> &mut Self {
244 self.downgrade = v;
245 self
246 }
247 pub fn downgrade(mut self, v: bool) -> Self {
253 self.downgrade = v;
254 self
255 }
256
257 #[cfg_attr(
263 feature = "lt2014_2",
264 doc = "flag to force inclusion of wildcards in filenames. See the",
265 doc = "File Specifications chapter for details."
266 )]
267 #[cfg_attr(
268 all(feature = "lt2015_1", not(feature = "lt2014_2")),
269 doc = "option to force inclusion of wildcards in filenames. See the",
270 doc = "“File Specifications” chapter for details."
271 )]
272 #[cfg_attr(
273 all(feature = "lt2017_1", not(feature = "lt2015_1")),
274 doc = "option to force inclusion of wildcards in filenames. See",
275 doc = "“File Specifications” for details."
276 )]
277 #[cfg_attr(
278 all(feature = "lt2018_2", not(feature = "lt2017_1")),
279 doc = "option to force inclusion of wildcards in filenames. See File",
280 doc = "Specifications for details."
281 )]
282 #[cfg_attr(
283 all(feature = "lt2025_1", not(feature = "lt2018_2")),
284 doc = "option to force inclusion of wildcards in filenames. See File",
285 doc = "specifications for details."
286 )]
287 #[cfg_attr(
288 not(feature = "lt2025_1"),
289 doc = "option to force inclusion of wildcards in filenames. See File",
290 doc = "specifications for details.",
291 doc = "",
292 doc = "Filenames that contain the special characters '@', '#', '%', or '*' are",
293 doc = "reformatted to encode the characters using ASCII hexadecimal",
294 doc = "representation. After the files are added, refer to them using the",
295 doc = "reformatted file name instead of the local file system name."
296 )]
297 pub fn get_force_literal_filenames(&self) -> bool {
298 self.force_literal_filenames
299 }
300
301 #[cfg_attr(
307 feature = "lt2014_2",
308 doc = "flag to force inclusion of wildcards in filenames. See the",
309 doc = "File Specifications chapter for details."
310 )]
311 #[cfg_attr(
312 all(feature = "lt2015_1", not(feature = "lt2014_2")),
313 doc = "option to force inclusion of wildcards in filenames. See the",
314 doc = "“File Specifications” chapter for details."
315 )]
316 #[cfg_attr(
317 all(feature = "lt2017_1", not(feature = "lt2015_1")),
318 doc = "option to force inclusion of wildcards in filenames. See",
319 doc = "“File Specifications” for details."
320 )]
321 #[cfg_attr(
322 all(feature = "lt2018_2", not(feature = "lt2017_1")),
323 doc = "option to force inclusion of wildcards in filenames. See File",
324 doc = "Specifications for details."
325 )]
326 #[cfg_attr(
327 all(feature = "lt2025_1", not(feature = "lt2018_2")),
328 doc = "option to force inclusion of wildcards in filenames. See File",
329 doc = "specifications for details."
330 )]
331 #[cfg_attr(
332 not(feature = "lt2025_1"),
333 doc = "option to force inclusion of wildcards in filenames. See File",
334 doc = "specifications for details.",
335 doc = "",
336 doc = "Filenames that contain the special characters '@', '#', '%', or '*' are",
337 doc = "reformatted to encode the characters using ASCII hexadecimal",
338 doc = "representation. After the files are added, refer to them using the",
339 doc = "reformatted file name instead of the local file system name."
340 )]
341 pub fn set_force_literal_filenames(&mut self, v: bool) -> &mut Self {
342 self.force_literal_filenames = v;
343 self
344 }
345
346 #[cfg_attr(
352 feature = "lt2014_2",
353 doc = "flag to force inclusion of wildcards in filenames. See the",
354 doc = "File Specifications chapter for details."
355 )]
356 #[cfg_attr(
357 all(feature = "lt2015_1", not(feature = "lt2014_2")),
358 doc = "option to force inclusion of wildcards in filenames. See the",
359 doc = "“File Specifications” chapter for details."
360 )]
361 #[cfg_attr(
362 all(feature = "lt2017_1", not(feature = "lt2015_1")),
363 doc = "option to force inclusion of wildcards in filenames. See",
364 doc = "“File Specifications” for details."
365 )]
366 #[cfg_attr(
367 all(feature = "lt2018_2", not(feature = "lt2017_1")),
368 doc = "option to force inclusion of wildcards in filenames. See File",
369 doc = "Specifications for details."
370 )]
371 #[cfg_attr(
372 all(feature = "lt2025_1", not(feature = "lt2018_2")),
373 doc = "option to force inclusion of wildcards in filenames. See File",
374 doc = "specifications for details."
375 )]
376 #[cfg_attr(
377 not(feature = "lt2025_1"),
378 doc = "option to force inclusion of wildcards in filenames. See File",
379 doc = "specifications for details.",
380 doc = "",
381 doc = "Filenames that contain the special characters '@', '#', '%', or '*' are",
382 doc = "reformatted to encode the characters using ASCII hexadecimal",
383 doc = "representation. After the files are added, refer to them using the",
384 doc = "reformatted file name instead of the local file system name."
385 )]
386 pub fn force_literal_filenames(mut self, v: bool) -> Self {
387 self.force_literal_filenames = v;
388 self
389 }
390
391 pub fn get_skip_ignore(&self) -> bool {
398 self.skip_ignore
399 }
400 pub fn set_skip_ignore(&mut self, v: bool) -> &mut Self {
407 self.skip_ignore = v;
408 self
409 }
410 pub fn skip_ignore(mut self, v: bool) -> Self {
417 self.skip_ignore = v;
418 self
419 }
420
421 pub fn get_preview(&self) -> bool {
428 self.preview
429 }
430 pub fn set_preview(&mut self, v: bool) -> &mut Self {
437 self.preview = v;
438 self
439 }
440 pub fn preview(mut self, v: bool) -> Self {
447 self.preview = v;
448 self
449 }
450
451 #[cfg_attr(
458 feature = "lt2014_2",
459 doc = "Please see the File Types chapter for a list of Perforce",
460 doc = "file types."
461 )]
462 #[cfg_attr(
463 all(feature = "lt2015_1", not(feature = "lt2014_2")),
464 doc = "Please see the “File Types” chapter for a list of",
465 doc = "Perforce file types."
466 )]
467 #[cfg_attr(
468 all(feature = "lt2017_1", not(feature = "lt2015_1")),
469 doc = "See “File Types” for a list of Perforce file types."
470 )]
471 #[cfg_attr(
472 all(feature = "lt2017_2", not(feature = "lt2017_1")),
473 doc = "See File Types for a list of Perforce file types."
474 )]
475 #[cfg_attr(
476 all(feature = "lt2018_2", not(feature = "lt2017_2")),
477 doc = "See File Types for a list of Helix Server file types."
478 )]
479 #[cfg_attr(
480 all(feature = "lt2019_1", not(feature = "lt2018_2")),
481 doc = "See File types for a list of Helix Server file types."
482 )]
483 #[cfg_attr(
484 all(feature = "lt2021_1", not(feature = "lt2019_1")),
485 doc = "See File types for a list of Helix server file types."
486 )]
487 #[cfg_attr(
488 all(feature = "lt2024_1", not(feature = "lt2021_1")),
489 doc = "See File types for a list of Helix Server file types."
490 )]
491 #[cfg_attr(
492 all(feature = "lt2025_2", not(feature = "lt2024_1")),
493 doc = "See File types as well as the lbr.autocompress",
494 doc = "configurable."
495 )]
496 #[cfg_attr(
497 not(feature = "lt2025_2"),
498 doc = "See Base file types and modifiers as well as the",
499 doc = "lbr.autocompress configurable."
500 )]
501 pub fn get_filetype(&self) -> Option<&String> {
502 self.filetype.as_ref()
503 }
504
505 #[cfg_attr(
512 feature = "lt2014_2",
513 doc = "Please see the File Types chapter for a list of Perforce",
514 doc = "file types."
515 )]
516 #[cfg_attr(
517 all(feature = "lt2015_1", not(feature = "lt2014_2")),
518 doc = "Please see the “File Types” chapter for a list of",
519 doc = "Perforce file types."
520 )]
521 #[cfg_attr(
522 all(feature = "lt2017_1", not(feature = "lt2015_1")),
523 doc = "See “File Types” for a list of Perforce file types."
524 )]
525 #[cfg_attr(
526 all(feature = "lt2017_2", not(feature = "lt2017_1")),
527 doc = "See File Types for a list of Perforce file types."
528 )]
529 #[cfg_attr(
530 all(feature = "lt2018_2", not(feature = "lt2017_2")),
531 doc = "See File Types for a list of Helix Server file types."
532 )]
533 #[cfg_attr(
534 all(feature = "lt2019_1", not(feature = "lt2018_2")),
535 doc = "See File types for a list of Helix Server file types."
536 )]
537 #[cfg_attr(
538 all(feature = "lt2021_1", not(feature = "lt2019_1")),
539 doc = "See File types for a list of Helix server file types."
540 )]
541 #[cfg_attr(
542 all(feature = "lt2024_1", not(feature = "lt2021_1")),
543 doc = "See File types for a list of Helix Server file types."
544 )]
545 #[cfg_attr(
546 all(feature = "lt2025_2", not(feature = "lt2024_1")),
547 doc = "See File types as well as the lbr.autocompress",
548 doc = "configurable."
549 )]
550 #[cfg_attr(
551 not(feature = "lt2025_2"),
552 doc = "See Base file types and modifiers as well as the",
553 doc = "lbr.autocompress configurable."
554 )]
555 pub fn set_filetype(&mut self, v: impl Into<String>) -> &mut Self {
556 self.filetype = Some(v.into());
557 self
558 }
559
560 #[cfg_attr(
567 feature = "lt2014_2",
568 doc = "Please see the File Types chapter for a list of Perforce",
569 doc = "file types."
570 )]
571 #[cfg_attr(
572 all(feature = "lt2015_1", not(feature = "lt2014_2")),
573 doc = "Please see the “File Types” chapter for a list of",
574 doc = "Perforce file types."
575 )]
576 #[cfg_attr(
577 all(feature = "lt2017_1", not(feature = "lt2015_1")),
578 doc = "See “File Types” for a list of Perforce file types."
579 )]
580 #[cfg_attr(
581 all(feature = "lt2017_2", not(feature = "lt2017_1")),
582 doc = "See File Types for a list of Perforce file types."
583 )]
584 #[cfg_attr(
585 all(feature = "lt2018_2", not(feature = "lt2017_2")),
586 doc = "See File Types for a list of Helix Server file types."
587 )]
588 #[cfg_attr(
589 all(feature = "lt2019_1", not(feature = "lt2018_2")),
590 doc = "See File types for a list of Helix Server file types."
591 )]
592 #[cfg_attr(
593 all(feature = "lt2021_1", not(feature = "lt2019_1")),
594 doc = "See File types for a list of Helix server file types."
595 )]
596 #[cfg_attr(
597 all(feature = "lt2024_1", not(feature = "lt2021_1")),
598 doc = "See File types for a list of Helix Server file types."
599 )]
600 #[cfg_attr(
601 all(feature = "lt2025_2", not(feature = "lt2024_1")),
602 doc = "See File types as well as the lbr.autocompress",
603 doc = "configurable."
604 )]
605 #[cfg_attr(
606 not(feature = "lt2025_2"),
607 doc = "See Base file types and modifiers as well as the",
608 doc = "lbr.autocompress configurable."
609 )]
610 pub fn filetype(mut self, v: impl Into<String>) -> Self {
611 self.filetype = Some(v.into());
612 self
613 }
614}
615
616#[cfg(test)]
617mod tests {
618 use super::*;
619 use crate::cmd::args_of;
620
621 #[test]
622 fn bin_accessors_update_executable_path() {
623 let mut command = Add::new("p4", GlobalOpts::new());
624 assert_eq!(command.get_bin(), Path::new("p4"));
625
626 command.set_bin("/usr/local/bin/p4");
627 assert_eq!(command.get_bin(), Path::new("/usr/local/bin/p4"));
628
629 let command = command.bin("p4.exe");
630 assert_eq!(command.get_bin(), Path::new("p4.exe"));
631 }
632
633 #[test]
636 fn without_options() {
637 let add = Add::new("p4", GlobalOpts::new());
638
639 let command = add.setup_command("p4");
640
641 assert_eq!(command.get_program(), OsStr::new("p4"));
642 assert_eq!(args_of(&command), ["add"]);
643 }
644
645 #[test]
646 fn all_local_options() {
647 let mut add = Add::new("p4", GlobalOpts::new());
648 add.set_change_list("42")
649 .set_downgrade(true)
650 .set_force_literal_filenames(true)
651 .set_skip_ignore(true)
652 .set_preview(true)
653 .set_filetype("text");
654
655 assert_eq!(
656 args_of(&add.setup_command("p4")),
657 ["add", "-c", "42", "-d", "-f", "-I", "-n", "-t", "text"]
658 );
659 }
660}