Skip to content

Commit 92d95bc

Browse files
authored
Merge pull request #3 from codeworm96/0.2
Version 0.2
2 parents 9247fc3 + d22d0cb commit 92d95bc

File tree

11 files changed

+1598
-281
lines changed

11 files changed

+1598
-281
lines changed

Cargo.lock

Lines changed: 1202 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,14 @@
11
[package]
22
name = "dy-weekly-generator"
3-
version = "0.1.0"
3+
version = "0.2.0"
44
authors = ["codeworm96 <codeworm96@outlook.com>"]
55

6+
[[bin]]
7+
name = "weekly-gen"
8+
69
[dependencies]
710
clap = {version = "2", features = ["yaml"]}
8-
hyper = "0.9"
9-
json = "0.11.0"
10-
yaml-rust = "*"
11-
regex = "0.1"
11+
reqwest = "0.8"
12+
json = "0.11"
13+
yaml-rust = "0.4"
14+
regex = "1.0"

src/cli.yml renamed to src/bin/cli.yml

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
name: dy-weekly-generator
2-
version: 0.1.0
2+
version: 0.2.0
33
author: codeworm96 <codeworm96@outlook.com>
44
about: Generates dy weekly
55
args:
@@ -9,7 +9,6 @@ args:
99
value_name: file
1010
help: The final weekly file
1111
takes_value: true
12-
required: true
1312
- repo:
1413
short: r
1514
long: repo

src/bin/weekly-gen.rs

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
extern crate dy_weekly_generator;
2+
use dy_weekly_generator::casual::Casual;
3+
use dy_weekly_generator::error::Error;
4+
use dy_weekly_generator::formal::Formal;
5+
use dy_weekly_generator::github;
6+
use dy_weekly_generator::weekly::WeeklyBuilder;
7+
8+
#[macro_use]
9+
extern crate clap;
10+
use clap::App;
11+
12+
use std::fs::File;
13+
use std::io;
14+
15+
fn work() -> Result<(), Error> {
16+
let yaml = load_yaml!("cli.yml");
17+
let matches = App::from_yaml(yaml).get_matches();
18+
19+
let file = matches.value_of("file");
20+
let repo = matches.value_of("repo").ok_or(Error::ConfigErr)?;
21+
let issue = matches.value_of("issue").ok_or(Error::ConfigErr)?;
22+
let key = matches.value_of("key");
23+
24+
let mut weekly = WeeklyBuilder::new()
25+
.add_extractor(Box::new(Formal::new()))
26+
.add_extractor(Box::new(Casual::new()))
27+
.build();
28+
for body in github::fetch(repo, issue, key)? {
29+
weekly.parse(&body);
30+
}
31+
if let Some(filename) = file {
32+
let mut f = File::create(filename)?;
33+
weekly.render(&mut f)
34+
} else {
35+
weekly.render(&mut io::stdout())
36+
}
37+
}
38+
39+
fn main() {
40+
match work() {
41+
Err(Error::ConfigErr) => println!("Invalid arguments!"),
42+
Err(Error::RequestErr(e)) => println!("Error while sending request ({:?})", e),
43+
Err(Error::FetchErr(r)) => println!("Error while fetching ({:#?})", r),
44+
Err(Error::JsonParseErr) => println!("Invalid json"),
45+
Err(Error::IOErr(e)) => println!("Error while file operations ({:?})", e),
46+
Ok(_) => {}
47+
};
48+
}

src/casual.rs

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
use std::io;
2+
3+
use regex::Regex;
4+
5+
use error::Error;
6+
use weekly::Extractor;
7+
8+
pub struct Casual {
9+
entries: Vec<String>,
10+
}
11+
12+
impl Casual {
13+
pub fn new() -> Casual {
14+
Casual {
15+
entries: Vec::new(),
16+
}
17+
}
18+
}
19+
20+
impl Extractor for Casual {
21+
fn extract(&mut self, comment: &str) -> bool {
22+
let url_pattern = Regex::new(r"(((http://)|(https://)|(ftp://)|(www\.))([a-zA-Z0-9_\-]+\.)*[a-zA-Z0-9\-]+\.[a-zA-Z0-9\-]+)").unwrap();
23+
let res = url_pattern.is_match(comment);
24+
if res {
25+
self.entries.push(comment.to_owned())
26+
}
27+
res
28+
}
29+
30+
fn render(&self, out: &mut io::Write) -> Result<(), Error> {
31+
for entry in &self.entries {
32+
write!(out, "{}\n", entry)?
33+
}
34+
Ok(())
35+
}
36+
}

