use std::{
ffi::OsStr,
path::PathBuf,
process::{Child, Command, Stdio},
};
use super::{ExclusiveOption, SubCommand, Unselected};
use crate::global::GlobalOpts;
use crate::spawn::ParameterizedSpawn;
#[derive(Debug, Clone, Default)]
pub struct FileEditMode {
keep_workspace: bool,
preview: bool,
remote_server: Option<String>,
filetype: Option<String>,
}
impl ExclusiveOption for FileEditMode {
fn inject_args(&self, command: &mut Command) {
if self.keep_workspace {
command.arg("-k");
}
if self.preview {
command.arg("-n");
}
if let Some(remote) = &self.remote_server {
command.arg(format!("--remote={remote}"));
}
if let Some(filetype) = &self.filetype {
command.arg("-t").arg(filetype);
}
}
}
#[derive(Debug, Clone, Copy, Default)]
pub struct StreamSpecEditMode;
impl ExclusiveOption for StreamSpecEditMode {
fn inject_args(&self, command: &mut Command) {
command.arg("-So");
}
}
#[cfg_attr(
feature = "lt2019_1",
doc = "`p4 [g-opts] edit [-c changelist] [-k -n] [-t type] [--remote=remote] file ...`"
)]
#[cfg_attr(
not(feature = "lt2019_1"),
doc = "`p4 [g-opts] edit [-c changelist] [-k -n] [-t type] [--remote=remote] file ...`\n\n\
`p4 [g-opts] edit -So [-c changelist]`"
)]
#[derive(Debug, Clone, Default)]
pub struct Edit<M = Unselected> {
bin: PathBuf,
global_opts: GlobalOpts,
change_list: Option<String>,
mode: M,
}
impl Edit<Unselected> {
pub fn new(bin: impl Into<PathBuf>, global_opts: GlobalOpts) -> Self {
Self {
bin: bin.into(),
global_opts,
change_list: None,
mode: Unselected,
}
}
pub fn keep_workspace(self, v: bool) -> Edit<FileEditMode> {
Edit {
bin: self.bin,
global_opts: self.global_opts,
change_list: self.change_list,
mode: FileEditMode {
keep_workspace: v,
preview: false,
remote_server: None,
filetype: None,
},
}
}
pub fn preview(self, v: bool) -> Edit<FileEditMode> {
Edit {
bin: self.bin,
global_opts: self.global_opts,
change_list: self.change_list,
mode: FileEditMode {
keep_workspace: false,
preview: v,
remote_server: None,
filetype: None,
},
}
}
pub fn remote_server(self, v: impl Into<String>) -> Edit<FileEditMode> {
Edit {
bin: self.bin,
global_opts: self.global_opts,
change_list: self.change_list,
mode: FileEditMode {
keep_workspace: false,
preview: false,
remote_server: Some(v.into()),
filetype: None,
},
}
}
#[cfg_attr(feature = "lt2024_1", doc = "See File types for a list of file types.")]
#[cfg_attr(
not(feature = "lt2024_1"),
doc = "See File types as well as the lbr.autocompress configurable."
)]
pub fn filetype(self, v: impl Into<String>) -> Edit<FileEditMode> {
Edit {
bin: self.bin,
global_opts: self.global_opts,
change_list: self.change_list,
mode: FileEditMode {
keep_workspace: false,
preview: false,
remote_server: None,
filetype: Some(v.into()),
},
}
}
#[cfg(not(feature = "lt2019_1"))]
pub fn edit_stream_spec(self) -> Edit<StreamSpecEditMode> {
Edit {
bin: self.bin,
global_opts: self.global_opts,
change_list: self.change_list,
mode: StreamSpecEditMode,
}
}
}
impl ParameterizedSpawn for Edit<Unselected> {
type Input<'a> = &'a [&'a OsStr];
type Output<'a> = Child;
type Error = std::io::Error;
fn spawn_with<'a>(&mut self, files: Self::Input<'a>) -> Result<Self::Output<'a>, Self::Error> {
self.setup_command(&self.bin)
.args(files)
.stdout(Stdio::piped())
.stderr(Stdio::piped())
.spawn()
}
}
impl ParameterizedSpawn for Edit<FileEditMode> {
type Input<'a> = &'a [&'a OsStr];
type Output<'a> = Child;
type Error = std::io::Error;
fn spawn_with<'a>(&mut self, files: Self::Input<'a>) -> Result<Self::Output<'a>, Self::Error> {
self.setup_command(&self.bin)
.args(files)
.stdout(Stdio::piped())
.stderr(Stdio::piped())
.spawn()
}
}
impl Edit<FileEditMode> {
pub fn get_keep_workspace(&self) -> bool {
self.mode.keep_workspace
}
pub fn set_keep_workspace(&mut self, v: bool) -> &mut Self {
self.mode.keep_workspace = v;
self
}
pub fn keep_workspace(mut self, v: bool) -> Self {
self.mode.keep_workspace = v;
self
}
pub fn get_preview(&self) -> bool {
self.mode.preview
}
pub fn set_preview(&mut self, v: bool) -> &mut Self {
self.mode.preview = v;
self
}
pub fn preview(mut self, v: bool) -> Self {
self.mode.preview = v;
self
}
pub fn get_remote_server(&self) -> Option<&String> {
self.mode.remote_server.as_ref()
}
pub fn set_remote_server(&mut self, v: impl Into<String>) -> &mut Self {
self.mode.remote_server = Some(v.into());
self
}
pub fn remote_server(mut self, v: impl Into<String>) -> Self {
self.mode.remote_server = Some(v.into());
self
}
#[cfg_attr(feature = "lt2024_1", doc = "See File types for a list of file types.")]
#[cfg_attr(
not(feature = "lt2024_1"),
doc = "See File types as well as the lbr.autocompress configurable."
)]
pub fn get_filetype(&self) -> Option<&String> {
self.mode.filetype.as_ref()
}
#[cfg_attr(feature = "lt2024_1", doc = "See File types for a list of file types.")]
#[cfg_attr(
not(feature = "lt2024_1"),
doc = "See File types as well as the lbr.autocompress configurable."
)]
pub fn set_filetype(&mut self, v: impl Into<String>) -> &mut Self {
self.mode.filetype = Some(v.into());
self
}
#[cfg_attr(feature = "lt2024_1", doc = "See File types for a list of file types.")]
#[cfg_attr(
not(feature = "lt2024_1"),
doc = "See File types as well as the lbr.autocompress configurable."
)]
pub fn filetype(mut self, v: impl Into<String>) -> Self {
self.mode.filetype = Some(v.into());
self
}
}
#[cfg(not(feature = "lt2019_1"))]
impl ParameterizedSpawn for Edit<StreamSpecEditMode> {
type Input<'a> = ();
type Output<'a> = Child;
type Error = std::io::Error;
fn spawn_with<'a>(&mut self, (): Self::Input<'a>) -> Result<Self::Output<'a>, Self::Error> {
self.setup_command(&self.bin)
.stdout(Stdio::piped())
.stderr(Stdio::piped())
.spawn()
}
}
impl<M: ExclusiveOption> Edit<M> {
#[cfg_attr(
feature = "lt2014_2",
doc = "See the [Global Options](GlobalOpts) section."
)]
#[cfg_attr(
all(feature = "lt2015_1", not(feature = "lt2014_2")),
doc = "See the [“Global Options”](GlobalOpts) section."
)]
#[cfg_attr(
all(feature = "lt2017_1", not(feature = "lt2015_1")),
doc = "See [“Global Options”](GlobalOpts)."
)]
#[cfg_attr(
all(feature = "lt2018_2", not(feature = "lt2017_1")),
doc = "See [Global Options](GlobalOpts)."
)]
#[cfg_attr(not(feature = "lt2018_2"), doc = "See [Global options](GlobalOpts).")]
pub fn get_global_opts(&self) -> &GlobalOpts {
&self.global_opts
}
#[cfg_attr(
feature = "lt2014_2",
doc = "See the [Global Options](GlobalOpts) section."
)]
#[cfg_attr(
all(feature = "lt2015_1", not(feature = "lt2014_2")),
doc = "See the [“Global Options”](GlobalOpts) section."
)]
#[cfg_attr(
all(feature = "lt2017_1", not(feature = "lt2015_1")),
doc = "See [“Global Options”](GlobalOpts)."
)]
#[cfg_attr(
all(feature = "lt2018_2", not(feature = "lt2017_1")),
doc = "See [Global Options](GlobalOpts)."
)]
#[cfg_attr(not(feature = "lt2018_2"), doc = "See [Global options](GlobalOpts).")]
pub fn set_global_opts(&mut self, v: GlobalOpts) -> &mut Self {
self.global_opts = v;
self
}
#[cfg_attr(
feature = "lt2014_2",
doc = "See the [Global Options](GlobalOpts) section."
)]
#[cfg_attr(
all(feature = "lt2015_1", not(feature = "lt2014_2")),
doc = "See the [“Global Options”](GlobalOpts) section."
)]
#[cfg_attr(
all(feature = "lt2017_1", not(feature = "lt2015_1")),
doc = "See [“Global Options”](GlobalOpts)."
)]
#[cfg_attr(
all(feature = "lt2018_2", not(feature = "lt2017_1")),
doc = "See [Global Options](GlobalOpts)."
)]
#[cfg_attr(not(feature = "lt2018_2"), doc = "See [Global options](GlobalOpts).")]
pub fn global_opts(mut self, v: GlobalOpts) -> Self {
self.global_opts = v;
self
}
pub fn get_change_list(&self) -> Option<&String> {
self.change_list.as_ref()
}
pub fn set_change_list(&mut self, v: impl Into<String>) -> &mut Self {
self.change_list = Some(v.into());
self
}
pub fn change_list(mut self, v: impl Into<String>) -> Self {
self.change_list = Some(v.into());
self
}
}
impl<M: ExclusiveOption> SubCommand for Edit<M> {
fn name(&self) -> &str {
"edit"
}
fn inject_local_args(&self, command: &mut Command) {
if let Some(change_list) = &self.change_list {
command.arg("-c").arg(change_list);
}
self.mode.inject_args(command);
}
fn global_opts(&self) -> Option<&GlobalOpts> {
Some(&self.global_opts)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::cmd::args_of;
#[test]
fn without_options() {
let edit = Edit::new("p4", GlobalOpts::default());
let mut cmd = edit.setup_command("p4");
cmd.arg("//depot/file.txt");
assert_eq!(args_of(&cmd), vec!["edit", "//depot/file.txt"]);
}
#[test]
fn change_list() {
let edit = Edit::new("p4", GlobalOpts::default()).change_list("14");
let mut cmd = edit.setup_command("p4");
cmd.arg("//depot/file.txt");
assert_eq!(args_of(&cmd), vec!["edit", "-c", "14", "//depot/file.txt"]);
}
#[test]
fn keep_workspace_and_preview() {
let edit = Edit::new("p4", GlobalOpts::default())
.keep_workspace(true)
.preview(true);
let mut cmd = edit.setup_command("p4");
cmd.arg("//depot/file.txt");
assert_eq!(args_of(&cmd), vec!["edit", "-k", "-n", "//depot/file.txt"]);
}
#[test]
fn remote_option_uses_equals_sign() {
let edit = Edit::new("p4", GlobalOpts::default()).remote_server("origin");
let mut cmd = edit.setup_command("p4");
cmd.arg("//depot/file.txt");
assert_eq!(
args_of(&cmd),
vec!["edit", "--remote=origin", "//depot/file.txt"]
);
}
#[test]
fn filetype() {
let edit = Edit::new("p4", GlobalOpts::default()).filetype("text+k");
let mut cmd = edit.setup_command("p4");
cmd.arg("//depot/file.txt");
assert_eq!(
args_of(&cmd),
vec!["edit", "-t", "text+k", "//depot/file.txt"]
);
}
#[test]
fn file_mode_transition_preserves_change_list() {
let edit = Edit::new("p4", GlobalOpts::default())
.change_list("14")
.keep_workspace(true);
let mut cmd = edit.setup_command("p4");
cmd.arg("//depot/file.txt");
assert_eq!(
args_of(&cmd),
vec!["edit", "-c", "14", "-k", "//depot/file.txt"]
);
}
#[test]
fn file_mode_accessors() {
let mut edit = Edit::new("p4", GlobalOpts::default()).keep_workspace(true);
edit.set_remote_server("origin").set_preview(true);
assert!(edit.get_keep_workspace());
assert!(edit.get_preview());
assert_eq!(edit.get_remote_server(), Some(&"origin".to_string()));
assert_eq!(edit.get_filetype(), None);
let mut cmd = edit.setup_command("p4");
cmd.arg("//depot/file.txt");
assert_eq!(
args_of(&cmd),
vec!["edit", "-k", "-n", "--remote=origin", "//depot/file.txt"]
);
}
#[cfg(not(feature = "lt2019_1"))]
#[test]
fn stream_spec() {
let edit = Edit::new("p4", GlobalOpts::default()).edit_stream_spec();
let cmd = edit.setup_command("p4");
assert_eq!(args_of(&cmd), vec!["edit", "-So"]);
}
#[cfg(not(feature = "lt2019_1"))]
#[test]
fn stream_spec_with_change_list() {
let edit = Edit::new("p4", GlobalOpts::default())
.change_list("14")
.edit_stream_spec();
let cmd = edit.setup_command("p4");
assert_eq!(args_of(&cmd), vec!["edit", "-c", "14", "-So"]);
}
#[cfg(not(feature = "lt2019_1"))]
#[test]
fn stream_spec_change_list_after_transition() {
let edit = Edit::new("p4", GlobalOpts::default())
.edit_stream_spec()
.change_list("14");
let cmd = edit.setup_command("p4");
assert_eq!(args_of(&cmd), vec!["edit", "-c", "14", "-So"]);
}
#[test]
fn all_options_order() {
let edit = Edit::new("p4", GlobalOpts::default())
.change_list("14")
.keep_workspace(true)
.preview(true)
.remote_server("origin")
.filetype("binary");
let mut cmd = edit.setup_command("p4");
cmd.arg("//depot/file.txt");
assert_eq!(
args_of(&cmd),
vec![
"edit",
"-c",
"14",
"-k",
"-n",
"--remote=origin",
"-t",
"binary",
"//depot/file.txt"
]
);
}
#[test]
fn set_style_with_global_opts() {
let mut edit = Edit::new("p4", GlobalOpts::default());
edit.set_change_list("14");
let mut edit = edit.filetype("binary");
edit.set_preview(true);
let mut cmd = edit.setup_command("p4");
cmd.arg("//depot/file.txt");
assert_eq!(
args_of(&cmd),
vec!["edit", "-c", "14", "-n", "-t", "binary", "//depot/file.txt"]
);
}
}