github_stats/search/
query.rs

1use std::fmt;
2
3use crate::Repo;
4
5#[derive(Default)]
6pub struct Query {
7    author: Vec<String>,
8    repo: Vec<String>,
9    repos: Vec<String>,
10    is: Vec<String>,
11    r#in: Vec<In>,
12    assignee: Vec<String>,
13    user: Vec<String>,
14    org: Vec<String>,
15    fullname: Vec<FullName>,
16    label: Vec<String>,
17    r#type: Vec<String>,
18    state: Vec<String>,
19    no: Vec<String>,
20    language: Vec<String>,
21}
22
23impl Query {
24    pub fn new() -> Self {
25        Query {
26            ..Default::default()
27        }
28    }
29
30    pub fn from_repo(repo: Repo) -> Self {
31        let repo = vec![String::from(repo.full_name())];
32        Query {
33            repo,
34            ..Default::default()
35        }
36    }
37
38    /// *Adds* an author to the query.
39    ///
40    /// Result is `author:username`.
41    pub fn author(mut self, username: &str) -> Self {
42        self.author.push(username.to_owned());
43        self
44    }
45
46    /// *Adds* a repo to the query.
47    ///
48    /// Results in `repo:user/repo`.
49    pub fn repo(mut self, user: &str, repo: &str) -> Self {
50        self.repo.push(format!("{}/{}", user, repo));
51        self
52    }
53
54    /// Search for User by count of repositories.
55    ///
56    /// Results in `repos:n`.
57    /// `n` does not have to be a standard int. `>5` and `10..15` are also valid
58    /// values.
59    pub fn repos(mut self, n: &str) -> Self {
60        // NOTE Good luck not getting repo and repos mixed up
61        self.repos.push(n.to_owned());
62        self
63    }
64
65    /// *Adds* an `is` statement to the query.
66    ///
67    /// Results in `is:statement`.
68    pub fn is(mut self, statement: &str) -> Self {
69        self.is.push(String::from(statement));
70        self
71    }
72
73    /// *Adds* an `in` statement to the query
74    ///
75    /// Results in `keyword in:field`.
76    pub fn r#in(mut self, keyword: &str, field: &str) -> Self {
77        self.r#in.push(In(String::from(keyword), String::from(field)));
78        self
79    }
80
81    /// *Adds* an `assignee` statement to the query.
82    ///
83    /// Results in `assignee:username`.
84    pub fn assignee(mut self, username: &str) -> Self {
85        self.assignee.push(username.to_owned());
86        self
87    }
88
89    /// *Adds* an `user` statement to the query.
90    ///
91    /// Results in `user:statement`.
92    pub fn user(mut self, statement: &str) -> Self {
93        self.user.push(String::from(statement));
94        self
95    }
96
97    /// *Adds* an `org` statement to the query.
98    ///
99    /// Results in `org:statement`.
100    pub fn org(mut self, statement: &str) -> Self {
101        self.org.push(String::from(statement));
102        self
103    }
104
105    /// *Adds* a `fullname` statement to the query.
106    ///
107    /// Results in `fullname:first_name last_name`.
108    pub fn fullname(mut self, first_name: &str, last_name: &str) -> Self {
109        self.fullname.push(FullName::new(first_name, last_name));
110        self
111    }
112
113    /// *Adds* a `label` statement to the query.
114    ///
115    /// Results in `label:statement`.
116    pub fn label(mut self, statement: &str) -> Self {
117        self.label.push(String::from(statement));
118        self
119    }
120
121    /// *Adds* a `type` statement to the query.
122    ///
123    /// Results in `type:statement`.
124    ///
125    /// *Use `r#type` to escape `type` keyword.*
126    pub fn r#type(mut self, statement: &str) -> Self {
127        self.r#type.push(String::from(statement));
128        self
129    }
130
131    /// *Adds* a `no` statement to the query.
132    ///
133    /// Results in `no:statement`.
134    pub fn no(mut self, statement: &str) -> Self {
135        self.no.push(String::from(statement));
136        self
137    }
138
139    /// *Adds* a `language` statement to the query.
140    ///
141    /// Results in `language:statement`.
142    pub fn language(mut self, statement: &str) -> Self {
143        self.language.push(String::from(statement));
144        self
145    }
146}
147
148impl fmt::Display for Query {
149    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
150        let queries = {
151            let mut author: Vec<_> = self.author.iter().map(|s| format!("author:{}", s)).collect();
152            let mut repo: Vec<String> = self.repo.iter().map(|s| format!("repo:{}", s)).collect();
153            let mut repos: Vec<String> = self.repos.iter().map(|s| format!("repos:{}", s)).collect();
154            let mut is: Vec<String> = self.is.iter().map(|s| format!("is:{}", s)).collect();
155            let mut r#in: Vec<String> = self.r#in.iter().map(|s| s.to_string()).collect();
156            let mut user: Vec<String> = self.user.iter().map(|s| format!("user:{}", s)).collect();
157            let mut assignee: Vec<String> = self.assignee.iter().map(|s| format!("assignee:{}", s)).collect();
158            let mut org: Vec<String> = self.org.iter().map(|s| format!("org:{}", s)).collect();
159            let mut fullname: Vec<String> = self.fullname.iter().map(|s| s.to_string()).collect();
160            let mut label: Vec<String> = self.label.iter().map(|s| format!("label:{}", s)).collect();
161            let mut r#type: Vec<String> =
162                self.r#type.iter().map(|s| format!("type:{}", s)).collect();
163            let mut state: Vec<String> =
164                self.state.iter().map(|s| format!("state:{}", s)).collect();
165            let mut no: Vec<String> =
166                self.no.iter().map(|s| format!("no:{}", s)).collect();
167            let mut language: Vec<String> =
168                self.language.iter().map(|s| format!("language:{}", s)).collect();
169
170            let mut queries: Vec<String> =
171                Vec::with_capacity(
172                author.len()
173                    + repo.len()
174                    + repos.len()
175                    + is.len()
176                    + r#in.len()
177                    + assignee.len()
178                    + user.len()
179                    + org.len()
180                    + fullname.len()
181                    + label.len()
182                    + r#type.len()
183                    + state.len()
184                    + no.len()
185                    + language.len()
186                );
187
188            queries.append(&mut repo);
189            queries.append(&mut repos);
190            queries.append(&mut is);
191            queries.append(&mut r#in);
192            queries.append(&mut author);
193            queries.append(&mut assignee);
194            queries.append(&mut user);
195            queries.append(&mut org);
196            queries.append(&mut fullname);
197            queries.append(&mut label);
198            queries.append(&mut r#type);
199            queries.append(&mut state);
200            queries.append(&mut no);
201            queries.append(&mut language);
202            queries
203        };
204
205        let queries = queries.join("+");
206
207        write!(f, "{}", queries)
208    }
209}
210
211struct In(String, String);
212
213impl fmt::Display for In {
214    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
215        write!(f, "{} in:{}", self.0, self.1)
216    }
217}
218
219struct FullName(String, String);
220
221impl FullName {
222    fn new(first_name: &str, last_name: &str) -> Self {
223        FullName(String::from(first_name), String::from(last_name))
224    }
225}
226
227impl fmt::Display for FullName {
228    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
229        write!(f, "fullname:{} {}", self.0, self.1)
230    }
231}
232
233#[cfg(test)]
234mod tests {
235    use super::*;
236
237    #[test]
238    fn built_query() {
239        let query = Query::new()
240            .repo("rust-lang", "rust")
241            .r#type("pr")
242            .is("merged")
243            .label("hacktoberfest")
244            .no("assignee")
245            .r#in("[BUG]", "name")
246            .assignee("spenserblack")
247            .user("spenserblack")
248            .author("spenserblack")
249            .language("rust")
250            .to_string();
251
252        assert_eq!(
253            "repo:rust-lang/rust+is:merged+[BUG] in:name+author:spenserblack+assignee:spenserblack+user:spenserblack+label:hacktoberfest+type:pr+no:assignee+language:rust",
254            query
255        );
256    }
257
258    #[test]
259    fn org_query() {
260        let query = Query::new()
261            .org("rust-lang")
262            .to_string();
263
264        assert_eq!(
265            "org:rust-lang",
266            query
267        );
268    }
269
270    #[test]
271    fn in_string() {
272        let r#in = In(
273            String::from("Users"),
274            String::from("title"),
275        );
276
277        assert_eq!("Users in:title", r#in.to_string());
278    }
279
280    #[test]
281    fn repos_query() {
282        let query = Query::new()
283            .repos("<0")
284            .to_string();
285
286        assert_eq!("repos:<0", query);
287    }
288
289    #[test]
290    fn fullname_string() {
291        let fullname = FullName::new(
292            "Octo",
293            "Cat",
294        );
295
296        assert_eq!("fullname:Octo Cat", fullname.to_string());
297    }
298}