use std::{
ffi::OsStr,
path::{Path, PathBuf},
process::{Child, Command, Stdio},
};
use super::{ExclusiveOption, SubCommand, Unselected};
use crate::global::GlobalOpts;
use crate::spawn::ParameterizedSpawn;
#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub struct GeneralMode {
force: bool,
}
#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub struct DeleteMode {
force: bool,
}
#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub struct StdinMode {
force: bool,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct StreamSelected {
stream: String,
parent: Option<String>,
}
#[derive(Debug, Clone, Default)]
pub struct StdoutMode<S = Unselected> {
stream_opts: S,
}
impl ExclusiveOption for GeneralMode {
fn inject_args(&self, command: &mut Command) {
if self.force {
command.arg("-f");
}
}
}
impl ExclusiveOption for DeleteMode {
fn inject_args(&self, command: &mut Command) {
command.arg("-d");
if self.force {
command.arg("-f");
}
}
}
impl ExclusiveOption for StdinMode {
fn inject_args(&self, command: &mut Command) {
command.arg("-i");
if self.force {
command.arg("-f");
}
}
}
impl ExclusiveOption for StdoutMode<Unselected> {
fn inject_args(&self, command: &mut Command) {
command.arg("-o");
}
}
impl ExclusiveOption for StdoutMode<StreamSelected> {
fn inject_args(&self, command: &mut Command) {
command.arg("-S").arg(&self.stream_opts.stream);
if let Some(parent) = &self.stream_opts.parent {
command.arg("-P").arg(parent);
}
command.arg("-o");
}
}
#[derive(Debug, Clone, Default)]
pub struct Branch<M = Unselected> {
bin: PathBuf,
global_opts: GlobalOpts,
mode: M,
}
impl Branch<Unselected> {
pub fn new(bin: impl Into<PathBuf>, global_opts: GlobalOpts) -> Self {
Self {
bin: bin.into(),
global_opts,
..Default::default()
}
}
pub fn force(self, v: bool) -> Branch<GeneralMode> {
Branch {
bin: self.bin,
global_opts: self.global_opts,
mode: GeneralMode { force: v },
}
}
pub fn delete(self) -> Branch<DeleteMode> {
Branch {
bin: self.bin,
global_opts: self.global_opts,
mode: DeleteMode { force: false },
}
}
pub fn stdin(self) -> Branch<StdinMode> {
Branch {
bin: self.bin,
global_opts: self.global_opts,
mode: StdinMode { force: false },
}
}
pub fn stdout(self) -> Branch<StdoutMode> {
Branch {
bin: self.bin,
global_opts: self.global_opts,
mode: StdoutMode::default(),
}
}
}
impl<M> Branch<M> {
pub fn get_bin(&self) -> &Path {
&self.bin
}
pub fn set_bin(&mut self, bin: impl Into<PathBuf>) -> &mut Self {
self.bin = bin.into();
self
}
pub fn bin(mut self, bin: impl Into<PathBuf>) -> Self {
self.set_bin(bin);
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 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
}
}
impl Branch<GeneralMode> {
pub fn get_force(&self) -> bool {
self.mode.force
}
pub fn set_force(&mut self, v: bool) -> &mut Self {
self.mode.force = v;
self
}
pub fn force(mut self, v: bool) -> Self {
self.mode.force = v;
self
}
pub fn delete(self) -> Branch<DeleteMode> {
Branch {
bin: self.bin,
global_opts: self.global_opts,
mode: DeleteMode {
force: self.mode.force,
},
}
}
pub fn stdin(self) -> Branch<StdinMode> {
Branch {
bin: self.bin,
global_opts: self.global_opts,
mode: StdinMode {
force: self.mode.force,
},
}
}
}
impl Branch<DeleteMode> {
pub fn get_force(&self) -> bool {
self.mode.force
}
pub fn set_force(&mut self, v: bool) -> &mut Self {
self.mode.force = v;
self
}
pub fn force(mut self, v: bool) -> Self {
self.mode.force = v;
self
}
}
impl Branch<StdinMode> {
pub fn get_force(&self) -> bool {
self.mode.force
}
pub fn set_force(&mut self, v: bool) -> &mut Self {
self.mode.force = v;
self
}
pub fn force(mut self, v: bool) -> Self {
self.mode.force = v;
self
}
}
impl Branch<StdoutMode<Unselected>> {
pub fn stream(self, v: impl Into<String>) -> Branch<StdoutMode<StreamSelected>> {
Branch {
bin: self.bin,
global_opts: self.global_opts,
mode: StdoutMode {
stream_opts: StreamSelected {
stream: v.into(),
parent: None,
},
},
}
}
}
impl Branch<StdoutMode<StreamSelected>> {
pub fn get_stream(&self) -> &str {
&self.mode.stream_opts.stream
}
pub fn set_stream(&mut self, v: impl Into<String>) -> &mut Self {
self.mode.stream_opts.stream = v.into();
self
}
pub fn stream(mut self, v: impl Into<String>) -> Self {
self.mode.stream_opts.stream = v.into();
self
}
pub fn get_parent(&self) -> Option<&String> {
self.mode.stream_opts.parent.as_ref()
}
pub fn set_parent(&mut self, v: impl Into<String>) -> &mut Self {
self.mode.stream_opts.parent = Some(v.into());
self
}
pub fn parent(mut self, v: impl Into<String>) -> Self {
self.mode.stream_opts.parent = Some(v.into());
self
}
}
impl<M: ExclusiveOption> SubCommand for Branch<M> {
fn name(&self) -> &str {
"branch"
}
fn inject_local_args(&self, command: &mut Command) {
self.mode.inject_args(command);
}
fn global_opts(&self) -> Option<&GlobalOpts> {
Some(&self.global_opts)
}
}
impl ParameterizedSpawn<()> for Branch<GeneralMode> {
type Output = Child;
type Error = std::io::Error;
fn spawn_with(&mut self, (): ()) -> Result<Self::Output, Self::Error> {
self.setup_command(&self.bin)
.stdout(Stdio::piped())
.stderr(Stdio::piped())
.spawn()
}
}
impl<I: AsRef<OsStr>> ParameterizedSpawn<(I,)> for Branch<GeneralMode> {
type Output = Child;
type Error = std::io::Error;
fn spawn_with(&mut self, (branchspec,): (I,)) -> Result<Self::Output, Self::Error> {
self.setup_command(&self.bin)
.arg(branchspec)
.stdout(Stdio::piped())
.stderr(Stdio::piped())
.spawn()
}
}
impl<I: AsRef<OsStr>> ParameterizedSpawn<(I,)> for Branch<DeleteMode> {
type Output = Child;
type Error = std::io::Error;
fn spawn_with(&mut self, (branchspec,): (I,)) -> Result<Self::Output, Self::Error> {
self.setup_command(&self.bin)
.arg(branchspec)
.stdout(Stdio::piped())
.stderr(Stdio::piped())
.spawn()
}
}
impl ParameterizedSpawn<()> for Branch<StdinMode> {
type Output = Child;
type Error = std::io::Error;
fn spawn_with(&mut self, (): ()) -> Result<Self::Output, Self::Error> {
self.setup_command(&self.bin)
.stdin(Stdio::piped())
.stdout(Stdio::piped())
.stderr(Stdio::piped())
.spawn()
}
}
impl ParameterizedSpawn<(Stdio,)> for Branch<StdinMode> {
type Output = Child;
type Error = std::io::Error;
fn spawn_with(&mut self, (stdio,): (Stdio,)) -> Result<Self::Output, Self::Error> {
self.setup_command(&self.bin)
.stdin(stdio)
.stdout(Stdio::piped())
.stderr(Stdio::piped())
.spawn()
}
}
impl<S, I> ParameterizedSpawn<(I,)> for Branch<StdoutMode<S>>
where
StdoutMode<S>: ExclusiveOption,
I: AsRef<OsStr>,
{
type Output = Child;
type Error = std::io::Error;
fn spawn_with(&mut self, (placeholder,): (I,)) -> Result<Self::Output, Self::Error> {
self.setup_command(&self.bin)
.arg(placeholder)
.stdout(Stdio::piped())
.stderr(Stdio::piped())
.spawn()
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::cmd::args_of;
#[test]
fn bin_accessors_update_executable_path() {
let mut command = Branch::new("p4", GlobalOpts::new());
assert_eq!(command.get_bin(), Path::new("p4"));
command.set_bin("/usr/local/bin/p4");
assert_eq!(command.get_bin(), Path::new("/usr/local/bin/p4"));
let command = command.bin("p4.exe");
assert_eq!(command.get_bin(), Path::new("p4.exe"));
}
#[test]
fn general_form_without_force() {
let branch = Branch::new("p4", GlobalOpts::new()).force(false);
assert_eq!(args_of(&branch.setup_command("p4")), ["branch"]);
}
#[test]
fn general_form_with_force() {
let branch = Branch::new("p4", GlobalOpts::new()).force(true);
assert_eq!(args_of(&branch.setup_command("p4")), ["branch", "-f"]);
}
#[test]
fn delete_form_from_unselected() {
let branch = Branch::new("p4", GlobalOpts::new()).delete();
assert_eq!(args_of(&branch.setup_command("p4")), ["branch", "-d"]);
}
#[test]
fn delete_form_preserves_force_from_general() {
let branch = Branch::new("p4", GlobalOpts::new()).force(true).delete();
assert_eq!(args_of(&branch.setup_command("p4")), ["branch", "-d", "-f"]);
}
#[test]
fn stdin_form_from_unselected() {
let branch = Branch::new("p4", GlobalOpts::new()).stdin();
assert_eq!(args_of(&branch.setup_command("p4")), ["branch", "-i"]);
}
#[test]
fn stdin_form_preserves_force_from_general() {
let branch = Branch::new("p4", GlobalOpts::new()).force(true).stdin();
assert_eq!(args_of(&branch.setup_command("p4")), ["branch", "-i", "-f"]);
}
#[test]
fn stdout_form_from_unselected() {
let branch = Branch::new("p4", GlobalOpts::new()).stdout();
assert_eq!(args_of(&branch.setup_command("p4")), ["branch", "-o"]);
}
#[test]
fn stdout_with_stream() {
let branch = Branch::new("p4", GlobalOpts::new())
.stdout()
.stream("//depotstream1/5dev1");
assert_eq!(
args_of(&branch.setup_command("p4")),
["branch", "-S", "//depotstream1/5dev1", "-o"]
);
}
#[test]
fn stdout_with_stream_and_parent() {
let branch = Branch::new("p4", GlobalOpts::new())
.stdout()
.stream("//stream1/5dev1")
.parent("//stream1/main5");
assert_eq!(
args_of(&branch.setup_command("p4")),
[
"branch",
"-S",
"//stream1/5dev1",
"-P",
"//stream1/main5",
"-o"
]
);
}
#[test]
fn renaming_stream_preserves_parent() {
let branch = Branch::new("p4", GlobalOpts::new())
.stdout()
.stream("//stream1/5dev1")
.parent("//stream1/main5")
.stream("//stream2/5dev2");
assert_eq!(
args_of(&branch.setup_command("p4")),
[
"branch",
"-S",
"//stream2/5dev2",
"-P",
"//stream1/main5",
"-o"
]
);
}
#[test]
fn force_can_be_set_inside_general_delete_and_stdin() {
let general = Branch::new("p4", GlobalOpts::new())
.force(false)
.force(true);
assert_eq!(args_of(&general.setup_command("p4")), ["branch", "-f"]);
let delete = Branch::new("p4", GlobalOpts::new()).delete().force(true);
assert_eq!(args_of(&delete.setup_command("p4")), ["branch", "-d", "-f"]);
let stdin = Branch::new("p4", GlobalOpts::new()).stdin().force(true);
assert_eq!(args_of(&stdin.setup_command("p4")), ["branch", "-i", "-f"]);
}
#[test]
fn force_can_be_unset() {
let branch = Branch::new("p4", GlobalOpts::new())
.force(true)
.force(false);
assert_eq!(args_of(&branch.setup_command("p4")), ["branch"]);
}
#[test]
fn setters_update_the_same_command() {
let mut branch = Branch::new("p4", GlobalOpts::new()).force(false);
branch.set_force(true);
assert!(branch.get_force());
let mut stdout = Branch::new("p4", GlobalOpts::new())
.stdout()
.stream("//depot/main");
stdout.set_parent("//depot/dev");
assert_eq!(stdout.get_stream(), "//depot/main");
assert_eq!(stdout.get_parent(), Some(&String::from("//depot/dev")));
}
#[test]
fn global_opts_are_injected_once() {
let branch = Branch::new("p4", GlobalOpts::new().port("localhost:1666")).force(false);
assert_eq!(
args_of(&branch.setup_command("p4")),
["-p", "localhost:1666", "branch"]
);
}
#[test]
fn spawn_argument_counts_per_form() {
let plain = Branch::new("p4", GlobalOpts::new()).force(false);
assert_eq!(args_of(&plain.setup_command("p4")), ["branch"]);
let mut with_bs = plain.setup_command("p4");
with_bs.arg("mybranch");
assert_eq!(args_of(&with_bs), ["branch", "mybranch"]);
let delete = Branch::new("p4", GlobalOpts::new()).force(true).delete();
let mut delete_bs = delete.setup_command("p4");
delete_bs.arg("oldbranch");
assert_eq!(args_of(&delete_bs), ["branch", "-d", "-f", "oldbranch"]);
let stdin = Branch::new("p4", GlobalOpts::new()).stdin();
assert_eq!(args_of(&stdin.setup_command("p4")), ["branch", "-i"]);
let stdout = Branch::new("p4", GlobalOpts::new()).stdout();
let mut stdout_ph = stdout.setup_command("p4");
stdout_ph.arg("placeholder1");
assert_eq!(args_of(&stdout_ph), ["branch", "-o", "placeholder1"]);
let stream = Branch::new("p4", GlobalOpts::new())
.stdout()
.stream("//depotstream1/5dev1")
.parent("//stream1/main5");
let mut stream_ph = stream.setup_command("p4");
stream_ph.arg("placeholder2");
assert_eq!(
args_of(&stream_ph),
[
"branch",
"-S",
"//depotstream1/5dev1",
"-P",
"//stream1/main5",
"-o",
"placeholder2"
]
);
}
}