use std::{
ffi::OsStr,
path::PathBuf,
process::{Child, Command, Stdio},
};
use super::{DiffOptions, ExclusiveOption, SubCommand, Unselected};
use crate::global::GlobalOpts;
use crate::spawn::{ParameterizedSpawn, SpawnExt};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum DisplayOptions {
OpenedChangedOrMissing,
ResolvedThenModified,
UnopenedMissing,
UnopenedChanged,
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);
}
}
#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub struct StreamSpecMode;
impl ExclusiveOption for StreamSpecMode {
fn inject_args(&self, command: &mut Command) {
command.arg("-As");
}
}
#[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,
},
}
}
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> {
pub fn get_diff_options(&self) -> Option<&DiffOptions> {
self.diff_opts.as_ref()
}
pub fn set_diff_options(&mut self, v: impl Into<DiffOptions>) -> &mut Self {
self.diff_opts = Some(v.into());
self
}
pub fn diff_options(mut self, v: impl Into<DiffOptions>) -> Self {
self.diff_opts = Some(v.into());
self
}
}
impl<M: ExclusiveOption> Diff<M> {
pub fn get_global_opts(&self) -> &GlobalOpts {
&self.global_opts
}
pub fn set_global_opts(&mut self, v: GlobalOpts) -> &mut Self {
self.global_opts = v;
self
}
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>> {
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 },
},
}
}
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>> {
pub fn get_display_options(&self) -> &DisplayOptions {
&self.mode.mode.display_opts
}
pub fn set_display_options(&mut self, v: DisplayOptions) -> &mut Self {
self.mode.mode.display_opts = v;
self
}
}
impl Diff<WorkspaceMode<WorkspaceRegularMode>> {
pub fn get_limit(&self) -> u64 {
self.mode.mode.limit
}
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 ParameterizedSpawn for Diff<Unselected> {
type Input<'a> = &'a [&'a OsStr];
type Output<'a> = Child;
type Error = std::io::Error;
fn spawn_with<'a>(&mut self, files: Self::Input<'a>) -> Result<Self::Output<'a>, Self::Error> {
self.setup_command(&self.bin)
.args(files)
.stdout(Stdio::piped())
.stderr(Stdio::piped())
.spawn()
}
}
impl<M: ExclusiveOption> ParameterizedSpawn for Diff<WorkspaceMode<M>> {
type Input<'a> = &'a [&'a OsStr];
type Output<'a> = Child;
type Error = std::io::Error;
fn spawn_with<'a>(&mut self, files: Self::Input<'a>) -> Result<Self::Output<'a>, Self::Error> {
self.setup_command(&self.bin)
.args(files)
.stdout(Stdio::piped())
.stderr(Stdio::piped())
.spawn()
}
}
impl ParameterizedSpawn for Diff<StreamSpecMode> {
type Input<'a> = Option<&'a str>;
type Output<'a> = Child;
type Error = std::io::Error;
fn spawn_with<'a>(
&mut self,
stream_spec: Self::Input<'a>,
) -> Result<Self::Output<'a>, Self::Error> {
let mut command = self.setup_command(&self.bin);
if let Some(stream_spec) = stream_spec {
command.arg(stream_spec);
}
command
.stdout(Stdio::piped())
.stderr(Stdio::piped())
.spawn()
}
}
impl SpawnExt for Diff<StreamSpecMode> {
fn spawn<'a>(&mut self) -> Result<Self::Output<'a>, Self::Error> {
self.spawn_with(None)
}
}
#[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"]);
}
#[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"]);
}
#[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"]);
}
#[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"]);
}
}