src/error.rs

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
use json;
2+
use reqwest;
3+
use std::io;
4+
5+
pub enum Error {
6+
ConfigErr,
7+
RequestErr(reqwest::Error),
8+
FetchErr(reqwest::Response),
9+
JsonParseErr,
10+
IOErr(io::Error),
11+
}
12+
13+
impl From<reqwest::Error> for Error {
14+
fn from(err: reqwest::Error) -> Error {
15+
Error::RequestErr(err)
16+
}
17+
}
18+
19+
impl From<json::Error> for Error {
20+
fn from(_err: json::Error) -> Error {
21+
Error::JsonParseErr
22+
}
23+
}
24+
25+
impl From<io::Error> for Error {
26+
fn from(err: io::Error) -> Error {
27+
Error::IOErr(err)
28+
}
29+
}

src/formal.rs

Lines changed: 190 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,190 @@
1+
use std::collections::HashMap;
2+
use std::io;
3+
use std::mem;
4+
5+
use regex::Regex;
6+
use yaml_rust::YamlLoader;
7+
8+
use error::Error;
9+
use weekly::Extractor;
10+
11+
enum EntryType {
12+
Draft,
13+
Topic,
14+
}
15+
16+
struct Entry {
17+
name: String,
18+
kind: EntryType,
19+
link: Option<String>,
20+
description: Option<String>,
21+
quote: Option<String>,
22+
cc: Vec<String>,
23+
// TODO: tag? keyword?
24+
}
25+
26+
impl Entry {
27+
fn parse(yaml: &str) -> Option<Entry> {
28+
YamlLoader::load_from_str(yaml).ok().and_then(|docs| {
29+
docs.iter().next().and_then(|doc| {
30+
let name = doc["name"].as_str().map(|s| s.to_string());
31+
let kind = match doc["type"].as_str() {
32+
Some("draft") => Some(EntryType::Draft),
33+
Some("topic") => Some(EntryType::Topic),
34+
Some(_) => None,
35+
None => Some(EntryType::Draft),
36+
};
37+
let link = doc["link"].as_str().map(|s| s.to_string());
38+
let description = doc["description"].as_str().map(|s| s.to_string());
39+
let quote = doc["quote"].as_str().map(|s| s.to_string());
40+
41+
let mut cc = Vec::new();
42+
if let Some(raw_cc) = doc["cc"].as_vec() {
43+
for person in raw_cc {
44+
match person.as_str() {
45+
Some(c) => cc.push(c.to_string()),
46+
None => {}
47+
}
48+
}
49+
} else {
50+
doc["cc"]
51+
.as_str()
52+
.map(|s| format!("[{}]", s))
53+
.and_then(|s| {
54+
YamlLoader::load_from_str(&s).ok().and_then(|ds| {
55+
ds.iter().next().and_then(|d| d.as_vec()).map(|v| {
56+
for person in v {
57+
match person.as_str() {
58+
Some(c) => {
59+
cc.extend(c.split(' ').map(|s| s.to_string()))
60+
}
61+
None => {}
62+
}
63+
}
64+
})
65+
})
66+
});
67+
}
68+
69+
match (name, kind) {
70+
(Some(name), Some(kind)) => Some(Entry {
71+
name: name,
72+
kind: kind,
73+
link: link,
74+
description: description,
75+
quote: quote,
76+
cc: cc,
77+
}),
78+
_ => None,
79+
}
80+
})
81+
})
82+
}
83+
84+
fn field_append(a: &mut Option<String>, b: &mut Option<String>) {
85+
match mem::replace(b, None) {
86+
Some(s2) => {
87+
if a.is_some() {
88+
a.as_mut().map(|s1| s1.push_str(&s2));
89+
} else {
90+
mem::replace(a, Some(s2));
91+
}
92+
}
93+
None => {}
94+
}
95+
}
96+
97+
fn merge(&mut self, mut other: Entry) {
98+
assert_eq!(self.name, other.name);
99+
self.kind = other.kind;
100+
Self::field_append(&mut self.link, &mut other.link); // FIXME: not a proper way to merge link
101+
Self::field_append(&mut self.description, &mut other.description);
102+
Self::field_append(&mut self.quote, &mut other.quote);
103+
self.cc.append(&mut other.cc);
104+
}
105+
106+
fn render(&self, out: &mut io::Write) -> Result<(), Error> {
107+
write!(out, "- ")?;
108+
match self.link.as_ref() {
109+
Some(link) => write!(out, "[{}]({})", self.name, link)?,
110+
None => write!(out, "{}", self.name)?,
111+
}
112+
match self.description.as_ref() {
113+
Some(desc) => write!(out, ", {}\n", desc)?,
114+
None => write!(out, "\n")?,
115+
}
116+
match self.quote.as_ref() {
117+
Some(quote) => {
118+
for line in quote.lines() {
119+
write!(out, " > {}\n", line)?;
120+
}
121+
}
122+
None => {}
123+
}
124+
if self.cc.len() > 0 {
125+
let cc_list: Vec<_> = self.cc
126+
.iter()
127+
.map(|person| format!("[@{}](https://github.com/{})", person, person))
128+
.collect();
129+
write!(out, "{}\n", cc_list.join(", "))?;
130+
}
131+
Ok(())
132+
}
133+
}
134+
135+
pub struct Formal {
136+
entries: HashMap<String, Entry>,
137+
}
138+
139+
impl Formal {
140+
pub fn new() -> Formal {
141+
Formal {
142+
entries: HashMap::new(),
143+
}
144+
}
145+
146+
fn parse(&mut self, yaml: &str) {
147+
let entry = Entry::parse(yaml);
148+
match entry {
149+
Some(e) => {
150+
if let Some(ent) = self.entries.get_mut(&e.name) {
151+
ent.merge(e);
152+
return;
153+
}
154+
self.entries.insert(e.name.clone(), e);
155+
}
156+
None => {}
157+
}
158+
}
159+
}
160+
161+
impl Extractor for Formal {
162+
fn extract(&mut self, comment: &str) -> bool {
163+
let begin = Regex::new(r"```[:space:]*(yaml|yml)").unwrap();
164+
let end = Regex::new(r"```").unwrap();
165+
let mut entry = String::new();
166+
let mut in_yaml = false;
167+
let mut res = false;
168+
for line in comment.lines() {
169+
if begin.is_match(line) {
170+
entry = String::new();
171+
in_yaml = true;
172+
} else if end.is_match(line) {
173+
res = true;
174+
self.parse(&entry);
175+
in_yaml = false;
176+
} else if in_yaml {
177+
entry.push_str(line);
178+
entry.push_str("\n");
179+
}
180+
}
181+
res
182+
}
183+
184+
fn render(&self, out: &mut io::Write) -> Result<(), Error> {
185+
for entry in self.entries.values() {
186+
entry.render(out)?;
187+
}
188+
Ok(())
189+
}
190+
}

