diff options
| author | Teddy Wing | 2020-08-01 15:32:17 +0200 | 
|---|---|---|
| committer | Teddy Wing | 2020-08-01 15:32:17 +0200 | 
| commit | fdc1cc7bc3d237ef4976f9f9d2ffc09b7ba164d5 (patch) | |
| tree | 4dc68f07d4a2e7ad8193f2b5f9a90c9fe6792aec /src | |
| parent | b218abebdf8a0aed73bfe6f61ab22e51a0f2f43c (diff) | |
| download | git-suggestion-fdc1cc7bc3d237ef4976f9f9d2ffc09b7ba164d5.tar.bz2 | |
Move library to a separate crate
Keep the binaries in the root crate, and add a new crate for the
`github-suggestion` library.
I want to add some library code to the CLI programs to extract common
functionality between the different binaries.
Diffstat (limited to 'src')
| -rw-r--r-- | src/client.rs | 58 | ||||
| -rw-r--r-- | src/lib.rs | 11 | ||||
| -rw-r--r-- | src/suggestion.rs | 346 | ||||
| -rw-r--r-- | src/url.rs | 52 | 
4 files changed, 0 insertions, 467 deletions
| diff --git a/src/client.rs b/src/client.rs deleted file mode 100644 index c6ae1f1..0000000 --- a/src/client.rs +++ /dev/null @@ -1,58 +0,0 @@ -use github_rs::client::{Executor, Github}; -use serde_json::Value; -use thiserror::Error; - -use crate::suggestion::Suggestion; - - -#[derive(Debug, Error)] -pub enum Error { -    #[error("GitHub client error: {0}")] -    Github(String), - -    #[error("Unable to deserialize")] -    Deserialize(#[from] serde_json::error::Error), -} - - -pub struct Client<'a> { -    client: Github, -    owner: &'a str, -    repo: &'a str, -} - -impl<'a> Client<'a> { -    pub fn new( -        token: &str, -        owner: &'a str, repo: &'a str, -    ) -> Result<Self, Error> { -        let client = match Github::new(&token) { -            Ok(g) => g, -            Err(e) => return Err(Error::Github(e.to_string())), -        }; - -        Ok(Client { client, owner, repo }) -    } - -    pub fn fetch(&self, id: &str) -> Result<Suggestion, Error> { -        let response = self.client -            .get() -            .repos() -            .owner(self.owner) -            .repo(self.repo) -            .pulls() -            .comments() -            .id(id) -            .execute::<Value>(); - -        match response { -            Ok((_, _, Some(json))) => { -                let suggestion = serde_json::from_value(json)?; - -                Ok(suggestion) -            }, -            Ok((_, _, None)) => Err(Error::Github("no response".to_owned())), -            Err(e) => Err(Error::Github(e.to_string())), -        } -    } -} diff --git a/src/lib.rs b/src/lib.rs deleted file mode 100644 index c082736..0000000 --- a/src/lib.rs +++ /dev/null @@ -1,11 +0,0 @@ -#![warn(rust_2018_idioms)] - - -pub mod client; -pub mod suggestion; - -mod url; - -pub use crate::client::Client; -pub use crate::suggestion::Suggestion; -pub use crate::url::SuggestionUrl; diff --git a/src/suggestion.rs b/src/suggestion.rs deleted file mode 100644 index 2c92e6a..0000000 --- a/src/suggestion.rs +++ /dev/null @@ -1,346 +0,0 @@ -use std::io::{BufRead, BufReader, BufWriter, Write}; -use std::path::Path; - -use git2::{Patch, Repository}; -use regex::Regex; -use serde::Deserialize; -use thiserror::Error; - - -#[derive(Debug, Error)] -pub enum Error { -    #[error(transparent)] -    Git(#[from] git2::Error), - -    #[error("{0} is not a blob")] -    GitObjectNotBlob(git2::Oid), - -    #[error("{message}")] -    BufWriter { -        source: std::io::IntoInnerError<BufWriter<Vec<u8>>>, -        message: String, -    }, - -    #[error("{message}: {source}")] -    Io { -        source: std::io::Error, -        message: String, -    }, - -    #[error("{0} is not valid UTF-8")] -    InvalidUtf8(String), - -    #[error("Regex error: {0}")] -    Regex(#[from] regex::Error), -} - -#[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 { -    pub fn diff(&self) -> Result<String, Error> { -        let repo = Repository::open(".")?; - -        self.diff_with_repo(&repo) -    } - -    fn diff_with_repo(&self, repo: &Repository) -> Result<String, Error> { -        let commit = repo.find_commit(self.commit.parse()?)?; - -        let path = Path::new(&self.path); - -        let object = commit -            .tree()? -            .get_path(path)? -            .to_object(&repo)?; - -        let blob = object.as_blob() -            .ok_or_else(|| Error::GitObjectNotBlob(object.id()))?; - -        let blob_reader = BufReader::new(blob.content()); -        let mut new = BufWriter::new(Vec::new()); -        self.apply_to(blob_reader, &mut new)?; -        let new_buffer = new.into_inner() -            .map_err(|e| Error::BufWriter { -                source: e, -                message: "unable to read right side of patch".to_owned(), -            })?; - -        let mut diff = Patch::from_blob_and_buffer( -            blob, -            Some(&path), -            &new_buffer, -            Some(&path), -            None, -        )?; - -        Ok( -            diff.to_buf()? -                .as_str() -                .ok_or_else(|| Error::InvalidUtf8("diff".to_owned()))? -                .to_owned() -        ) -    } - -    fn suggestion_with_line_ending( -        &self, -        line_ending: &LineEnding, -    ) -> Result<String, Error> { -        let re = Regex::new(r"(?s).*(?-s)```\s*suggestion.*\n")?; -        let s = re.replace(&self.comment, ""); -        let s = s.replace("```", ""); - -        // Suggestion blocks use CRLF by default. -        if *line_ending == LineEnding::Lf { -            return Ok(s.replace('\r', "")); -        } - -        Ok(s) -    } - -    pub fn apply(&self) -> Result<(), Error> { -        let repo = Repository::open(".")?; - -        let diff_text = self.diff_with_repo(&repo)?; -        let diff = git2::Diff::from_buffer(diff_text.as_bytes())?; - -        repo.apply( -            &diff, -            git2::ApplyLocation::WorkDir, -            None, -        )?; - -        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; - -            let line = line.map_err(|e| Error::Io { -                source: e, -                message: "Unable to read line".to_owned(), -            })?; - -            // Determine which line endings the file uses by looking at the -            // first line. -            if line_number == 1 && is_line_crlf(&line) { -                line_ending = LineEnding::CrLf; -            } - -            if line_number == self.original_end_line { -                write!( -                    writer, -                    "{}", -                    self.suggestion_with_line_ending(&line_ending)?, -                ).map_err(|e| Error::Io { -                    source: e, -                    message: "Write error".to_owned(), -                })?; -            } else if self.original_start_line.is_none() -                    || line_number < self.original_start_line.unwrap() -                    || line_number > self.original_end_line { -                writeln!(writer, "{}", line) -                    .map_err(|e| Error::Io { -                        source: e, -                        message: "Write error".to_owned(), -                    })?; -            } -        } - -        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::*; - -    #[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).unwrap(), -            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, -        ); -    } -} diff --git a/src/url.rs b/src/url.rs deleted file mode 100644 index 60a3d0e..0000000 --- a/src/url.rs +++ /dev/null @@ -1,52 +0,0 @@ -use std::str::FromStr; - -use thiserror::Error; - -use url; -use url::Url; - - -#[derive(Debug, Error)] -pub enum Error { -    #[error("Unable to parse URL")] -    Url(#[from] url::ParseError), - -    #[error("URL has no path")] -    NoPath, - -    #[error("URL has no fragment")] -    NoFragment, - -    #[error("Unable to parse owner or repo")] -    NoOwnerRepo, -} - -#[derive(Debug)] -pub struct SuggestionUrl { -    pub owner: String, -    pub repo: String, -    pub comment_id: String, -} - -impl FromStr for SuggestionUrl { -    type Err = Error; - -    fn from_str(s: &str) -> Result<Self, Self::Err> { -        let url = Url::parse(s)?; -        let path = url.path_segments() -            .ok_or(Error::NoPath)? -            .collect::<Vec<_>>(); - -        if path.len() < 2 { -            return Err(Error::NoOwnerRepo); -        } - -        Ok(SuggestionUrl { -            owner: path[0].to_owned(), -            repo: path[1].to_owned(), -            comment_id: url.fragment() -                .ok_or(Error::NoFragment)? -                .replacen("discussion_r", "", 1), -        }) -    } -} | 
