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, Default)]
18pub struct FileEditMode {
19 keep_workspace: bool,
20
21 preview: bool,
22
23 remote_server: Option<String>,
24
25 filetype: Option<String>,
26}
27
28impl ExclusiveOption for FileEditMode {
29 fn inject_args(&self, command: &mut Command) {
30 if self.keep_workspace {
31 command.arg("-k");
32 }
33
34 if self.preview {
35 command.arg("-n");
36 }
37
38 if let Some(remote) = &self.remote_server {
39 command.arg(format!("--remote={remote}"));
40 }
41
42 if let Some(filetype) = &self.filetype {
43 command.arg("-t").arg(filetype);
44 }
45 }
46}
47
48#[derive(Debug, Clone, Copy, Default)]
54pub struct StreamSpecEditMode;
55
56impl ExclusiveOption for StreamSpecEditMode {
57 fn inject_args(&self, command: &mut Command) {
58 command.arg("-So");
59 }
60}
61
62#[cfg_attr(
63 feature = "lt2019_1",
64 doc = "`p4 [g-opts] edit [-c changelist] [-k -n] [-t type] [--remote=remote] file ...`"
65)]
66#[cfg_attr(
67 not(feature = "lt2019_1"),
68 doc = "`p4 [g-opts] edit [-c changelist] [-k -n] [-t type] [--remote=remote] file ...`\n\n\
69 `p4 [g-opts] edit -So [-c changelist]`"
70)]
71#[derive(Debug, Clone, Default)]
82pub struct Edit<M = Unselected> {
83 bin: PathBuf,
84
85 global_opts: GlobalOpts,
86
87 change_list: Option<String>,
88
89 mode: M,
90}
91
92impl Edit<Unselected> {
93 pub fn new(bin: impl Into<PathBuf>, global_opts: GlobalOpts) -> Self {
97 Self {
98 bin: bin.into(),
99 global_opts,
100 change_list: None,
101 mode: Unselected,
102 }
103 }
104
105 pub fn keep_workspace(self, v: bool) -> Edit<FileEditMode> {
116 Edit {
117 bin: self.bin,
118 global_opts: self.global_opts,
119 change_list: self.change_list,
120 mode: FileEditMode {
121 keep_workspace: v,
122 preview: false,
123 remote_server: None,
124 filetype: None,
125 },
126 }
127 }
128
129 pub fn preview(self, v: bool) -> Edit<FileEditMode> {
138 Edit {
139 bin: self.bin,
140 global_opts: self.global_opts,
141 change_list: self.change_list,
142 mode: FileEditMode {
143 keep_workspace: false,
144 preview: v,
145 remote_server: None,
146 filetype: None,
147 },
148 }
149 }
150
151 pub fn remote_server(self, v: impl Into<String>) -> Edit<FileEditMode> {
161 Edit {
162 bin: self.bin,
163 global_opts: self.global_opts,
164 change_list: self.change_list,
165 mode: FileEditMode {
166 keep_workspace: false,
167 preview: false,
168 remote_server: Some(v.into()),
169 filetype: None,
170 },
171 }
172 }
173
174 #[cfg_attr(feature = "lt2024_1", doc = "See File types for a list of file types.")]
184 #[cfg_attr(
185 not(feature = "lt2024_1"),
186 doc = "See File types as well as the lbr.autocompress configurable."
187 )]
188 pub fn filetype(self, v: impl Into<String>) -> Edit<FileEditMode> {
191 Edit {
192 bin: self.bin,
193 global_opts: self.global_opts,
194 change_list: self.change_list,
195 mode: FileEditMode {
196 keep_workspace: false,
197 preview: false,
198 remote_server: None,
199 filetype: Some(v.into()),
200 },
201 }
202 }
203
204 #[cfg(not(feature = "lt2019_1"))]
214 pub fn edit_stream_spec(self) -> Edit<StreamSpecEditMode> {
215 Edit {
216 bin: self.bin,
217 global_opts: self.global_opts,
218 change_list: self.change_list,
219 mode: StreamSpecEditMode,
220 }
221 }
222}
223
224impl ParameterizedSpawn for Edit<Unselected> {
225 type Input<'a> = &'a [&'a OsStr];
226 type Output<'a> = Child;
227 type Error = std::io::Error;
228
229 fn spawn_with<'a>(&mut self, files: Self::Input<'a>) -> Result<Self::Output<'a>, Self::Error> {
236 self.setup_command(&self.bin)
237 .args(files)
238 .stdout(Stdio::piped())
239 .stderr(Stdio::piped())
240 .spawn()
241 }
242}
243
244impl ParameterizedSpawn for Edit<FileEditMode> {
245 type Input<'a> = &'a [&'a OsStr];
246 type Output<'a> = Child;
247 type Error = std::io::Error;
248
249 fn spawn_with<'a>(&mut self, files: Self::Input<'a>) -> Result<Self::Output<'a>, Self::Error> {
256 self.setup_command(&self.bin)
257 .args(files)
258 .stdout(Stdio::piped())
259 .stderr(Stdio::piped())
260 .spawn()
261 }
262}
263
264impl Edit<FileEditMode> {
265 pub fn get_keep_workspace(&self) -> bool {
274 self.mode.keep_workspace
275 }
276
277 pub fn set_keep_workspace(&mut self, v: bool) -> &mut Self {
286 self.mode.keep_workspace = v;
287 self
288 }
289
290 pub fn keep_workspace(mut self, v: bool) -> Self {
299 self.mode.keep_workspace = v;
300 self
301 }
302
303 pub fn get_preview(&self) -> bool {
310 self.mode.preview
311 }
312
313 pub fn set_preview(&mut self, v: bool) -> &mut Self {
320 self.mode.preview = v;
321 self
322 }
323
324 pub fn preview(mut self, v: bool) -> Self {
331 self.mode.preview = v;
332 self
333 }
334
335 pub fn get_remote_server(&self) -> Option<&String> {
343 self.mode.remote_server.as_ref()
344 }
345
346 pub fn set_remote_server(&mut self, v: impl Into<String>) -> &mut Self {
354 self.mode.remote_server = Some(v.into());
355 self
356 }
357
358 pub fn remote_server(mut self, v: impl Into<String>) -> Self {
366 self.mode.remote_server = Some(v.into());
367 self
368 }
369
370 #[cfg_attr(feature = "lt2024_1", doc = "See File types for a list of file types.")]
380 #[cfg_attr(
381 not(feature = "lt2024_1"),
382 doc = "See File types as well as the lbr.autocompress configurable."
383 )]
384 pub fn get_filetype(&self) -> Option<&String> {
385 self.mode.filetype.as_ref()
386 }
387
388 #[cfg_attr(feature = "lt2024_1", doc = "See File types for a list of file types.")]
398 #[cfg_attr(
399 not(feature = "lt2024_1"),
400 doc = "See File types as well as the lbr.autocompress configurable."
401 )]
402 pub fn set_filetype(&mut self, v: impl Into<String>) -> &mut Self {
403 self.mode.filetype = Some(v.into());
404 self
405 }
406
407 #[cfg_attr(feature = "lt2024_1", doc = "See File types for a list of file types.")]
417 #[cfg_attr(
418 not(feature = "lt2024_1"),
419 doc = "See File types as well as the lbr.autocompress configurable."
420 )]
421 pub fn filetype(mut self, v: impl Into<String>) -> Self {
422 self.mode.filetype = Some(v.into());
423 self
424 }
425}
426
427#[cfg(not(feature = "lt2019_1"))]
428impl ParameterizedSpawn for Edit<StreamSpecEditMode> {
429 type Input<'a> = ();
430 type Output<'a> = Child;
431 type Error = std::io::Error;
432
433 fn spawn_with<'a>(&mut self, (): Self::Input<'a>) -> Result<Self::Output<'a>, Self::Error> {
440 self.setup_command(&self.bin)
441 .stdout(Stdio::piped())
442 .stderr(Stdio::piped())
443 .spawn()
444 }
445}
446
447impl<M: ExclusiveOption> Edit<M> {
448 #[cfg_attr(
453 feature = "lt2014_2",
454 doc = "See the [Global Options](GlobalOpts) section."
455 )]
456 #[cfg_attr(
457 all(feature = "lt2015_1", not(feature = "lt2014_2")),
458 doc = "See the [“Global Options”](GlobalOpts) section."
459 )]
460 #[cfg_attr(
461 all(feature = "lt2017_1", not(feature = "lt2015_1")),
462 doc = "See [“Global Options”](GlobalOpts)."
463 )]
464 #[cfg_attr(
465 all(feature = "lt2018_2", not(feature = "lt2017_1")),
466 doc = "See [Global Options](GlobalOpts)."
467 )]
468 #[cfg_attr(not(feature = "lt2018_2"), doc = "See [Global options](GlobalOpts).")]
469 pub fn get_global_opts(&self) -> &GlobalOpts {
470 &self.global_opts
471 }
472
473 #[cfg_attr(
478 feature = "lt2014_2",
479 doc = "See the [Global Options](GlobalOpts) section."
480 )]
481 #[cfg_attr(
482 all(feature = "lt2015_1", not(feature = "lt2014_2")),
483 doc = "See the [“Global Options”](GlobalOpts) section."
484 )]
485 #[cfg_attr(
486 all(feature = "lt2017_1", not(feature = "lt2015_1")),
487 doc = "See [“Global Options”](GlobalOpts)."
488 )]
489 #[cfg_attr(
490 all(feature = "lt2018_2", not(feature = "lt2017_1")),
491 doc = "See [Global Options](GlobalOpts)."
492 )]
493 #[cfg_attr(not(feature = "lt2018_2"), doc = "See [Global options](GlobalOpts).")]
494 pub fn set_global_opts(&mut self, v: GlobalOpts) -> &mut Self {
495 self.global_opts = v;
496 self
497 }
498
499 #[cfg_attr(
504 feature = "lt2014_2",
505 doc = "See the [Global Options](GlobalOpts) section."
506 )]
507 #[cfg_attr(
508 all(feature = "lt2015_1", not(feature = "lt2014_2")),
509 doc = "See the [“Global Options”](GlobalOpts) section."
510 )]
511 #[cfg_attr(
512 all(feature = "lt2017_1", not(feature = "lt2015_1")),
513 doc = "See [“Global Options”](GlobalOpts)."
514 )]
515 #[cfg_attr(
516 all(feature = "lt2018_2", not(feature = "lt2017_1")),
517 doc = "See [Global Options](GlobalOpts)."
518 )]
519 #[cfg_attr(not(feature = "lt2018_2"), doc = "See [Global options](GlobalOpts).")]
520 pub fn global_opts(mut self, v: GlobalOpts) -> Self {
521 self.global_opts = v;
522 self
523 }
524
525 pub fn get_change_list(&self) -> Option<&String> {
532 self.change_list.as_ref()
533 }
534
535 pub fn set_change_list(&mut self, v: impl Into<String>) -> &mut Self {
542 self.change_list = Some(v.into());
543 self
544 }
545
546 pub fn change_list(mut self, v: impl Into<String>) -> Self {
553 self.change_list = Some(v.into());
554 self
555 }
556}
557
558impl<M: ExclusiveOption> SubCommand for Edit<M> {
559 fn name(&self) -> &str {
560 "edit"
561 }
562
563 fn inject_local_args(&self, command: &mut Command) {
564 if let Some(change_list) = &self.change_list {
565 command.arg("-c").arg(change_list);
566 }
567
568 self.mode.inject_args(command);
569 }
570
571 fn global_opts(&self) -> Option<&GlobalOpts> {
572 Some(&self.global_opts)
573 }
574}
575
576#[cfg(test)]
577mod tests {
578 use super::*;
579 use crate::cmd::args_of;
580
581 #[test]
582 fn without_options() {
583 let edit = Edit::new("p4", GlobalOpts::default());
584 let mut cmd = edit.setup_command("p4");
585 cmd.arg("//depot/file.txt");
586 assert_eq!(args_of(&cmd), vec!["edit", "//depot/file.txt"]);
587 }
588
589 #[test]
590 fn change_list() {
591 let edit = Edit::new("p4", GlobalOpts::default()).change_list("14");
592 let mut cmd = edit.setup_command("p4");
593 cmd.arg("//depot/file.txt");
594 assert_eq!(args_of(&cmd), vec!["edit", "-c", "14", "//depot/file.txt"]);
595 }
596
597 #[test]
598 fn keep_workspace_and_preview() {
599 let edit = Edit::new("p4", GlobalOpts::default())
600 .keep_workspace(true)
601 .preview(true);
602 let mut cmd = edit.setup_command("p4");
603 cmd.arg("//depot/file.txt");
604 assert_eq!(args_of(&cmd), vec!["edit", "-k", "-n", "//depot/file.txt"]);
605 }
606
607 #[test]
608 fn remote_option_uses_equals_sign() {
609 let edit = Edit::new("p4", GlobalOpts::default()).remote_server("origin");
610 let mut cmd = edit.setup_command("p4");
611 cmd.arg("//depot/file.txt");
612 assert_eq!(
613 args_of(&cmd),
614 vec!["edit", "--remote=origin", "//depot/file.txt"]
615 );
616 }
617
618 #[test]
619 fn filetype() {
620 let edit = Edit::new("p4", GlobalOpts::default()).filetype("text+k");
621 let mut cmd = edit.setup_command("p4");
622 cmd.arg("//depot/file.txt");
623 assert_eq!(
624 args_of(&cmd),
625 vec!["edit", "-t", "text+k", "//depot/file.txt"]
626 );
627 }
628
629 #[test]
630 fn file_mode_transition_preserves_change_list() {
631 let edit = Edit::new("p4", GlobalOpts::default())
632 .change_list("14")
633 .keep_workspace(true);
634 let mut cmd = edit.setup_command("p4");
635 cmd.arg("//depot/file.txt");
636 assert_eq!(
637 args_of(&cmd),
638 vec!["edit", "-c", "14", "-k", "//depot/file.txt"]
639 );
640 }
641
642 #[test]
643 fn file_mode_accessors() {
644 let mut edit = Edit::new("p4", GlobalOpts::default()).keep_workspace(true);
645 edit.set_remote_server("origin").set_preview(true);
646 assert!(edit.get_keep_workspace());
647 assert!(edit.get_preview());
648 assert_eq!(edit.get_remote_server(), Some(&"origin".to_string()));
649 assert_eq!(edit.get_filetype(), None);
650
651 let mut cmd = edit.setup_command("p4");
652 cmd.arg("//depot/file.txt");
653 assert_eq!(
654 args_of(&cmd),
655 vec!["edit", "-k", "-n", "--remote=origin", "//depot/file.txt"]
656 );
657 }
658
659 #[cfg(not(feature = "lt2019_1"))]
660 #[test]
661 fn stream_spec() {
662 let edit = Edit::new("p4", GlobalOpts::default()).edit_stream_spec();
663 let cmd = edit.setup_command("p4");
664 assert_eq!(args_of(&cmd), vec!["edit", "-So"]);
665 }
666
667 #[cfg(not(feature = "lt2019_1"))]
668 #[test]
669 fn stream_spec_with_change_list() {
670 let edit = Edit::new("p4", GlobalOpts::default())
671 .change_list("14")
672 .edit_stream_spec();
673 let cmd = edit.setup_command("p4");
674 assert_eq!(args_of(&cmd), vec!["edit", "-c", "14", "-So"]);
675 }
676
677 #[cfg(not(feature = "lt2019_1"))]
678 #[test]
679 fn stream_spec_change_list_after_transition() {
680 let edit = Edit::new("p4", GlobalOpts::default())
681 .edit_stream_spec()
682 .change_list("14");
683 let cmd = edit.setup_command("p4");
684 assert_eq!(args_of(&cmd), vec!["edit", "-c", "14", "-So"]);
685 }
686
687 #[test]
688 fn all_options_order() {
689 let edit = Edit::new("p4", GlobalOpts::default())
690 .change_list("14")
691 .keep_workspace(true)
692 .preview(true)
693 .remote_server("origin")
694 .filetype("binary");
695 let mut cmd = edit.setup_command("p4");
696 cmd.arg("//depot/file.txt");
697 assert_eq!(
698 args_of(&cmd),
699 vec![
700 "edit",
701 "-c",
702 "14",
703 "-k",
704 "-n",
705 "--remote=origin",
706 "-t",
707 "binary",
708 "//depot/file.txt"
709 ]
710 );
711 }
712
713 #[test]
714 fn set_style_with_global_opts() {
715 let mut edit = Edit::new("p4", GlobalOpts::default());
716 edit.set_change_list("14");
717 let mut edit = edit.filetype("binary");
718 edit.set_preview(true);
719 let mut cmd = edit.setup_command("p4");
720 cmd.arg("//depot/file.txt");
721 assert_eq!(
722 args_of(&cmd),
723 vec!["edit", "-c", "14", "-n", "-t", "binary", "//depot/file.txt"]
724 );
725 }
726}