aboutsummaryrefslogtreecommitdiffstats
path: root/src/lib.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/lib.rs')
-rw-r--r--src/lib.rs447
1 files changed, 3 insertions, 444 deletions
diff --git a/src/lib.rs b/src/lib.rs
index 31df669..4a15852 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -1,18 +1,15 @@
#![warn(rust_2018_idioms)]
+pub mod suggestion;
+
mod url;
+pub use crate::suggestion::Suggestion;
pub use crate::url::SuggestionUrl;
-use std::io::{BufRead, BufReader, BufWriter, Write};
-use std::path::Path;
-
-use git2::{Patch, Repository};
use github_rs::client::{Executor, Github};
-use regex::Regex;
-use serde::Deserialize;
use serde_json::Value;
use thiserror::Error;
@@ -63,182 +60,6 @@ impl<'a> Client<'a> {
}
}
-#[derive(Debug, PartialEq)]
-enum LineEnding {
- Lf,
- CrLf,
-}
-
-#[derive(Debug, Deserialize)]
-pub struct Suggestion {
- #[serde(rename = "diff_hunk")]
- diff: String,
-
- #[serde(rename = "body")]
- comment: String,
-
- #[serde(rename = "original_commit_id")]
- commit: String,
-
- path: String,
-
- original_start_line: Option<usize>,
-
- #[serde(rename = "original_line")]
- original_end_line: usize,
-}
-
-impl Suggestion {
- // TODO: Rename to `diff`
- pub fn patch(&self) -> String {
- let mut diff: Vec<_> = self.diff.lines()
- .filter(|l| !l.starts_with("-"))
- .map(|l| {
- if l.starts_with("+") {
- return l.replacen("+", " ", 1);
- }
-
- l.to_owned()
- })
- .collect();
-
- let last = diff.len() - 1;
- diff[last] = diff.last().unwrap()
- .replacen(" ", "-", 1);
-
- diff.push(self.suggestion_patch());
-
- diff.join("\n")
- }
-
- pub fn diff(&self) -> String {
- let repo = Repository::open(".").unwrap();
-
- self.diff_with_repo(&repo)
- }
-
- fn diff_with_repo(&self, repo: &Repository) -> String {
- let commit = repo.find_commit(self.commit.parse().unwrap()).unwrap();
-
- let path = Path::new(&self.path);
-
- let object = commit
- .tree().unwrap()
- .get_path(path).unwrap()
- .to_object(&repo).unwrap();
-
- let blob = object.as_blob().unwrap();
-
- let blob_reader = BufReader::new(blob.content());
- let mut new = BufWriter::new(Vec::new());
- self.apply_to(blob_reader, &mut new).unwrap();
- let new_buffer = new.into_inner().unwrap();
-
- let mut diff = Patch::from_blob_and_buffer(
- blob,
- Some(&path),
- &new_buffer,
- Some(&path),
- None,
- ).unwrap();
-
- diff.to_buf()
- .unwrap()
- .as_str()
- .unwrap_or("")
- .to_owned()
- }
-
- fn suggestion_patch(&self) -> String {
- let re = Regex::new(r"(?s).*(?-s)```\s*suggestion.*\n").unwrap();
- let s = re.replace(&self.comment, "+");
- s.replace("```", "")
- }
-
- fn suggestion(&self) -> String {
- self.suggestion_with_line_ending(&LineEnding::Lf)
- }
-
- fn suggestion_with_line_ending(&self, line_ending: &LineEnding) -> String {
- let re = Regex::new(r"(?s).*(?-s)```\s*suggestion.*\n").unwrap();
- let s = re.replace(&self.comment, "");
- let s = s.replace("```", "");
-
- // Suggestion blocks use CRLF by default.
- if *line_ending == LineEnding::Lf {
- return s.replace('\r', "");
- }
-
- s
- }
-
- pub fn apply(&self) -> Result<(), Error> {
- let repo = Repository::open(".").unwrap();
-
- let diff_text = self.diff_with_repo(&repo);
- let diff = git2::Diff::from_buffer(diff_text.as_bytes()).unwrap();
-
- repo.apply(
- &diff,
- git2::ApplyLocation::WorkDir,
- None,
- ).unwrap();
-
- Ok(())
- }
-
- fn apply_to<R: BufRead, W: Write>(
- &self,
- reader: R,
- writer: &mut W,
- ) -> Result<(), Error> {
- let mut line_ending = LineEnding::Lf;
-
- for (i, line) in reader.lines().enumerate() {
- let line_number = i + 1;
-
- match line {
- Ok(l) => {
- // Determine which line endings the file uses by looking at
- // the first line.
- if line_number == 1 && is_line_crlf(&l) {
- line_ending = LineEnding::CrLf;
- }
-
- if line_number == self.original_end_line {
- write!(
- writer,
- "{}",
- self.suggestion_with_line_ending(&line_ending),
- ).unwrap();
- } else if self.original_start_line.is_none()
- || line_number < self.original_start_line.unwrap()
- || line_number > self.original_end_line {
- writeln!(writer, "{}", l).unwrap();
- }
- },
- Err(e) => panic!(e),
- }
- }
-
- Ok(())
- }
-}
-
-/// Determine the line ending for `line`.
-///
-/// If the second-to-last character on the first line is "\r", assume CRLF.
-/// Otherwise, default to LF.
-fn is_line_crlf(line: &str) -> bool {
- if let Some(c) = line.chars().rev().nth(2) {
- if c == '\r' {
- return true;
- }
- }
-
- false
-}
-
#[cfg(test)]
mod tests {
use super::*;
@@ -256,266 +77,4 @@ mod tests {
println!("{:?}", suggestion);
}
-
- #[test]
- fn suggestion_patch_generates_patch() {
- // Diff from gabgodBB (https://github.com/gabgodBB) and suggestion from
- // probablycorey (https://github.com/probablycorey) in this pull
- // request: https://github.com/cli/cli/pull/1123
-
- let suggestion = Suggestion {
- diff: r#"@@ -1, 9 +1, 11 @@
- package command
-
- import (
-+ "bufio" // used to input comment
- "errors"
- "fmt"
- "io"
-+ "os" // used to input comment"#.to_owned(),
- comment: r#"It's ok to leave these uncommented
-
-```suggestion
- "os"
-```"#.to_owned(),
- commit: "".to_owned(),
- path: "".to_owned(),
- original_start_line: Some(8),
- original_end_line: 8,
- };
-
- assert_eq!(
- suggestion.patch(),
- r#"@@ -1, 9 +1, 11 @@
- package command
-
- import (
- "bufio" // used to input comment
- "errors"
- "fmt"
- "io"
-- "os" // used to input comment
-+ "os"
-"#,
- );
- }
-
- #[test]
- fn unified_diff() {
- use unidiff::PatchSet;
-
- let diff = r#"--- a/command/pr.go
-+++ b/command/pr.go
-@@ -1,9 +1,11 @@
- package command
-
- import (
-+ "bufio" // used to input comment
- "errors"
- "fmt"
- "io"
-+ "os" // used to input comment
-"#;
-
- let mut patch = PatchSet::new();
- patch.parse(diff).unwrap();
-
- println!("{:?}", patch);
- println!("{}", patch);
-
- let lines = patch.files_mut()[0].hunks_mut()[0].lines_mut();
-
- // for line in &lines {
- // if line.is_removed() {
- // } else if line.is_added() {
- // line.line_type = unidiff::LINE_TYPE_CONTEXT.to_owned();
- // }
- // }
-
- lines
- .iter_mut()
- .filter(|l| !l.is_removed())
- // .map(|l| {
- .for_each(|l| {
- if l.is_added() {
- l.line_type = unidiff::LINE_TYPE_CONTEXT.to_owned();
- }
- });
-
- lines[lines.len() - 2].line_type = unidiff::LINE_TYPE_REMOVED.to_owned();
-
- patch.files_mut()[0].hunks_mut()[0].append(unidiff::Line::new(
- r#" "os""#,
- unidiff::LINE_TYPE_ADDED,
- ));
-
- println!("{}", patch);
- }
-
- #[test]
- fn read_git_blob() {
- use std::path::Path;
-
- use git2::Repository;
-
- let repo = Repository::open("./private/suggestion-test").unwrap();
- let commit = repo.find_commit("b58be52880a0a0c0d397052351be31f19acdeca4".parse().unwrap()).unwrap();
-
- let object = commit
- .tree().unwrap()
- .get_path(Path::new("src/server.rs")).unwrap()
- .to_object(&repo).unwrap();
-
- let blob = object
- .as_blob().unwrap()
- .content();
-
- println!("{:?}", commit);
- println!("{}", std::str::from_utf8(blob).unwrap());
- }
-
- #[test]
- fn suggestion_diff_with_repo_generates_diff() {
- use tempfile::tempdir;
-
-
- let git_root = tempdir().unwrap();
- let repo = Repository::init(git_root.path()).unwrap();
-
- let file = r#"
- ‘Beware the Jabberwock, my son!
- The jaws that bite, the claws that catch!
- Beware the Jubjub bird, and shun
- The frumious Bandersnatch!’
-
- He took his vorpal blade in hand:
- Long time the manxome foe he sought--
- So rested he by the Tumtum tree,
- And stood awhile in thought.
-"#;
-
- let path = "poems/Jabberwocky.txt";
-
- let mut index = repo.index().unwrap();
- index.add_frombuffer(
- &git2::IndexEntry {
- ctime: git2::IndexTime::new(0, 0),
- mtime: git2::IndexTime::new(0, 0),
- dev: 0,
- ino: 0,
- mode: 0o100644,
- uid: 0,
- gid: 0,
- file_size: file.len() as u32,
- id: git2::Oid::zero(),
- flags: 0,
- flags_extended: 0,
- path: path.as_bytes().to_vec(),
- },
- file.as_bytes(),
- ).unwrap();
- let tree_oid = index.write_tree().unwrap();
- let tree = repo.find_tree(tree_oid).unwrap();
-
- let author = git2::Signature::now(
- "Oshino Shinobu",
- "oshino.shinobu@example.com",
- ).unwrap();
-
- let commit = repo.commit(
- Some("HEAD"),
- &author,
- &author,
- "Sample commit",
- &tree,
- &[],
- ).unwrap();
-
- let suggestion = Suggestion {
- diff: "".to_owned(),
- comment: r#"``` suggestion
- He took his vorpal sword in hand:
- Long time the manxome foe he sought—
-```"#.to_owned(),
- commit: commit.to_string(),
- path: path.to_owned(),
- original_start_line: Some(7),
- original_end_line: 8,
- };
-
- let expected = r#"diff --git a/poems/Jabberwocky.txt b/poems/Jabberwocky.txt
-index 89840a2..06acdfc 100644
---- a/poems/Jabberwocky.txt
-+++ b/poems/Jabberwocky.txt
-@@ -4,7 +4,7 @@
- Beware the Jubjub bird, and shun
- The frumious Bandersnatch!’
-
-- He took his vorpal blade in hand:
-- Long time the manxome foe he sought--
-+ He took his vorpal sword in hand:
-+ Long time the manxome foe he sought—
- So rested he by the Tumtum tree,
- And stood awhile in thought.
-"#;
-
- assert_eq!(
- suggestion.diff_with_repo(&repo),
- expected,
- );
- }
-
- #[test]
- fn suggestion_apply_to_writes_patch_to_writer() {
- use std::io::Cursor;
-
-
- let mut original_buffer = Vec::new();
- let original = r#"
- ‘Beware the Jabberwock, my son!
- The jaws that bite, the claws that catch!
- Beware the Jubjub bird, and shun
- The frumious Bandersnatch!’
-
- He took his vorpal blade in hand:
- Long time the manxome foe he sought--
- So rested he by the Tumtum tree,
- And stood awhile in thought.
-"#;
-
- write!(original_buffer, "{}", original).unwrap();
-
- let suggestion = Suggestion {
- diff: "".to_owned(),
- comment: r#"``` suggestion
- He took his vorpal sword in hand:
- Long time the manxome foe he sought—
-```"#.to_owned(),
- commit: "".to_owned(),
- path: "".to_owned(),
- original_start_line: Some(7),
- original_end_line: 8,
- };
-
- let expected = r#"
- ‘Beware the Jabberwock, my son!
- The jaws that bite, the claws that catch!
- Beware the Jubjub bird, and shun
- The frumious Bandersnatch!’
-
- He took his vorpal sword in hand:
- Long time the manxome foe he sought—
- So rested he by the Tumtum tree,
- And stood awhile in thought.
-"#;
-
- let original_reader = Cursor::new(original_buffer);
- let mut actual = Cursor::new(Vec::new());
- suggestion.apply_to(original_reader, &mut actual).unwrap();
-
- assert_eq!(
- std::str::from_utf8(&actual.into_inner()).unwrap(),
- expected,
- );
- }
}