use std::{
ffi::OsStr,
path::PathBuf,
process::{Child, Command, Stdio},
};
use super::{DiffOptions, ExclusiveOption, SubCommand, Unselected};
use crate::global::GlobalOpts;
use crate::spawn::ParameterizedSpawn;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum DisplayOptions {
OpenedChangedOrMissing,
ResolvedThenModified,
UnopenedMissing,
UnopenedChanged,
#[cfg_attr(
feature = "lt2014_2",
doc = "If you use the `-f` flag together with the `-sl` flag, files that are open for edit are also compared and their status is listed."
)]
#[cfg_attr(
not(feature = "lt2014_2"),
doc = "If you use the `-f` option together with the `-sl` option, files that are open for edit are also compared and their status is listed."
)]
ListWithStatus,
OpenedIdentical,
}
impl DisplayOptions {
pub fn as_str(&self) -> &'static str {
match self {
DisplayOptions::OpenedChangedOrMissing => "-sa",
DisplayOptions::ResolvedThenModified => "-sb",
DisplayOptions::UnopenedMissing => "-sd",
DisplayOptions::UnopenedChanged => "-se",
DisplayOptions::ListWithStatus => "-sl",
DisplayOptions::OpenedIdentical => "-sr",
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct WorkspaceDisplayMode {
display_opts: DisplayOptions,
}
impl ExclusiveOption for WorkspaceDisplayMode {
fn inject_args(&self, command: &mut Command) {
command.arg(self.display_opts.as_str());
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct WorkspaceRegularMode {
limit: u64,
}
impl ExclusiveOption for WorkspaceRegularMode {
fn inject_args(&self, command: &mut Command) {
command.arg("-m").arg(self.limit.to_string());
}
}
#[derive(Debug, Clone, Default)]
pub struct WorkspaceMode<M = Unselected> {
force: bool,
differing_only: bool,
diff_nontext: bool,
mode: M,
}
impl<M: ExclusiveOption> ExclusiveOption for WorkspaceMode<M> {
fn inject_args(&self, command: &mut Command) {
if self.force {
command.arg("-f");
}
if self.diff_nontext {
command.arg("-t");
}
if self.differing_only {
command.arg("-Od");
}
self.mode.inject_args(command);
}
}
#[cfg(not(feature = "lt2019_1"))]
#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub struct StreamSpecMode;
#[cfg(not(feature = "lt2019_1"))]
impl ExclusiveOption for StreamSpecMode {
fn inject_args(&self, command: &mut Command) {
command.arg("-As");
}
}
#[cfg_attr(
feature = "lt2014_2",
doc = "`p4 [g-opts] diff [-dflags -f -m max -Od -sa -sb -sd -se -sr -sl -t] [file[rev#]...]`"
)]
#[cfg_attr(
all(feature = "lt2015_1", not(feature = "lt2014_2")),
doc = "`p4 [g-opts] diff [-doptions -f -m max -Od -sa -sb -sd -se -sr -sl -t] [file[rev#]...]`"
)]
#[cfg_attr(
all(feature = "lt2016_1", not(feature = "lt2015_1")),
doc = "`p4 [g-opts] diff [-doptions] [-f -t -Od] [-m max] [-soptions] [file[rev] …]`"
)]
#[cfg_attr(
all(feature = "lt2019_1", not(feature = "lt2016_1")),
doc = "`p4 [g-opts] diff [-doptions] [-f -t -Od] [-m max] [-soptions] [file[rev] ...]`"
)]
#[cfg_attr(
not(feature = "lt2019_1"),
doc = "`p4 [g-opts] diff [-doptions] [-f -t -Od] [-m max] [-soptions] [file[rev] ...]`",
doc = "",
doc = "`p4 [g-opts] diff [-doptions] -As [streamname[@change]]`"
)]
#[cfg_attr(not(feature = "lt2019_1"), doc = "Also for stream spec comparison.")]
#[cfg_attr(
feature = "lt2019_1",
doc = "The `M` type parameter tracks the command mode at compile time. The default [`Unselected`] state offers the workspace content options; [`Self::force`], [`Self::differing_only`], and [`Self::diff_nontext`] transition to the [`WorkspaceMode`] state."
)]
#[cfg_attr(
not(feature = "lt2019_1"),
doc = "The `M` type parameter tracks the command mode at compile time. The default [`Unselected`] state offers neither the workspace content options nor `-As`; [`Self::force`], [`Self::differing_only`], and [`Self::diff_nontext`] transition to the [`WorkspaceMode`] state, while [`Self::stream_spec_mode`] transitions to the [`StreamSpecMode`] state."
)]
#[derive(Debug, Clone, Default)]
pub struct Diff<M = Unselected> {
bin: PathBuf,
global_opts: GlobalOpts,
diff_opts: Option<DiffOptions>,
mode: M,
}
impl Diff<Unselected> {
pub fn new(bin: impl Into<PathBuf>, global_opts: GlobalOpts) -> Self {
Self {
bin: bin.into(),
global_opts,
diff_opts: None,
mode: Unselected,
}
}
pub fn force(self, v: bool) -> Diff<WorkspaceMode<Unselected>> {
Diff {
bin: self.bin,
global_opts: self.global_opts,
diff_opts: self.diff_opts,
mode: WorkspaceMode {
force: v,
differing_only: false,
diff_nontext: false,
mode: Unselected,
},
}
}
pub fn differing_only(self, v: bool) -> Diff<WorkspaceMode<Unselected>> {
Diff {
bin: self.bin,
global_opts: self.global_opts,
diff_opts: self.diff_opts,
mode: WorkspaceMode {
force: false,
differing_only: v,
diff_nontext: false,
mode: Unselected,
},
}
}
pub fn diff_nontext(self, v: bool) -> Diff<WorkspaceMode<Unselected>> {
Diff {
bin: self.bin,
global_opts: self.global_opts,
diff_opts: self.diff_opts,
mode: WorkspaceMode {
force: false,
differing_only: false,
diff_nontext: v,
mode: Unselected,
},
}
}
#[cfg_attr(
feature = "lt2024_2",
doc = "Limitation: Although this option requires the user have at least the `list` access to the stream path, it ignores any other entry in the protections table, including any minus sign (`-`) that would otherwise block the operation."
)]
#[cfg_attr(
not(feature = "lt2024_2"),
doc = "Although this option requires the user have at least the `list` access to the stream path, it ignores any other entry in the protections table, including any minus sign (`-`) that would otherwise block the operation."
)]
#[cfg(not(feature = "lt2019_1"))]
pub fn stream_spec_mode(self) -> Diff<StreamSpecMode> {
Diff {
bin: self.bin,
global_opts: self.global_opts,
diff_opts: self.diff_opts,
mode: StreamSpecMode,
}
}
}
impl<M> Diff<M> {
#[cfg_attr(
feature = "lt2014_2",
doc = "Pass flags to the underlying diff routine (see the Usage Notes below for details)"
)]
#[cfg_attr(
all(feature = "lt2015_1", not(feature = "lt2014_2")),
doc = "Pass options to the underlying diff routine (see the Usage Notes below for details)."
)]
#[cfg_attr(
all(feature = "lt2024_1", not(feature = "lt2015_1")),
doc = "Pass options to the underlying diff routine (see Usage Notes for details)."
)]
#[cfg_attr(
not(feature = "lt2024_1"),
doc = "Pass options to the underlying diff routine (see Usage notes for details)."
)]
pub fn get_diff_options(&self) -> Option<&DiffOptions> {
self.diff_opts.as_ref()
}
#[cfg_attr(
feature = "lt2014_2",
doc = "Pass flags to the underlying diff routine (see the Usage Notes below for details)"
)]
#[cfg_attr(
all(feature = "lt2015_1", not(feature = "lt2014_2")),
doc = "Pass options to the underlying diff routine (see the Usage Notes below for details)."
)]
#[cfg_attr(
all(feature = "lt2024_1", not(feature = "lt2015_1")),
doc = "Pass options to the underlying diff routine (see Usage Notes for details)."
)]
#[cfg_attr(
not(feature = "lt2024_1"),
doc = "Pass options to the underlying diff routine (see Usage notes for details)."
)]
pub fn set_diff_options(&mut self, v: impl Into<DiffOptions>) -> &mut Self {
self.diff_opts = Some(v.into());
self
}
#[cfg_attr(
feature = "lt2014_2",
doc = "Pass flags to the underlying diff routine (see the Usage Notes below for details)"
)]
#[cfg_attr(
all(feature = "lt2015_1", not(feature = "lt2014_2")),
doc = "Pass options to the underlying diff routine (see the Usage Notes below for details)."
)]
#[cfg_attr(
all(feature = "lt2024_1", not(feature = "lt2015_1")),
doc = "Pass options to the underlying diff routine (see Usage Notes for details)."
)]
#[cfg_attr(
not(feature = "lt2024_1"),
doc = "Pass options to the underlying diff routine (see Usage notes for details)."
)]
pub fn diff_options(mut self, v: impl Into<DiffOptions>) -> Self {
self.diff_opts = Some(v.into());
self
}
}
impl<M: ExclusiveOption> Diff<M> {
#[cfg_attr(
feature = "lt2014_2",
doc = "See the [Global Options](GlobalOpts) section."
)]
#[cfg_attr(
all(feature = "lt2015_1", not(feature = "lt2014_2")),
doc = "See the [“Global Options”](GlobalOpts) section."
)]
#[cfg_attr(
all(feature = "lt2017_1", not(feature = "lt2015_1")),
doc = "See [“Global Options”](GlobalOpts)."
)]
#[cfg_attr(
all(feature = "lt2018_2", not(feature = "lt2017_1")),
doc = "See [Global Options](GlobalOpts)."
)]
#[cfg_attr(not(feature = "lt2018_2"), doc = "See [Global options](GlobalOpts).")]
pub fn get_global_opts(&self) -> &GlobalOpts {
&self.global_opts
}
#[cfg_attr(
feature = "lt2014_2",
doc = "See the [Global Options](GlobalOpts) section."
)]
#[cfg_attr(
all(feature = "lt2015_1", not(feature = "lt2014_2")),
doc = "See the [“Global Options”](GlobalOpts) section."
)]
#[cfg_attr(
all(feature = "lt2017_1", not(feature = "lt2015_1")),
doc = "See [“Global Options”](GlobalOpts)."
)]
#[cfg_attr(
all(feature = "lt2018_2", not(feature = "lt2017_1")),
doc = "See [Global Options](GlobalOpts)."
)]
#[cfg_attr(not(feature = "lt2018_2"), doc = "See [Global options](GlobalOpts).")]
pub fn set_global_opts(&mut self, v: GlobalOpts) -> &mut Self {
self.global_opts = v;
self
}
#[cfg_attr(
feature = "lt2014_2",
doc = "See the [Global Options](GlobalOpts) section."
)]
#[cfg_attr(
all(feature = "lt2015_1", not(feature = "lt2014_2")),
doc = "See the [“Global Options”](GlobalOpts) section."
)]
#[cfg_attr(
all(feature = "lt2017_1", not(feature = "lt2015_1")),
doc = "See [“Global Options”](GlobalOpts)."
)]
#[cfg_attr(
all(feature = "lt2018_2", not(feature = "lt2017_1")),
doc = "See [Global Options](GlobalOpts)."
)]
#[cfg_attr(not(feature = "lt2018_2"), doc = "See [Global options](GlobalOpts).")]
pub fn global_opts(mut self, v: GlobalOpts) -> Self {
self.global_opts = v;
self
}
}
impl<M> Diff<WorkspaceMode<M>> {
pub fn get_force(&self) -> bool {
self.mode.force
}
pub fn set_force(&mut self, v: bool) -> &mut Self {
self.mode.force = v;
self
}
pub fn force(mut self, v: bool) -> Self {
self.mode.force = v;
self
}
pub fn get_differing_only(&self) -> bool {
self.mode.differing_only
}
pub fn set_differing_only(&mut self, v: bool) -> &mut Self {
self.mode.differing_only = v;
self
}
pub fn differing_only(mut self, v: bool) -> Self {
self.mode.differing_only = v;
self
}
pub fn get_diff_nontext(&self) -> bool {
self.mode.diff_nontext
}
pub fn set_diff_nontext(&mut self, v: bool) -> &mut Self {
self.mode.diff_nontext = v;
self
}
pub fn diff_nontext(mut self, v: bool) -> Self {
self.mode.diff_nontext = v;
self
}
}
impl Diff<WorkspaceMode<Unselected>> {
#[cfg_attr(feature = "lt2015_1", doc = "-sa, -sb, -sd, -se, -sr, -sl file ...")]
#[cfg_attr(not(feature = "lt2015_1"), doc = "-soptions")]
#[cfg_attr(
feature = "lt2015_1",
doc = "Pass one of the display options; see the individual option descriptions for details."
)]
#[cfg_attr(
all(feature = "lt2024_1", not(feature = "lt2015_1")),
doc = "Pass display options to the underlying diff routine (see Usage Notes for details)."
)]
#[cfg_attr(
not(feature = "lt2024_1"),
doc = "Pass display options to the underlying diff routine (see Usage notes for details)."
)]
pub fn display_options(self, v: DisplayOptions) -> Diff<WorkspaceMode<WorkspaceDisplayMode>> {
Diff {
bin: self.bin,
global_opts: self.global_opts,
diff_opts: self.diff_opts,
mode: WorkspaceMode {
force: self.mode.force,
differing_only: self.mode.differing_only,
diff_nontext: self.mode.diff_nontext,
mode: WorkspaceDisplayMode { display_opts: v },
},
}
}
#[cfg_attr(
feature = "lt2014_2",
doc = "Limit output to diffs (or status) of only the first `max` files, unless the `-s` flag is used, in which case the `-m` flag is ignored."
)]
#[cfg_attr(
not(feature = "lt2014_2"),
doc = "Limit output to diffs (or status) of only the first `max` files, unless the `-s` option is used, in which case the `-m` option is ignored."
)]
pub fn limit(self, max: u64) -> Diff<WorkspaceMode<WorkspaceRegularMode>> {
Diff {
bin: self.bin,
global_opts: self.global_opts,
diff_opts: self.diff_opts,
mode: WorkspaceMode {
force: self.mode.force,
differing_only: self.mode.differing_only,
diff_nontext: self.mode.diff_nontext,
mode: WorkspaceRegularMode { limit: max },
},
}
}
}
impl Diff<WorkspaceMode<WorkspaceDisplayMode>> {
#[cfg_attr(feature = "lt2015_1", doc = "-sa, -sb, -sd, -se, -sr, -sl file ...")]
#[cfg_attr(not(feature = "lt2015_1"), doc = "-soptions")]
#[cfg_attr(
feature = "lt2015_1",
doc = "Pass one of the display options; see the individual option descriptions for details."
)]
#[cfg_attr(
all(feature = "lt2024_1", not(feature = "lt2015_1")),
doc = "Pass display options to the underlying diff routine (see Usage Notes for details)."
)]
#[cfg_attr(
not(feature = "lt2024_1"),
doc = "Pass display options to the underlying diff routine (see Usage notes for details)."
)]
pub fn get_display_options(&self) -> &DisplayOptions {
&self.mode.mode.display_opts
}
#[cfg_attr(feature = "lt2015_1", doc = "-sa, -sb, -sd, -se, -sr, -sl file ...")]
#[cfg_attr(not(feature = "lt2015_1"), doc = "-soptions")]
#[cfg_attr(
feature = "lt2015_1",
doc = "Pass one of the display options; see the individual option descriptions for details."
)]
#[cfg_attr(
all(feature = "lt2024_1", not(feature = "lt2015_1")),
doc = "Pass display options to the underlying diff routine (see Usage Notes for details)."
)]
#[cfg_attr(
not(feature = "lt2024_1"),
doc = "Pass display options to the underlying diff routine (see Usage notes for details)."
)]
pub fn set_display_options(&mut self, v: DisplayOptions) -> &mut Self {
self.mode.mode.display_opts = v;
self
}
}
impl Diff<WorkspaceMode<WorkspaceRegularMode>> {
#[cfg_attr(
feature = "lt2014_2",
doc = "Limit output to diffs (or status) of only the first `max` files, unless the `-s` flag is used, in which case the `-m` flag is ignored."
)]
#[cfg_attr(
not(feature = "lt2014_2"),
doc = "Limit output to diffs (or status) of only the first `max` files, unless the `-s` option is used, in which case the `-m` option is ignored."
)]
pub fn get_limit(&self) -> u64 {
self.mode.mode.limit
}
#[cfg_attr(
feature = "lt2014_2",
doc = "Limit output to diffs (or status) of only the first `max` files, unless the `-s` flag is used, in which case the `-m` flag is ignored."
)]
#[cfg_attr(
not(feature = "lt2014_2"),
doc = "Limit output to diffs (or status) of only the first `max` files, unless the `-s` option is used, in which case the `-m` option is ignored."
)]
pub fn set_limit(&mut self, v: u64) -> &mut Self {
self.mode.mode.limit = v;
self
}
}
impl<M: ExclusiveOption> SubCommand for Diff<M> {
fn name(&self) -> &str {
"diff"
}
fn inject_local_args(&self, command: &mut Command) {
if let Some(diff_opts) = &self.diff_opts {
diff_opts.inject_arg(command);
}
self.mode.inject_args(command);
}
fn global_opts(&self) -> Option<&GlobalOpts> {
Some(&self.global_opts)
}
}
impl<S, I> ParameterizedSpawn<(S,)> for Diff<Unselected>
where
S: IntoIterator<Item = I>,
I: AsRef<OsStr>,
{
type Output = Child;
type Error = std::io::Error;
fn spawn_with(&mut self, (files,): (S,)) -> Result<Self::Output, Self::Error> {
self.setup_command(&self.bin)
.args(files)
.stdout(Stdio::piped())
.stderr(Stdio::piped())
.spawn()
}
}
impl<M: ExclusiveOption, S, I> ParameterizedSpawn<(S,)> for Diff<WorkspaceMode<M>>
where
S: IntoIterator<Item = I>,
I: AsRef<OsStr>,
{
type Output = Child;
type Error = std::io::Error;
fn spawn_with(&mut self, (files,): (S,)) -> Result<Self::Output, Self::Error> {
self.setup_command(&self.bin)
.args(files)
.stdout(Stdio::piped())
.stderr(Stdio::piped())
.spawn()
}
}
#[cfg(not(feature = "lt2019_1"))]
impl<I> ParameterizedSpawn<(I,)> for Diff<StreamSpecMode>
where
I: AsRef<OsStr>,
{
type Output = Child;
type Error = std::io::Error;
fn spawn_with(&mut self, (stream_spec,): (I,)) -> Result<Self::Output, Self::Error> {
self.setup_command(&self.bin)
.arg(stream_spec)
.stdout(Stdio::piped())
.stderr(Stdio::piped())
.spawn()
}
}
#[cfg(not(feature = "lt2019_1"))]
impl ParameterizedSpawn<()> for Diff<StreamSpecMode> {
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::DiffOptionsBuilder;
use crate::cmd::args_of;
#[test]
fn without_options() {
let diff = Diff::new("p4", GlobalOpts::new());
assert_eq!(args_of(&diff.setup_command("p4")), ["diff"]);
}
#[test]
fn workspace_mode_via_force() {
let diff = Diff::new("p4", GlobalOpts::new()).force(true);
assert!(diff.get_force());
assert_eq!(args_of(&diff.setup_command("p4")), ["diff", "-f"]);
}
#[test]
fn workspace_mode_via_differing_only() {
let diff = Diff::new("p4", GlobalOpts::new()).differing_only(true);
assert_eq!(args_of(&diff.setup_command("p4")), ["diff", "-Od"]);
}
#[test]
fn workspace_mode_via_diff_nontext() {
let diff = Diff::new("p4", GlobalOpts::new()).diff_nontext(true);
assert_eq!(args_of(&diff.setup_command("p4")), ["diff", "-t"]);
}
#[test]
fn workspace_mode_combines_options() {
let mut diff = Diff::new("p4", GlobalOpts::new()).diff_nontext(true);
diff.set_force(true).set_differing_only(true);
assert_eq!(
args_of(&diff.setup_command("p4")),
["diff", "-f", "-t", "-Od"]
);
}
#[test]
fn workspace_mode_preserves_diff_options() {
let mut diff = Diff::new("p4", GlobalOpts::new())
.diff_options(DiffOptionsBuilder::unified(None))
.force(true);
diff.set_diff_options(DiffOptionsBuilder::summary());
assert_eq!(args_of(&diff.setup_command("p4")), ["diff", "-ds", "-f"]);
}
#[test]
fn display_mode_injects_filter() {
let diff = Diff::new("p4", GlobalOpts::new())
.force(true)
.display_options(DisplayOptions::UnopenedChanged);
assert_eq!(diff.get_display_options(), &DisplayOptions::UnopenedChanged);
assert_eq!(args_of(&diff.setup_command("p4")), ["diff", "-f", "-se"]);
}
#[test]
fn display_mode_flag_mapping() {
assert_eq!(DisplayOptions::OpenedChangedOrMissing.as_str(), "-sa");
assert_eq!(DisplayOptions::ResolvedThenModified.as_str(), "-sb");
assert_eq!(DisplayOptions::UnopenedMissing.as_str(), "-sd");
assert_eq!(DisplayOptions::UnopenedChanged.as_str(), "-se");
assert_eq!(DisplayOptions::ListWithStatus.as_str(), "-sl");
assert_eq!(DisplayOptions::OpenedIdentical.as_str(), "-sr");
}
#[test]
fn regular_mode_injects_limit() {
let diff = Diff::new("p4", GlobalOpts::new()).force(false).limit(10);
assert_eq!(diff.get_limit(), 10);
assert_eq!(args_of(&diff.setup_command("p4")), ["diff", "-m", "10"]);
}
#[cfg(not(feature = "lt2019_1"))]
#[test]
fn stream_spec_mode_bare() {
let diff = Diff::new("p4", GlobalOpts::new()).stream_spec_mode();
assert_eq!(args_of(&diff.setup_command("p4")), ["diff", "-As"]);
}
#[cfg(not(feature = "lt2019_1"))]
#[test]
fn stream_spec_mode_with_spec() {
let diff = Diff::new("p4", GlobalOpts::new()).stream_spec_mode();
let mut command = diff.setup_command("p4");
command.arg("myStream@head");
assert_eq!(args_of(&command), ["diff", "-As", "myStream@head"]);
}
#[cfg(not(feature = "lt2019_1"))]
#[test]
fn stream_spec_mode_preserves_diff_options() {
let diff = Diff::new("p4", GlobalOpts::new())
.diff_options(DiffOptionsBuilder::unified(None))
.stream_spec_mode();
let mut command = diff.setup_command("p4");
command.arg("myStream");
assert_eq!(args_of(&command), ["diff", "-du", "-As", "myStream"]);
}
}