use std::{
path::{Path, PathBuf},
process::{Child, Command, Stdio},
};
use super::{ExclusiveOption, SubCommand, Unselected};
use crate::global::GlobalOpts;
use crate::spawn::ParameterizedSpawn;
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Filter {
CaseSensitiveMode(String),
InCaseSensitiveMode(String),
}
impl ExclusiveOption for Filter {
fn inject_args(&self, command: &mut Command) {
match self {
Filter::CaseSensitiveMode(name) => {
command.arg("-e").arg(name);
}
Filter::InCaseSensitiveMode(name) => {
command.arg("-E").arg(name);
}
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct User {
user: String,
#[cfg(not(feature = "lt2024_2"))]
case_insensitive: bool,
}
impl ExclusiveOption for User {
fn inject_args(&self, command: &mut Command) {
command.arg("-u").arg(&self.user);
#[cfg(not(feature = "lt2024_2"))]
if self.case_insensitive {
command.arg("--user-case-insensitive");
}
}
}
#[cfg(not(feature = "lt2016_1"))]
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Me {}
#[cfg(not(feature = "lt2016_1"))]
impl ExclusiveOption for Me {
fn inject_args(&self, command: &mut Command) {
command.arg("--me");
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ListAll {}
impl ExclusiveOption for ListAll {
fn inject_args(&self, command: &mut Command) {
command.arg("-a");
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ListSpecified {
server_id: String,
}
impl ExclusiveOption for ListSpecified {
fn inject_args(&self, command: &mut Command) {
command.arg("-s").arg(&self.server_id);
}
}
#[derive(Debug, Clone)]
pub struct RegularMode<F = Unselected, U = Unselected, C = Unselected> {
display_time: bool,
limit: Option<u64>,
stream: Option<String>,
name_filter: F,
user_filter: U,
list_category: C,
}
#[derive(Debug, Clone, Default)]
pub struct UnloadedMode {}
#[cfg_attr(
feature = "lt2015_1",
doc = "`p4 [g-opts] clients [-t] [ -u user ] [ [-e|-E] filter -m max ] -S stream [-a | -s serverID]`"
)]
#[cfg_attr(
all(feature = "lt2016_1", not(feature = "lt2015_1")),
doc = "`p4 [g-opts] clients [-t] [-u user] [[-e|-E] filter] [-m max] [-S stream] [-a | -s serverID]`"
)]
#[cfg_attr(
all(feature = "lt2024_2", not(feature = "lt2016_1")),
doc = "`p4 [g-opts] clients [-t] [-u user | --me] [[-e|-E] filter] [-m max] [-S stream] [-a | -s serverID]`"
)]
#[cfg_attr(
not(feature = "lt2024_2"),
doc = "`p4 [g-opts] clients [-t] [-u user [--user-case-insensitive] | --me] [[-e|-E] filter] [-m max] [-S stream] [-a | -s serverID]`"
)]
#[derive(Debug, Clone, Default)]
pub struct Clients<M = Unselected> {
bin: PathBuf,
global_opts: GlobalOpts,
mode: M,
}
impl Clients<Unselected> {
pub fn new(bin: impl Into<PathBuf>, global_opts: GlobalOpts) -> Self {
Self {
bin: bin.into(),
global_opts,
..Default::default()
}
}
pub fn unloaded(self) -> Clients<UnloadedMode> {
Clients {
bin: self.bin,
global_opts: self.global_opts,
mode: UnloadedMode {},
}
}
pub fn display_time(self, v: bool) -> Clients<RegularMode> {
Clients {
bin: self.bin,
global_opts: self.global_opts,
mode: RegularMode {
display_time: v,
limit: None,
stream: None,
name_filter: Unselected,
user_filter: Unselected,
list_category: Unselected,
},
}
}
pub fn limit(self, v: u64) -> Clients<RegularMode> {
Clients {
bin: self.bin,
global_opts: self.global_opts,
mode: RegularMode {
display_time: false,
limit: Some(v),
stream: None,
name_filter: Unselected,
user_filter: Unselected,
list_category: Unselected,
},
}
}
pub fn stream(self, v: impl Into<String>) -> Clients<RegularMode> {
Clients {
bin: self.bin,
global_opts: self.global_opts,
mode: RegularMode {
display_time: false,
limit: None,
stream: Some(v.into()),
name_filter: Unselected,
user_filter: Unselected,
list_category: Unselected,
},
}
}
#[cfg_attr(
not(feature = "lt2024_2"),
doc = "",
doc = "`--client-case-insensitive` is equivalent to `-E`."
)]
pub fn filter(
self,
v: impl Into<String>,
case_insensitive: bool,
) -> Clients<RegularMode<Filter, Unselected, Unselected>> {
Clients {
bin: self.bin,
global_opts: self.global_opts,
mode: RegularMode {
display_time: false,
limit: None,
stream: None,
name_filter: if case_insensitive {
Filter::InCaseSensitiveMode(v.into())
} else {
Filter::CaseSensitiveMode(v.into())
},
user_filter: Unselected,
list_category: Unselected,
},
}
}
#[cfg_attr(feature = "lt2024_2", doc = "-u user")]
#[cfg_attr(not(feature = "lt2024_2"), doc = "-u user [--user-case-insensitive]")]
#[cfg_attr(
feature = "lt2024_2",
doc = "List only client workspaces owned by `v`."
)]
#[cfg_attr(
not(feature = "lt2024_2"),
doc = "List client workspaces that are owned by `v`. This can include `*` wildcards to form a search pattern. If wildcards are used, enclose the search pattern in double quotes (`\"`).",
doc = "",
doc = "For example:",
doc = "",
doc = "`p4 clients -u \"O*hara\"`",
doc = "",
doc = "Call [`Clients::case_insensitive`] on the entered state to add the `--user-case-insensitive` suboption."
)]
pub fn user(self, v: impl Into<String>) -> Clients<RegularMode<Unselected, User, Unselected>> {
Clients {
bin: self.bin,
global_opts: self.global_opts,
mode: RegularMode {
display_time: false,
limit: None,
stream: None,
name_filter: Unselected,
user_filter: User {
user: v.into(),
#[cfg(not(feature = "lt2024_2"))]
case_insensitive: false,
},
list_category: Unselected,
},
}
}
#[cfg(not(feature = "lt2016_1"))]
pub fn filter_me(self) -> Clients<RegularMode<Unselected, Me, Unselected>> {
Clients {
bin: self.bin,
global_opts: self.global_opts,
mode: RegularMode {
display_time: false,
limit: None,
stream: None,
name_filter: Unselected,
user_filter: Me {},
list_category: Unselected,
},
}
}
pub fn all(self) -> Clients<RegularMode<Unselected, Unselected, ListAll>> {
Clients {
bin: self.bin,
global_opts: self.global_opts,
mode: RegularMode {
display_time: false,
limit: None,
stream: None,
name_filter: Unselected,
user_filter: Unselected,
list_category: ListAll {},
},
}
}
pub fn server_id(
self,
v: impl Into<String>,
) -> Clients<RegularMode<Unselected, Unselected, ListSpecified>> {
Clients {
bin: self.bin,
global_opts: self.global_opts,
mode: RegularMode {
display_time: false,
limit: None,
stream: None,
name_filter: Unselected,
user_filter: Unselected,
list_category: ListSpecified {
server_id: v.into(),
},
},
}
}
}
impl<M> Clients<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<F, U, C> Clients<RegularMode<F, U, C>> {
pub fn get_limit(&self) -> Option<u64> {
self.mode.limit
}
pub fn set_limit(&mut self, v: u64) -> &mut Self {
self.mode.limit = Some(v);
self
}
pub fn limit(mut self, v: u64) -> Self {
self.mode.limit = Some(v);
self
}
pub fn get_display_time(&self) -> bool {
self.mode.display_time
}
pub fn set_display_time(&mut self, v: bool) -> &mut Self {
self.mode.display_time = v;
self
}
pub fn display_time(mut self, v: bool) -> Self {
self.mode.display_time = v;
self
}
pub fn get_stream(&self) -> Option<&String> {
self.mode.stream.as_ref()
}
pub fn set_stream(&mut self, v: impl Into<String>) -> &mut Self {
self.mode.stream = Some(v.into());
self
}
pub fn stream(mut self, v: impl Into<String>) -> Self {
self.mode.stream = Some(v.into());
self
}
}
impl<U, C> Clients<RegularMode<Unselected, U, C>> {
#[cfg_attr(
not(feature = "lt2024_2"),
doc = "",
doc = "`--client-case-insensitive` is equivalent to `-E`."
)]
pub fn filter(
self,
v: impl Into<String>,
case_insensitive: bool,
) -> Clients<RegularMode<Filter, U, C>> {
Clients {
bin: self.bin,
global_opts: self.global_opts,
mode: RegularMode {
display_time: self.mode.display_time,
limit: self.mode.limit,
stream: self.mode.stream,
user_filter: self.mode.user_filter,
list_category: self.mode.list_category,
name_filter: if case_insensitive {
Filter::InCaseSensitiveMode(v.into())
} else {
Filter::CaseSensitiveMode(v.into())
},
},
}
}
}
impl<F, C> Clients<RegularMode<F, Unselected, C>> {
#[cfg_attr(feature = "lt2024_2", doc = "-u user")]
#[cfg_attr(not(feature = "lt2024_2"), doc = "-u user [--user-case-insensitive]")]
#[cfg_attr(
feature = "lt2024_2",
doc = "List only client workspaces owned by `v`."
)]
#[cfg_attr(
not(feature = "lt2024_2"),
doc = "List client workspaces that are owned by `v`. This can include `*` wildcards to form a search pattern. If wildcards are used, enclose the search pattern in double quotes (`\"`).",
doc = "",
doc = "For example:",
doc = "",
doc = "`p4 clients -u \"O*hara\"`",
doc = "",
doc = "Call [`Clients::case_insensitive`] on the entered state to add the `--user-case-insensitive` suboption."
)]
pub fn user(self, v: impl Into<String>) -> Clients<RegularMode<F, User, C>> {
Clients {
bin: self.bin,
global_opts: self.global_opts,
mode: RegularMode {
display_time: self.mode.display_time,
limit: self.mode.limit,
stream: self.mode.stream,
name_filter: self.mode.name_filter,
list_category: self.mode.list_category,
user_filter: User {
user: v.into(),
#[cfg(not(feature = "lt2024_2"))]
case_insensitive: false,
},
},
}
}
#[cfg(not(feature = "lt2016_1"))]
pub fn filter_me(self) -> Clients<RegularMode<F, Me, C>> {
Clients {
bin: self.bin,
global_opts: self.global_opts,
mode: RegularMode {
display_time: self.mode.display_time,
limit: self.mode.limit,
stream: self.mode.stream,
name_filter: self.mode.name_filter,
list_category: self.mode.list_category,
user_filter: Me {},
},
}
}
}
impl<F, C> Clients<RegularMode<F, User, C>> {
#[cfg(not(feature = "lt2024_2"))]
pub fn case_insensitive(self) -> Clients<RegularMode<F, User, C>> {
Clients {
bin: self.bin,
global_opts: self.global_opts,
mode: RegularMode {
display_time: self.mode.display_time,
limit: self.mode.limit,
stream: self.mode.stream,
name_filter: self.mode.name_filter,
list_category: self.mode.list_category,
user_filter: User {
user: self.mode.user_filter.user,
case_insensitive: true,
},
},
}
}
}
impl<F, U> Clients<RegularMode<F, U, Unselected>> {
pub fn all(self) -> Clients<RegularMode<F, U, ListAll>> {
Clients {
bin: self.bin,
global_opts: self.global_opts,
mode: RegularMode {
display_time: self.mode.display_time,
limit: self.mode.limit,
stream: self.mode.stream,
name_filter: self.mode.name_filter,
user_filter: self.mode.user_filter,
list_category: ListAll {},
},
}
}
pub fn server_id(self, v: impl Into<String>) -> Clients<RegularMode<F, U, ListSpecified>> {
Clients {
bin: self.bin,
global_opts: self.global_opts,
mode: RegularMode {
display_time: self.mode.display_time,
limit: self.mode.limit,
stream: self.mode.stream,
name_filter: self.mode.name_filter,
user_filter: self.mode.user_filter,
list_category: ListSpecified {
server_id: v.into(),
},
},
}
}
}
impl SubCommand for Clients<Unselected> {
fn name(&self) -> &str {
"clients"
}
fn inject_local_args(&self, _: &mut Command) {}
fn global_opts(&self) -> Option<&GlobalOpts> {
Some(&self.global_opts)
}
}
impl<F: ExclusiveOption, U: ExclusiveOption, C: ExclusiveOption> SubCommand
for Clients<RegularMode<F, U, C>>
{
fn name(&self) -> &str {
"clients"
}
fn inject_local_args(&self, command: &mut Command) {
if self.mode.display_time {
command.arg("-t");
}
self.mode.user_filter.inject_args(command);
self.mode.name_filter.inject_args(command);
if let Some(max) = self.mode.limit {
command.arg("-m").arg(max.to_string());
}
if let Some(ref stream) = self.mode.stream {
command.arg("-S").arg(stream);
}
self.mode.list_category.inject_args(command);
}
fn global_opts(&self) -> Option<&GlobalOpts> {
Some(&self.global_opts)
}
}
impl SubCommand for Clients<UnloadedMode> {
fn name(&self) -> &str {
"clients"
}
fn inject_local_args(&self, command: &mut Command) {
command.arg("-U");
}
fn global_opts(&self) -> Option<&GlobalOpts> {
Some(&self.global_opts)
}
}
impl ParameterizedSpawn<()> for Clients<Unselected> {
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<F: ExclusiveOption, U: ExclusiveOption, C: ExclusiveOption> ParameterizedSpawn<()>
for Clients<RegularMode<F, U, C>>
{
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 ParameterizedSpawn<()> for Clients<UnloadedMode> {
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()
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::cmd::args_of;
#[test]
fn bin_accessors_update_executable_path() {
let mut command = Clients::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 without_options() {
let clients = Clients::new("p4", GlobalOpts::new());
assert_eq!(args_of(&clients.setup_command("p4")), ["clients"]);
}
#[test]
fn limit() {
let clients = Clients::new("p4", GlobalOpts::new()).limit(5);
assert_eq!(
args_of(&clients.setup_command("p4")),
["clients", "-m", "5"]
);
}
#[test]
fn display_time() {
let clients = Clients::new("p4", GlobalOpts::new()).display_time(true);
assert_eq!(args_of(&clients.setup_command("p4")), ["clients", "-t"]);
}
#[test]
fn display_time_can_be_unset() {
let clients = Clients::new("p4", GlobalOpts::new()).display_time(false);
assert_eq!(args_of(&clients.setup_command("p4")), ["clients"]);
}
#[test]
fn stream() {
let clients = Clients::new("p4", GlobalOpts::new()).stream("//stream/main");
assert_eq!(
args_of(&clients.setup_command("p4")),
["clients", "-S", "//stream/main"]
);
}
#[test]
fn filter_case_sensitive() {
let clients = Clients::new("p4", GlobalOpts::new()).filter("p4vBeta*", false);
assert_eq!(
args_of(&clients.setup_command("p4")),
["clients", "-e", "p4vBeta*"]
);
}
#[test]
fn filter_case_insensitive() {
let clients = Clients::new("p4", GlobalOpts::new()).filter("*Us*rs", true);
assert_eq!(
args_of(&clients.setup_command("p4")),
["clients", "-E", "*Us*rs"]
);
}
#[test]
fn user() {
let clients = Clients::new("p4", GlobalOpts::new()).user("O*hara");
assert_eq!(
args_of(&clients.setup_command("p4")),
["clients", "-u", "O*hara"]
);
}
#[cfg(not(feature = "lt2024_2"))]
#[test]
fn user_case_insensitive() {
let clients = Clients::new("p4", GlobalOpts::new())
.user("O*hara")
.case_insensitive();
assert_eq!(
args_of(&clients.setup_command("p4")),
["clients", "-u", "O*hara", "--user-case-insensitive"]
);
}
#[cfg(not(feature = "lt2016_1"))]
#[test]
fn filter_me() {
let clients = Clients::new("p4", GlobalOpts::new()).filter_me();
assert_eq!(args_of(&clients.setup_command("p4")), ["clients", "--me"]);
}
#[test]
fn all() {
let clients = Clients::new("p4", GlobalOpts::new()).all();
assert_eq!(args_of(&clients.setup_command("p4")), ["clients", "-a"]);
}
#[test]
fn server_id() {
let clients = Clients::new("p4", GlobalOpts::new()).server_id("edge1");
assert_eq!(
args_of(&clients.setup_command("p4")),
["clients", "-s", "edge1"]
);
}
#[test]
fn unloaded() {
let clients = Clients::new("p4", GlobalOpts::new()).unloaded();
assert_eq!(args_of(&clients.setup_command("p4")), ["clients", "-U"]);
}
#[test]
fn transitions_preserve_other_options() {
let clients = Clients::new("p4", GlobalOpts::new())
.user("O*hara")
.filter("p4v*", false)
.limit(5)
.display_time(true)
.stream("//stream/main")
.all();
assert_eq!(
args_of(&clients.setup_command("p4")),
[
"clients",
"-t",
"-u",
"O*hara",
"-e",
"p4v*",
"-m",
"5",
"-S",
"//stream/main",
"-a"
]
);
let clients = Clients::new("p4", GlobalOpts::new())
.server_id("edge1")
.filter("*Us*rs", true);
assert_eq!(
args_of(&clients.setup_command("p4")),
["clients", "-E", "*Us*rs", "-s", "edge1"]
);
}
#[test]
fn setters_update_regular_options() {
let mut clients = Clients::new("p4", GlobalOpts::new()).filter("p4v*", false);
assert_eq!(clients.get_limit(), None);
clients.set_limit(10);
assert_eq!(clients.get_limit(), Some(10));
assert!(!clients.get_display_time());
clients.set_display_time(true);
assert!(clients.get_display_time());
assert_eq!(clients.get_stream(), None);
clients.set_stream("//stream/dev");
assert_eq!(clients.get_stream(), Some(&"//stream/dev".to_string()));
}
#[test]
fn all_options_order() {
let clients = Clients::new("p4", GlobalOpts::new())
.display_time(true)
.user("O*hara")
.filter("*Us*rs", true)
.limit(5)
.stream("//stream/main")
.server_id("edge1");
assert_eq!(
args_of(&clients.setup_command("p4")),
[
"clients",
"-t",
"-u",
"O*hara",
"-E",
"*Us*rs",
"-m",
"5",
"-S",
"//stream/main",
"-s",
"edge1"
]
);
}
#[test]
fn global_opts_are_injected_once() {
let clients = Clients::new("p4", GlobalOpts::new().port("localhost:1666"));
assert_eq!(
args_of(&clients.setup_command("p4")),
["-p", "localhost:1666", "clients"]
);
let clients = Clients::new("p4", GlobalOpts::new().port("localhost:1666")).limit(1);
assert_eq!(
args_of(&clients.setup_command("p4")),
["-p", "localhost:1666", "clients", "-m", "1"]
);
let clients = Clients::new("p4", GlobalOpts::new().port("localhost:1666")).unloaded();
assert_eq!(
args_of(&clients.setup_command("p4")),
["-p", "localhost:1666", "clients", "-U"]
);
}
}