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#[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 #[cfg_attr(
463 feature = "lt2014_2",
464 doc = "See the [Global Options](GlobalOpts) section."
465 )]
466 #[cfg_attr(
467 all(feature = "lt2015_1", not(feature = "lt2014_2")),
468 doc = "See the [“Global Options”](GlobalOpts) section."
469 )]
470 #[cfg_attr(
471 all(feature = "lt2017_1", not(feature = "lt2015_1")),
472 doc = "See [“Global Options”](GlobalOpts)."
473 )]
474 #[cfg_attr(
475 all(feature = "lt2018_2", not(feature = "lt2017_1")),
476 doc = "See [Global Options](GlobalOpts)."
477 )]
478 #[cfg_attr(not(feature = "lt2018_2"), doc = "See [Global options](GlobalOpts).")]
479 pub fn get_global_opts(&self) -> &GlobalOpts {
480 &self.global_opts
481 }
482
483 #[cfg_attr(
488 feature = "lt2014_2",
489 doc = "See the [Global Options](GlobalOpts) section."
490 )]
491 #[cfg_attr(
492 all(feature = "lt2015_1", not(feature = "lt2014_2")),
493 doc = "See the [“Global Options”](GlobalOpts) section."
494 )]
495 #[cfg_attr(
496 all(feature = "lt2017_1", not(feature = "lt2015_1")),
497 doc = "See [“Global Options”](GlobalOpts)."
498 )]
499 #[cfg_attr(
500 all(feature = "lt2018_2", not(feature = "lt2017_1")),
501 doc = "See [Global Options](GlobalOpts)."
502 )]
503 #[cfg_attr(not(feature = "lt2018_2"), doc = "See [Global options](GlobalOpts).")]
504 pub fn set_global_opts(&mut self, v: GlobalOpts) -> &mut Self {
505 self.global_opts = v;
506 self
507 }
508
509 #[cfg_attr(
514 feature = "lt2014_2",
515 doc = "See the [Global Options](GlobalOpts) section."
516 )]
517 #[cfg_attr(
518 all(feature = "lt2015_1", not(feature = "lt2014_2")),
519 doc = "See the [“Global Options”](GlobalOpts) section."
520 )]
521 #[cfg_attr(
522 all(feature = "lt2017_1", not(feature = "lt2015_1")),
523 doc = "See [“Global Options”](GlobalOpts)."
524 )]
525 #[cfg_attr(
526 all(feature = "lt2018_2", not(feature = "lt2017_1")),
527 doc = "See [Global Options](GlobalOpts)."
528 )]
529 #[cfg_attr(not(feature = "lt2018_2"), doc = "See [Global options](GlobalOpts).")]
530 pub fn global_opts(mut self, v: GlobalOpts) -> Self {
531 self.global_opts = v;
532 self
533 }
534
535 pub fn get_change_list(&self) -> Option<&String> {
542 self.change_list.as_ref()
543 }
544
545 pub fn set_change_list(&mut self, v: impl Into<String>) -> &mut Self {
552 self.change_list = Some(v.into());
553 self
554 }
555
556 pub fn change_list(mut self, v: impl Into<String>) -> Self {
563 self.change_list = Some(v.into());
564 self
565 }
566}
567
568impl<M: ExclusiveOption> SubCommand for Edit<M> {
569 fn name(&self) -> &str {
570 "edit"
571 }
572
573 fn inject_local_args(&self, command: &mut Command) {
574 if let Some(change_list) = &self.change_list {
575 command.arg("-c").arg(change_list);
576 }
577
578 self.mode.inject_args(command);
579 }
580
581 fn global_opts(&self) -> Option<&GlobalOpts> {
582 Some(&self.global_opts)
583 }
584}
585
586#[cfg(test)]
587mod tests {
588 use super::*;
589 use crate::cmd::args_of;
590
591 #[test]
592 fn without_options() {
593 let edit = Edit::new("p4", GlobalOpts::default());
594 let mut cmd = edit.setup_command("p4");
595 cmd.arg("//depot/file.txt");
596 assert_eq!(args_of(&cmd), vec!["edit", "//depot/file.txt"]);
597 }
598
599 #[test]
600 fn change_list() {
601 let edit = Edit::new("p4", GlobalOpts::default()).change_list("14");
602 let mut cmd = edit.setup_command("p4");
603 cmd.arg("//depot/file.txt");
604 assert_eq!(args_of(&cmd), vec!["edit", "-c", "14", "//depot/file.txt"]);
605 }
606
607 #[test]
608 fn keep_workspace_and_preview() {
609 let edit = Edit::new("p4", GlobalOpts::default())
610 .keep_workspace(true)
611 .preview(true);
612 let mut cmd = edit.setup_command("p4");
613 cmd.arg("//depot/file.txt");
614 assert_eq!(args_of(&cmd), vec!["edit", "-k", "-n", "//depot/file.txt"]);
615 }
616
617 #[test]
618 fn remote_option_uses_equals_sign() {
619 let edit = Edit::new("p4", GlobalOpts::default()).remote_server("origin");
620 let mut cmd = edit.setup_command("p4");
621 cmd.arg("//depot/file.txt");
622 assert_eq!(
623 args_of(&cmd),
624 vec!["edit", "--remote=origin", "//depot/file.txt"]
625 );
626 }
627
628 #[test]
629 fn filetype() {
630 let edit = Edit::new("p4", GlobalOpts::default()).filetype("text+k");
631 let mut cmd = edit.setup_command("p4");
632 cmd.arg("//depot/file.txt");
633 assert_eq!(
634 args_of(&cmd),
635 vec!["edit", "-t", "text+k", "//depot/file.txt"]
636 );
637 }
638
639 #[test]
640 fn file_mode_transition_preserves_change_list() {
641 let edit = Edit::new("p4", GlobalOpts::default())
642 .change_list("14")
643 .keep_workspace(true);
644 let mut cmd = edit.setup_command("p4");
645 cmd.arg("//depot/file.txt");
646 assert_eq!(
647 args_of(&cmd),
648 vec!["edit", "-c", "14", "-k", "//depot/file.txt"]
649 );
650 }
651
652 #[test]
653 fn file_mode_accessors() {
654 let mut edit = Edit::new("p4", GlobalOpts::default()).keep_workspace(true);
655 edit.set_remote_server("origin").set_preview(true);
656 assert!(edit.get_keep_workspace());
657 assert!(edit.get_preview());
658 assert_eq!(edit.get_remote_server(), Some(&"origin".to_string()));
659 assert_eq!(edit.get_filetype(), None);
660
661 let mut cmd = edit.setup_command("p4");
662 cmd.arg("//depot/file.txt");
663 assert_eq!(
664 args_of(&cmd),
665 vec!["edit", "-k", "-n", "--remote=origin", "//depot/file.txt"]
666 );
667 }
668
669 #[cfg(not(feature = "lt2019_1"))]
670 #[test]
671 fn stream_spec() {
672 let edit = Edit::new("p4", GlobalOpts::default()).edit_stream_spec();
673 let cmd = edit.setup_command("p4");
674 assert_eq!(args_of(&cmd), vec!["edit", "-So"]);
675 }
676
677 #[cfg(not(feature = "lt2019_1"))]
678 #[test]
679 fn stream_spec_with_change_list() {
680 let edit = Edit::new("p4", GlobalOpts::default())
681 .change_list("14")
682 .edit_stream_spec();
683 let cmd = edit.setup_command("p4");
684 assert_eq!(args_of(&cmd), vec!["edit", "-c", "14", "-So"]);
685 }
686
687 #[cfg(not(feature = "lt2019_1"))]
688 #[test]
689 fn stream_spec_change_list_after_transition() {
690 let edit = Edit::new("p4", GlobalOpts::default())
691 .edit_stream_spec()
692 .change_list("14");
693 let cmd = edit.setup_command("p4");
694 assert_eq!(args_of(&cmd), vec!["edit", "-c", "14", "-So"]);
695 }
696
697 #[test]
698 fn all_options_order() {
699 let edit = Edit::new("p4", GlobalOpts::default())
700 .change_list("14")
701 .keep_workspace(true)
702 .preview(true)
703 .remote_server("origin")
704 .filetype("binary");
705 let mut cmd = edit.setup_command("p4");
706 cmd.arg("//depot/file.txt");
707 assert_eq!(
708 args_of(&cmd),
709 vec![
710 "edit",
711 "-c",
712 "14",
713 "-k",
714 "-n",
715 "--remote=origin",
716 "-t",
717 "binary",
718 "//depot/file.txt"
719 ]
720 );
721 }
722
723 #[test]
724 fn set_style_with_global_opts() {
725 let mut edit = Edit::new("p4", GlobalOpts::default());
726 edit.set_change_list("14");
727 let mut edit = edit.filetype("binary");
728 edit.set_preview(true);
729 let mut cmd = edit.setup_command("p4");
730 cmd.arg("//depot/file.txt");
731 assert_eq!(
732 args_of(&cmd),
733 vec!["edit", "-c", "14", "-n", "-t", "binary", "//depot/file.txt"]
734 );
735 }
736}