Skip to main content

perforce_cli/cmd/
client.rs

1use std::{
2    ffi::OsStr,
3    marker::PhantomData,
4    path::{Path, PathBuf},
5    process::{Child, Command, Stdio},
6};
7
8use super::{ExclusiveOption, SubCommand, Unselected};
9
10use crate::global::GlobalOpts;
11use crate::spawn::ParameterizedSpawn;
12
13// ---------------------------------------------------------------------------
14// Option dimensions: `-f` (force), `-t` (template), `-T` (workspace type)
15// ---------------------------------------------------------------------------
16
17/// The `-f` (force) flag is set.
18///
19/// Entered with [`Client::force`]; valid in the [`GeneralMode`],
20/// [`DeleteMode`],
21#[cfg_attr(not(feature = "lt2022_1"), doc = "[`SwitchViewMode`],")]
22/// and [`StdinMode`] states.
23#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
24pub struct ForceSet;
25
26impl ExclusiveOption for ForceSet {
27    fn inject_args(&self, command: &mut Command) {
28        command.arg("-f");
29    }
30}
31
32/// The `-t template` option is set: copy client workspace `template`'s view
33/// and options into the `View:` and `Options:` field of this workspace.
34///
35/// Entered with [`Client::template`]; valid in the [`GeneralMode`] and
36/// [`StdoutMode`] states.
37#[derive(Debug, Clone, Default, PartialEq, Eq)]
38pub struct TemplateSet {
39    template: String,
40}
41
42impl ExclusiveOption for TemplateSet {
43    fn inject_args(&self, command: &mut Command) {
44        command.arg("-t").arg(&self.template);
45    }
46}
47
48/// The `-T type` option is set: create the client workspace with a specific
49/// [`WorkSpaceType`].
50///
51/// Entered with [`Client::workspace_type`]; valid in the [`GeneralMode`]
52/// and [`StdoutMode`] states.
53#[cfg(not(feature = "lt2016_2"))]
54#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
55pub struct TypeSet {
56    workspace_type: WorkSpaceType,
57}
58
59#[cfg(not(feature = "lt2016_2"))]
60impl ExclusiveOption for TypeSet {
61    fn inject_args(&self, command: &mut Command) {
62        command.arg("-T").arg(self.workspace_type.as_str());
63    }
64}
65
66/// The type of a client workspace, set with `-T type` or the `Type:` field.
67///
68/// By default, clients are [`Writable`](WorkSpaceType::Writable).
69#[cfg(not(feature = "lt2016_2"))]
70#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
71pub enum WorkSpaceType {
72    /// `writeable`: for general use; the have list is tracked in the
73    /// central `db.have` table.
74    #[default]
75    Writable,
76    /// `readonly`: for short lived clients used in build automation scripts
77    /// that do not edit or submit files.
78    Readonly,
79    /// `partitioned`: for short lived clients used in build automation
80    /// scripts that do edit or submit files.
81    Partitioned,
82    /// `partitioned-jnl`: like `partitioned`, but journaled, checkpointed,
83    /// and recoverable.
84    #[cfg(not(feature = "lt2024_1"))]
85    PartitionedJnl,
86    /// `graph`: for use with depots and repos of type graph and Git
87    /// Connector.
88    #[cfg(not(feature = "lt2024_1"))]
89    Graph,
90}
91
92#[cfg(not(feature = "lt2016_2"))]
93impl WorkSpaceType {
94    fn as_str(&self) -> &'static str {
95        match self {
96            WorkSpaceType::Writable => "writeable",
97            WorkSpaceType::Readonly => "readonly",
98            WorkSpaceType::Partitioned => "partitioned",
99            #[cfg(not(feature = "lt2024_1"))]
100            WorkSpaceType::PartitionedJnl => "partitioned-jnl",
101            #[cfg(not(feature = "lt2024_1"))]
102            WorkSpaceType::Graph => "graph",
103        }
104    }
105}
106
107// ---------------------------------------------------------------------------
108// Command modes
109// ---------------------------------------------------------------------------
110
111#[cfg_attr(
112    feature = "lt2016_2",
113    doc = "The default form: `p4 client [-f] [-t template] [name]`.",
114    doc = "",
115    doc = "Create or edit a client workspace specification. This is the initial state of [`Client`]; from here the command can transition into any of the other modes, subject to the options already set:",
116    doc = "",
117    doc = "- with `-f` set, [`Client::delete`] and [`Client::stdin`] remain available (both of them accept `-f`)",
118    doc = "- with `-t` set, [`Client::stdout`] remains available (the `-o` form accepts it)"
119)]
120#[cfg_attr(
121    all(feature = "lt2022_1", not(feature = "lt2016_2")),
122    doc = "The default form: `p4 client [-f] [-t template] [-T type] [name]`.",
123    doc = "",
124    doc = "Create or edit a client workspace specification. This is the initial state of [`Client`]; from here the command can transition into any of the other modes, subject to the options already set:",
125    doc = "",
126    doc = "- with `-f` set, [`Client::delete`] and [`Client::stdin`] remain available (both of them accept `-f`)",
127    doc = "- with `-t` / `-T` set, [`Client::stdout`] remains available (the `-o` form accepts both)"
128)]
129#[cfg_attr(
130    not(feature = "lt2022_1"),
131    doc = "The default form: `p4 client [-f] [-t template] [-T type] [name]`.",
132    doc = "",
133    doc = "Create or edit a client workspace specification. This is the initial state of [`Client`]; from here the command can transition into any of the other modes, subject to the options already set:",
134    doc = "",
135    doc = "- with `-f` set, [`Client::delete`], [`Client::switch_view_stream`], [`Client::switch_view_template`], [`Client::switch_view`], and [`Client::stdin`] remain available (all of them accept `-f`)",
136    doc = "- with `-t` / `-T` set, [`Client::stdout`] remains available (the `-o` form accepts both)"
137)]
138#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
139pub struct GeneralMode;
140
141/// How `-d -f` treats shelved changes of the deleted client workspace.
142#[derive(Debug, Clone, Copy, PartialEq, Eq)]
143pub enum ShelvedHandling {
144    /// `-Fs`: the client is deleted and the shelved changes are left
145    /// intact.
146    Keep,
147    /// `-Fd`: the client and the shelved changes are both deleted.
148    #[cfg(not(feature = "lt2024_1"))]
149    Delete,
150}
151
152/// Marker: the `--serverid=X` option is set on the delete form.
153#[cfg(not(feature = "lt2015_2"))]
154#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
155pub struct ServerIdSet;
156
157/// Marker: the `-Fs` option is set; the client is deleted and the shelved
158/// changes are left intact.
159#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
160pub struct ShelvedKept;
161
162/// Marker: the `-Fd` option is set; the client and the shelved changes
163/// are both deleted.
164#[cfg(not(feature = "lt2024_1"))]
165#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
166pub struct ShelvedDeleted;
167
168/// Tracks whether the `--serverid=X` option is present on the delete form.
169#[doc(hidden)]
170pub trait ServerIdState {
171    /// Whether the `--serverid=X` option has been selected.
172    const SET: bool;
173}
174
175impl ServerIdState for Unselected {
176    const SET: bool = false;
177}
178
179#[cfg(not(feature = "lt2015_2"))]
180impl ServerIdState for ServerIdSet {
181    const SET: bool = true;
182}
183
184/// Tracks the shelved-changes handling (`-Fs` / `-Fd`) on the delete form.
185#[doc(hidden)]
186pub trait ShelvedState {
187    /// The selected shelved-changes handling, if any.
188    fn handling() -> Option<ShelvedHandling>;
189
190    /// Inject the `-Fs` / `-Fd` argument, if selected.
191    fn inject_shelved(command: &mut Command);
192}
193
194impl ShelvedState for Unselected {
195    fn handling() -> Option<ShelvedHandling> {
196        None
197    }
198
199    fn inject_shelved(_: &mut Command) {}
200}
201
202impl ShelvedState for ShelvedKept {
203    fn handling() -> Option<ShelvedHandling> {
204        Some(ShelvedHandling::Keep)
205    }
206
207    fn inject_shelved(command: &mut Command) {
208        command.arg("-Fs");
209    }
210}
211
212#[cfg(not(feature = "lt2024_1"))]
213impl ShelvedState for ShelvedDeleted {
214    fn handling() -> Option<ShelvedHandling> {
215        Some(ShelvedHandling::Delete)
216    }
217
218    fn inject_shelved(command: &mut Command) {
219        command.arg("-Fd");
220    }
221}
222
223/// The shelved states with which `--serverid=X` may be combined: no
224/// shelved option, or `-Fs`. The documented form is
225/// `p4 client -d -f --serverid=X [-Fs] name`, so `-Fd` is excluded.
226#[doc(hidden)]
227pub trait ServerIdCompatible: ShelvedState {}
228
229impl ServerIdCompatible for Unselected {}
230impl ServerIdCompatible for ShelvedKept {}
231
232/// Marks the `(serverid state, shelved state, force state)` triples of the
233/// delete form that match a documented command line and are therefore
234/// spawnable:
235///
236/// - `(Unselected, Unselected, Unselected)` — `p4 client -d name`
237/// - `(Unselected, Unselected, ForceSet)` — `p4 client -d -f name`
238/// - `(Unselected, ShelvedKept, ForceSet)` — `p4 client -d -f -Fs name`
239#[cfg_attr(
240    not(feature = "lt2024_1"),
241    doc = "- `(Unselected, ShelvedDeleted, ForceSet)` — `p4 client -d -f -Fd name`"
242)]
243#[cfg_attr(
244    not(feature = "lt2015_2"),
245    doc = "- `(ServerIdSet, Unselected, ForceSet)` — `p4 client -d -f --serverid=X name`",
246    doc = "- `(ServerIdSet, ShelvedKept, ForceSet)` — `p4 client -d -f --serverid=X -Fs name`"
247)]
248///
249/// States where an option requiring `-f` is selected without `-f` are
250/// reachable as intermediate builder states (the options may be selected
251/// in any order) but are not marked here, so they cannot be spawned until
252/// [`Client::force`] is called.
253#[doc(hidden)]
254pub trait ValidDeleteCombination {}
255
256impl ValidDeleteCombination for (Unselected, Unselected, Unselected) {}
257impl ValidDeleteCombination for (Unselected, Unselected, ForceSet) {}
258impl ValidDeleteCombination for (Unselected, ShelvedKept, ForceSet) {}
259#[cfg(not(feature = "lt2024_1"))]
260impl ValidDeleteCombination for (Unselected, ShelvedDeleted, ForceSet) {}
261#[cfg(not(feature = "lt2015_2"))]
262impl ValidDeleteCombination for (ServerIdSet, Unselected, ForceSet) {}
263#[cfg(not(feature = "lt2015_2"))]
264impl ValidDeleteCombination for (ServerIdSet, ShelvedKept, ForceSet) {}
265
266#[cfg_attr(
267    feature = "lt2015_2",
268    doc = "The delete form: `p4 client -d [-f [-Fs]] name`.",
269    doc = "",
270    doc = "The `S` type parameter tracks the shelved handling: [`Unselected`] or [`ShelvedKept`] (`-Fs`).",
271    doc = "",
272    doc = "The options may be selected in any order (for example `keep_shelved().force()` is equivalent to `force().keep_shelved()`); a state where `-Fs` is set without `-f` is an intermediate state that cannot be spawned until [`Client::force`] is called. Entered with [`Client::delete`], or directly from the [`GeneralMode`] state via [`Client::keep_shelved`]."
273)]
274#[cfg_attr(
275    all(feature = "lt2024_1", not(feature = "lt2015_2")),
276    doc = "The delete form: `p4 client -d [-f [-Fs]] name` and `p4 client -d -f --serverid=X [-Fs] name`.",
277    doc = "",
278    doc = "The two type parameters encode the documented compatibility matrix at compile time:",
279    doc = "",
280    doc = "- `I` tracks `--serverid=X`: [`Unselected`] or [`ServerIdSet`]",
281    doc = "- `S` tracks the shelved handling: [`Unselected`] or [`ShelvedKept`] (`-Fs`)",
282    doc = "",
283    doc = "`--serverid=X` can only be combined with `-Fs`; the illegal combinations have no reachable state. The options may be selected in any order (for example `keep_shelved().force()` is equivalent to `force().keep_shelved()`); a state where `-Fs` or `--serverid=X` is set without `-f` is an intermediate state that cannot be spawned until [`Client::force`] is called. Entered with [`Client::delete`], or directly from the [`GeneralMode`] state via [`Client::server_id`] and [`Client::keep_shelved`]."
284)]
285#[cfg_attr(
286    not(feature = "lt2024_1"),
287    doc = "The delete form: `p4 client -d [-f [-Fs|-Fd]] name` and `p4 client -d -f --serverid=X [-Fs] name`.",
288    doc = "",
289    doc = "The two type parameters encode the documented compatibility matrix at compile time:",
290    doc = "",
291    doc = "- `I` tracks `--serverid=X`: [`Unselected`] or [`ServerIdSet`]",
292    doc = "- `S` tracks the shelved handling: [`Unselected`], [`ShelvedKept`] (`-Fs`), or [`ShelvedDeleted`] (`-Fd`)",
293    doc = "",
294    doc = "`-Fs` and `-Fd` are mutually exclusive, and `--serverid=X` can only be combined with `-Fs`, never with `-Fd`; the illegal combinations have no reachable state. The options may be selected in any order (for example `delete_shelved().force()` is equivalent to `force().delete_shelved()`); a state where `-Fs`, `-Fd`, or `--serverid=X` is set without `-f` is an intermediate state that cannot be spawned until [`Client::force`] is called. Entered with [`Client::delete`], or directly from the [`GeneralMode`] state via [`Client::server_id`], [`Client::keep_shelved`], and [`Client::delete_shelved`]."
295)]
296#[derive(Debug, Clone, Default, PartialEq, Eq)]
297pub struct DeleteMode<I = Unselected, S = Unselected> {
298    server_id: Option<String>,
299    serverid_state: PhantomData<I>,
300    shelved: S,
301}
302
303#[cfg_attr(
304    feature = "lt2016_2",
305    doc = "The output form: `p4 client -o [-t template] [name]`."
306)]
307#[cfg_attr(
308    not(feature = "lt2016_2"),
309    doc = "The output form: `p4 client -o [-t template] [-T type] [name]`."
310)]
311///
312/// Write the client workspace specification to standard output. Entered
313/// with [`Client::stdout`];
314#[cfg_attr(
315    feature = "lt2016_2",
316    doc = "carries the `-t` option over from [`GeneralMode`]."
317)]
318#[cfg_attr(
319    not(feature = "lt2016_2"),
320    doc = "carries the `-t` and `-T` options over from [`GeneralMode`]."
321)]
322#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
323pub struct StdoutMode;
324
325/// The `-o` part of the stream form: display the workspace specification
326/// that would have been created for the stream.
327///
328/// Entered with [`Client::stdout`] in the [`StreamMode`] state; unlocks
329/// the `-c change` option.
330#[derive(Debug, Clone, Default, PartialEq, Eq)]
331pub struct StdoutOutput {
332    change: Option<String>,
333}
334
335impl ExclusiveOption for StdoutOutput {
336    fn inject_args(&self, command: &mut Command) {
337        if let Some(change) = &self.change {
338            command.arg("-c").arg(change);
339        }
340        command.arg("-o");
341    }
342}
343
344/// The stream form: `p4 client -S stream [[-c change] -o] [name]`.
345///
346/// Associate the workspace with the specified stream, which is used to
347/// generate its workspace view. Entered with [`Client::stream`]; before
348/// `-o` is selected, [`Client::switch_view`] can add `-s` to switch the
349/// stream associated with an existing workspace.
350#[derive(Debug, Clone, Default, PartialEq, Eq)]
351pub struct StreamMode<O = Unselected> {
352    stream: String,
353    output: O,
354}
355
356/// Switch the workspace view to a stream: `p4 client -s [-f] -S stream
357/// [name]`.
358///
359/// Entered with [`Client::switch_view_stream`] or [`Client::switch_view`].
360#[derive(Debug, Clone, Default, PartialEq, Eq)]
361pub struct StreamSwitch {
362    stream: String,
363}
364
365impl ExclusiveOption for StreamSwitch {
366    fn inject_args(&self, command: &mut Command) {
367        command.arg("-S").arg(&self.stream);
368    }
369}
370
371/// Switch the workspace view to the view defined for another workspace:
372/// `p4 client -s [-f] -t template [name]`.
373///
374/// Entered with [`Client::switch_view_template`] or [`Client::switch_view`].
375#[derive(Debug, Clone, Default, PartialEq, Eq)]
376pub struct TemplateSwitch {
377    template: String,
378}
379
380impl ExclusiveOption for TemplateSwitch {
381    fn inject_args(&self, command: &mut Command) {
382        command.arg("-t").arg(&self.template);
383    }
384}
385
386#[cfg_attr(
387    feature = "lt2022_1",
388    doc = "The switch-view form: `p4 client -s (-S stream | -t template) [name]`."
389)]
390#[cfg_attr(
391    not(feature = "lt2022_1"),
392    doc = "The switch-view form: `p4 client -s [-f] (-S stream | -t template) [name]`."
393)]
394///
395/// Switching views is not allowed in a client that has opened files.
396#[cfg_attr(
397    not(feature = "lt2022_1"),
398    doc = "The `-f` option can be used with `-s` to force switching with opened files."
399)]
400///
401/// View switching has no effect on files in a client workspace until
402/// `p4 sync` is run.
403///
404/// Entered with [`Client::switch_view_stream`] or [`Client::switch_view`].
405#[derive(Debug, Clone, Default, PartialEq, Eq)]
406pub struct SwitchViewMode<V> {
407    view: V,
408}
409
410/// Marks the `-f` states that may be carried into the switch-view form:
411/// always [`Unselected`]; [`ForceSet`] where the selected feature set
412/// accepts `-f` on the `-s` form.
413#[doc(hidden)]
414pub trait SwitchViewForceCompatible: ExclusiveOption {}
415
416impl SwitchViewForceCompatible for Unselected {}
417
418#[cfg(not(feature = "lt2022_1"))]
419impl SwitchViewForceCompatible for ForceSet {}
420
421/// The input form: `p4 client -i [-f]`.
422///
423/// Read the client workspace specification from standard input. Entered
424/// with [`Client::stdin`].
425#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
426pub struct StdinMode;
427
428// ---------------------------------------------------------------------------
429// Mode argument injection
430// ---------------------------------------------------------------------------
431
432/// Injects the mode-specific arguments in syntax order, given the
433/// orthogonal option dimensions `F` (`-f`), `T` (`-t`), and `W` (`-T`).
434pub trait ClientMode<F, T, W> {
435    /// Inject the mode flag(s) and the carried options into `command`.
436    fn inject_mode_args(&self, command: &mut Command, force: &F, template: &T, workspace_type: &W);
437}
438
439impl<F, T, W> ClientMode<F, T, W> for GeneralMode
440where
441    F: ExclusiveOption,
442    T: ExclusiveOption,
443    W: ExclusiveOption,
444{
445    fn inject_mode_args(&self, command: &mut Command, force: &F, template: &T, workspace_type: &W) {
446        force.inject_args(command);
447        template.inject_args(command);
448        workspace_type.inject_args(command);
449    }
450}
451
452impl<I, S, F> ClientMode<F, Unselected, Unselected> for DeleteMode<I, S>
453where
454    I: ServerIdState,
455    S: ShelvedState,
456    F: ExclusiveOption,
457{
458    fn inject_mode_args(&self, command: &mut Command, force: &F, _: &Unselected, _: &Unselected) {
459        command.arg("-d");
460        force.inject_args(command);
461
462        if I::SET
463            && let Some(server_id) = &self.server_id
464        {
465            command.arg(format!("--serverid={server_id}"));
466        }
467
468        S::inject_shelved(command);
469    }
470}
471
472impl<T, W> ClientMode<Unselected, T, W> for StdoutMode
473where
474    T: ExclusiveOption,
475    W: ExclusiveOption,
476{
477    fn inject_mode_args(
478        &self,
479        command: &mut Command,
480        _: &Unselected,
481        template: &T,
482        workspace_type: &W,
483    ) {
484        command.arg("-o");
485        template.inject_args(command);
486        workspace_type.inject_args(command);
487    }
488}
489
490impl<O> ClientMode<Unselected, Unselected, Unselected> for StreamMode<O>
491where
492    O: ExclusiveOption,
493{
494    fn inject_mode_args(
495        &self,
496        command: &mut Command,
497        _: &Unselected,
498        _: &Unselected,
499        _: &Unselected,
500    ) {
501        command.arg("-S").arg(&self.stream);
502        self.output.inject_args(command);
503    }
504}
505
506impl<F> ClientMode<F, Unselected, Unselected> for SwitchViewMode<StreamSwitch>
507where
508    F: ExclusiveOption,
509{
510    fn inject_mode_args(&self, command: &mut Command, force: &F, _: &Unselected, _: &Unselected) {
511        command.arg("-s");
512        force.inject_args(command);
513        self.view.inject_args(command);
514    }
515}
516
517impl<F> ClientMode<F, Unselected, Unselected> for SwitchViewMode<TemplateSwitch>
518where
519    F: ExclusiveOption,
520{
521    fn inject_mode_args(&self, command: &mut Command, force: &F, _: &Unselected, _: &Unselected) {
522        command.arg("-s");
523        force.inject_args(command);
524        self.view.inject_args(command);
525    }
526}
527
528impl<F> ClientMode<F, Unselected, Unselected> for StdinMode
529where
530    F: ExclusiveOption,
531{
532    fn inject_mode_args(&self, command: &mut Command, force: &F, _: &Unselected, _: &Unselected) {
533        command.arg("-i");
534        force.inject_args(command);
535    }
536}
537
538// ---------------------------------------------------------------------------
539// Client
540// ---------------------------------------------------------------------------
541
542#[cfg_attr(
543    feature = "lt2015_2",
544    doc = "```text",
545    doc = "p4 client [-f] [-t template] [clientname]",
546    doc = "p4 client -o [-t template] [clientname]",
547    doc = "p4 client -d [-f [-Fs]] clientname",
548    doc = "p4 client -s [-S stream | -t clientname] clientname",
549    doc = "p4 client -S stream [[-c change] -o] [clientname]",
550    doc = "p4 client -i [-f]",
551    doc = "```"
552)]
553#[cfg_attr(
554    all(feature = "lt2016_2", not(feature = "lt2015_2")),
555    doc = "```text",
556    doc = "p4 client [-f] [-t template] [clientname]",
557    doc = "p4 client -o [-t template] [clientname]",
558    doc = "p4 client -d [-f [-Fs]] clientname",
559    doc = "p4 client -s [-S stream | -t clientname] clientname",
560    doc = "p4 client -S stream [[-c change] -o] [clientname]",
561    doc = "p4 client -i [-f]",
562    doc = "p4 client -d -f --serverid=serverid [-Fs]",
563    doc = "```"
564)]
565#[cfg_attr(
566    all(feature = "lt2022_1", not(feature = "lt2016_2")),
567    doc = "```text",
568    doc = "p4 client [-f] [-t template] [-T type] [clientname]",
569    doc = "p4 client -o [-t template] [-T type] [clientname]",
570    doc = "p4 client -d [-f [-Fs]] clientname",
571    doc = "p4 client -s [-S stream | -t clientname] clientname",
572    doc = "p4 client -S stream [[-c change] -o] [clientname]",
573    doc = "p4 client -i [-f]",
574    doc = "p4 client -d -f --serverid=serverid [-Fs]",
575    doc = "```"
576)]
577#[cfg_attr(
578    all(feature = "lt2024_1", not(feature = "lt2022_1")),
579    doc = "```text",
580    doc = "p4 client [-f] [-t template] [-T type] [name]",
581    doc = "p4 client -d [-f [-Fs]] name",
582    doc = "p4 client -o [-t template] [-T type] [name]",
583    doc = "p4 client -S stream [[-c change] -o] [name]",
584    doc = "p4 client -s [-f] -S stream [name]",
585    doc = "p4 client -s [-f] -t template [name]",
586    doc = "p4 client -i [-f]",
587    doc = "p4 client -d -f --serverid=X [-Fs] name",
588    doc = "```"
589)]
590#[cfg_attr(
591    not(feature = "lt2024_1"),
592    doc = "```text",
593    doc = "p4 client [-f] [-t template] [-T type] [name]",
594    doc = "p4 client -d [-f [-Fs|-Fd]] name",
595    doc = "p4 client -o [-t template] [-T type] [name]",
596    doc = "p4 client -S stream [[-c change] -o] [name]",
597    doc = "p4 client -s [-f] -S stream [name]",
598    doc = "p4 client -s [-f] -t template [name]",
599    doc = "p4 client -i [-f]",
600    doc = "p4 client -d -f --serverid=X [-Fs] name",
601    doc = "```"
602)]
603///
604/// Create or edit a client workspace specification and its view.
605///
606#[cfg_attr(
607    feature = "lt2016_2",
608    doc = "The type parameters track the mode `M` and the orthogonal option dimensions `F` (`-f`) and `T` (`-t template`) at compile time, so a transition is only available when every option already set is valid in the target form:",
609    doc = "",
610    doc = "- [`Self::delete`] requires `-t` to be unset and ends in the terminal [`DeleteMode`] state, from which no further transitions are possible",
611    doc = "- [`Self::stdout`] requires `-f` to be unset; `-t` is carried into the [`StdoutMode`] state",
612    doc = "- [`Self::stream`] requires `-f` / `-t` to be unset",
613    doc = "- [`Self::switch_view_stream`] / [`Self::switch_view_template`] / [`Self::switch_view`] switch the view: for the stream form `-t` must be unset, while the template form requires `-t` to be set",
614    doc = "- the `-s -S stream` form has a second entry path: select the stream with [`Self::stream`] and then [`Self::switch_view`] (only before `-o` is selected)",
615    doc = "- [`Self::stdin`] requires `-t` to be unset; `-f` is carried into the [`StdinMode`] state"
616)]
617#[cfg_attr(
618    all(feature = "lt2022_1", not(feature = "lt2016_2")),
619    doc = "The type parameters track the mode `M` and the orthogonal option dimensions `F` (`-f`), `T` (`-t template`), and `W` (`-T type`) at compile time, so a transition is only available when every option already set is valid in the target form:",
620    doc = "",
621    doc = "- [`Self::delete`] requires `-t` / `-T` to be unset and ends in the terminal [`DeleteMode`] state, from which no further transitions are possible",
622    doc = "- [`Self::stdout`] requires `-f` to be unset; `-t` / `-T` are carried into the [`StdoutMode`] state",
623    doc = "- [`Self::stream`] requires `-f` / `-t` / `-T` to be unset",
624    doc = "- [`Self::switch_view_stream`] / [`Self::switch_view_template`] / [`Self::switch_view`] require `-T` to be unset; for the stream form `-t` must also be unset, while the template form requires `-t` to be set",
625    doc = "- the `-s -S stream` form has a second entry path: select the stream with [`Self::stream`] and then [`Self::switch_view`] (only before `-o` is selected)",
626    doc = "- [`Self::stdin`] requires `-t` / `-T` to be unset; `-f` is carried into the [`StdinMode`] state"
627)]
628#[cfg_attr(
629    not(feature = "lt2022_1"),
630    doc = "The type parameters track the mode `M` and the orthogonal option dimensions `F` (`-f`), `T` (`-t template`), and `W` (`-T type`) at compile time, so a transition is only available when every option already set is valid in the target form:",
631    doc = "",
632    doc = "- [`Self::delete`] requires `-t` / `-T` to be unset and ends in the terminal [`DeleteMode`] state, from which no further transitions are possible",
633    doc = "- [`Self::stdout`] requires `-f` to be unset; `-t` / `-T` are carried into the [`StdoutMode`] state",
634    doc = "- [`Self::stream`] requires `-f` / `-t` / `-T` to be unset",
635    doc = "- [`Self::switch_view_stream`] / [`Self::switch_view_template`] / [`Self::switch_view`] require `-T` to be unset; for the stream form `-t` must also be unset, while the template form requires `-t` to be set. `-f` is carried into the [`SwitchViewMode`] state",
636    doc = "- the `-s -S stream` form has a second entry path: select the stream with [`Self::stream`] and then [`Self::switch_view`] (only before `-o` is selected); `-f` is added afterwards if needed",
637    doc = "- [`Self::stdin`] requires `-t` / `-T` to be unset; `-f` is carried into the [`StdinMode`] state"
638)]
639#[derive(Debug, Clone, Default)]
640pub struct Client<M = GeneralMode, F = Unselected, T = Unselected, W = Unselected> {
641    bin: PathBuf,
642
643    global_opts: GlobalOpts,
644
645    mode: M,
646
647    force: F,
648
649    template: T,
650
651    workspace_type: W,
652}
653
654impl<M, F, T, W> SubCommand for Client<M, F, T, W>
655where
656    M: ClientMode<F, T, W>,
657{
658    fn name(&self) -> &str {
659        "client"
660    }
661
662    fn inject_local_args(&self, command: &mut Command) {
663        self.mode
664            .inject_mode_args(command, &self.force, &self.template, &self.workspace_type);
665    }
666
667    fn global_opts(&self) -> Option<&GlobalOpts> {
668        Some(&self.global_opts)
669    }
670}
671
672// ---------------------------------------------------------------------------
673// Spawning: GeneralMode — `p4 client [-f] [-t template] [-T type] [name]`
674// ---------------------------------------------------------------------------
675
676impl<F, T, W> ParameterizedSpawn<()> for Client<GeneralMode, F, T, W>
677where
678    F: ExclusiveOption,
679    T: ExclusiveOption,
680    W: ExclusiveOption,
681{
682    type Output = Child;
683    type Error = std::io::Error;
684
685    /// Spawns `p4 client` for the workspace named by `P4CLIENT` as a child
686    /// process; the editor configured by `P4EDITOR` is invoked on the
687    /// client workspace specification form.
688    ///
689    /// The child process has its standard output and error streams piped;
690    /// use the returned [`Child`] handle to wait for it or interact with it.
691    fn spawn_with(&mut self, (): ()) -> Result<Self::Output, Self::Error> {
692        self.setup_command(&self.bin)
693            .stdout(Stdio::piped())
694            .stderr(Stdio::piped())
695            .spawn()
696    }
697}
698
699impl<F, T, W, I> ParameterizedSpawn<(I,)> for Client<GeneralMode, F, T, W>
700where
701    F: ExclusiveOption,
702    T: ExclusiveOption,
703    W: ExclusiveOption,
704    I: AsRef<OsStr>,
705{
706    type Output = Child;
707    type Error = std::io::Error;
708
709    /// Spawns `p4 client` for the named workspace as a child process; the
710    /// editor configured by `P4EDITOR` is invoked on the client workspace
711    /// specification form.
712    ///
713    /// The child process has its standard output and error streams piped;
714    /// use the returned [`Child`] handle to wait for it or interact with it.
715    fn spawn_with(&mut self, (name,): (I,)) -> Result<Self::Output, Self::Error> {
716        self.setup_command(&self.bin)
717            .arg(name)
718            .stdout(Stdio::piped())
719            .stderr(Stdio::piped())
720            .spawn()
721    }
722}
723
724// ---------------------------------------------------------------------------
725// Spawning: StdoutMode — `p4 client -o [-t template] [-T type] [name]`
726// ---------------------------------------------------------------------------
727
728impl<T, W> ParameterizedSpawn<()> for Client<StdoutMode, Unselected, T, W>
729where
730    T: ExclusiveOption,
731    W: ExclusiveOption,
732{
733    type Output = Child;
734    type Error = std::io::Error;
735
736    /// Spawns `p4 client -o` for the workspace named by `P4CLIENT` as a
737    /// child process, writing the client workspace specification to
738    /// standard output.
739    ///
740    /// The child process has its standard output and error streams piped;
741    /// use the returned [`Child`] handle to wait for it or interact with it.
742    fn spawn_with(&mut self, (): ()) -> Result<Self::Output, Self::Error> {
743        self.setup_command(&self.bin)
744            .stdout(Stdio::piped())
745            .stderr(Stdio::piped())
746            .spawn()
747    }
748}
749
750impl<T, W, I> ParameterizedSpawn<(I,)> for Client<StdoutMode, Unselected, T, W>
751where
752    T: ExclusiveOption,
753    W: ExclusiveOption,
754    I: AsRef<OsStr>,
755{
756    type Output = Child;
757    type Error = std::io::Error;
758
759    /// Spawns `p4 client -o` for the named workspace as a child process,
760    /// writing the client workspace specification to standard output.
761    ///
762    /// The child process has its standard output and error streams piped;
763    /// use the returned [`Child`] handle to wait for it or interact with it.
764    fn spawn_with(&mut self, (name,): (I,)) -> Result<Self::Output, Self::Error> {
765        self.setup_command(&self.bin)
766            .arg(name)
767            .stdout(Stdio::piped())
768            .stderr(Stdio::piped())
769            .spawn()
770    }
771}
772
773// ---------------------------------------------------------------------------
774// Spawning: DeleteMode — `p4 client -d [-f [-Fs|-Fd]] name`
775// ---------------------------------------------------------------------------
776
777impl<I, S, F, A> ParameterizedSpawn<(A,)> for Client<DeleteMode<I, S>, F, Unselected, Unselected>
778where
779    I: ServerIdState,
780    S: ShelvedState,
781    F: ExclusiveOption,
782    (I, S, F): ValidDeleteCombination,
783    A: AsRef<OsStr>,
784{
785    type Output = Child;
786    type Error = std::io::Error;
787
788    /// Spawns `p4 client -d` for the named workspace as a child process.
789    ///
790    /// The child process has its standard output and error streams piped;
791    /// use the returned [`Child`] handle to wait for it or interact with it.
792    fn spawn_with(&mut self, (name,): (A,)) -> Result<Self::Output, Self::Error> {
793        self.setup_command(&self.bin)
794            .arg(name)
795            .stdout(Stdio::piped())
796            .stderr(Stdio::piped())
797            .spawn()
798    }
799}
800
801// ---------------------------------------------------------------------------
802// Spawning: StreamMode — `p4 client -S stream [[-c change] -o] [name]`
803// ---------------------------------------------------------------------------
804
805impl<O> ParameterizedSpawn<()> for Client<StreamMode<O>, Unselected, Unselected, Unselected>
806where
807    O: ExclusiveOption,
808{
809    type Output = Child;
810    type Error = std::io::Error;
811
812    /// Spawns `p4 client -S stream` for the workspace named by `P4CLIENT`
813    /// as a child process, associating the workspace with the stream.
814    ///
815    /// The child process has its standard output and error streams piped;
816    /// use the returned [`Child`] handle to wait for it or interact with it.
817    fn spawn_with(&mut self, (): ()) -> Result<Self::Output, Self::Error> {
818        self.setup_command(&self.bin)
819            .stdout(Stdio::piped())
820            .stderr(Stdio::piped())
821            .spawn()
822    }
823}
824
825impl<O, I> ParameterizedSpawn<(I,)> for Client<StreamMode<O>, Unselected, Unselected, Unselected>
826where
827    O: ExclusiveOption,
828    I: AsRef<OsStr>,
829{
830    type Output = Child;
831    type Error = std::io::Error;
832
833    /// Spawns `p4 client -S stream` for the named workspace as a child
834    /// process, associating the workspace with the stream.
835    ///
836    /// The child process has its standard output and error streams piped;
837    /// use the returned [`Child`] handle to wait for it or interact with it.
838    fn spawn_with(&mut self, (name,): (I,)) -> Result<Self::Output, Self::Error> {
839        self.setup_command(&self.bin)
840            .arg(name)
841            .stdout(Stdio::piped())
842            .stderr(Stdio::piped())
843            .spawn()
844    }
845}
846
847// ---------------------------------------------------------------------------
848// Spawning: SwitchViewMode — `p4 client -s [-f] (-S stream | -t template)
849// [name]`
850// ---------------------------------------------------------------------------
851
852impl<V, F> ParameterizedSpawn<()> for Client<SwitchViewMode<V>, F, Unselected, Unselected>
853where
854    SwitchViewMode<V>: ClientMode<F, Unselected, Unselected>,
855    F: ExclusiveOption,
856{
857    type Output = Child;
858    type Error = std::io::Error;
859
860    /// Spawns `p4 client -s` for the workspace named by `P4CLIENT` as a
861    /// child process, switching its view.
862    ///
863    /// The child process has its standard output and error streams piped;
864    /// use the returned [`Child`] handle to wait for it or interact with it.
865    fn spawn_with(&mut self, (): ()) -> Result<Self::Output, Self::Error> {
866        self.setup_command(&self.bin)
867            .stdout(Stdio::piped())
868            .stderr(Stdio::piped())
869            .spawn()
870    }
871}
872
873impl<V, F, I> ParameterizedSpawn<(I,)> for Client<SwitchViewMode<V>, F, Unselected, Unselected>
874where
875    SwitchViewMode<V>: ClientMode<F, Unselected, Unselected>,
876    F: ExclusiveOption,
877    I: AsRef<OsStr>,
878{
879    type Output = Child;
880    type Error = std::io::Error;
881
882    /// Spawns `p4 client -s` for the named workspace as a child process,
883    /// switching its view.
884    ///
885    /// The child process has its standard output and error streams piped;
886    /// use the returned [`Child`] handle to wait for it or interact with it.
887    fn spawn_with(&mut self, (name,): (I,)) -> Result<Self::Output, Self::Error> {
888        self.setup_command(&self.bin)
889            .arg(name)
890            .stdout(Stdio::piped())
891            .stderr(Stdio::piped())
892            .spawn()
893    }
894}
895
896// ---------------------------------------------------------------------------
897// Spawning: StdinMode — `p4 client -i [-f]`
898// ---------------------------------------------------------------------------
899
900impl<F> ParameterizedSpawn<()> for Client<StdinMode, F, Unselected, Unselected>
901where
902    F: ExclusiveOption,
903{
904    type Output = Child;
905    type Error = std::io::Error;
906
907    /// Spawns `p4 client -i` with a piped standard input stream (and piped
908    /// standard output and error streams); write the client workspace
909    /// specification to the returned [`Child`]'s stdin handle.
910    fn spawn_with(&mut self, (): ()) -> Result<Self::Output, Self::Error> {
911        self.setup_command(&self.bin)
912            .stdin(Stdio::piped())
913            .stdout(Stdio::piped())
914            .stderr(Stdio::piped())
915            .spawn()
916    }
917}
918
919impl<F> ParameterizedSpawn<(Stdio,)> for Client<StdinMode, F, Unselected, Unselected>
920where
921    F: ExclusiveOption,
922{
923    type Output = Child;
924    type Error = std::io::Error;
925
926    /// Spawns `p4 client -i` with the given standard input configuration
927    /// and piped standard output and error streams; use the returned
928    /// [`Child`] handle to wait for it or interact with it.
929    ///
930    /// Pass `Stdio::piped()` to obtain a writable `child.stdin` handle and
931    /// feed the client workspace specification into it; pass
932    /// `Stdio::inherit()` to read the specification from the spawning
933    /// process's own standard input.
934    fn spawn_with(&mut self, (stdio,): (Stdio,)) -> Result<Self::Output, Self::Error> {
935        self.setup_command(&self.bin)
936            .stdin(stdio)
937            .stdout(Stdio::piped())
938            .stderr(Stdio::piped())
939            .spawn()
940    }
941}
942
943// ---------------------------------------------------------------------------
944// General accessors and option builders
945// ---------------------------------------------------------------------------
946
947impl Client<GeneralMode, Unselected, Unselected, Unselected> {
948    /// Creates a new `p4 client` command in the [`GeneralMode`] state.
949    ///
950    /// `bin` is the path to the Perforce command-line executable.
951    pub fn new(bin: impl Into<PathBuf>, global_opts: GlobalOpts) -> Self {
952        Self {
953            bin: bin.into(),
954            global_opts,
955            mode: GeneralMode,
956            force: Unselected,
957            template: Unselected,
958            workspace_type: Unselected,
959        }
960    }
961}
962
963impl<M, F, T, W> Client<M, F, T, W> {
964    /// Get the Perforce command path.
965    pub fn get_bin(&self) -> &Path {
966        &self.bin
967    }
968
969    /// Set the Perforce command path.
970    pub fn set_bin(&mut self, bin: impl Into<PathBuf>) -> &mut Self {
971        self.bin = bin.into();
972        self
973    }
974
975    /// Set the Perforce command path.
976    pub fn bin(mut self, bin: impl Into<PathBuf>) -> Self {
977        self.set_bin(bin);
978        self
979    }
980
981    /// Get the global options.
982    pub fn get_global_opts(&self) -> &GlobalOpts {
983        &self.global_opts
984    }
985
986    /// Set the global options.
987    pub fn set_global_opts(&mut self, v: GlobalOpts) -> &mut Self {
988        self.global_opts = v;
989        self
990    }
991
992    /// Set the global options.
993    pub fn global_opts(mut self, v: GlobalOpts) -> Self {
994        self.set_global_opts(v);
995        self
996    }
997}
998
999// `-f` is valid in the GeneralMode, DeleteMode, SwitchViewMode, and
1000// StdinMode states.
1001
1002impl<T, W> Client<GeneralMode, Unselected, T, W> {
1003    /// # Description
1004    ///
1005    /// -f
1006    ///
1007    /// Allows the last modification date, which is normally read-only, to
1008    /// be set. Administrators can use the `-f` option to delete or modify
1009    /// locked workspaces owned by other users. Use of this option requires
1010    /// `admin` access granted by `p4 protect`.
1011    pub fn force(self) -> Client<GeneralMode, ForceSet, T, W> {
1012        Client {
1013            bin: self.bin,
1014            global_opts: self.global_opts,
1015            mode: self.mode,
1016            force: ForceSet,
1017            template: self.template,
1018            workspace_type: self.workspace_type,
1019        }
1020    }
1021}
1022
1023impl<I, S> Client<DeleteMode<I, S>, Unselected, Unselected, Unselected>
1024where
1025    I: ServerIdState,
1026    S: ShelvedState,
1027{
1028    /// # Description
1029    ///
1030    /// -f
1031    ///
1032    /// The combination of `-d` with `-f` allows the P4 Server
1033    /// administrator to delete a locked workspace that is owned by other
1034    /// user. This also reverts any files opened on the workspace.
1035    ///
1036    #[cfg_attr(
1037        feature = "lt2015_2",
1038        doc = "`-f` is required by the `-Fs` option, but it may be selected before or after it; calling `force` on a state carrying it completes the documented form and makes the command spawnable."
1039    )]
1040    #[cfg_attr(
1041        all(feature = "lt2024_1", not(feature = "lt2015_2")),
1042        doc = "`-f` is required by the `-Fs` and `--serverid=X` options, but it may be selected before or after them; calling `force` on a state carrying those options completes the documented form and makes the command spawnable."
1043    )]
1044    #[cfg_attr(
1045        not(feature = "lt2024_1"),
1046        doc = "`-f` is required by the `-Fs`, `-Fd`, and `--serverid=X` options, but it may be selected before or after them; calling `force` on a state carrying those options completes the documented form and makes the command spawnable."
1047    )]
1048    pub fn force(self) -> Client<DeleteMode<I, S>, ForceSet, Unselected, Unselected> {
1049        Client {
1050            bin: self.bin,
1051            global_opts: self.global_opts,
1052            mode: self.mode,
1053            force: ForceSet,
1054            template: self.template,
1055            workspace_type: self.workspace_type,
1056        }
1057    }
1058}
1059
1060#[cfg(not(feature = "lt2022_1"))]
1061impl<V> Client<SwitchViewMode<V>, Unselected, Unselected, Unselected> {
1062    /// # Description
1063    ///
1064    /// -f
1065    ///
1066    /// The `-f` option can be used with `-s` to force switching with
1067    /// opened files.
1068    pub fn force(self) -> Client<SwitchViewMode<V>, ForceSet, Unselected, Unselected> {
1069        Client {
1070            bin: self.bin,
1071            global_opts: self.global_opts,
1072            mode: self.mode,
1073            force: ForceSet,
1074            template: self.template,
1075            workspace_type: self.workspace_type,
1076        }
1077    }
1078}
1079
1080impl Client<StdinMode, Unselected, Unselected, Unselected> {
1081    /// # Description
1082    ///
1083    /// -f
1084    ///
1085    /// Allows the last modification date, which is normally read-only, to
1086    /// be set. Use of this option requires `admin` access granted by
1087    /// `p4 protect`.
1088    pub fn force(self) -> Client<StdinMode, ForceSet, Unselected, Unselected> {
1089        Client {
1090            bin: self.bin,
1091            global_opts: self.global_opts,
1092            mode: self.mode,
1093            force: ForceSet,
1094            template: self.template,
1095            workspace_type: self.workspace_type,
1096        }
1097    }
1098}
1099
1100// `-t` is valid in the GeneralMode and StdoutMode states.
1101
1102impl<F, W> Client<GeneralMode, F, Unselected, W> {
1103    /// # Description
1104    ///
1105    /// -t name
1106    ///
1107    /// Copy client workspace `template`'s view and options into the
1108    /// `View:` and `Options:` field of this workspace. If you specify a
1109    /// default client template using the `template.client` configurable,
1110    /// you do not have to specify this option.
1111    pub fn template(self, template: impl Into<String>) -> Client<GeneralMode, F, TemplateSet, W> {
1112        Client {
1113            bin: self.bin,
1114            global_opts: self.global_opts,
1115            mode: self.mode,
1116            force: self.force,
1117            template: TemplateSet {
1118                template: template.into(),
1119            },
1120            workspace_type: self.workspace_type,
1121        }
1122    }
1123}
1124
1125impl<W> Client<StdoutMode, Unselected, Unselected, W> {
1126    /// # Description
1127    ///
1128    /// -t name
1129    ///
1130    /// Copy client workspace `template`'s view and options into the
1131    /// `View:` and `Options:` field of this workspace.
1132    pub fn template(
1133        self,
1134        template: impl Into<String>,
1135    ) -> Client<StdoutMode, Unselected, TemplateSet, W> {
1136        Client {
1137            bin: self.bin,
1138            global_opts: self.global_opts,
1139            mode: self.mode,
1140            force: self.force,
1141            template: TemplateSet {
1142                template: template.into(),
1143            },
1144            workspace_type: self.workspace_type,
1145        }
1146    }
1147}
1148
1149// `-T` is valid in the GeneralMode and StdoutMode states.
1150
1151#[cfg(not(feature = "lt2016_2"))]
1152impl<F, T> Client<GeneralMode, F, T, Unselected> {
1153    /// # Description
1154    ///
1155    /// -T type
1156    ///
1157    /// By default, clients are `writeable`. When you create a client
1158    /// workspace, use `-T type` to set a specific type.
1159    pub fn workspace_type(
1160        self,
1161        workspace_type: WorkSpaceType,
1162    ) -> Client<GeneralMode, F, T, TypeSet> {
1163        Client {
1164            bin: self.bin,
1165            global_opts: self.global_opts,
1166            mode: self.mode,
1167            force: self.force,
1168            template: self.template,
1169            workspace_type: TypeSet { workspace_type },
1170        }
1171    }
1172}
1173
1174#[cfg(not(feature = "lt2016_2"))]
1175impl<T> Client<StdoutMode, Unselected, T, Unselected> {
1176    /// # Description
1177    ///
1178    /// -T type
1179    ///
1180    /// By default, clients are `writeable`. When you create a client
1181    /// workspace, use `-T type` to set a specific type.
1182    pub fn workspace_type(
1183        self,
1184        workspace_type: WorkSpaceType,
1185    ) -> Client<StdoutMode, Unselected, T, TypeSet> {
1186        Client {
1187            bin: self.bin,
1188            global_opts: self.global_opts,
1189            mode: self.mode,
1190            force: self.force,
1191            template: self.template,
1192            workspace_type: TypeSet { workspace_type },
1193        }
1194    }
1195}
1196
1197// Option accessors for the set states.
1198
1199impl<M, F, W> Client<M, F, TemplateSet, W> {
1200    /// Get the template client workspace name set with `-t`.
1201    pub fn get_template(&self) -> &str {
1202        &self.template.template
1203    }
1204
1205    /// # Description
1206    ///
1207    /// -t name
1208    ///
1209    /// Copy client workspace `template`'s view and options into the
1210    /// `View:` and `Options:` field of this workspace.
1211    pub fn set_template(&mut self, template: impl Into<String>) -> &mut Self {
1212        self.template.template = template.into();
1213        self
1214    }
1215}
1216
1217#[cfg(not(feature = "lt2016_2"))]
1218impl<M, F, T> Client<M, F, T, TypeSet> {
1219    /// Get the client workspace type set with `-T`.
1220    pub fn get_workspace_type(&self) -> WorkSpaceType {
1221        self.workspace_type.workspace_type
1222    }
1223
1224    /// # Description
1225    ///
1226    /// -T type
1227    ///
1228    /// By default, clients are `writeable`. When you create a client
1229    /// workspace, use `-T type` to set a specific type.
1230    pub fn set_workspace_type(&mut self, workspace_type: WorkSpaceType) -> &mut Self {
1231        self.workspace_type.workspace_type = workspace_type;
1232        self
1233    }
1234}
1235
1236// ---------------------------------------------------------------------------
1237// Mode transitions out of GeneralMode
1238// ---------------------------------------------------------------------------
1239
1240impl<F> Client<GeneralMode, F, Unselected, Unselected> {
1241    /// # Description
1242    ///
1243    /// -d name
1244    ///
1245    /// Delete the specified client workspace whether or not the workspace
1246    /// is owned by the user. The workspace must be unlocked and must have
1247    /// no opened files or pending changes.
1248    ///
1249    /// This transitions into the terminal [`DeleteMode`] state, from which
1250    /// no further mode transitions are possible.
1251    pub fn delete(self) -> Client<DeleteMode<Unselected, Unselected>, F, Unselected, Unselected> {
1252        Client {
1253            bin: self.bin,
1254            global_opts: self.global_opts,
1255            mode: DeleteMode {
1256                server_id: None,
1257                serverid_state: PhantomData,
1258                shelved: Unselected,
1259            },
1260            force: self.force,
1261            template: self.template,
1262            workspace_type: self.workspace_type,
1263        }
1264    }
1265
1266    /// # Description
1267    ///
1268    /// -i
1269    ///
1270    /// Read the client workspace specification from standard input.
1271    ///
1272    /// This transitions into the [`StdinMode`] state, carrying the `-f`
1273    /// option.
1274    pub fn stdin(self) -> Client<StdinMode, F, Unselected, Unselected> {
1275        Client {
1276            bin: self.bin,
1277            global_opts: self.global_opts,
1278            mode: StdinMode,
1279            force: self.force,
1280            template: self.template,
1281            workspace_type: self.workspace_type,
1282        }
1283    }
1284}
1285
1286// The `-f` states accepted on the switch-view form are tracked by
1287// [`SwitchViewForceCompatible`].
1288
1289impl<F> Client<GeneralMode, F, Unselected, Unselected>
1290where
1291    F: SwitchViewForceCompatible,
1292{
1293    /// # Description
1294    ///
1295    /// -s -S stream
1296    ///
1297    /// Switch the workspace view to the specified stream.
1298    ///
1299    /// This transitions into the [`SwitchViewMode`] state.
1300    #[cfg_attr(
1301        not(feature = "lt2022_1"),
1302        doc = "",
1303        doc = "The `-f` option may be selected before this transition, or added afterwards with [`Client::force`]."
1304    )]
1305    pub fn switch_view_stream(
1306        self,
1307        stream: impl Into<String>,
1308    ) -> Client<SwitchViewMode<StreamSwitch>, F, Unselected, Unselected> {
1309        Client {
1310            bin: self.bin,
1311            global_opts: self.global_opts,
1312            mode: SwitchViewMode {
1313                view: StreamSwitch {
1314                    stream: stream.into(),
1315                },
1316            },
1317            force: self.force,
1318            template: self.template,
1319            workspace_type: self.workspace_type,
1320        }
1321    }
1322
1323    /// # Description
1324    ///
1325    /// -s -t template
1326    ///
1327    /// Switch the workspace view to the view defined for another
1328    /// workspace.
1329    ///
1330    /// This transitions into the [`SwitchViewMode`] state.
1331    #[cfg_attr(
1332        not(feature = "lt2022_1"),
1333        doc = "",
1334        doc = "The `-f` option may be selected before this transition, or added afterwards with [`Client::force`]."
1335    )]
1336    pub fn switch_view_template(
1337        self,
1338        template: impl Into<String>,
1339    ) -> Client<SwitchViewMode<TemplateSwitch>, F, Unselected, Unselected> {
1340        Client {
1341            bin: self.bin,
1342            global_opts: self.global_opts,
1343            mode: SwitchViewMode {
1344                view: TemplateSwitch {
1345                    template: template.into(),
1346                },
1347            },
1348            force: self.force,
1349            template: self.template,
1350            workspace_type: self.workspace_type,
1351        }
1352    }
1353}
1354
1355impl<F> Client<GeneralMode, F, TemplateSet, Unselected>
1356where
1357    F: SwitchViewForceCompatible,
1358{
1359    /// # Description
1360    ///
1361    /// -s -t template
1362    ///
1363    /// Switch the workspace view to the view defined for the template
1364    /// workspace already selected with [`Client::template`].
1365    ///
1366    /// This transitions into the [`SwitchViewMode`] state.
1367    #[cfg_attr(
1368        not(feature = "lt2022_1"),
1369        doc = "",
1370        doc = "The `-f` option may be selected before this transition, or added afterwards with [`Client::force`]."
1371    )]
1372    pub fn switch_view(self) -> Client<SwitchViewMode<TemplateSwitch>, F, Unselected, Unselected> {
1373        Client {
1374            bin: self.bin,
1375            global_opts: self.global_opts,
1376            mode: SwitchViewMode {
1377                view: TemplateSwitch {
1378                    template: self.template.template,
1379                },
1380            },
1381            force: self.force,
1382            template: Unselected,
1383            workspace_type: self.workspace_type,
1384        }
1385    }
1386}
1387
1388impl<T, W> Client<GeneralMode, Unselected, T, W> {
1389    /// # Description
1390    ///
1391    /// -o
1392    ///
1393    /// Write the client workspace specification to standard output.
1394    ///
1395    /// This transitions into the [`StdoutMode`] state, carrying the `-t`
1396    /// and `-T` options.
1397    pub fn stdout(self) -> Client<StdoutMode, Unselected, T, W> {
1398        Client {
1399            bin: self.bin,
1400            global_opts: self.global_opts,
1401            mode: StdoutMode,
1402            force: self.force,
1403            template: self.template,
1404            workspace_type: self.workspace_type,
1405        }
1406    }
1407}
1408
1409impl Client<GeneralMode, Unselected, Unselected, Unselected> {
1410    /// # Description
1411    ///
1412    /// -S stream
1413    ///
1414    /// Associates the workspace with the specified stream, which is used
1415    /// to generate its workspace view.
1416    ///
1417    /// This transitions into the [`StreamMode`] state.
1418    pub fn stream(
1419        self,
1420        stream: impl Into<String>,
1421    ) -> Client<StreamMode<Unselected>, Unselected, Unselected, Unselected> {
1422        Client {
1423            bin: self.bin,
1424            global_opts: self.global_opts,
1425            mode: StreamMode {
1426                stream: stream.into(),
1427                output: Unselected,
1428            },
1429            force: self.force,
1430            template: self.template,
1431            workspace_type: self.workspace_type,
1432        }
1433    }
1434}
1435
1436// One-shot transitions from `GeneralMode` into a delete form. Since
1437// `--serverid`, `-Fs`, and `-Fd` only occur on a `-d -f` form, selecting
1438// any of them implies `-d` and transitions directly into the
1439// [`DeleteMode`] state, carrying along `-f` if it was already selected.
1440// `-f` may also be selected afterwards via [`Client::force`]; until then
1441// the state is an intermediate, non-spawnable state.
1442
1443impl<F> Client<GeneralMode, F, Unselected, Unselected>
1444where
1445    F: ExclusiveOption,
1446{
1447    /// # Description
1448    ///
1449    /// -d [-f] --serverid=X
1450    ///
1451    /// If you try to forcibly delete a client bound to another server, you
1452    /// need to specify the `--serverid` option and specify the server id
1453    /// of the other server. This ensures that you do not accidentally
1454    /// delete the client believing it to be connected to your own server.
1455    /// This variant of the `p4 client` command must be issued directly to
1456    /// the commit server.
1457    ///
1458    /// Selecting this option implies `-d`; `-f` may be selected before or
1459    /// after it. The resulting command is spawnable only once `-f` is set.
1460    #[cfg(not(feature = "lt2015_2"))]
1461    pub fn server_id(
1462        self,
1463        server_id: impl Into<String>,
1464    ) -> Client<DeleteMode<ServerIdSet, Unselected>, F, Unselected, Unselected> {
1465        Client {
1466            bin: self.bin,
1467            global_opts: self.global_opts,
1468            mode: DeleteMode {
1469                server_id: Some(server_id.into()),
1470                serverid_state: PhantomData,
1471                shelved: Unselected,
1472            },
1473            force: self.force,
1474            template: self.template,
1475            workspace_type: self.workspace_type,
1476        }
1477    }
1478
1479    /// # Description
1480    ///
1481    /// -d [-f] -Fs
1482    ///
1483    /// Allows the deletion with `-d` of a client even when that client
1484    /// contains shelved changes. The client is deleted and the shelved
1485    /// changes are left intact. (You must use the `-f` option with the
1486    /// `-Fs` option.)
1487    ///
1488    /// Selecting this option implies `-d`; `-f` may be selected before or
1489    /// after it. The resulting command is spawnable only once `-f` is set.
1490    pub fn keep_shelved(
1491        self,
1492    ) -> Client<DeleteMode<Unselected, ShelvedKept>, F, Unselected, Unselected> {
1493        Client {
1494            bin: self.bin,
1495            global_opts: self.global_opts,
1496            mode: DeleteMode {
1497                server_id: None,
1498                serverid_state: PhantomData,
1499                shelved: ShelvedKept,
1500            },
1501            force: self.force,
1502            template: self.template,
1503            workspace_type: self.workspace_type,
1504        }
1505    }
1506
1507    /// # Description
1508    ///
1509    /// -d [-f] -Fd
1510    ///
1511    /// Allows the deletion with `-d` of a client even when that client
1512    /// contains shelved changes. The client and the shelved changes are
1513    /// both deleted. (You must use the `-f` option with the `-Fd`
1514    /// option.)
1515    ///
1516    /// Selecting this option implies `-d`; `-f` may be selected before or
1517    /// after it. The resulting command is spawnable only once `-f` is set.
1518    #[cfg(not(feature = "lt2024_1"))]
1519    pub fn delete_shelved(
1520        self,
1521    ) -> Client<DeleteMode<Unselected, ShelvedDeleted>, F, Unselected, Unselected> {
1522        Client {
1523            bin: self.bin,
1524            global_opts: self.global_opts,
1525            mode: DeleteMode {
1526                server_id: None,
1527                serverid_state: PhantomData,
1528                shelved: ShelvedDeleted,
1529            },
1530            force: self.force,
1531            template: self.template,
1532            workspace_type: self.workspace_type,
1533        }
1534    }
1535}
1536
1537// ---------------------------------------------------------------------------
1538// DeleteMode options
1539// ---------------------------------------------------------------------------
1540
1541// The `--serverid=X` transition: available when it has not been selected
1542// yet, and only in shelved states compatible with the documented form
1543// `p4 client -d -f --serverid=X [-Fs] name` (never after `-Fd`).
1544
1545#[cfg(not(feature = "lt2015_2"))]
1546impl<S, F> Client<DeleteMode<Unselected, S>, F, Unselected, Unselected>
1547where
1548    S: ServerIdCompatible,
1549    F: ExclusiveOption,
1550{
1551    /// # Description
1552    ///
1553    /// --serverid=X
1554    ///
1555    /// If you try to forcibly delete a client bound to another server, you
1556    /// need to specify the `--serverid` option and specify the server id
1557    /// of the other server. This ensures that you do not accidentally
1558    /// delete the client believing it to be connected to your own server.
1559    /// This variant of the `p4 client` command must be issued directly to
1560    /// the commit server.
1561    ///
1562    /// Only the `-Fs` shelved handling can be combined with `--serverid`;
1563    /// this transition is not available once `-Fd` has been selected. The
1564    /// required `-f` may be selected before or afterwards.
1565    pub fn server_id(
1566        self,
1567        server_id: impl Into<String>,
1568    ) -> Client<DeleteMode<ServerIdSet, S>, F, Unselected, Unselected> {
1569        Client {
1570            bin: self.bin,
1571            global_opts: self.global_opts,
1572            mode: DeleteMode {
1573                server_id: Some(server_id.into()),
1574                serverid_state: PhantomData,
1575                shelved: self.mode.shelved,
1576            },
1577            force: self.force,
1578            template: self.template,
1579            workspace_type: self.workspace_type,
1580        }
1581    }
1582}
1583
1584// The `--serverid=X` value is always present in this state.
1585
1586#[cfg(not(feature = "lt2015_2"))]
1587impl<S, F> Client<DeleteMode<ServerIdSet, S>, F, Unselected, Unselected>
1588where
1589    S: ServerIdCompatible,
1590    F: ExclusiveOption,
1591{
1592    /// Get the server ID set with `--serverid=X`.
1593    pub fn get_server_id(&self) -> &str {
1594        self.mode.server_id.as_deref().expect("server ID is set")
1595    }
1596
1597    /// # Description
1598    ///
1599    /// --serverid=X
1600    ///
1601    /// Update the server ID of the other server to which the deleted
1602    /// client workspace is bound.
1603    pub fn set_server_id(&mut self, server_id: impl Into<String>) -> &mut Self {
1604        self.mode.server_id = Some(server_id.into());
1605        self
1606    }
1607}
1608
1609// Shelved handling getters are available in every delete state.
1610
1611impl<I, S, F> Client<DeleteMode<I, S>, F, Unselected, Unselected>
1612where
1613    I: ServerIdState,
1614    S: ShelvedState,
1615    F: ExclusiveOption,
1616{
1617    /// Get how shelved changes of the deleted client workspace are
1618    /// handled.
1619    pub fn get_shelved(&self) -> Option<ShelvedHandling> {
1620        S::handling()
1621    }
1622}
1623
1624// The `-Fs` transition: available when neither `-Fs` nor `-Fd` is set,
1625// regardless of whether `--serverid=X` is set (the documented
1626// `--serverid=X [-Fs]` form accepts it).
1627
1628impl<I, F> Client<DeleteMode<I, Unselected>, F, Unselected, Unselected>
1629where
1630    I: ServerIdState,
1631    F: ExclusiveOption,
1632{
1633    /// # Description
1634    ///
1635    /// -Fs
1636    ///
1637    /// Allows the deletion with `-d` of a client even when that client
1638    /// contains shelved changes. The client is deleted and the shelved
1639    /// changes are left intact. (You must use the `-f` option with the
1640    /// `-Fs` option.)
1641    ///
1642    /// After this transition neither `-Fs` nor `-Fd` can be selected
1643    /// again; the required `-f` may be selected before or afterwards.
1644    pub fn keep_shelved(self) -> Client<DeleteMode<I, ShelvedKept>, F, Unselected, Unselected> {
1645        Client {
1646            bin: self.bin,
1647            global_opts: self.global_opts,
1648            mode: DeleteMode {
1649                server_id: self.mode.server_id,
1650                serverid_state: PhantomData,
1651                shelved: ShelvedKept,
1652            },
1653            force: self.force,
1654            template: self.template,
1655            workspace_type: self.workspace_type,
1656        }
1657    }
1658}
1659
1660// The `-Fd` transition: only available without `--serverid=X` and without
1661// a previously selected shelved handling.
1662
1663#[cfg(not(feature = "lt2024_1"))]
1664impl<F> Client<DeleteMode<Unselected, Unselected>, F, Unselected, Unselected>
1665where
1666    F: ExclusiveOption,
1667{
1668    /// # Description
1669    ///
1670    /// -Fd
1671    ///
1672    /// Allows the deletion with `-d` of a client even when that client
1673    /// contains shelved changes. The client and the shelved changes are
1674    /// both deleted. (You must use the `-f` option with the `-Fd`
1675    /// option.)
1676    ///
1677    /// This is the terminal shelved state: it cannot be combined with
1678    /// `--serverid=X`, and neither `-Fs` nor `-Fd` can be selected
1679    /// again; the required `-f` may be selected before or afterwards.
1680    pub fn delete_shelved(
1681        self,
1682    ) -> Client<DeleteMode<Unselected, ShelvedDeleted>, F, Unselected, Unselected> {
1683        Client {
1684            bin: self.bin,
1685            global_opts: self.global_opts,
1686            mode: DeleteMode {
1687                server_id: None,
1688                serverid_state: PhantomData,
1689                shelved: ShelvedDeleted,
1690            },
1691            force: self.force,
1692            template: self.template,
1693            workspace_type: self.workspace_type,
1694        }
1695    }
1696}
1697
1698// ---------------------------------------------------------------------------
1699// StreamMode options
1700// ---------------------------------------------------------------------------
1701
1702impl<O> Client<StreamMode<O>, Unselected, Unselected, Unselected> {
1703    /// Get the stream set with `-S`.
1704    pub fn get_stream(&self) -> &str {
1705        &self.mode.stream
1706    }
1707
1708    /// # Description
1709    ///
1710    /// -S stream
1711    ///
1712    /// Associates the workspace with the specified stream, which is used
1713    /// to generate its workspace view.
1714    pub fn set_stream(&mut self, stream: impl Into<String>) -> &mut Self {
1715        self.mode.stream = stream.into();
1716        self
1717    }
1718
1719    /// # Description
1720    ///
1721    /// -S stream
1722    ///
1723    /// Associates the workspace with the specified stream, which is used
1724    /// to generate its workspace view.
1725    pub fn stream(mut self, stream: impl Into<String>) -> Self {
1726        self.set_stream(stream);
1727        self
1728    }
1729}
1730
1731// Adding `-s` to a selected `-S stream` reaches the switch form
1732// `p4 client -s [-f] -S stream [name]`; this is only possible before the
1733// `-o` option is selected, since the `-S stream -o` form carries no `-s`.
1734
1735impl Client<StreamMode<Unselected>, Unselected, Unselected, Unselected> {
1736    /// # Description
1737    ///
1738    /// -s -S stream
1739    ///
1740    /// Switch the workspace view to the stream already selected with
1741    /// [`Client::stream`], changing the stream associated with the
1742    /// workspace.
1743    ///
1744    /// This transitions into the [`SwitchViewMode`] state; the
1745    /// intermediate `-S stream` form does not accept `-f`.
1746    #[cfg_attr(
1747        not(feature = "lt2022_1"),
1748        doc = "The optional `-f` can be added after this transition with [`Client::force`]."
1749    )]
1750    pub fn switch_view(
1751        self,
1752    ) -> Client<SwitchViewMode<StreamSwitch>, Unselected, Unselected, Unselected> {
1753        Client {
1754            bin: self.bin,
1755            global_opts: self.global_opts,
1756            mode: SwitchViewMode {
1757                view: StreamSwitch {
1758                    stream: self.mode.stream,
1759                },
1760            },
1761            force: self.force,
1762            template: self.template,
1763            workspace_type: self.workspace_type,
1764        }
1765    }
1766
1767    /// # Description
1768    ///
1769    /// -o
1770    ///
1771    /// When used with `-S stream`, displays the workspace specification
1772    /// that would have been created for the stream.
1773    ///
1774    /// Unlocks the [`Client::change`] option.
1775    pub fn stdout(self) -> Client<StreamMode<StdoutOutput>, Unselected, Unselected, Unselected> {
1776        Client {
1777            bin: self.bin,
1778            global_opts: self.global_opts,
1779            mode: StreamMode {
1780                stream: self.mode.stream,
1781                output: StdoutOutput { change: None },
1782            },
1783            force: self.force,
1784            template: self.template,
1785            workspace_type: self.workspace_type,
1786        }
1787    }
1788}
1789
1790impl Client<StreamMode<StdoutOutput>, Unselected, Unselected, Unselected> {
1791    /// Get the changelist number set with `-c`.
1792    pub fn get_change(&self) -> Option<&str> {
1793        self.mode.output.change.as_deref()
1794    }
1795
1796    /// # Description
1797    ///
1798    /// -c change
1799    ///
1800    /// When used with `-S stream` and `-o`, displays the workspace
1801    /// specification that would have been created for a stream at the
1802    /// moment the change was submitted.
1803    pub fn set_change(&mut self, change: impl Into<String>) -> &mut Self {
1804        self.mode.output.change = Some(change.into());
1805        self
1806    }
1807
1808    /// # Description
1809    ///
1810    /// -c change
1811    ///
1812    /// When used with `-S stream` and `-o`, displays the workspace
1813    /// specification that would have been created for a stream at the
1814    /// moment the change was submitted.
1815    pub fn change(mut self, change: impl Into<String>) -> Self {
1816        self.set_change(change);
1817        self
1818    }
1819}
1820
1821// ---------------------------------------------------------------------------
1822// SwitchViewMode accessors
1823// ---------------------------------------------------------------------------
1824
1825impl<F> Client<SwitchViewMode<StreamSwitch>, F, Unselected, Unselected> {
1826    /// Get the stream the workspace view is switched to.
1827    pub fn get_stream(&self) -> &str {
1828        &self.mode.view.stream
1829    }
1830
1831    /// # Description
1832    ///
1833    /// -S stream
1834    ///
1835    /// Switch the workspace view to the specified stream.
1836    pub fn set_stream(&mut self, stream: impl Into<String>) -> &mut Self {
1837        self.mode.view.stream = stream.into();
1838        self
1839    }
1840
1841    /// # Description
1842    ///
1843    /// -S stream
1844    ///
1845    /// Switch the workspace view to the specified stream.
1846    pub fn stream(mut self, stream: impl Into<String>) -> Self {
1847        self.set_stream(stream);
1848        self
1849    }
1850}
1851
1852impl<F> Client<SwitchViewMode<TemplateSwitch>, F, Unselected, Unselected> {
1853    /// Get the template workspace whose view is switched to.
1854    pub fn get_template(&self) -> &str {
1855        &self.mode.view.template
1856    }
1857
1858    /// # Description
1859    ///
1860    /// -t template
1861    ///
1862    /// Switch the workspace view to the view defined for another
1863    /// workspace.
1864    pub fn set_template(&mut self, template: impl Into<String>) -> &mut Self {
1865        self.mode.view.template = template.into();
1866        self
1867    }
1868
1869    /// # Description
1870    ///
1871    /// -t template
1872    ///
1873    /// Switch the workspace view to the view defined for another
1874    /// workspace.
1875    pub fn template(mut self, template: impl Into<String>) -> Self {
1876        self.set_template(template);
1877        self
1878    }
1879}
1880
1881#[cfg(test)]
1882mod tests {
1883    use super::*;
1884    use crate::cmd::args_of;
1885
1886    /// Dry-run checks of the assembled `p4 client` command line; no process
1887    /// is spawned.
1888    #[test]
1889    fn general_mode_without_options() {
1890        let client = Client::new("p4", GlobalOpts::new());
1891
1892        assert_eq!(args_of(&client.setup_command("p4")), ["client"]);
1893    }
1894
1895    #[test]
1896    fn general_mode_with_force_and_template() {
1897        let client = Client::new("p4", GlobalOpts::new()).force().template("tpl");
1898
1899        assert_eq!(
1900            args_of(&client.setup_command("p4")),
1901            ["client", "-f", "-t", "tpl"]
1902        );
1903        assert_eq!(client.get_template(), "tpl");
1904
1905        // The `set_*` variants mutate an already-selected option.
1906        let mut client = Client::new("p4", GlobalOpts::new()).template("tmp");
1907        client.set_template("tpl");
1908        assert_eq!(client.get_template(), "tpl");
1909        assert_eq!(
1910            args_of(&client.setup_command("p4")),
1911            ["client", "-t", "tpl"]
1912        );
1913    }
1914
1915    #[cfg(not(feature = "lt2016_2"))]
1916    #[test]
1917    fn general_mode_with_all_options() {
1918        let client = Client::new("p4", GlobalOpts::new())
1919            .force()
1920            .template("tpl")
1921            .workspace_type(WorkSpaceType::Readonly);
1922
1923        assert_eq!(
1924            args_of(&client.setup_command("p4")),
1925            ["client", "-f", "-t", "tpl", "-T", "readonly"]
1926        );
1927        assert_eq!(client.get_template(), "tpl");
1928        assert_eq!(client.get_workspace_type(), WorkSpaceType::Readonly);
1929    }
1930
1931    #[cfg(not(feature = "lt2016_2"))]
1932    #[test]
1933    fn workspace_type_values() {
1934        for (ty, s) in [
1935            (WorkSpaceType::Writable, "writeable"),
1936            (WorkSpaceType::Readonly, "readonly"),
1937            (WorkSpaceType::Partitioned, "partitioned"),
1938            #[cfg(not(feature = "lt2024_1"))]
1939            (WorkSpaceType::PartitionedJnl, "partitioned-jnl"),
1940            #[cfg(not(feature = "lt2024_1"))]
1941            (WorkSpaceType::Graph, "graph"),
1942        ] {
1943            let client = Client::new("p4", GlobalOpts::new()).workspace_type(ty);
1944            assert_eq!(args_of(&client.setup_command("p4")), ["client", "-T", s]);
1945        }
1946    }
1947
1948    #[test]
1949    fn stdout_mode_carries_template() {
1950        let client = Client::new("p4", GlobalOpts::new())
1951            .template("tpl")
1952            .stdout();
1953
1954        assert_eq!(
1955            args_of(&client.setup_command("p4")),
1956            ["client", "-o", "-t", "tpl"]
1957        );
1958    }
1959
1960    #[cfg(not(feature = "lt2016_2"))]
1961    #[test]
1962    fn stdout_mode_carries_template_and_type() {
1963        let client = Client::new("p4", GlobalOpts::new())
1964            .template("tpl")
1965            .workspace_type(WorkSpaceType::Partitioned)
1966            .stdout();
1967
1968        assert_eq!(
1969            args_of(&client.setup_command("p4")),
1970            ["client", "-o", "-t", "tpl", "-T", "partitioned"]
1971        );
1972    }
1973
1974    #[test]
1975    fn delete_mode_is_terminal() {
1976        let client = Client::new("p4", GlobalOpts::new()).delete();
1977
1978        assert_eq!(args_of(&client.setup_command("p4")), ["client", "-d"]);
1979    }
1980
1981    #[test]
1982    fn delete_mode_with_force_and_keep_shelved() {
1983        let client = Client::new("p4", GlobalOpts::new())
1984            .delete()
1985            .force()
1986            .keep_shelved();
1987
1988        assert_eq!(client.get_shelved(), Some(ShelvedHandling::Keep));
1989        assert_eq!(
1990            args_of(&client.setup_command("p4")),
1991            ["client", "-d", "-f", "-Fs"]
1992        );
1993    }
1994
1995    #[cfg(not(feature = "lt2015_2"))]
1996    #[test]
1997    fn delete_mode_with_force_and_server_id() {
1998        // `--serverid=X` followed by `-Fs`: the documented
1999        // `-d -f --serverid=X [-Fs]` form.
2000        let mut client = Client::new("p4", GlobalOpts::new())
2001            .delete()
2002            .force()
2003            .server_id("edge.1667");
2004
2005        assert_eq!(client.get_server_id(), "edge.1667");
2006        assert_eq!(client.get_shelved(), None);
2007        client.set_server_id("edge.42");
2008
2009        let client = client.keep_shelved();
2010
2011        assert_eq!(client.get_server_id(), "edge.42");
2012        assert_eq!(client.get_shelved(), Some(ShelvedHandling::Keep));
2013        assert_eq!(
2014            args_of(&client.setup_command("p4")),
2015            ["client", "-d", "-f", "--serverid=edge.42", "-Fs"]
2016        );
2017
2018        // `-Fs` selected first, then `--serverid=X`: the same documented
2019        // form reached in the opposite order.
2020        let client = Client::new("p4", GlobalOpts::new())
2021            .delete()
2022            .force()
2023            .keep_shelved()
2024            .server_id("edge.1667");
2025
2026        assert_eq!(client.get_server_id(), "edge.1667");
2027        assert_eq!(
2028            args_of(&client.setup_command("p4")),
2029            ["client", "-d", "-f", "--serverid=edge.1667", "-Fs"]
2030        );
2031    }
2032
2033    #[cfg(not(feature = "lt2024_1"))]
2034    #[test]
2035    fn delete_mode_with_force_and_delete_shelved() {
2036        // `-Fd` without `--serverid=X`: the `-d -f -Fd` form.
2037        let client = Client::new("p4", GlobalOpts::new())
2038            .delete()
2039            .force()
2040            .delete_shelved();
2041
2042        assert_eq!(client.get_shelved(), Some(ShelvedHandling::Delete));
2043        assert_eq!(
2044            args_of(&client.setup_command("p4")),
2045            ["client", "-d", "-f", "-Fd"]
2046        );
2047    }
2048
2049    #[test]
2050    fn force_set_in_general_mode_can_delete() {
2051        let client = Client::new("p4", GlobalOpts::new()).force().delete();
2052
2053        assert_eq!(args_of(&client.setup_command("p4")), ["client", "-d", "-f"]);
2054    }
2055
2056    #[test]
2057    fn force_unlocks_keep_shelved_one_shot() {
2058        let client = Client::new("p4", GlobalOpts::new()).force().keep_shelved();
2059
2060        assert_eq!(client.get_shelved(), Some(ShelvedHandling::Keep));
2061        assert_eq!(
2062            args_of(&client.setup_command("p4")),
2063            ["client", "-d", "-f", "-Fs"]
2064        );
2065    }
2066
2067    #[cfg(not(feature = "lt2015_2"))]
2068    #[test]
2069    fn force_unlocks_server_id_one_shot() {
2070        let client = Client::new("p4", GlobalOpts::new())
2071            .force()
2072            .server_id("edge.1667")
2073            .keep_shelved();
2074
2075        assert_eq!(client.get_server_id(), "edge.1667");
2076        assert_eq!(client.get_shelved(), Some(ShelvedHandling::Keep));
2077        assert_eq!(
2078            args_of(&client.setup_command("p4")),
2079            ["client", "-d", "-f", "--serverid=edge.1667", "-Fs"]
2080        );
2081    }
2082
2083    #[cfg(not(feature = "lt2024_1"))]
2084    #[test]
2085    fn force_unlocks_delete_shelved_one_shot() {
2086        let client = Client::new("p4", GlobalOpts::new())
2087            .force()
2088            .delete_shelved();
2089
2090        assert_eq!(client.get_shelved(), Some(ShelvedHandling::Delete));
2091        assert_eq!(
2092            args_of(&client.setup_command("p4")),
2093            ["client", "-d", "-f", "-Fd"]
2094        );
2095    }
2096
2097    #[test]
2098    fn stream_mode() {
2099        let client = Client::new("p4", GlobalOpts::new()).stream("//stream/main");
2100
2101        assert_eq!(
2102            args_of(&client.setup_command("p4")),
2103            ["client", "-S", "//stream/main"]
2104        );
2105        assert_eq!(client.get_stream(), "//stream/main");
2106
2107        let client = client.stdout();
2108
2109        assert_eq!(
2110            args_of(&client.setup_command("p4")),
2111            ["client", "-S", "//stream/main", "-o"]
2112        );
2113
2114        let client = client.change("4321");
2115
2116        assert_eq!(client.get_change(), Some("4321"));
2117        assert_eq!(
2118            args_of(&client.setup_command("p4")),
2119            ["client", "-S", "//stream/main", "-c", "4321", "-o"]
2120        );
2121    }
2122
2123    #[cfg(not(feature = "lt2022_1"))]
2124    #[test]
2125    fn switch_view_stream_carries_force() {
2126        let client = Client::new("p4", GlobalOpts::new())
2127            .force()
2128            .switch_view_stream("//stream/dev");
2129
2130        assert_eq!(
2131            args_of(&client.setup_command("p4")),
2132            ["client", "-s", "-f", "-S", "//stream/dev"]
2133        );
2134        assert_eq!(client.get_stream(), "//stream/dev");
2135    }
2136
2137    #[test]
2138    fn switch_view_reuses_stream() {
2139        // `-S stream` followed by `switch_view`: the documented
2140        // `-s -S stream` form reached on the two-step path.
2141        let client = Client::new("p4", GlobalOpts::new())
2142            .stream("//stream/dev")
2143            .switch_view();
2144
2145        assert_eq!(
2146            args_of(&client.setup_command("p4")),
2147            ["client", "-s", "-S", "//stream/dev"]
2148        );
2149        assert_eq!(client.get_stream(), "//stream/dev");
2150
2151        // The stream value remains editable on the intermediate form.
2152        let client = Client::new("p4", GlobalOpts::new())
2153            .stream("old")
2154            .stream("//stream/dev")
2155            .switch_view();
2156
2157        assert_eq!(
2158            args_of(&client.setup_command("p4")),
2159            ["client", "-s", "-S", "//stream/dev"]
2160        );
2161    }
2162
2163    #[cfg(not(feature = "lt2022_1"))]
2164    #[test]
2165    fn switch_view_reuses_stream_then_force() {
2166        // The intermediate `-S` form rejects `-f`; it is added after the
2167        // switch.
2168        let client = Client::new("p4", GlobalOpts::new())
2169            .stream("//stream/dev")
2170            .switch_view()
2171            .force();
2172
2173        assert_eq!(
2174            args_of(&client.setup_command("p4")),
2175            ["client", "-s", "-f", "-S", "//stream/dev"]
2176        );
2177    }
2178
2179    #[test]
2180    fn switch_view_template() {
2181        let client = Client::new("p4", GlobalOpts::new()).switch_view_template("tpl");
2182
2183        assert_eq!(
2184            args_of(&client.setup_command("p4")),
2185            ["client", "-s", "-t", "tpl"]
2186        );
2187        assert_eq!(client.get_template(), "tpl");
2188    }
2189
2190    #[test]
2191    fn switch_view_reuses_template() {
2192        let client = Client::new("p4", GlobalOpts::new())
2193            .template("tpl")
2194            .switch_view();
2195
2196        assert_eq!(
2197            args_of(&client.setup_command("p4")),
2198            ["client", "-s", "-t", "tpl"]
2199        );
2200        assert_eq!(client.get_template(), "tpl");
2201    }
2202
2203    #[test]
2204    fn stdin_mode() {
2205        let client = Client::new("p4", GlobalOpts::new()).stdin();
2206
2207        assert_eq!(args_of(&client.setup_command("p4")), ["client", "-i"]);
2208
2209        let client = Client::new("p4", GlobalOpts::new()).force().stdin();
2210
2211        assert_eq!(args_of(&client.setup_command("p4")), ["client", "-i", "-f"]);
2212    }
2213
2214    #[test]
2215    fn global_opts_are_injected_once() {
2216        let client = Client::new("p4", GlobalOpts::new().port("localhost:1666"));
2217
2218        assert_eq!(
2219            args_of(&client.setup_command("p4")),
2220            ["-p", "localhost:1666", "client"]
2221        );
2222    }
2223}