actions.rs 3.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127
  1. use chrono::{DateTime, Utc};
  2. use std::collections::HashMap;
  3. use async_trait::async_trait;
  4. use reqwest::Client;
  5. use tera::{Context, Tera};
  6. use crate::github::{self, GithubClient, Repository};
  7. #[async_trait]
  8. pub trait Action {
  9. async fn call(&self) -> String;
  10. }
  11. pub struct Step<'a> {
  12. pub name: &'a str,
  13. pub actions: Vec<Query<'a>>,
  14. }
  15. pub struct Query<'a> {
  16. /// Vec of (owner, name)
  17. pub repos: Vec<(&'a str, &'a str)>,
  18. pub queries: Vec<QueryMap<'a>>,
  19. }
  20. pub enum QueryKind {
  21. List,
  22. Count,
  23. }
  24. pub struct QueryMap<'a> {
  25. pub name: &'a str,
  26. pub kind: QueryKind,
  27. pub query: Box<dyn github::IssuesQuery + Send + Sync>,
  28. }
  29. #[derive(serde::Serialize)]
  30. pub struct IssueDecorator {
  31. pub number: u64,
  32. pub title: String,
  33. pub html_url: String,
  34. pub repo_name: String,
  35. pub labels: String,
  36. pub assignees: String,
  37. pub updated_at: String,
  38. }
  39. lazy_static! {
  40. pub static ref TEMPLATES: Tera = {
  41. match Tera::new("templates/*") {
  42. Ok(t) => t,
  43. Err(e) => {
  44. println!("Parsing error(s): {}", e);
  45. ::std::process::exit(1);
  46. }
  47. }
  48. };
  49. }
  50. pub fn to_human(d: DateTime<Utc>) -> String {
  51. let d1 = chrono::Utc::now() - d;
  52. let days = d1.num_days();
  53. if days > 60 {
  54. format!("{} months ago", days / 30)
  55. } else {
  56. format!("about {} days ago", days)
  57. }
  58. }
  59. #[async_trait]
  60. impl<'a> Action for Step<'a> {
  61. async fn call(&self) -> String {
  62. let gh = GithubClient::new_with_default_token(Client::new());
  63. let mut context = Context::new();
  64. let mut results = HashMap::new();
  65. for Query { repos, queries } in &self.actions {
  66. for repo in repos {
  67. let repository = Repository {
  68. full_name: format!("{}/{}", repo.0, repo.1),
  69. };
  70. for QueryMap { name, kind, query } in queries {
  71. let issues = query.query(&repository, &gh).await;
  72. match issues {
  73. Ok(issues_decorator) => match kind {
  74. QueryKind::List => {
  75. results
  76. .entry(*name)
  77. .or_insert(Vec::new())
  78. .extend(issues_decorator);
  79. }
  80. QueryKind::Count => {
  81. let count = issues_decorator.len();
  82. let result = if let Some(value) = context.get(*name) {
  83. value.as_u64().unwrap() + count as u64
  84. } else {
  85. count as u64
  86. };
  87. context.insert(*name, &result);
  88. }
  89. },
  90. Err(err) => {
  91. eprintln!("ERROR: {}", err);
  92. err.chain()
  93. .skip(1)
  94. .for_each(|cause| eprintln!("because: {}", cause));
  95. std::process::exit(1);
  96. }
  97. }
  98. }
  99. }
  100. }
  101. for (name, issues) in &results {
  102. context.insert(*name, issues);
  103. }
  104. TEMPLATES
  105. .render(&format!("{}.tt", self.name), &context)
  106. .unwrap()
  107. }
  108. }