src/github.rs

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
use json;
2+
use reqwest;
3+
use reqwest::header::{qitem, Accept, Authorization, Headers, UserAgent};
4+
use reqwest::mime::Mime;
5+
use reqwest::Client;
6+
7+
use std::io::Read;
8+
9+
use error::Error;
10+
11+
const API_ROOT: &'static str = "https://api.github.com";
12+
13+
pub fn fetch<'a>(
14+
repo: &str,
15+
issue: &str,
16+
key: Option<&str>,
17+
) -> Result<impl Iterator<Item = String>, Error> {
18+
let client = Client::new();
19+
let url = format!("{}/repos/{}/issues/{}/comments", API_ROOT, repo, issue);
20+
21+
let mut headers = Headers::new();
22+
let accept_mime: Mime = "application/vnd.github.v3+json".parse().unwrap();
23+
headers.set(Accept(vec![qitem(accept_mime)]));
24+
headers.set(UserAgent::new("dy-weekly-generator/0.2.0".to_string()));
25+
match key {
26+
Some(k) => headers.set(Authorization(format!("token {}", k))),
27+
None => {}
28+
}
29+
30+
let mut res = client.get(&url).headers(headers).send()?;
31+
32+
if res.status() != reqwest::StatusCode::Ok {
33+
Err(Error::FetchErr(res))
34+
} else {
35+
let mut content = String::new();
36+
res.read_to_string(&mut content)?;
37+
let comments = json::parse(&content)?;
38+
match comments {
39+
json::JsonValue::Array(cs) => {
40+
Ok(cs.into_iter().flat_map(|mut c| match c["body"].take() {
41+
json::JsonValue::String(s) => Some(s),
42+
_ => None,
43+
}))
44+
}
45+
_ => Err(Error::JsonParseErr),
46+
}
47+
}
48+
}

0 commit comments

Comments
 (0)