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<S, I> ParameterizedSpawn<(S,)> for Edit<Unselected>
225where
226 S: IntoIterator<Item = I>,
227 I: AsRef<OsStr>,
228{
229 type Output = Child;
230 type Error = std::io::Error;
231
232 fn spawn_with(&mut self, (files,): (S,)) -> Result<Self::Output, Self::Error> {
239 self.setup_command(&self.bin)
240 .args(files)
241 .stdout(Stdio::piped())
242 .stderr(Stdio::piped())
243 .spawn()
244 }
245}
246
247impl<S, I> ParameterizedSpawn<(S,)> for Edit<FileEditMode>
248where
249 S: IntoIterator<Item = I>,
250 I: AsRef<OsStr>,
251{
252 type Output = Child;
253 type Error = std::io::Error;
254
255 fn spawn_with(&mut self, (files,): (S,)) -> Result<Self::Output, Self::Error> {
262 self.setup_command(&self.bin)
263 .args(files)
264 .stdout(Stdio::piped())
265 .stderr(Stdio::piped())
266 .spawn()
267 }
268}
269
270impl Edit<FileEditMode> {
271 pub fn get_keep_workspace(&self) -> bool {
280 self.mode.keep_workspace
281 }
282
283 pub fn set_keep_workspace(&mut self, v: bool) -> &mut Self {
292 self.mode.keep_workspace = v;
293 self
294 }
295
296 pub fn keep_workspace(mut self, v: bool) -> Self {
305 self.mode.keep_workspace = v;
306 self
307 }
308
309 pub fn get_preview(&self) -> bool {
316 self.mode.preview
317 }
318
319 pub fn set_preview(&mut self, v: bool) -> &mut Self {
326 self.mode.preview = v;
327 self
328 }
329
330 pub fn preview(mut self, v: bool) -> Self {
337 self.mode.preview = v;
338 self
339 }
340
341 pub fn get_remote_server(&self) -> Option<&String> {
349 self.mode.remote_server.as_ref()
350 }
351
352 pub fn set_remote_server(&mut self, v: impl Into<String>) -> &mut Self {
360 self.mode.remote_server = Some(v.into());
361 self
362 }
363
364 pub fn remote_server(mut self, v: impl Into<String>) -> Self {
372 self.mode.remote_server = Some(v.into());
373 self
374 }
375
376 #[cfg_attr(feature = "lt2024_1", doc = "See File types for a list of file types.")]
386 #[cfg_attr(
387 not(feature = "lt2024_1"),
388 doc = "See File types as well as the lbr.autocompress configurable."
389 )]
390 pub fn get_filetype(&self) -> Option<&String> {
391 self.mode.filetype.as_ref()
392 }
393
394 #[cfg_attr(feature = "lt2024_1", doc = "See File types for a list of file types.")]
404 #[cfg_attr(
405 not(feature = "lt2024_1"),
406 doc = "See File types as well as the lbr.autocompress configurable."
407 )]
408 pub fn set_filetype(&mut self, v: impl Into<String>) -> &mut Self {
409 self.mode.filetype = Some(v.into());
410 self
411 }
412
413 #[cfg_attr(feature = "lt2024_1", doc = "See File types for a list of file types.")]
423 #[cfg_attr(
424 not(feature = "lt2024_1"),
425 doc = "See File types as well as the lbr.autocompress configurable."
426 )]
427 pub fn filetype(mut self, v: impl Into<String>) -> Self {
428 self.mode.filetype = Some(v.into());
429 self
430 }
431}
432
433#[cfg(not(feature = "lt2019_1"))]
434impl ParameterizedSpawn<()> for Edit<StreamSpecEditMode> {
435 type Output = Child;
436 type Error = std::io::Error;
437
438 fn spawn_with(&mut self, (): ()) -> Result<Self::Output, Self::Error> {
445 self.setup_command(&self.bin)
446 .stdout(Stdio::piped())
447 .stderr(Stdio::piped())
448 .spawn()
449 }
450}
451
452impl<M: ExclusiveOption> Edit<M> {
453 #[cfg_attr(
458 feature = "lt2014_2",
459 doc = "See the [Global Options](GlobalOpts) section."
460 )]
461 #[cfg_attr(
462 all(feature = "lt2015_1", not(feature = "lt2014_2")),
463 doc = "See the [“Global Options”](GlobalOpts) section."
464 )]
465 #[cfg_attr(
466 all(feature = "lt2017_1", not(feature = "lt2015_1")),
467 doc = "See [“Global Options”](GlobalOpts)."
468 )]
469 #[cfg_attr(
470 all(feature = "lt2018_2", not(feature = "lt2017_1")),
471 doc = "See [Global Options](GlobalOpts)."
472 )]
473 #[cfg_attr(not(feature = "lt2018_2"), doc = "See [Global options](GlobalOpts).")]
474 pub fn get_global_opts(&self) -> &GlobalOpts {
475 &self.global_opts
476 }
477
478 #[cfg_attr(
483 feature = "lt2014_2",
484 doc = "See the [Global Options](GlobalOpts) section."
485 )]
486 #[cfg_attr(
487 all(feature = "lt2015_1", not(feature = "lt2014_2")),
488 doc = "See the [“Global Options”](GlobalOpts) section."
489 )]
490 #[cfg_attr(
491 all(feature = "lt2017_1", not(feature = "lt2015_1")),
492 doc = "See [“Global Options”](GlobalOpts)."
493 )]
494 #[cfg_attr(
495 all(feature = "lt2018_2", not(feature = "lt2017_1")),
496 doc = "See [Global Options](GlobalOpts)."
497 )]
498 #[cfg_attr(not(feature = "lt2018_2"), doc = "See [Global options](GlobalOpts).")]
499 pub fn set_global_opts(&mut self, v: GlobalOpts) -> &mut Self {
500 self.global_opts = v;
501 self
502 }
503
504 #[cfg_attr(
509 feature = "lt2014_2",
510 doc = "See the [Global Options](GlobalOpts) section."
511 )]
512 #[cfg_attr(
513 all(feature = "lt2015_1", not(feature = "lt2014_2")),
514 doc = "See the [“Global Options”](GlobalOpts) section."
515 )]
516 #[cfg_attr(
517 all(feature = "lt2017_1", not(feature = "lt2015_1")),
518 doc = "See [“Global Options”](GlobalOpts)."
519 )]
520 #[cfg_attr(
521 all(feature = "lt2018_2", not(feature = "lt2017_1")),
522 doc = "See [Global Options](GlobalOpts)."
523 )]
524 #[cfg_attr(not(feature = "lt2018_2"), doc = "See [Global options](GlobalOpts).")]
525 pub fn global_opts(mut self, v: GlobalOpts) -> Self {
526 self.global_opts = v;
527 self
528 }
529
530 pub fn get_change_list(&self) -> Option<&String> {
537 self.change_list.as_ref()
538 }
539
540 pub fn set_change_list(&mut self, v: impl Into<String>) -> &mut Self {
547 self.change_list = Some(v.into());
548 self
549 }
550
551 pub fn change_list(mut self, v: impl Into<String>) -> Self {
558 self.change_list = Some(v.into());
559 self
560 }
561}
562
563impl<M: ExclusiveOption> SubCommand for Edit<M> {
564 fn name(&self) -> &str {
565 "edit"
566 }
567
568 fn inject_local_args(&self, command: &mut Command) {
569 if let Some(change_list) = &self.change_list {
570 command.arg("-c").arg(change_list);
571 }
572
573 self.mode.inject_args(command);
574 }
575
576 fn global_opts(&self) -> Option<&GlobalOpts> {
577 Some(&self.global_opts)
578 }
579}
580
581#[cfg(test)]
582mod tests {
583 use super::*;
584 use crate::cmd::args_of;
585
586 #[test]
587 fn without_options() {
588 let edit = Edit::new("p4", GlobalOpts::default());
589 let mut cmd = edit.setup_command("p4");
590 cmd.arg("//depot/file.txt");
591 assert_eq!(args_of(&cmd), vec!["edit", "//depot/file.txt"]);
592 }
593
594 #[test]
595 fn change_list() {
596 let edit = Edit::new("p4", GlobalOpts::default()).change_list("14");
597 let mut cmd = edit.setup_command("p4");
598 cmd.arg("//depot/file.txt");
599 assert_eq!(args_of(&cmd), vec!["edit", "-c", "14", "//depot/file.txt"]);
600 }
601
602 #[test]
603 fn keep_workspace_and_preview() {
604 let edit = Edit::new("p4", GlobalOpts::default())
605 .keep_workspace(true)
606 .preview(true);
607 let mut cmd = edit.setup_command("p4");
608 cmd.arg("//depot/file.txt");
609 assert_eq!(args_of(&cmd), vec!["edit", "-k", "-n", "//depot/file.txt"]);
610 }
611
612 #[test]
613 fn remote_option_uses_equals_sign() {
614 let edit = Edit::new("p4", GlobalOpts::default()).remote_server("origin");
615 let mut cmd = edit.setup_command("p4");
616 cmd.arg("//depot/file.txt");
617 assert_eq!(
618 args_of(&cmd),
619 vec!["edit", "--remote=origin", "//depot/file.txt"]
620 );
621 }
622
623 #[test]
624 fn filetype() {
625 let edit = Edit::new("p4", GlobalOpts::default()).filetype("text+k");
626 let mut cmd = edit.setup_command("p4");
627 cmd.arg("//depot/file.txt");
628 assert_eq!(
629 args_of(&cmd),
630 vec!["edit", "-t", "text+k", "//depot/file.txt"]
631 );
632 }
633
634 #[test]
635 fn file_mode_transition_preserves_change_list() {
636 let edit = Edit::new("p4", GlobalOpts::default())
637 .change_list("14")
638 .keep_workspace(true);
639 let mut cmd = edit.setup_command("p4");
640 cmd.arg("//depot/file.txt");
641 assert_eq!(
642 args_of(&cmd),
643 vec!["edit", "-c", "14", "-k", "//depot/file.txt"]
644 );
645 }
646
647 #[test]
648 fn file_mode_accessors() {
649 let mut edit = Edit::new("p4", GlobalOpts::default()).keep_workspace(true);
650 edit.set_remote_server("origin").set_preview(true);
651 assert!(edit.get_keep_workspace());
652 assert!(edit.get_preview());
653 assert_eq!(edit.get_remote_server(), Some(&"origin".to_string()));
654 assert_eq!(edit.get_filetype(), None);
655
656 let mut cmd = edit.setup_command("p4");
657 cmd.arg("//depot/file.txt");
658 assert_eq!(
659 args_of(&cmd),
660 vec!["edit", "-k", "-n", "--remote=origin", "//depot/file.txt"]
661 );
662 }
663
664 #[cfg(not(feature = "lt2019_1"))]
665 #[test]
666 fn stream_spec() {
667 let edit = Edit::new("p4", GlobalOpts::default()).edit_stream_spec();
668 let cmd = edit.setup_command("p4");
669 assert_eq!(args_of(&cmd), vec!["edit", "-So"]);
670 }
671
672 #[cfg(not(feature = "lt2019_1"))]
673 #[test]
674 fn stream_spec_with_change_list() {
675 let edit = Edit::new("p4", GlobalOpts::default())
676 .change_list("14")
677 .edit_stream_spec();
678 let cmd = edit.setup_command("p4");
679 assert_eq!(args_of(&cmd), vec!["edit", "-c", "14", "-So"]);
680 }
681
682 #[cfg(not(feature = "lt2019_1"))]
683 #[test]
684 fn stream_spec_change_list_after_transition() {
685 let edit = Edit::new("p4", GlobalOpts::default())
686 .edit_stream_spec()
687 .change_list("14");
688 let cmd = edit.setup_command("p4");
689 assert_eq!(args_of(&cmd), vec!["edit", "-c", "14", "-So"]);
690 }
691
692 #[test]
693 fn all_options_order() {
694 let edit = Edit::new("p4", GlobalOpts::default())
695 .change_list("14")
696 .keep_workspace(true)
697 .preview(true)
698 .remote_server("origin")
699 .filetype("binary");
700 let mut cmd = edit.setup_command("p4");
701 cmd.arg("//depot/file.txt");
702 assert_eq!(
703 args_of(&cmd),
704 vec![
705 "edit",
706 "-c",
707 "14",
708 "-k",
709 "-n",
710 "--remote=origin",
711 "-t",
712 "binary",
713 "//depot/file.txt"
714 ]
715 );
716 }
717
718 #[test]
719 fn set_style_with_global_opts() {
720 let mut edit = Edit::new("p4", GlobalOpts::default());
721 edit.set_change_list("14");
722 let mut edit = edit.filetype("binary");
723 edit.set_preview(true);
724 let mut cmd = edit.setup_command("p4");
725 cmd.arg("//depot/file.txt");
726 assert_eq!(
727 args_of(&cmd),
728 vec!["edit", "-c", "14", "-n", "-t", "binary", "//depot/file.txt"]
729 );
730 }
731}