pub mod add;
pub mod admin;
#[cfg(not(feature = "lt2016_1"))]
pub mod aliases;
pub mod annotate;
pub mod archive;
pub mod attribute;
pub mod changes;
pub mod describe;
pub mod diff;
pub mod diff2;
pub mod edit;
pub mod filelog;
pub mod print;
pub mod sync;
pub mod r#where;
pub use add::Add;
pub use admin::AdminEntry;
#[cfg(not(feature = "lt2016_1"))]
pub use aliases::Aliases;
pub use annotate::Annotate;
pub use archive::Archive;
pub use attribute::Attribute;
pub use changes::Changes;
pub use describe::Describe;
pub use diff::Diff;
pub use diff::DisplayOptions;
pub use diff2::Diff2;
pub use diff2::Diff2Parameters;
pub use edit::Edit;
pub use filelog::FileLog;
pub use print::Print;
pub use sync::Sync;
pub use r#where::Where;
use std::{ffi::OsStr, process::Command};
use crate::global::GlobalOpts;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum LongOutput {
Default,
Truncated,
}
impl LongOutput {
pub fn as_str(&self) -> &'static str {
match self {
LongOutput::Default => "-l",
LongOutput::Truncated => "-L",
}
}
}
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
pub enum DiffFormat {
#[default]
Default,
Context(Option<u32>),
Rcs,
Summary,
Unified(Option<u32>),
}
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
pub enum WhitespaceHandling {
#[default]
None,
IgnoreLineEndings,
IgnoreChangesWithinWhitespace,
IgnoreAllWhitespace,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum DiffOptions {
Typed {
format: DiffFormat,
whitespace: WhitespaceHandling,
},
Raw(String),
}
impl Default for DiffOptions {
fn default() -> Self {
DiffOptions::Typed {
format: DiffFormat::Default,
whitespace: WhitespaceHandling::None,
}
}
}
impl DiffOptions {
pub fn format(&self) -> Option<DiffFormat> {
match self {
DiffOptions::Typed { format, .. } => Some(*format),
DiffOptions::Raw(_) => None,
}
}
pub fn whitespace_handling(&self) -> Option<WhitespaceHandling> {
match self {
DiffOptions::Typed { whitespace, .. } => Some(*whitespace),
DiffOptions::Raw(_) => None,
}
}
pub fn raw_str(&self) -> Option<&str> {
match self {
DiffOptions::Typed { .. } => None,
DiffOptions::Raw(s) => Some(s),
}
}
pub fn inject_arg(&self, command: &mut Command) {
use DiffFormat::*;
use WhitespaceHandling::*;
let (format, whitespace) = match self {
DiffOptions::Typed { format, whitespace } => (*format, *whitespace),
DiffOptions::Raw(s) => {
command.arg(format!("-d{s}"));
return;
}
};
if matches!(format, Default) && matches!(whitespace, None) {
return;
}
let mut s = String::from("-d");
match format {
Default => {}
Context(num) => {
s.push('c');
if let Some(n) = num {
s.push_str(&n.to_string());
}
}
Rcs => s.push('n'),
Summary => s.push('s'),
Unified(num) => {
s.push('u');
if let Some(n) = num {
s.push_str(&n.to_string());
}
}
}
match whitespace {
None => {}
IgnoreLineEndings => s.push('l'),
IgnoreChangesWithinWhitespace => s.push('b'),
IgnoreAllWhitespace => s.push('w'),
}
command.arg(s);
}
}
impl From<String> for DiffOptions {
fn from(s: String) -> Self {
DiffOptions::Raw(s)
}
}
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
pub struct DiffOptionsTypedMode {
format: DiffFormat,
whitespace: WhitespaceHandling,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct DiffOptionsRawMode {
raw: String,
}
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
pub struct DiffOptionsBuilder<M> {
mode: M,
}
impl DiffOptionsBuilder<DiffOptionsTypedMode> {
pub fn new() -> Self {
Self {
mode: DiffOptionsTypedMode::default(),
}
}
pub fn context(num: Option<u32>) -> Self {
Self {
mode: DiffOptionsTypedMode {
format: DiffFormat::Context(num),
whitespace: WhitespaceHandling::None,
},
}
}
pub fn rcs() -> Self {
Self {
mode: DiffOptionsTypedMode {
format: DiffFormat::Rcs,
whitespace: WhitespaceHandling::None,
},
}
}
pub fn summary() -> Self {
Self {
mode: DiffOptionsTypedMode {
format: DiffFormat::Summary,
whitespace: WhitespaceHandling::None,
},
}
}
pub fn unified(num: Option<u32>) -> Self {
Self {
mode: DiffOptionsTypedMode {
format: DiffFormat::Unified(num),
whitespace: WhitespaceHandling::None,
},
}
}
pub fn ignore_line_endings(mut self) -> Self {
self.mode.whitespace = WhitespaceHandling::IgnoreLineEndings;
self
}
pub fn ignore_changes_within_whitespace(mut self) -> Self {
self.mode.whitespace = WhitespaceHandling::IgnoreChangesWithinWhitespace;
self
}
pub fn ignore_all_whitespace(mut self) -> Self {
self.mode.whitespace = WhitespaceHandling::IgnoreAllWhitespace;
self
}
}
impl DiffOptionsBuilder<DiffOptionsRawMode> {
pub fn raw(s: impl Into<String>) -> Self {
Self {
mode: DiffOptionsRawMode { raw: s.into() },
}
}
}
impl From<DiffOptionsBuilder<DiffOptionsTypedMode>> for DiffOptions {
fn from(builder: DiffOptionsBuilder<DiffOptionsTypedMode>) -> Self {
DiffOptions::Typed {
format: builder.mode.format,
whitespace: builder.mode.whitespace,
}
}
}
impl From<DiffOptionsBuilder<DiffOptionsRawMode>> for DiffOptions {
fn from(builder: DiffOptionsBuilder<DiffOptionsRawMode>) -> Self {
DiffOptions::Raw(builder.mode.raw)
}
}
#[derive(Debug, Clone, Copy, Default)]
pub struct Unselected;
impl ExclusiveOption for Unselected {
fn inject_args(&self, _: &mut Command) {}
}
pub trait ExclusiveOption {
#[allow(unused_variables)]
fn inject_args(&self, command: &mut Command) {}
}
pub trait SubCommand {
fn name(&self) -> &str;
fn inject_local_args(&self, command: &mut Command);
fn global_opts(&self) -> Option<&GlobalOpts> {
None
}
fn inject_args(&self, command: &mut Command) {
if let Some(global_opts) = self.global_opts() {
global_opts.setup_args(command);
};
self.inject_local_args(command.arg(self.name()));
}
fn setup_command<S: AsRef<OsStr>>(&self, bin: S) -> Command {
let mut cmd = Command::new(bin);
self.inject_args(&mut cmd);
cmd
}
}
#[cfg(test)]
pub(crate) fn args_of(command: &Command) -> Vec<String> {
command
.get_args()
.map(|arg| arg.to_string_lossy().into_owned())
.collect()
}
#[cfg(test)]
mod diff_options_tests {
use super::*;
fn injected(opts: impl Into<DiffOptions>) -> Vec<String> {
let mut cmd = Command::new("p4");
opts.into().inject_arg(&mut cmd);
args_of(&cmd)
}
#[test]
fn default_emits_nothing() {
assert!(injected(DiffOptionsBuilder::new()).is_empty());
assert!(injected(DiffOptions::default()).is_empty());
}
#[test]
fn unified_format() {
assert_eq!(injected(DiffOptionsBuilder::unified(None)), ["-du"]);
}
#[test]
fn unified_format_with_context() {
assert_eq!(injected(DiffOptionsBuilder::unified(Some(3))), ["-du3"]);
}
#[test]
fn context_format() {
assert_eq!(injected(DiffOptionsBuilder::context(None)), ["-dc"]);
assert_eq!(injected(DiffOptionsBuilder::context(Some(5))), ["-dc5"]);
}
#[test]
fn rcs_format() {
assert_eq!(injected(DiffOptionsBuilder::rcs()), ["-dn"]);
}
#[test]
fn summary_format() {
assert_eq!(injected(DiffOptionsBuilder::summary()), ["-ds"]);
}
#[test]
fn whitespace_only() {
assert_eq!(
injected(DiffOptionsBuilder::new().ignore_line_endings()),
["-dl"]
);
}
#[test]
fn unified_with_ignore_changes_within_whitespace() {
assert_eq!(
injected(DiffOptionsBuilder::unified(None).ignore_changes_within_whitespace()),
["-dub"]
);
}
#[test]
fn context_with_ignore_all_whitespace() {
assert_eq!(
injected(DiffOptionsBuilder::context(Some(2)).ignore_all_whitespace()),
["-dc2w"]
);
}
#[test]
fn raw_passthrough() {
assert_eq!(injected(DiffOptionsBuilder::raw("-C 25")), ["-d-C 25"]);
assert_eq!(injected(DiffOptionsBuilder::raw("--brief")), ["-d--brief"]);
}
#[test]
fn getters_reflect_variant() {
let typed = DiffOptions::from(DiffOptionsBuilder::unified(Some(3)).ignore_line_endings());
assert_eq!(typed.format(), Some(DiffFormat::Unified(Some(3))));
assert_eq!(
typed.whitespace_handling(),
Some(WhitespaceHandling::IgnoreLineEndings)
);
assert_eq!(typed.raw_str(), None);
let raw = DiffOptions::from(DiffOptionsBuilder::raw("abc"));
assert_eq!(raw.format(), None);
assert_eq!(raw.whitespace_handling(), None);
assert_eq!(raw.raw_str(), Some("abc"));
}
}