Loading acorn-cli/src/commands/gather/mod.rs +5 −2 Original line number Diff line number Diff line Loading @@ -30,7 +30,7 @@ pub async fn run( // let langs = gitlab::languages().await?; // dbg!(langs); // gitlab_merge_request_note_example().await; // gitlab_example().await; gitlab_example().await; // orcid_example().await; // ror_example().await; Ok(()) Loading @@ -54,6 +54,7 @@ Here is a list #[allow(dead_code)] async fn gitlab_example() { let group = "24758"; let project = "16689"; let options = gitlab::Options::from_env(); let runners = gitlab::runners(options.clone()).await; println!("Runners: {:#?}", runners); Loading @@ -67,7 +68,9 @@ async fn gitlab_example() { | Err(ref why) => panic!("Runner: Err({why:#?})"), } let options = options.with_identifier(group); println!("Groups: {:#?}", gitlab::groups(options).await); println!("Groups: {:#?}", gitlab::groups(options.clone()).await); let options = options.with_identifier(project); println!("Languages: {:#?}", gitlab::language_use(options).await.unwrap().entries()); } #[allow(dead_code)] async fn orcid_example() { Loading acorn-lib/assets/constants/application.json +5 −0 Original line number Diff line number Diff line Loading @@ -70,6 +70,11 @@ "method": "get", "template": "{{ base }}/runners/{{ identifier }}/jobs{{ query }}" }, { "name": "languages", "method": "get", "template": "{{ base }}/projects/{{ identifier }}/languages{{ query }}" }, { "name": "merge-request-note", "method": "post", Loading acorn-lib/src/io/api/gitlab.rs +66 −0 Original line number Diff line number Diff line Loading @@ -27,6 +27,8 @@ pub type RunnersResponse = Vec<RunnerDetails>; pub type TreeResponse = Vec<TreeEntry>; /// Type for GitLab programming language entries pub type ProgrammingLanguageEntries = Vec<ProgrammingLanguageMetadata>; /// Type for GitLab project programming language usage entries pub type ProgrammingLanguageUseEntries = Vec<ProgrammingLanguageUseMetadata>; /// Access level of the runner #[derive(Clone, Debug, Serialize, Deserialize)] #[serde(rename_all = "snake_case")] Loading Loading @@ -71,6 +73,20 @@ pub struct ProgrammingLanguagesResponse { /// Flattened language metadata entries pub languages: ProgrammingLanguageEntries, } /// Programming language usage entry for a project #[derive(Clone, Debug, Default, Serialize, Deserialize)] pub struct ProgrammingLanguageUseMetadata { /// Display name of the language pub name: String, /// Relative share of repository content for this language pub percentage: f64, } /// Parsed response for GitLab project language usage #[derive(Clone, Debug, Default, Serialize)] pub struct ProgrammingLanguageUseResponse { /// Flattened language usage entries pub languages: ProgrammingLanguageUseEntries, } /// Group visibility level #[derive(Clone, Debug, Default, Serialize, Deserialize)] #[serde(rename_all = "snake_case")] Loading Loading @@ -652,11 +668,41 @@ impl ProgrammingLanguagesResponse { Self { languages } } } impl ProgrammingLanguageUseResponse { /// Parse a raw language-to-percentage map into normalized entries pub fn parse(data: HashMap<String, f64>) -> Self { let mut languages = data .into_iter() .map(|(name, percentage)| ProgrammingLanguageUseMetadata { name, percentage }) .collect::<ProgrammingLanguageUseEntries>(); languages.sort_by(|a, b| a.name.cmp(&b.name)); Self { languages } } /// Get language data entry tuples, (name, percentage), sorted by percentage in descending order pub fn entries(&self) -> Vec<(String, f64)> { let mut entries = self .languages .iter() .map(|ProgrammingLanguageUseMetadata { name, percentage }| (name.clone(), *percentage)) .collect::<Vec<_>>(); entries.sort_by(|a, b| b.1.partial_cmp(&a.1).unwrap_or(core::cmp::Ordering::Equal)); entries } /// Get language names, sorted by percentage in descending order pub fn names(&self) -> Vec<String> { self.entries().into_iter().map(|(name, _)| name).collect() } } impl<'de> serde::Deserialize<'de> for ProgrammingLanguagesResponse { fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> { HashMap::<String, ProgrammingLanguageDetails>::deserialize(deserializer).map(Self::parse) } } impl<'de> serde::Deserialize<'de> for ProgrammingLanguageUseResponse { fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> { HashMap::<String, f64>::deserialize(deserializer).map(Self::parse) } } #[async_trait] impl DatabasePersistence for ProgrammingLanguagesResponse { /// Persist GitLab programming language metadata to local database Loading Loading @@ -781,6 +827,26 @@ pub async fn groups(options: Options) -> ApiResult<GroupsResponse> { | Err(why) => Err(why), } } /// Get programming languages used by a GitLab project /// /// See <https://docs.gitlab.com/api/projects/?utm_source=perplexity#retrieve-programming-language-usage-information> for more information on GitLab API. pub async fn language_use(options: Options) -> ApiResult<ProgrammingLanguageUseResponse> { let action = "languages"; let Options { token, identifier, domain, .. } = options; match Endpoint::from_template("gitlab::api").map(|e| e.with_domain(&domain)) { | Ok(endpoint) => { let params = vec![ param!(Header, "PRIVATE-TOKEN", &token), param!(TemplateValue, "identifier", &identifier.unwrap_or_default()), ]; let response = endpoint.invoke_with::<PaginationField, EmptyField>(action, Some(params)).await; endpoint.handle::<ProgrammingLanguageUseResponse>(response) } | Err(why) => Err(why), } } /// Download programming language metadata from GitLab linguist source file pub async fn languages() -> ApiResult<ProgrammingLanguagesResponse> { let names = ["gitlab::org", "github::org"]; Loading acorn-lib/src/io/api/tests/mod.rs +13 −3 Original line number Diff line number Diff line Loading @@ -213,7 +213,7 @@ mod github_api { #[test] fn test_template_endpoint_for() { let endpoint = Endpoint::from_template("github").map(|e| e.with_domain("api.github.com")).unwrap(); let endpoint = Endpoint::from_template("github::api").map(|e| e.with_domain("api.github.com")).unwrap(); assert_eq!(endpoint.base(), "https://api.github.com"); assert!(endpoint.resources.iter().any(|r| r.name == "tree")); } Loading Loading @@ -264,7 +264,7 @@ mod github_api { #[cfg(test)] mod gitlab_api { use super::*; use crate::io::api::gitlab::{PaginationField, ProgrammingLanguageDetails, ProgrammingLanguagesResponse}; use crate::io::api::gitlab::{PaginationField, ProgrammingLanguageDetails, ProgrammingLanguageUseResponse, ProgrammingLanguagesResponse}; #[test] fn test_query_string() { Loading Loading @@ -320,6 +320,16 @@ mod gitlab_api { assert_eq!(response.languages[0].name, "Python"); assert_eq!(response.languages[0].language_id, Some(303)); } #[test] fn test_programming_language_use_response_deserializes_map() { let json = r#"{"Rust":98.12,"Makefile":0.5,"Python":0.49}"#; let response: ProgrammingLanguageUseResponse = serde_json::from_str(json).expect("should deserialize language usage map"); assert_eq!(response.languages.len(), 3); assert_eq!(response.languages[0].name, "Makefile"); assert_eq!(response.languages[0].percentage, 0.5); assert_eq!(response.languages[1].name, "Python"); assert_eq!(response.languages[2].name, "Rust"); } } #[cfg(test)] mod orcid_api { Loading Loading @@ -666,7 +676,7 @@ mod orcid_api { } #[test] fn test_from_template_with_domain() { let result = Endpoint::from_template("gitlab").map(|e| e.with_domain("custom-gitlab.example.com")); let result = Endpoint::from_template("gitlab::api").map(|e| e.with_domain("custom-gitlab.example.com")); assert!(result.is_ok()); let endpoint = result.unwrap(); assert_eq!(endpoint.domain, "custom-gitlab.example.com"); Loading lcov.info +559 −511 File changed.Preview size limit exceeded, changes collapsed. Show changes Loading
acorn-cli/src/commands/gather/mod.rs +5 −2 Original line number Diff line number Diff line Loading @@ -30,7 +30,7 @@ pub async fn run( // let langs = gitlab::languages().await?; // dbg!(langs); // gitlab_merge_request_note_example().await; // gitlab_example().await; gitlab_example().await; // orcid_example().await; // ror_example().await; Ok(()) Loading @@ -54,6 +54,7 @@ Here is a list #[allow(dead_code)] async fn gitlab_example() { let group = "24758"; let project = "16689"; let options = gitlab::Options::from_env(); let runners = gitlab::runners(options.clone()).await; println!("Runners: {:#?}", runners); Loading @@ -67,7 +68,9 @@ async fn gitlab_example() { | Err(ref why) => panic!("Runner: Err({why:#?})"), } let options = options.with_identifier(group); println!("Groups: {:#?}", gitlab::groups(options).await); println!("Groups: {:#?}", gitlab::groups(options.clone()).await); let options = options.with_identifier(project); println!("Languages: {:#?}", gitlab::language_use(options).await.unwrap().entries()); } #[allow(dead_code)] async fn orcid_example() { Loading
acorn-lib/assets/constants/application.json +5 −0 Original line number Diff line number Diff line Loading @@ -70,6 +70,11 @@ "method": "get", "template": "{{ base }}/runners/{{ identifier }}/jobs{{ query }}" }, { "name": "languages", "method": "get", "template": "{{ base }}/projects/{{ identifier }}/languages{{ query }}" }, { "name": "merge-request-note", "method": "post", Loading
acorn-lib/src/io/api/gitlab.rs +66 −0 Original line number Diff line number Diff line Loading @@ -27,6 +27,8 @@ pub type RunnersResponse = Vec<RunnerDetails>; pub type TreeResponse = Vec<TreeEntry>; /// Type for GitLab programming language entries pub type ProgrammingLanguageEntries = Vec<ProgrammingLanguageMetadata>; /// Type for GitLab project programming language usage entries pub type ProgrammingLanguageUseEntries = Vec<ProgrammingLanguageUseMetadata>; /// Access level of the runner #[derive(Clone, Debug, Serialize, Deserialize)] #[serde(rename_all = "snake_case")] Loading Loading @@ -71,6 +73,20 @@ pub struct ProgrammingLanguagesResponse { /// Flattened language metadata entries pub languages: ProgrammingLanguageEntries, } /// Programming language usage entry for a project #[derive(Clone, Debug, Default, Serialize, Deserialize)] pub struct ProgrammingLanguageUseMetadata { /// Display name of the language pub name: String, /// Relative share of repository content for this language pub percentage: f64, } /// Parsed response for GitLab project language usage #[derive(Clone, Debug, Default, Serialize)] pub struct ProgrammingLanguageUseResponse { /// Flattened language usage entries pub languages: ProgrammingLanguageUseEntries, } /// Group visibility level #[derive(Clone, Debug, Default, Serialize, Deserialize)] #[serde(rename_all = "snake_case")] Loading Loading @@ -652,11 +668,41 @@ impl ProgrammingLanguagesResponse { Self { languages } } } impl ProgrammingLanguageUseResponse { /// Parse a raw language-to-percentage map into normalized entries pub fn parse(data: HashMap<String, f64>) -> Self { let mut languages = data .into_iter() .map(|(name, percentage)| ProgrammingLanguageUseMetadata { name, percentage }) .collect::<ProgrammingLanguageUseEntries>(); languages.sort_by(|a, b| a.name.cmp(&b.name)); Self { languages } } /// Get language data entry tuples, (name, percentage), sorted by percentage in descending order pub fn entries(&self) -> Vec<(String, f64)> { let mut entries = self .languages .iter() .map(|ProgrammingLanguageUseMetadata { name, percentage }| (name.clone(), *percentage)) .collect::<Vec<_>>(); entries.sort_by(|a, b| b.1.partial_cmp(&a.1).unwrap_or(core::cmp::Ordering::Equal)); entries } /// Get language names, sorted by percentage in descending order pub fn names(&self) -> Vec<String> { self.entries().into_iter().map(|(name, _)| name).collect() } } impl<'de> serde::Deserialize<'de> for ProgrammingLanguagesResponse { fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> { HashMap::<String, ProgrammingLanguageDetails>::deserialize(deserializer).map(Self::parse) } } impl<'de> serde::Deserialize<'de> for ProgrammingLanguageUseResponse { fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> { HashMap::<String, f64>::deserialize(deserializer).map(Self::parse) } } #[async_trait] impl DatabasePersistence for ProgrammingLanguagesResponse { /// Persist GitLab programming language metadata to local database Loading Loading @@ -781,6 +827,26 @@ pub async fn groups(options: Options) -> ApiResult<GroupsResponse> { | Err(why) => Err(why), } } /// Get programming languages used by a GitLab project /// /// See <https://docs.gitlab.com/api/projects/?utm_source=perplexity#retrieve-programming-language-usage-information> for more information on GitLab API. pub async fn language_use(options: Options) -> ApiResult<ProgrammingLanguageUseResponse> { let action = "languages"; let Options { token, identifier, domain, .. } = options; match Endpoint::from_template("gitlab::api").map(|e| e.with_domain(&domain)) { | Ok(endpoint) => { let params = vec![ param!(Header, "PRIVATE-TOKEN", &token), param!(TemplateValue, "identifier", &identifier.unwrap_or_default()), ]; let response = endpoint.invoke_with::<PaginationField, EmptyField>(action, Some(params)).await; endpoint.handle::<ProgrammingLanguageUseResponse>(response) } | Err(why) => Err(why), } } /// Download programming language metadata from GitLab linguist source file pub async fn languages() -> ApiResult<ProgrammingLanguagesResponse> { let names = ["gitlab::org", "github::org"]; Loading
acorn-lib/src/io/api/tests/mod.rs +13 −3 Original line number Diff line number Diff line Loading @@ -213,7 +213,7 @@ mod github_api { #[test] fn test_template_endpoint_for() { let endpoint = Endpoint::from_template("github").map(|e| e.with_domain("api.github.com")).unwrap(); let endpoint = Endpoint::from_template("github::api").map(|e| e.with_domain("api.github.com")).unwrap(); assert_eq!(endpoint.base(), "https://api.github.com"); assert!(endpoint.resources.iter().any(|r| r.name == "tree")); } Loading Loading @@ -264,7 +264,7 @@ mod github_api { #[cfg(test)] mod gitlab_api { use super::*; use crate::io::api::gitlab::{PaginationField, ProgrammingLanguageDetails, ProgrammingLanguagesResponse}; use crate::io::api::gitlab::{PaginationField, ProgrammingLanguageDetails, ProgrammingLanguageUseResponse, ProgrammingLanguagesResponse}; #[test] fn test_query_string() { Loading Loading @@ -320,6 +320,16 @@ mod gitlab_api { assert_eq!(response.languages[0].name, "Python"); assert_eq!(response.languages[0].language_id, Some(303)); } #[test] fn test_programming_language_use_response_deserializes_map() { let json = r#"{"Rust":98.12,"Makefile":0.5,"Python":0.49}"#; let response: ProgrammingLanguageUseResponse = serde_json::from_str(json).expect("should deserialize language usage map"); assert_eq!(response.languages.len(), 3); assert_eq!(response.languages[0].name, "Makefile"); assert_eq!(response.languages[0].percentage, 0.5); assert_eq!(response.languages[1].name, "Python"); assert_eq!(response.languages[2].name, "Rust"); } } #[cfg(test)] mod orcid_api { Loading Loading @@ -666,7 +676,7 @@ mod orcid_api { } #[test] fn test_from_template_with_domain() { let result = Endpoint::from_template("gitlab").map(|e| e.with_domain("custom-gitlab.example.com")); let result = Endpoint::from_template("gitlab::api").map(|e| e.with_domain("custom-gitlab.example.com")); assert!(result.is_ok()); let endpoint = result.unwrap(); assert_eq!(endpoint.domain, "custom-gitlab.example.com"); Loading