Skip to content

Commit

Permalink
ci: Add basic CI (#7)
Browse files Browse the repository at this point in the history
  • Loading branch information
fischeti committed Jan 27, 2024
1 parent a8a0f45 commit d5a5d95
Show file tree
Hide file tree
Showing 7 changed files with 145 additions and 127 deletions.
28 changes: 28 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
on: [push, pull_request]
name: ci

# Make sure CI fails on all warnings, including Clippy lints
env:
RUSTFLAGS: "-Dwarnings"

jobs:
clippy_check:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Run Clippy
run: cargo clippy --all-targets --all-features

format_check:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Run Rustfmt
run: cargo fmt --all -- --check

build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Build
run: cargo build --all-targets --all-features
16 changes: 5 additions & 11 deletions src/config.rs
Original file line number Diff line number Diff line change
@@ -1,25 +1,22 @@
use serde::Deserialize;

#[derive(Deserialize)]
#[derive(Debug)]
#[derive(Deserialize, Debug)]
pub struct Config {
// The mail configuration
pub mail: MailConfig,
// The statistics configuration
pub stats: StatsConfig,
}

#[derive(Deserialize)]
#[derive(Debug)]
#[derive(Deserialize, Debug)]
pub struct MailConfig {
// The login configuration
pub login: MailLogin,
// The fetch configuration
pub fetch: MailFetch,
}

#[derive(Deserialize)]
#[derive(Debug)]
#[derive(Deserialize, Debug)]
pub struct MailLogin {
// The IMAP server to connect to
pub server: String,
Expand All @@ -31,9 +28,7 @@ pub struct MailLogin {
pub password: Option<String>,
}

#[derive(Deserialize)]
#[derive(Debug)]
#[derive(Clone)]
#[derive(Deserialize, Debug, Clone)]
pub struct MailFetch {
// The mailboxes to fetch from the WRs you sent
pub wr_mailboxes: Vec<String>,
Expand All @@ -49,8 +44,7 @@ pub struct MailFetch {
pub year: u32,
}

#[derive(Deserialize)]
#[derive(Debug)]
#[derive(Deserialize, Debug)]
pub struct StatsConfig {
// The number of holiday weeks you took this year, where you didn't had
// to write a WR, this includes sick days, vacation, etc.
Expand Down
116 changes: 63 additions & 53 deletions src/mail.rs
Original file line number Diff line number Diff line change
@@ -1,36 +1,48 @@
extern crate chrono;
extern crate imap;
extern crate native_tls;
extern crate chrono;

use chrono::{DateTime, FixedOffset};
use imap::ImapConnection;
use itertools::join;
use chrono::{DateTime, FixedOffset};
use std::str::from_utf8;
use log::{info, warn};
use std::str::from_utf8;

use crate::config::{MailConfig, MailLogin, MailFetch};
use crate::config::{MailConfig, MailFetch, MailLogin};
use crate::error::{Result, WrError};

#[derive(Debug, Clone)]
pub struct Address {
pub name: Option<String>,
pub user: Option<String>,
pub email: Option<String>
pub email: Option<String>,
}

impl Address {
pub fn from_imap_address(addr: &imap_proto::types::Address) -> Self {
Address {
name: addr.name.as_ref().map(|s| String::from_utf8_lossy(s).to_string()),
user: addr.mailbox.as_ref().map(|s| String::from_utf8_lossy(s).to_string()),
name: addr
.name
.as_ref()
.map(|s| String::from_utf8_lossy(s).to_string()),
user: addr
.mailbox
.as_ref()
.map(|s| String::from_utf8_lossy(s).to_string()),
email: {
let host = addr.host.as_ref().map(|s| String::from_utf8_lossy(s).to_string());
let mailbox = addr.mailbox.as_ref().map(|s| String::from_utf8_lossy(s).to_string());
let host = addr
.host
.as_ref()
.map(|s| String::from_utf8_lossy(s).to_string());
let mailbox = addr
.mailbox
.as_ref()
.map(|s| String::from_utf8_lossy(s).to_string());
match (mailbox, host) {
(Some(mailbox), Some(host)) => Some(format!("{}@{}", mailbox, host)),
_ => None,
}
}
},
}
}
}
Expand All @@ -48,21 +60,31 @@ impl Envelope {
pub fn from_imap_envelope(envelope: &imap_proto::types::Envelope) -> Self {
Envelope {
date: {
let date_str = envelope.date.as_ref().map(|s| String::from_utf8_lossy(s).to_string());
let date_str = envelope
.date
.as_ref()
.map(|s| String::from_utf8_lossy(s).to_string());
DateTime::parse_from_rfc2822(&date_str.unwrap()).unwrap()
},
subject: String::from_utf8_lossy(envelope.subject.as_ref().unwrap()).to_string(),
cc: envelope.cc.as_ref().map(|cc| cc.iter().map(|addr| Address::from_imap_address(addr)).collect()),
in_reply_to: envelope.in_reply_to.as_ref().map(|s| String::from_utf8_lossy(s).to_string()),
message_id: envelope.message_id.as_ref().map(|s| String::from_utf8_lossy(s).to_string()),
cc: envelope.cc.as_ref().map(|cc| {
cc.iter()
.map(|addr| Address::from_imap_address(addr))
.collect()
}),
in_reply_to: envelope
.in_reply_to
.as_ref()
.map(|s| String::from_utf8_lossy(s).to_string()),
message_id: envelope
.message_id
.as_ref()
.map(|s| String::from_utf8_lossy(s).to_string()),
}
}
}

fn imap_login(
login: &MailLogin,
) -> Result<imap::Session<Box<dyn ImapConnection>>> {

fn imap_login(login: &MailLogin) -> Result<imap::Session<Box<dyn ImapConnection>>> {
let domain = login.server.as_str();
let port = login.port;
let username = login.username.clone().unwrap();
Expand All @@ -72,17 +94,12 @@ fn imap_login(
let client = imap::ClientBuilder::new(domain, port).connect()?;

// Login to the IMAP server
let imap_session = client
.login(username, password)
.map_err(|e| e.0)?;
let imap_session = client.login(username, password).map_err(|e| e.0)?;

Ok(imap_session)
}

pub fn list_mailboxes(
config: &MailConfig,
) -> Result<()> {

pub fn list_mailboxes(config: &MailConfig) -> Result<()> {
// Login to the IMAP server
let mut imap_session = imap_login(&config.login)?;

Expand All @@ -98,10 +115,7 @@ pub fn list_mailboxes(
Ok(())
}

pub fn fetch_inbox(
config: &MailConfig,
) -> Result<()> {

pub fn fetch_inbox(config: &MailConfig) -> Result<()> {
// Login to the IMAP server
let mut imap_session = imap_login(&config.login)?;

Expand All @@ -127,24 +141,26 @@ pub fn fetch_inbox(
Ok(())
}

fn build_imap_search_query(
fetch: &MailFetch
) -> Result<String> {

fn build_imap_search_query(fetch: &MailFetch) -> Result<String> {
// Check that patterns is not empty
if fetch.pattern.is_empty() {
return Err(WrError::QueryError("No pattern specified".to_string()));
}

// Check that patterns has at most two elements
if fetch.pattern.len() > 2 {
return Err(WrError::QueryError("IMAP search query supports a maximum of two patterns".to_string()));
return Err(WrError::QueryError(
"IMAP search query supports a maximum of two patterns".to_string(),
));
}

// Format the subject of the query
let mut query = match fetch.pattern.len() {
1 => format!("SUBJECT \"{}\"", fetch.pattern[0]),
2 => format!("SUBJECT \"{}\" OR SUBJECT \"{}\"", fetch.pattern[0], fetch.pattern[1]),
2 => format!(
"SUBJECT \"{}\" OR SUBJECT \"{}\"",
fetch.pattern[0], fetch.pattern[1]
),
_ => unreachable!(),
};

Expand All @@ -158,10 +174,7 @@ fn build_imap_search_query(
Ok(query)
}

pub fn fetch_wrs(
config: &MailConfig,
) -> Result<Vec<Envelope>> {

pub fn fetch_wrs(config: &MailConfig) -> Result<Vec<Envelope>> {
// Login to the IMAP server
let mut imap_session = imap_login(&config.login)?;

Expand All @@ -174,11 +187,11 @@ pub fn fetch_wrs(
for mailbox in config.fetch.wr_mailboxes.iter() {
// Select the mailbox
match imap_session.select(mailbox) {
Ok(_) => {},
Ok(_) => {}
Err(e) => {
warn!("Could not select mailbox {}: {}", mailbox, e);
continue;
},
}
}

// Search for messages that contain the pattern
Expand All @@ -192,21 +205,22 @@ pub fn fetch_wrs(
// Print the subjects of the messages
for message in messages.iter() {
let envelope = message.envelope().unwrap();
let reply_pattern= vec!["Re:", "RE:", "Aw:", "AW:"];
let reply_pattern = ["Re:", "RE:", "Aw:", "AW:"];

match envelope.in_reply_to {
None => {
let env = Envelope::from_imap_envelope(envelope);
wrs.push(env);
},
}
Some(_) => {
let subject = from_utf8(envelope.subject.as_ref().unwrap().as_ref()).expect("No subject in the envelope");
let subject = from_utf8(envelope.subject.as_ref().unwrap().as_ref())
.expect("No subject in the envelope");
if reply_pattern.iter().any(|&s| subject.contains(s)) {
continue;
}
let env = Envelope::from_imap_envelope(envelope);
wrs.push(env);
},
}
};
}
}
Expand All @@ -217,14 +231,10 @@ pub fn fetch_wrs(
Ok(wrs)
}

pub fn fetch_replies(
config: &MailConfig,
) -> Result<Vec<Envelope>> {

pub fn fetch_replies(config: &MailConfig) -> Result<Vec<Envelope>> {
// Login to the IMAP server
let mut imap_session = imap_login(&config.login)?;


let mut reply_fetch = config.fetch.clone();
// Swap `from` and `to` in the fetch configuration
std::mem::swap(&mut reply_fetch.from, &mut reply_fetch.to);
Expand All @@ -238,11 +248,11 @@ pub fn fetch_replies(
for mailbox in config.fetch.re_mailboxes.iter() {
// Select the mailbox
match imap_session.select(mailbox) {
Ok(_) => {},
Ok(_) => {}
Err(e) => {
warn!("Could not select mailbox {}: {}", mailbox, e);
continue;
},
}
}

// Search for messages that contain the pattern
Expand All @@ -262,7 +272,7 @@ pub fn fetch_replies(
Some(_) => {
let env = Envelope::from_imap_envelope(envelope);
wr_replies.push(env);
},
}
None => continue,
};
}
Expand Down
Loading

0 comments on commit d5a5d95

Please sign in to comment.