patch 0.7.0

Parse patches in the unified diff format
Documentation
use chrono::DateTime;
use patch::{File, FileMetadata, ParseError, Patch};

use pretty_assertions::assert_eq;

#[test]
fn test_parse() -> Result<(), ParseError<'static>> {
    let sample = "\
--- before.py
+++ after.py
@@ -1,4 +1,4 @@
-bacon
-eggs
-ham
+python
+eggy
+hamster
 guido\n";
    let patch = Patch::from_single(sample)?;
    assert_eq!(
        patch.old,
        File {
            path: "before.py".into(),
            meta: None
        }
    );
    assert_eq!(
        patch.new,
        File {
            path: "after.py".into(),
            meta: None
        }
    );
    assert!(patch.end_newline);

    assert_eq!(format!("{}\n", patch), sample);

    Ok(())
}

#[test]
fn test_parse_no_newline_indicator() -> Result<(), ParseError<'static>> {
    let sample = "\
--- before.py
+++ after.py
@@ -1,4 +1,4 @@
-bacon
-eggs
-ham
+python
+eggy
+hamster
 guido
\\ No newline at end of file\n";
    let patch = Patch::from_single(sample)?;
    assert_eq!(
        patch.old,
        File {
            path: "before.py".into(),
            meta: None
        }
    );
    assert_eq!(
        patch.new,
        File {
            path: "after.py".into(),
            meta: None
        }
    );
    assert!(!patch.end_newline);

    assert_eq!(format!("{}\n", patch), sample);

    Ok(())
}

#[test]
fn test_parse_timestamps() -> Result<(), ParseError<'static>> {
    let sample = "\
--- before.py\t2002-02-21 23:30:39.942229878 -0800
+++ after.py\t2002-02-21 23:30:50 -0800
@@ -1,4 +1,4 @@
-bacon
-eggs
-ham
+python
+eggy
+hamster
 guido
\\ No newline at end of file";
    let patch = Patch::from_single(sample)?;
    assert_eq!(
        patch.old,
        File {
            path: "before.py".into(),
            meta: Some(FileMetadata::DateTime(
                DateTime::parse_from_rfc3339("2002-02-21T23:30:39.942229878-08:00").unwrap()
            )),
        }
    );
    assert_eq!(
        patch.new,
        File {
            path: "after.py".into(),
            meta: Some(FileMetadata::DateTime(
                DateTime::parse_from_rfc3339("2002-02-21T23:30:50-08:00").unwrap()
            )),
        }
    );
    assert!(!patch.end_newline);

    // to_string() uses Display but adds no trailing newline
    assert_eq!(patch.to_string(), sample);

    Ok(())
}

#[test]
fn test_parse_other() -> Result<(), ParseError<'static>> {
    let sample = "\
--- before.py\t08f78e0addd5bf7b7aa8887e406493e75e8d2b55
+++ after.py\te044048282ce75186ecc7a214fd3d9ba478a2816
@@ -1,4 +1,4 @@
-bacon
-eggs
-ham
+python
+eggy
+hamster
 guido\n";
    let patch = Patch::from_single(sample)?;
    assert_eq!(
        patch.old,
        File {
            path: "before.py".into(),
            meta: Some(FileMetadata::Other(
                "08f78e0addd5bf7b7aa8887e406493e75e8d2b55".into()
            )),
        }
    );
    assert_eq!(
        patch.new,
        File {
            path: "after.py".into(),
            meta: Some(FileMetadata::Other(
                "e044048282ce75186ecc7a214fd3d9ba478a2816".into()
            )),
        }
    );
    assert!(patch.end_newline);

    assert_eq!(format!("{}\n", patch), sample);

    Ok(())
}

#[test]
fn test_parse_escaped() -> Result<(), ParseError<'static>> {
    let sample = "\
--- before.py\t\"asdf \\\\ \\n \\t \\0 \\r \\\" \"
+++ \"My Work/after.py\"\t\"My project is cool! Wow!!; SELECT * FROM USERS;\"
@@ -1,4 +1,4 @@
-bacon
-eggs
-ham
+python
+eggy
+hamster
 guido\n";
    let patch = Patch::from_single(sample)?;
    assert_eq!(
        patch.old,
        File {
            path: "before.py".into(),
            meta: Some(FileMetadata::Other("asdf \\ \n \t \0 \r \" ".into())),
        }
    );
    assert_eq!(
        patch.new,
        File {
            path: "My Work/after.py".into(),
            meta: Some(FileMetadata::Other(
                "My project is cool! Wow!!; SELECT * FROM USERS;".into()
            )),
        }
    );
    assert!(patch.end_newline);

    assert_eq!(format!("{}\n", patch), sample);

    Ok(())
}

#[test]
fn test_parse_triple_plus_minus() -> Result<(), ParseError<'static>> {
    // Our parser has some hacky rules to make sure that lines starting with +++ or --- aren't
    // interpreted as regular addition/removal lines that could be part of a hunk. This test checks
    // to make sure that code is working.
    let sample = r#"--- main.c
+++ main.c
@@ -1,4 +1,7 @@
+#include<stdio.h>
+
 int main() {
 double a;
---a;
+++a;
+printf("%d\n", a);
 }
"#;
    let patches = Patch::from_multiple(sample).unwrap();
    assert_eq!(patches.len(), 1);

    let patch = &patches[0];
    assert_eq!(
        patch.old,
        File {
            path: "main.c".into(),
            meta: None
        }
    );
    assert_eq!(
        patch.new,
        File {
            path: "main.c".into(),
            meta: None
        }
    );
    assert!(patch.end_newline);

    assert_eq!(patch.hunks.len(), 1);
    assert_eq!(patch.hunks[0].lines.len(), 8);

    assert_eq!(format!("{}\n", patch), sample);

    Ok(())
}

//FIXME: This test should NOT panic. When we have more sophisticated chunk line parsing that
// actually takes the hunk ranges into account, the #[should_panic] annotation should be removed.
// See the FIXME comment on top of the chunk_line parser.
#[test]
#[should_panic]
fn test_parse_triple_plus_minus_hack() {
    // Our parser has some hacky rules to make sure that lines starting with +++ or --- aren't
    // interpreted as regular addition/removal lines that could be part of a hunk. This test
    // demonstrates that these rules are not foolproof. The only differences between this test and
    // test_parse_triple_plus_minus are `--- a` and `+++ a` vs `---a` and `+++a`. In either case,
    // we should be able to determine that those lines do not start a new patch based on the ranges
    // provided for the hunk.
    let sample = r#"--- main.c
+++ main.c
@@ -1,4 +1,7 @@
+#include<stdio.h>
+
 int main() {
 double a;
--- a;
+++ a;
+printf("%d\n", a);
 }
"#;
    let patches = Patch::from_multiple(sample).unwrap();
    assert_eq!(patches.len(), 1);

    let patch = &patches[0];
    assert_eq!(
        patch.old,
        File {
            path: "main.c".into(),
            meta: None
        }
    );
    assert_eq!(
        patch.new,
        File {
            path: "main.c".into(),
            meta: None
        }
    );
    assert!(patch.end_newline);

    assert_eq!(patch.hunks.len(), 1);
    assert_eq!(patch.hunks[0].lines.len(), 8);

    assert_eq!(format!("{}\n", patch), sample);
}