mirror of
https://github.com/uutils/diffutils.git
synced 2026-06-28 22:58:30 -04:00
Compare commits
26 Commits
v0.4.0
...
cargo-dist
| Author | SHA1 | Date | |
|---|---|---|---|
| 80b993141b | |||
| d922313c8c | |||
| 3e246ab36c | |||
| 4b70969ff1 | |||
| 767c6f6c4a | |||
| 1f896ca1ac | |||
| 713bd210ab | |||
| 61314eaf4e | |||
| bf9147733d | |||
| ce8457cbdb | |||
| df778c610b | |||
| d92132e721 | |||
| 99d4d02985 | |||
| e7dc6558c6 | |||
| 8c6a648aef | |||
| 0304391bc5 | |||
| 8de0ca60d1 | |||
| 43b9c524d9 | |||
| 3dc3fdf5cd | |||
| b7261a43f4 | |||
| 37fe1ae808 | |||
| 22d973fce6 | |||
| fe28610f21 | |||
| 476e69ee20 | |||
| 65993d6a13 | |||
| 39d2ece187 |
@@ -28,6 +28,9 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: dtolnay/rust-toolchain@stable
|
||||
- name: install GNU patch on MacOS
|
||||
if: runner.os == 'macOS'
|
||||
run: brew install gpatch
|
||||
- name: set up PATH on Windows
|
||||
# Needed to use GNU's patch.exe instead of Strawberry Perl patch
|
||||
if: runner.os == 'Windows'
|
||||
@@ -107,6 +110,9 @@ jobs:
|
||||
|
||||
- name: rust toolchain ~ install
|
||||
uses: dtolnay/rust-toolchain@nightly
|
||||
- name: install GNU patch on MacOS
|
||||
if: runner.os == 'macOS'
|
||||
run: brew install gpatch
|
||||
- name: set up PATH on Windows
|
||||
# Needed to use GNU's patch.exe instead of Strawberry Perl patch
|
||||
if: runner.os == 'Windows'
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# Copyright 2022-2023, axodotdev
|
||||
# Copyright 2022-2024, axodotdev
|
||||
# SPDX-License-Identifier: MIT or Apache-2.0
|
||||
#
|
||||
# CI that:
|
||||
@@ -6,9 +6,9 @@
|
||||
# * checks for a Git Tag that looks like a release
|
||||
# * builds artifacts with cargo-dist (archives, installers, hashes)
|
||||
# * uploads those artifacts to temporary workflow zip
|
||||
# * on success, uploads the artifacts to a Github Release
|
||||
# * on success, uploads the artifacts to a GitHub Release
|
||||
#
|
||||
# Note that the Github Release will be created with a generated
|
||||
# Note that the GitHub Release will be created with a generated
|
||||
# title/body based on your changelogs.
|
||||
|
||||
name: Release
|
||||
@@ -31,7 +31,7 @@ permissions:
|
||||
# packages versioned/released in lockstep).
|
||||
#
|
||||
# If you push multiple tags at once, separate instances of this workflow will
|
||||
# spin up, creating an independent announcement for each one. However Github
|
||||
# spin up, creating an independent announcement for each one. However, GitHub
|
||||
# will hard limit this to 3 tags per commit, as it will assume more tags is a
|
||||
# mistake.
|
||||
#
|
||||
@@ -62,7 +62,7 @@ jobs:
|
||||
# we specify bash to get pipefail; it guards against the `curl` command
|
||||
# failing. otherwise `sh` won't catch that `curl` returned non-0
|
||||
shell: bash
|
||||
run: "curl --proto '=https' --tlsv1.2 -LsSf https://github.com/axodotdev/cargo-dist/releases/download/v0.12.0/cargo-dist-installer.sh | sh"
|
||||
run: "curl --proto '=https' --tlsv1.2 -LsSf https://github.com/axodotdev/cargo-dist/releases/download/v0.13.3/cargo-dist-installer.sh | sh"
|
||||
# sure would be cool if github gave us proper conditionals...
|
||||
# so here's a doubly-nested ternary-via-truthiness to try to provide the best possible
|
||||
# functionality based on whether this is a pull_request, and whether it's from a fork.
|
||||
@@ -105,10 +105,15 @@ jobs:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
BUILD_MANIFEST_NAME: target/distrib/${{ join(matrix.targets, '-') }}-dist-manifest.json
|
||||
steps:
|
||||
- name: enable windows longpaths
|
||||
run: |
|
||||
git config --global core.longpaths true
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: recursive
|
||||
- uses: swatinem/rust-cache@v2
|
||||
with:
|
||||
key: ${{ join(matrix.targets, '-') }}
|
||||
- name: Install cargo-dist
|
||||
run: ${{ matrix.install_dist }}
|
||||
# Get the dist-manifest
|
||||
@@ -135,7 +140,7 @@ jobs:
|
||||
run: |
|
||||
# Parse out what we just built and upload it to scratch storage
|
||||
echo "paths<<EOF" >> "$GITHUB_OUTPUT"
|
||||
jq --raw-output ".artifacts[]?.path | select( . != null )" dist-manifest.json >> "$GITHUB_OUTPUT"
|
||||
jq --raw-output ".upload_files[]" dist-manifest.json >> "$GITHUB_OUTPUT"
|
||||
echo "EOF" >> "$GITHUB_OUTPUT"
|
||||
|
||||
cp dist-manifest.json "$BUILD_MANIFEST_NAME"
|
||||
@@ -162,7 +167,7 @@ jobs:
|
||||
submodules: recursive
|
||||
- name: Install cargo-dist
|
||||
shell: bash
|
||||
run: "curl --proto '=https' --tlsv1.2 -LsSf https://github.com/axodotdev/cargo-dist/releases/download/v0.12.0/cargo-dist-installer.sh | sh"
|
||||
run: "curl --proto '=https' --tlsv1.2 -LsSf https://github.com/axodotdev/cargo-dist/releases/download/v0.13.3/cargo-dist-installer.sh | sh"
|
||||
# Get all the local artifacts for the global tasks to use (for e.g. checksums)
|
||||
- name: Fetch local artifacts
|
||||
uses: actions/download-artifact@v4
|
||||
@@ -178,7 +183,7 @@ jobs:
|
||||
|
||||
# Parse out what we just built and upload it to scratch storage
|
||||
echo "paths<<EOF" >> "$GITHUB_OUTPUT"
|
||||
jq --raw-output ".artifacts[]?.path | select( . != null )" dist-manifest.json >> "$GITHUB_OUTPUT"
|
||||
jq --raw-output ".upload_files[]" dist-manifest.json >> "$GITHUB_OUTPUT"
|
||||
echo "EOF" >> "$GITHUB_OUTPUT"
|
||||
|
||||
cp dist-manifest.json "$BUILD_MANIFEST_NAME"
|
||||
@@ -207,7 +212,7 @@ jobs:
|
||||
with:
|
||||
submodules: recursive
|
||||
- name: Install cargo-dist
|
||||
run: "curl --proto '=https' --tlsv1.2 -LsSf https://github.com/axodotdev/cargo-dist/releases/download/v0.12.0/cargo-dist-installer.sh | sh"
|
||||
run: "curl --proto '=https' --tlsv1.2 -LsSf https://github.com/axodotdev/cargo-dist/releases/download/v0.13.3/cargo-dist-installer.sh | sh"
|
||||
# Fetch artifacts from scratch-storage
|
||||
- name: Fetch artifacts
|
||||
uses: actions/download-artifact@v4
|
||||
@@ -215,7 +220,7 @@ jobs:
|
||||
pattern: artifacts-*
|
||||
path: target/distrib/
|
||||
merge-multiple: true
|
||||
# This is a harmless no-op for Github Releases, hosting for that happens in "announce"
|
||||
# This is a harmless no-op for GitHub Releases, hosting for that happens in "announce"
|
||||
- id: host
|
||||
shell: bash
|
||||
run: |
|
||||
@@ -230,7 +235,7 @@ jobs:
|
||||
name: artifacts-dist-manifest
|
||||
path: dist-manifest.json
|
||||
|
||||
# Create a Github Release while uploading all files to it
|
||||
# Create a GitHub Release while uploading all files to it
|
||||
announce:
|
||||
needs:
|
||||
- plan
|
||||
@@ -246,7 +251,7 @@ jobs:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: recursive
|
||||
- name: "Download Github Artifacts"
|
||||
- name: "Download GitHub Artifacts"
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
pattern: artifacts-*
|
||||
@@ -256,7 +261,7 @@ jobs:
|
||||
run: |
|
||||
# Remove the granular manifests
|
||||
rm -f artifacts/*-dist-manifest.json
|
||||
- name: Create Github Release
|
||||
- name: Create GitHub Release
|
||||
uses: ncipollo/release-action@v1
|
||||
with:
|
||||
tag: ${{ needs.plan.outputs.tag }}
|
||||
|
||||
Generated
+3
-3
@@ -122,7 +122,7 @@ checksum = "6184e33543162437515c2e2b48714794e37845ec9851711914eec9d308f6ebe8"
|
||||
|
||||
[[package]]
|
||||
name = "diffutils"
|
||||
version = "0.4.0"
|
||||
version = "0.4.1"
|
||||
dependencies = [
|
||||
"assert_cmd",
|
||||
"chrono",
|
||||
@@ -409,9 +409,9 @@ checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-width"
|
||||
version = "0.1.11"
|
||||
version = "0.1.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e51733f11c9c4f72aa0c160008246859e340b00807569a0da0e7a1079b27ba85"
|
||||
checksum = "68f5e5f3158ecfd4b8ff6fe086db7c8467a2dfdac97fe420f2b7c4aa97af66d6"
|
||||
|
||||
[[package]]
|
||||
name = "wait-timeout"
|
||||
|
||||
+5
-5
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "diffutils"
|
||||
version = "0.4.0"
|
||||
version = "0.4.1"
|
||||
edition = "2021"
|
||||
description = "A CLI app for generating diff files"
|
||||
license = "MIT OR Apache-2.0"
|
||||
@@ -19,10 +19,10 @@ chrono = "0.4.38"
|
||||
diff = "0.1.13"
|
||||
regex = "1.10.4"
|
||||
same-file = "1.0.6"
|
||||
unicode-width = "0.1.11"
|
||||
unicode-width = "0.1.12"
|
||||
|
||||
[dev-dependencies]
|
||||
pretty_assertions = "1"
|
||||
pretty_assertions = "1.4.0"
|
||||
assert_cmd = "2.0.14"
|
||||
predicates = "3.1.0"
|
||||
tempfile = "3.10.1"
|
||||
@@ -35,12 +35,12 @@ lto = "thin"
|
||||
# Config for 'cargo dist'
|
||||
[workspace.metadata.dist]
|
||||
# The preferred cargo-dist version to use in CI (Cargo.toml SemVer syntax)
|
||||
cargo-dist-version = "0.12.0"
|
||||
cargo-dist-version = "0.13.3"
|
||||
# CI backends to support
|
||||
ci = ["github"]
|
||||
# The installers to generate for each app
|
||||
installers = []
|
||||
# Target platforms to build apps for (Rust target-triple syntax)
|
||||
targets = ["aarch64-apple-darwin", "x86_64-apple-darwin", "x86_64-unknown-linux-gnu", "x86_64-pc-windows-msvc"]
|
||||
targets = ["aarch64-apple-darwin", "aarch64-unknown-linux-gnu", "aarch64-unknown-linux-musl", "x86_64-apple-darwin", "x86_64-unknown-linux-gnu", "x86_64-unknown-linux-musl", "x86_64-pc-windows-msvc"]
|
||||
# Publish jobs to run in CI
|
||||
pr-run-mode = "plan"
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
Copyright (c) Michael Howell
|
||||
Copyright (c) uutils developers
|
||||
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
Copyright (c) Michael Howell
|
||||
Copyright (c) uutils developers
|
||||
|
||||
Permission is hereby granted, free of charge, to any
|
||||
person obtaining a copy of this software and associated
|
||||
documentation files (the "Software"), to deal in the
|
||||
|
||||
+1
-1
@@ -9,7 +9,7 @@ edition = "2018"
|
||||
cargo-fuzz = true
|
||||
|
||||
[dependencies]
|
||||
libfuzzer-sys = "0.4"
|
||||
libfuzzer-sys = "0.4.7"
|
||||
diffutils = { path = "../" }
|
||||
|
||||
# Prevent this from interfering with workspaces
|
||||
|
||||
+314
-69
@@ -1,4 +1,5 @@
|
||||
use std::ffi::{OsStr, OsString};
|
||||
use std::ffi::OsString;
|
||||
use std::path::PathBuf;
|
||||
|
||||
use regex::Regex;
|
||||
|
||||
@@ -11,17 +12,6 @@ pub enum Format {
|
||||
Ed,
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
fn osstr_bytes(osstr: &OsStr) -> &[u8] {
|
||||
use std::os::unix::ffi::OsStrExt;
|
||||
osstr.as_bytes()
|
||||
}
|
||||
|
||||
#[cfg(not(unix))]
|
||||
fn osstr_bytes(osstr: &OsStr) -> Vec<u8> {
|
||||
osstr.to_string_lossy().bytes().collect()
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||
pub struct Params {
|
||||
pub from: OsString,
|
||||
@@ -50,7 +40,7 @@ impl Default for Params {
|
||||
}
|
||||
|
||||
pub fn parse_params<I: IntoIterator<Item = OsString>>(opts: I) -> Result<Params, String> {
|
||||
let mut opts = opts.into_iter();
|
||||
let mut opts = opts.into_iter().peekable();
|
||||
// parse CLI
|
||||
|
||||
let Some(exe) = opts.next() else {
|
||||
@@ -60,8 +50,10 @@ pub fn parse_params<I: IntoIterator<Item = OsString>>(opts: I) -> Result<Params,
|
||||
let mut from = None;
|
||||
let mut to = None;
|
||||
let mut format = None;
|
||||
let mut context = None;
|
||||
let tabsize_re = Regex::new(r"^--tabsize=(?<num>\d+)$").unwrap();
|
||||
while let Some(param) = opts.next() {
|
||||
let next_param = opts.peek();
|
||||
if param == "--" {
|
||||
break;
|
||||
}
|
||||
@@ -87,6 +79,20 @@ pub fn parse_params<I: IntoIterator<Item = OsString>>(opts: I) -> Result<Params,
|
||||
params.expand_tabs = true;
|
||||
continue;
|
||||
}
|
||||
if param == "--normal" {
|
||||
if format.is_some() && format != Some(Format::Normal) {
|
||||
return Err("Conflicting output style options".to_string());
|
||||
}
|
||||
format = Some(Format::Normal);
|
||||
continue;
|
||||
}
|
||||
if param == "-e" || param == "--ed" {
|
||||
if format.is_some() && format != Some(Format::Ed) {
|
||||
return Err("Conflicting output style options".to_string());
|
||||
}
|
||||
format = Some(Format::Ed);
|
||||
continue;
|
||||
}
|
||||
if tabsize_re.is_match(param.to_string_lossy().as_ref()) {
|
||||
// Because param matches the regular expression,
|
||||
// it is safe to assume it is valid UTF-8.
|
||||
@@ -103,60 +109,48 @@ pub fn parse_params<I: IntoIterator<Item = OsString>>(opts: I) -> Result<Params,
|
||||
};
|
||||
continue;
|
||||
}
|
||||
let p = osstr_bytes(¶m);
|
||||
if p.first() == Some(&b'-') && p.get(1) != Some(&b'-') {
|
||||
let mut bit = p[1..].iter().copied().peekable();
|
||||
// Can't use a for loop because `diff -30u` is supposed to make a diff
|
||||
// with 30 lines of context.
|
||||
while let Some(b) = bit.next() {
|
||||
match b {
|
||||
b'0'..=b'9' => {
|
||||
params.context_count = (b - b'0') as usize;
|
||||
while let Some(b'0'..=b'9') = bit.peek() {
|
||||
params.context_count *= 10;
|
||||
params.context_count += (bit.next().unwrap() - b'0') as usize;
|
||||
}
|
||||
match match_context_diff_params(¶m, next_param, format) {
|
||||
Ok(DiffStyleMatch {
|
||||
is_match,
|
||||
context_count,
|
||||
next_param_consumed,
|
||||
}) => {
|
||||
if is_match {
|
||||
format = Some(Format::Context);
|
||||
if context_count.is_some() {
|
||||
context = context_count;
|
||||
}
|
||||
b'c' => {
|
||||
if format.is_some() && format != Some(Format::Context) {
|
||||
return Err("Conflicting output style options".to_string());
|
||||
}
|
||||
format = Some(Format::Context);
|
||||
if next_param_consumed {
|
||||
opts.next();
|
||||
}
|
||||
b'e' => {
|
||||
if format.is_some() && format != Some(Format::Ed) {
|
||||
return Err("Conflicting output style options".to_string());
|
||||
}
|
||||
format = Some(Format::Ed);
|
||||
}
|
||||
b'u' => {
|
||||
if format.is_some() && format != Some(Format::Unified) {
|
||||
return Err("Conflicting output style options".to_string());
|
||||
}
|
||||
format = Some(Format::Unified);
|
||||
}
|
||||
b'U' => {
|
||||
if format.is_some() && format != Some(Format::Unified) {
|
||||
return Err("Conflicting output style options".to_string());
|
||||
}
|
||||
format = Some(Format::Unified);
|
||||
let context_count_maybe = if bit.peek().is_some() {
|
||||
String::from_utf8(bit.collect::<Vec<u8>>()).ok()
|
||||
} else {
|
||||
opts.next().map(|x| x.to_string_lossy().into_owned())
|
||||
};
|
||||
if let Some(context_count_maybe) =
|
||||
context_count_maybe.and_then(|x| x.parse().ok())
|
||||
{
|
||||
params.context_count = context_count_maybe;
|
||||
break;
|
||||
}
|
||||
return Err("Invalid context count".to_string());
|
||||
}
|
||||
_ => return Err(format!("Unknown option: {}", String::from_utf8_lossy(&[b]))),
|
||||
continue;
|
||||
}
|
||||
}
|
||||
} else if from.is_none() {
|
||||
Err(error) => return Err(error),
|
||||
}
|
||||
match match_unified_diff_params(¶m, next_param, format) {
|
||||
Ok(DiffStyleMatch {
|
||||
is_match,
|
||||
context_count,
|
||||
next_param_consumed,
|
||||
}) => {
|
||||
if is_match {
|
||||
format = Some(Format::Unified);
|
||||
if context_count.is_some() {
|
||||
context = context_count;
|
||||
}
|
||||
if next_param_consumed {
|
||||
opts.next();
|
||||
}
|
||||
continue;
|
||||
}
|
||||
}
|
||||
Err(error) => return Err(error),
|
||||
}
|
||||
if param.to_string_lossy().starts_with('-') {
|
||||
return Err(format!("Unknown option: {:?}", param));
|
||||
}
|
||||
if from.is_none() {
|
||||
from = Some(param);
|
||||
} else if to.is_none() {
|
||||
to = Some(param);
|
||||
@@ -178,10 +172,125 @@ pub fn parse_params<I: IntoIterator<Item = OsString>>(opts: I) -> Result<Params,
|
||||
} else {
|
||||
return Err(format!("Usage: {} <from> <to>", exe.to_string_lossy()));
|
||||
};
|
||||
|
||||
// diff DIRECTORY FILE => diff DIRECTORY/FILE FILE
|
||||
// diff FILE DIRECTORY => diff FILE DIRECTORY/FILE
|
||||
let mut from_path: PathBuf = PathBuf::from(¶ms.from);
|
||||
let mut to_path: PathBuf = PathBuf::from(¶ms.to);
|
||||
|
||||
if from_path.is_dir() && to_path.is_file() {
|
||||
from_path.push(to_path.file_name().unwrap());
|
||||
params.from = from_path.into_os_string();
|
||||
} else if from_path.is_file() && to_path.is_dir() {
|
||||
to_path.push(from_path.file_name().unwrap());
|
||||
params.to = to_path.into_os_string();
|
||||
}
|
||||
|
||||
params.format = format.unwrap_or(Format::default());
|
||||
if let Some(context_count) = context {
|
||||
params.context_count = context_count;
|
||||
}
|
||||
Ok(params)
|
||||
}
|
||||
|
||||
struct DiffStyleMatch {
|
||||
is_match: bool,
|
||||
context_count: Option<usize>,
|
||||
next_param_consumed: bool,
|
||||
}
|
||||
|
||||
fn match_context_diff_params(
|
||||
param: &OsString,
|
||||
next_param: Option<&OsString>,
|
||||
format: Option<Format>,
|
||||
) -> Result<DiffStyleMatch, String> {
|
||||
const CONTEXT_RE: &str = r"^(-[cC](?<num1>\d*)|--context(=(?<num2>\d*))?|-(?<num3>\d+)c)$";
|
||||
let regex = Regex::new(CONTEXT_RE).unwrap();
|
||||
let is_match = regex.is_match(param.to_string_lossy().as_ref());
|
||||
let mut context_count = None;
|
||||
let mut next_param_consumed = false;
|
||||
if is_match {
|
||||
if format.is_some() && format != Some(Format::Context) {
|
||||
return Err("Conflicting output style options".to_string());
|
||||
}
|
||||
let captures = regex.captures(param.to_str().unwrap()).unwrap();
|
||||
let num = captures
|
||||
.name("num1")
|
||||
.or(captures.name("num2"))
|
||||
.or(captures.name("num3"));
|
||||
if let Some(numvalue) = num {
|
||||
if !numvalue.as_str().is_empty() {
|
||||
context_count = Some(numvalue.as_str().parse::<usize>().unwrap());
|
||||
}
|
||||
}
|
||||
if param == "-C" && next_param.is_some() {
|
||||
match next_param.unwrap().to_string_lossy().parse::<usize>() {
|
||||
Ok(context_size) => {
|
||||
context_count = Some(context_size);
|
||||
next_param_consumed = true;
|
||||
}
|
||||
Err(_) => {
|
||||
return Err(format!(
|
||||
"invalid context length '{}'",
|
||||
next_param.unwrap().to_string_lossy()
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(DiffStyleMatch {
|
||||
is_match,
|
||||
context_count,
|
||||
next_param_consumed,
|
||||
})
|
||||
}
|
||||
|
||||
fn match_unified_diff_params(
|
||||
param: &OsString,
|
||||
next_param: Option<&OsString>,
|
||||
format: Option<Format>,
|
||||
) -> Result<DiffStyleMatch, String> {
|
||||
const UNIFIED_RE: &str = r"^(-[uU](?<num1>\d*)|--unified(=(?<num2>\d*))?|-(?<num3>\d+)u)$";
|
||||
let regex = Regex::new(UNIFIED_RE).unwrap();
|
||||
let is_match = regex.is_match(param.to_string_lossy().as_ref());
|
||||
let mut context_count = None;
|
||||
let mut next_param_consumed = false;
|
||||
if is_match {
|
||||
if format.is_some() && format != Some(Format::Unified) {
|
||||
return Err("Conflicting output style options".to_string());
|
||||
}
|
||||
let captures = regex.captures(param.to_str().unwrap()).unwrap();
|
||||
let num = captures
|
||||
.name("num1")
|
||||
.or(captures.name("num2"))
|
||||
.or(captures.name("num3"));
|
||||
if let Some(numvalue) = num {
|
||||
if !numvalue.as_str().is_empty() {
|
||||
context_count = Some(numvalue.as_str().parse::<usize>().unwrap());
|
||||
}
|
||||
}
|
||||
if param == "-U" && next_param.is_some() {
|
||||
match next_param.unwrap().to_string_lossy().parse::<usize>() {
|
||||
Ok(context_size) => {
|
||||
context_count = Some(context_size);
|
||||
next_param_consumed = true;
|
||||
}
|
||||
Err(_) => {
|
||||
return Err(format!(
|
||||
"invalid context length '{}'",
|
||||
next_param.unwrap().to_string_lossy()
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(DiffStyleMatch {
|
||||
is_match,
|
||||
context_count,
|
||||
next_param_consumed,
|
||||
})
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
@@ -198,20 +307,148 @@ mod tests {
|
||||
}),
|
||||
parse_params([os("diff"), os("foo"), os("bar")].iter().cloned())
|
||||
);
|
||||
}
|
||||
#[test]
|
||||
fn basics_ed() {
|
||||
assert_eq!(
|
||||
Ok(Params {
|
||||
from: os("foo"),
|
||||
to: os("bar"),
|
||||
format: Format::Ed,
|
||||
..Default::default()
|
||||
}),
|
||||
parse_params([os("diff"), os("-e"), os("foo"), os("bar")].iter().cloned())
|
||||
parse_params(
|
||||
[os("diff"), os("--normal"), os("foo"), os("bar")]
|
||||
.iter()
|
||||
.cloned()
|
||||
)
|
||||
);
|
||||
}
|
||||
#[test]
|
||||
fn basics_ed() {
|
||||
for arg in ["-e", "--ed"] {
|
||||
assert_eq!(
|
||||
Ok(Params {
|
||||
from: os("foo"),
|
||||
to: os("bar"),
|
||||
format: Format::Ed,
|
||||
..Default::default()
|
||||
}),
|
||||
parse_params([os("diff"), os(arg), os("foo"), os("bar")].iter().cloned())
|
||||
);
|
||||
}
|
||||
}
|
||||
#[test]
|
||||
fn context_valid() {
|
||||
for args in [vec!["-c"], vec!["--context"], vec!["--context="]] {
|
||||
let mut params = vec!["diff"];
|
||||
params.extend(args);
|
||||
params.extend(["foo", "bar"]);
|
||||
assert_eq!(
|
||||
Ok(Params {
|
||||
from: os("foo"),
|
||||
to: os("bar"),
|
||||
format: Format::Context,
|
||||
..Default::default()
|
||||
}),
|
||||
parse_params(params.iter().map(|x| os(x)))
|
||||
);
|
||||
}
|
||||
for args in [
|
||||
vec!["-c42"],
|
||||
vec!["-C42"],
|
||||
vec!["-C", "42"],
|
||||
vec!["--context=42"],
|
||||
vec!["-42c"],
|
||||
] {
|
||||
let mut params = vec!["diff"];
|
||||
params.extend(args);
|
||||
params.extend(["foo", "bar"]);
|
||||
assert_eq!(
|
||||
Ok(Params {
|
||||
from: os("foo"),
|
||||
to: os("bar"),
|
||||
format: Format::Context,
|
||||
context_count: 42,
|
||||
..Default::default()
|
||||
}),
|
||||
parse_params(params.iter().map(|x| os(x)))
|
||||
);
|
||||
}
|
||||
}
|
||||
#[test]
|
||||
fn context_invalid() {
|
||||
for args in [
|
||||
vec!["-c", "42"],
|
||||
vec!["-c=42"],
|
||||
vec!["-c="],
|
||||
vec!["-C"],
|
||||
vec!["-C=42"],
|
||||
vec!["-C="],
|
||||
vec!["--context42"],
|
||||
vec!["--context", "42"],
|
||||
vec!["-42C"],
|
||||
] {
|
||||
let mut params = vec!["diff"];
|
||||
params.extend(args);
|
||||
params.extend(["foo", "bar"]);
|
||||
assert!(parse_params(params.iter().map(|x| os(x))).is_err());
|
||||
}
|
||||
}
|
||||
#[test]
|
||||
fn unified_valid() {
|
||||
for args in [vec!["-u"], vec!["--unified"], vec!["--unified="]] {
|
||||
let mut params = vec!["diff"];
|
||||
params.extend(args);
|
||||
params.extend(["foo", "bar"]);
|
||||
assert_eq!(
|
||||
Ok(Params {
|
||||
from: os("foo"),
|
||||
to: os("bar"),
|
||||
format: Format::Unified,
|
||||
..Default::default()
|
||||
}),
|
||||
parse_params(params.iter().map(|x| os(x)))
|
||||
);
|
||||
}
|
||||
for args in [
|
||||
vec!["-u42"],
|
||||
vec!["-U42"],
|
||||
vec!["-U", "42"],
|
||||
vec!["--unified=42"],
|
||||
vec!["-42u"],
|
||||
] {
|
||||
let mut params = vec!["diff"];
|
||||
params.extend(args);
|
||||
params.extend(["foo", "bar"]);
|
||||
assert_eq!(
|
||||
Ok(Params {
|
||||
from: os("foo"),
|
||||
to: os("bar"),
|
||||
format: Format::Unified,
|
||||
context_count: 42,
|
||||
..Default::default()
|
||||
}),
|
||||
parse_params(params.iter().map(|x| os(x)))
|
||||
);
|
||||
}
|
||||
}
|
||||
#[test]
|
||||
fn unified_invalid() {
|
||||
for args in [
|
||||
vec!["-u", "42"],
|
||||
vec!["-u=42"],
|
||||
vec!["-u="],
|
||||
vec!["-U"],
|
||||
vec!["-U=42"],
|
||||
vec!["-U="],
|
||||
vec!["--unified42"],
|
||||
vec!["--unified", "42"],
|
||||
vec!["-42U"],
|
||||
] {
|
||||
let mut params = vec!["diff"];
|
||||
params.extend(args);
|
||||
params.extend(["foo", "bar"]);
|
||||
assert!(parse_params(params.iter().map(|x| os(x))).is_err());
|
||||
}
|
||||
}
|
||||
#[test]
|
||||
fn context_count() {
|
||||
assert_eq!(
|
||||
Ok(Params {
|
||||
@@ -504,7 +741,15 @@ mod tests {
|
||||
}
|
||||
#[test]
|
||||
fn conflicting_output_styles() {
|
||||
for (arg1, arg2) in [("-u", "-c"), ("-u", "-e"), ("-c", "-u"), ("-c", "-U42")] {
|
||||
for (arg1, arg2) in [
|
||||
("-u", "-c"),
|
||||
("-u", "-e"),
|
||||
("-c", "-u"),
|
||||
("-c", "-U42"),
|
||||
("-u", "--normal"),
|
||||
("--normal", "-e"),
|
||||
("--context", "--normal"),
|
||||
] {
|
||||
assert!(parse_params(
|
||||
[os("diff"), os(arg1), os(arg2), os("foo"), os("bar")]
|
||||
.iter()
|
||||
|
||||
+56
-5
@@ -6,8 +6,9 @@
|
||||
use assert_cmd::cmd::Command;
|
||||
use diffutilslib::assert_diff_eq;
|
||||
use predicates::prelude::*;
|
||||
use std::fs::File;
|
||||
use std::io::Write;
|
||||
use tempfile::NamedTempFile;
|
||||
use tempfile::{tempdir, NamedTempFile};
|
||||
|
||||
// Integration tests for the diffutils command
|
||||
|
||||
@@ -18,7 +19,7 @@ fn unknown_param() -> Result<(), Box<dyn std::error::Error>> {
|
||||
cmd.assert()
|
||||
.code(predicate::eq(2))
|
||||
.failure()
|
||||
.stderr(predicate::str::starts_with("Usage: "));
|
||||
.stderr(predicate::str::starts_with("Unknown option: \"--foobar\""));
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -26,22 +27,26 @@ fn unknown_param() -> Result<(), Box<dyn std::error::Error>> {
|
||||
fn cannot_read_files() -> Result<(), Box<dyn std::error::Error>> {
|
||||
let file = NamedTempFile::new()?;
|
||||
|
||||
let nofile = NamedTempFile::new()?;
|
||||
let nopath = nofile.into_temp_path();
|
||||
std::fs::remove_file(&nopath)?;
|
||||
|
||||
let mut cmd = Command::cargo_bin("diffutils")?;
|
||||
cmd.arg("foo.txt").arg(file.path());
|
||||
cmd.arg(&nopath).arg(file.path());
|
||||
cmd.assert()
|
||||
.code(predicate::eq(2))
|
||||
.failure()
|
||||
.stderr(predicate::str::starts_with("Failed to read from-file"));
|
||||
|
||||
let mut cmd = Command::cargo_bin("diffutils")?;
|
||||
cmd.arg(file.path()).arg("foo.txt");
|
||||
cmd.arg(file.path()).arg(&nopath);
|
||||
cmd.assert()
|
||||
.code(predicate::eq(2))
|
||||
.failure()
|
||||
.stderr(predicate::str::starts_with("Failed to read to-file"));
|
||||
|
||||
let mut cmd = Command::cargo_bin("diffutils")?;
|
||||
cmd.arg("foo.txt").arg("foo.txt");
|
||||
cmd.arg(&nopath).arg(&nopath);
|
||||
cmd.assert()
|
||||
.code(predicate::eq(2))
|
||||
.failure()
|
||||
@@ -234,3 +239,49 @@ fn read_from_stdin() -> Result<(), Box<dyn std::error::Error>> {
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn compare_file_to_directory() -> Result<(), Box<dyn std::error::Error>> {
|
||||
let tmp_dir = tempdir()?;
|
||||
|
||||
let directory = tmp_dir.path().join("d");
|
||||
let _ = std::fs::create_dir(&directory);
|
||||
|
||||
let a_path = tmp_dir.path().join("a");
|
||||
let mut a = File::create(&a_path).unwrap();
|
||||
a.write_all(b"a\n").unwrap();
|
||||
|
||||
let da_path = directory.join("a");
|
||||
let mut da = File::create(&da_path).unwrap();
|
||||
da.write_all(b"da\n").unwrap();
|
||||
|
||||
let mut cmd = Command::cargo_bin("diffutils")?;
|
||||
cmd.arg("-u").arg(&directory).arg(&a_path);
|
||||
cmd.assert().code(predicate::eq(1)).failure();
|
||||
|
||||
let output = cmd.output().unwrap().stdout;
|
||||
assert_diff_eq!(
|
||||
output,
|
||||
format!(
|
||||
"--- {}\tTIMESTAMP\n+++ {}\tTIMESTAMP\n@@ -1 +1 @@\n-da\n+a\n",
|
||||
da_path.display(),
|
||||
a_path.display()
|
||||
)
|
||||
);
|
||||
|
||||
let mut cmd = Command::cargo_bin("diffutils")?;
|
||||
cmd.arg("-u").arg(&a_path).arg(&directory);
|
||||
cmd.assert().code(predicate::eq(1)).failure();
|
||||
|
||||
let output = cmd.output().unwrap().stdout;
|
||||
assert_diff_eq!(
|
||||
output,
|
||||
format!(
|
||||
"--- {}\tTIMESTAMP\n+++ {}\tTIMESTAMP\n@@ -1 +1 @@\n-a\n+da\n",
|
||||
a_path.display(),
|
||||
da_path.display()
|
||||
)
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user