1use std::{
2 ffi::OsStr,
3 path::{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#[cfg(not(feature = "lt2019_1"))]
54#[derive(Debug, Clone, Copy, Default)]
55pub struct StreamSpecEditMode;
56
57#[cfg(not(feature = "lt2019_1"))]
58impl ExclusiveOption for StreamSpecEditMode {
59 fn inject_args(&self, command: &mut Command) {
60 command.arg("-So");
61 }
62}
63
64#[cfg_attr(
65 feature = "lt2019_1",
66 doc = "`p4 [g-opts] edit [-c changelist] [-k -n] [-t type] [--remote=remote] file ...`"
67)]
68#[cfg_attr(
69 not(feature = "lt2019_1"),
70 doc = "`p4 [g-opts] edit [-c changelist] [-k -n] [-t type] [--remote=remote] file ...`\n\n\
71 `p4 [g-opts] edit -So [-c changelist]`"
72)]
73#[cfg_attr(feature = "lt2019_1", doc = ".")]
82#[cfg_attr(
83 not(feature = "lt2019_1"),
84 doc = ", while [`Self::edit_stream_spec`] transitions to the [`StreamSpecEditMode`] state."
85)]
86#[derive(Debug, Clone, Default)]
87pub struct Edit<M = Unselected> {
88 bin: PathBuf,
89
90 global_opts: GlobalOpts,
91
92 change_list: Option<String>,
93
94 mode: M,
95}
96
97impl Edit<Unselected> {
98 pub fn new(bin: impl Into<PathBuf>, global_opts: GlobalOpts) -> Self {
102 Self {
103 bin: bin.into(),
104 global_opts,
105 change_list: None,
106 mode: Unselected,
107 }
108 }
109
110 pub fn keep_workspace(self, v: bool) -> Edit<FileEditMode> {
121 Edit {
122 bin: self.bin,
123 global_opts: self.global_opts,
124 change_list: self.change_list,
125 mode: FileEditMode {
126 keep_workspace: v,
127 preview: false,
128 remote_server: None,
129 filetype: None,
130 },
131 }
132 }
133
134 pub fn preview(self, v: bool) -> Edit<FileEditMode> {
143 Edit {
144 bin: self.bin,
145 global_opts: self.global_opts,
146 change_list: self.change_list,
147 mode: FileEditMode {
148 keep_workspace: false,
149 preview: v,
150 remote_server: None,
151 filetype: None,
152 },
153 }
154 }
155
156 pub fn remote_server(self, v: impl Into<String>) -> Edit<FileEditMode> {
166 Edit {
167 bin: self.bin,
168 global_opts: self.global_opts,
169 change_list: self.change_list,
170 mode: FileEditMode {
171 keep_workspace: false,
172 preview: false,
173 remote_server: Some(v.into()),
174 filetype: None,
175 },
176 }
177 }
178
179 #[cfg_attr(feature = "lt2024_1", doc = "See File types for a list of file types.")]
189 #[cfg_attr(
190 not(feature = "lt2024_1"),
191 doc = "See File types as well as the lbr.autocompress configurable."
192 )]
193 pub fn filetype(self, v: impl Into<String>) -> Edit<FileEditMode> {
196 Edit {
197 bin: self.bin,
198 global_opts: self.global_opts,
199 change_list: self.change_list,
200 mode: FileEditMode {
201 keep_workspace: false,
202 preview: false,
203 remote_server: None,
204 filetype: Some(v.into()),
205 },
206 }
207 }
208
209 #[cfg(not(feature = "lt2019_1"))]
219 pub fn edit_stream_spec(self) -> Edit<StreamSpecEditMode> {
220 Edit {
221 bin: self.bin,
222 global_opts: self.global_opts,
223 change_list: self.change_list,
224 mode: StreamSpecEditMode,
225 }
226 }
227}
228
229impl<S, I> ParameterizedSpawn<(S,)> for Edit<Unselected>
230where
231 S: IntoIterator<Item = I>,
232 I: AsRef<OsStr>,
233{
234 type Output = Child;
235 type Error = std::io::Error;
236
237 fn spawn_with(&mut self, (files,): (S,)) -> Result<Self::Output, Self::Error> {
244 self.setup_command(&self.bin)
245 .args(files)
246 .stdout(Stdio::piped())
247 .stderr(Stdio::piped())
248 .spawn()
249 }
250}
251
252impl<S, I> ParameterizedSpawn<(S,)> for Edit<FileEditMode>
253where
254 S: IntoIterator<Item = I>,
255 I: AsRef<OsStr>,
256{
257 type Output = Child;
258 type Error = std::io::Error;
259
260 fn spawn_with(&mut self, (files,): (S,)) -> Result<Self::Output, Self::Error> {
267 self.setup_command(&self.bin)
268 .args(files)
269 .stdout(Stdio::piped())
270 .stderr(Stdio::piped())
271 .spawn()
272 }
273}
274
275impl Edit<FileEditMode> {
276 pub fn get_keep_workspace(&self) -> bool {
285 self.mode.keep_workspace
286 }
287
288 pub fn set_keep_workspace(&mut self, v: bool) -> &mut Self {
297 self.mode.keep_workspace = v;
298 self
299 }
300
301 pub fn keep_workspace(mut self, v: bool) -> Self {
310 self.mode.keep_workspace = v;
311 self
312 }
313
314 pub fn get_preview(&self) -> bool {
321 self.mode.preview
322 }
323
324 pub fn set_preview(&mut self, v: bool) -> &mut Self {
331 self.mode.preview = v;
332 self
333 }
334
335 pub fn preview(mut self, v: bool) -> Self {
342 self.mode.preview = v;
343 self
344 }
345
346 pub fn get_remote_server(&self) -> Option<&String> {
354 self.mode.remote_server.as_ref()
355 }
356
357 pub fn set_remote_server(&mut self, v: impl Into<String>) -> &mut Self {
365 self.mode.remote_server = Some(v.into());
366 self
367 }
368
369 pub fn remote_server(mut self, v: impl Into<String>) -> Self {
377 self.mode.remote_server = Some(v.into());
378 self
379 }
380
381 #[cfg_attr(feature = "lt2024_1", doc = "See File types for a list of file types.")]
391 #[cfg_attr(
392 not(feature = "lt2024_1"),
393 doc = "See File types as well as the lbr.autocompress configurable."
394 )]
395 pub fn get_filetype(&self) -> Option<&String> {
396 self.mode.filetype.as_ref()
397 }
398
399 #[cfg_attr(feature = "lt2024_1", doc = "See File types for a list of file types.")]
409 #[cfg_attr(
410 not(feature = "lt2024_1"),
411 doc = "See File types as well as the lbr.autocompress configurable."
412 )]
413 pub fn set_filetype(&mut self, v: impl Into<String>) -> &mut Self {
414 self.mode.filetype = Some(v.into());
415 self
416 }
417
418 #[cfg_attr(feature = "lt2024_1", doc = "See File types for a list of file types.")]
428 #[cfg_attr(
429 not(feature = "lt2024_1"),
430 doc = "See File types as well as the lbr.autocompress configurable."
431 )]
432 pub fn filetype(mut self, v: impl Into<String>) -> Self {
433 self.mode.filetype = Some(v.into());
434 self
435 }
436}
437
438#[cfg(not(feature = "lt2019_1"))]
439impl ParameterizedSpawn<()> for Edit<StreamSpecEditMode> {
440 type Output = Child;
441 type Error = std::io::Error;
442
443 fn spawn_with(&mut self, (): ()) -> Result<Self::Output, Self::Error> {
450 self.setup_command(&self.bin)
451 .stdout(Stdio::piped())
452 .stderr(Stdio::piped())
453 .spawn()
454 }
455}
456
457impl<M: ExclusiveOption> Edit<M> {
458 pub fn get_bin(&self) -> &Path {
460 &self.bin
461 }
462
463 pub fn set_bin(&mut self, bin: impl Into<PathBuf>) -> &mut Self {
465 self.bin = bin.into();
466 self
467 }
468
469 pub fn bin(mut self, bin: impl Into<PathBuf>) -> Self {
471 self.set_bin(bin);
472 self
473 }
474
475 #[cfg_attr(
480 feature = "lt2014_2",
481 doc = "See the [Global Options](GlobalOpts) section."
482 )]
483 #[cfg_attr(
484 all(feature = "lt2015_1", not(feature = "lt2014_2")),
485 doc = "See the [“Global Options”](GlobalOpts) section."
486 )]
487 #[cfg_attr(
488 all(feature = "lt2017_1", not(feature = "lt2015_1")),
489 doc = "See [“Global Options”](GlobalOpts)."
490 )]
491 #[cfg_attr(
492 all(feature = "lt2018_2", not(feature = "lt2017_1")),
493 doc = "See [Global Options](GlobalOpts)."
494 )]
495 #[cfg_attr(not(feature = "lt2018_2"), doc = "See [Global options](GlobalOpts).")]
496 pub fn get_global_opts(&self) -> &GlobalOpts {
497 &self.global_opts
498 }
499
500 #[cfg_attr(
505 feature = "lt2014_2",
506 doc = "See the [Global Options](GlobalOpts) section."
507 )]
508 #[cfg_attr(
509 all(feature = "lt2015_1", not(feature = "lt2014_2")),
510 doc = "See the [“Global Options”](GlobalOpts) section."
511 )]
512 #[cfg_attr(
513 all(feature = "lt2017_1", not(feature = "lt2015_1")),
514 doc = "See [“Global Options”](GlobalOpts)."
515 )]
516 #[cfg_attr(
517 all(feature = "lt2018_2", not(feature = "lt2017_1")),
518 doc = "See [Global Options](GlobalOpts)."
519 )]
520 #[cfg_attr(not(feature = "lt2018_2"), doc = "See [Global options](GlobalOpts).")]
521 pub fn set_global_opts(&mut self, v: GlobalOpts) -> &mut Self {
522 self.global_opts = v;
523 self
524 }
525
526 #[cfg_attr(
531 feature = "lt2014_2",
532 doc = "See the [Global Options](GlobalOpts) section."
533 )]
534 #[cfg_attr(
535 all(feature = "lt2015_1", not(feature = "lt2014_2")),
536 doc = "See the [“Global Options”](GlobalOpts) section."
537 )]
538 #[cfg_attr(
539 all(feature = "lt2017_1", not(feature = "lt2015_1")),
540 doc = "See [“Global Options”](GlobalOpts)."
541 )]
542 #[cfg_attr(
543 all(feature = "lt2018_2", not(feature = "lt2017_1")),
544 doc = "See [Global Options](GlobalOpts)."
545 )]
546 #[cfg_attr(not(feature = "lt2018_2"), doc = "See [Global options](GlobalOpts).")]
547 pub fn global_opts(mut self, v: GlobalOpts) -> Self {
548 self.global_opts = v;
549 self
550 }
551
552 pub fn get_change_list(&self) -> Option<&String> {
559 self.change_list.as_ref()
560 }
561
562 pub fn set_change_list(&mut self, v: impl Into<String>) -> &mut Self {
569 self.change_list = Some(v.into());
570 self
571 }
572
573 pub fn change_list(mut self, v: impl Into<String>) -> Self {
580 self.change_list = Some(v.into());
581 self
582 }
583}
584
585impl<M: ExclusiveOption> SubCommand for Edit<M> {
586 fn name(&self) -> &str {
587 "edit"
588 }
589
590 fn inject_local_args(&self, command: &mut Command) {
591 if let Some(change_list) = &self.change_list {
592 command.arg("-c").arg(change_list);
593 }
594
595 self.mode.inject_args(command);
596 }
597
598 fn global_opts(&self) -> Option<&GlobalOpts> {
599 Some(&self.global_opts)
600 }
601}
602
603#[cfg(test)]
604mod tests {
605 use super::*;
606 use crate::cmd::args_of;
607
608 #[test]
609 fn bin_accessors_update_executable_path() {
610 let mut command = Edit::new("p4", GlobalOpts::new());
611 assert_eq!(command.get_bin(), Path::new("p4"));
612
613 command.set_bin("/usr/local/bin/p4");
614 assert_eq!(command.get_bin(), Path::new("/usr/local/bin/p4"));
615
616 let command = command.bin("p4.exe");
617 assert_eq!(command.get_bin(), Path::new("p4.exe"));
618 }
619
620 #[test]
621 fn without_options() {
622 let edit = Edit::new("p4", GlobalOpts::default());
623 let mut cmd = edit.setup_command("p4");
624 cmd.arg("//depot/file.txt");
625 assert_eq!(args_of(&cmd), vec!["edit", "//depot/file.txt"]);
626 }
627
628 #[test]
629 fn change_list() {
630 let edit = Edit::new("p4", GlobalOpts::default()).change_list("14");
631 let mut cmd = edit.setup_command("p4");
632 cmd.arg("//depot/file.txt");
633 assert_eq!(args_of(&cmd), vec!["edit", "-c", "14", "//depot/file.txt"]);
634 }
635
636 #[test]
637 fn keep_workspace_and_preview() {
638 let edit = Edit::new("p4", GlobalOpts::default())
639 .keep_workspace(true)
640 .preview(true);
641 let mut cmd = edit.setup_command("p4");
642 cmd.arg("//depot/file.txt");
643 assert_eq!(args_of(&cmd), vec!["edit", "-k", "-n", "//depot/file.txt"]);
644 }
645
646 #[test]
647 fn remote_option_uses_equals_sign() {
648 let edit = Edit::new("p4", GlobalOpts::default()).remote_server("origin");
649 let mut cmd = edit.setup_command("p4");
650 cmd.arg("//depot/file.txt");
651 assert_eq!(
652 args_of(&cmd),
653 vec!["edit", "--remote=origin", "//depot/file.txt"]
654 );
655 }
656
657 #[test]
658 fn filetype() {
659 let edit = Edit::new("p4", GlobalOpts::default()).filetype("text+k");
660 let mut cmd = edit.setup_command("p4");
661 cmd.arg("//depot/file.txt");
662 assert_eq!(
663 args_of(&cmd),
664 vec!["edit", "-t", "text+k", "//depot/file.txt"]
665 );
666 }
667
668 #[test]
669 fn file_mode_transition_preserves_change_list() {
670 let edit = Edit::new("p4", GlobalOpts::default())
671 .change_list("14")
672 .keep_workspace(true);
673 let mut cmd = edit.setup_command("p4");
674 cmd.arg("//depot/file.txt");
675 assert_eq!(
676 args_of(&cmd),
677 vec!["edit", "-c", "14", "-k", "//depot/file.txt"]
678 );
679 }
680
681 #[test]
682 fn file_mode_accessors() {
683 let mut edit = Edit::new("p4", GlobalOpts::default()).keep_workspace(true);
684 edit.set_remote_server("origin").set_preview(true);
685 assert!(edit.get_keep_workspace());
686 assert!(edit.get_preview());
687 assert_eq!(edit.get_remote_server(), Some(&"origin".to_string()));
688 assert_eq!(edit.get_filetype(), None);
689
690 let mut cmd = edit.setup_command("p4");
691 cmd.arg("//depot/file.txt");
692 assert_eq!(
693 args_of(&cmd),
694 vec!["edit", "-k", "-n", "--remote=origin", "//depot/file.txt"]
695 );
696 }
697
698 #[cfg(not(feature = "lt2019_1"))]
699 #[test]
700 fn stream_spec() {
701 let edit = Edit::new("p4", GlobalOpts::default()).edit_stream_spec();
702 let cmd = edit.setup_command("p4");
703 assert_eq!(args_of(&cmd), vec!["edit", "-So"]);
704 }
705
706 #[cfg(not(feature = "lt2019_1"))]
707 #[test]
708 fn stream_spec_with_change_list() {
709 let edit = Edit::new("p4", GlobalOpts::default())
710 .change_list("14")
711 .edit_stream_spec();
712 let cmd = edit.setup_command("p4");
713 assert_eq!(args_of(&cmd), vec!["edit", "-c", "14", "-So"]);
714 }
715
716 #[cfg(not(feature = "lt2019_1"))]
717 #[test]
718 fn stream_spec_change_list_after_transition() {
719 let edit = Edit::new("p4", GlobalOpts::default())
720 .edit_stream_spec()
721 .change_list("14");
722 let cmd = edit.setup_command("p4");
723 assert_eq!(args_of(&cmd), vec!["edit", "-c", "14", "-So"]);
724 }
725
726 #[test]
727 fn all_options_order() {
728 let edit = Edit::new("p4", GlobalOpts::default())
729 .change_list("14")
730 .keep_workspace(true)
731 .preview(true)
732 .remote_server("origin")
733 .filetype("binary");
734 let mut cmd = edit.setup_command("p4");
735 cmd.arg("//depot/file.txt");
736 assert_eq!(
737 args_of(&cmd),
738 vec![
739 "edit",
740 "-c",
741 "14",
742 "-k",
743 "-n",
744 "--remote=origin",
745 "-t",
746 "binary",
747 "//depot/file.txt"
748 ]
749 );
750 }
751
752 #[test]
753 fn set_style_with_global_opts() {
754 let mut edit = Edit::new("p4", GlobalOpts::default());
755 edit.set_change_list("14");
756 let mut edit = edit.filetype("binary");
757 edit.set_preview(true);
758 let mut cmd = edit.setup_command("p4");
759 cmd.arg("//depot/file.txt");
760 assert_eq!(
761 args_of(&cmd),
762 vec!["edit", "-c", "14", "-n", "-t", "binary", "//depot/file.txt"]
763 );
764 }
765}