Compare commits
8 Commits
86f3430993
...
4069d8ac31
Author | SHA1 | Date |
---|---|---|
Jacob Kiers | 4069d8ac31 | |
Jacob Kiers | 7407654e60 | |
Jacob Kiers | e7fd41ff95 | |
Jacob Kiers | c2d09621aa | |
Jacob Kiers | 9129f7e11b | |
Jacob Kiers | 71371cb3e1 | |
Jacob Kiers | f61e635721 | |
Jacob Kiers | d3e4c9e790 |
|
@ -19,7 +19,7 @@ local add_build_steps() = [
|
|||
volumes: [
|
||||
{
|
||||
name: 'dockersock',
|
||||
path: '/var/run/docker.sock',
|
||||
path: '/var/run',
|
||||
},
|
||||
],
|
||||
commands: [
|
||||
|
@ -29,7 +29,7 @@ local add_build_steps() = [
|
|||
'rm -rf target/' + arch.target + '/release/*',
|
||||
],
|
||||
environment: {
|
||||
CROSS_DOCKER_IN_DOCKER: true,
|
||||
CROSS_REMOTE: true,
|
||||
},
|
||||
depends_on: ['Wait for Docker'],
|
||||
}
|
||||
|
@ -57,11 +57,11 @@ local add_build_steps() = [
|
|||
'docker pull hello-world:latest',
|
||||
],
|
||||
environment: {
|
||||
CROSS_DOCKER_IN_DOCKER: true,
|
||||
CROSS_REMOTE: true,
|
||||
},
|
||||
volumes: [{
|
||||
name: 'dockersock',
|
||||
path: '/var/run/docker.sock',
|
||||
path: '/var/run',
|
||||
}],
|
||||
}] +
|
||||
add_build_steps() +
|
||||
|
@ -92,12 +92,26 @@ local add_build_steps() = [
|
|||
},
|
||||
],
|
||||
|
||||
services: [{
|
||||
name: 'docker',
|
||||
image: 'docker:dind',
|
||||
privileged: true,
|
||||
volumes: [
|
||||
{
|
||||
name: 'dockersock',
|
||||
path: '/var/run',
|
||||
},
|
||||
{
|
||||
name: 'docker-storage',
|
||||
path: '/var/lib/docker',
|
||||
},
|
||||
],
|
||||
}],
|
||||
|
||||
volumes: [
|
||||
{
|
||||
name: 'dockersock',
|
||||
host: {
|
||||
path: '/var/run/docker.sock',
|
||||
},
|
||||
temp: {},
|
||||
},
|
||||
],
|
||||
|
||||
|
|
|
@ -29,7 +29,7 @@ checksum = "23b62fc65de8e4e7f52534fb52b0f3ed04746ae267519eef2a83941e8085068b"
|
|||
[[package]]
|
||||
name = "atom_syndication"
|
||||
version = "0.11.0"
|
||||
source = "git+https://github.com/jacobkiers/atom-syndication?rev=525c41508db23634c565ea312bfedb51b5bd3deb#525c41508db23634c565ea312bfedb51b5bd3deb"
|
||||
source = "git+https://github.com/jacobkiers/atom-syndication?rev=add7083b56b9d737f0fa1d3383aa82789b6c38ad#add7083b56b9d737f0fa1d3383aa82789b6c38ad"
|
||||
dependencies = [
|
||||
"chrono",
|
||||
"derive_builder",
|
||||
|
|
|
@ -21,4 +21,4 @@ rustls-connector = { version = "^0.16.1", default-features = false, features = [
|
|||
sha2 = "^0.10.2"
|
||||
|
||||
[patch.crates-io]
|
||||
atom_syndication = { git = "https://github.com/jacobkiers/atom-syndication", rev = "525c41508db23634c565ea312bfedb51b5bd3deb" }
|
||||
atom_syndication = { git = "https://github.com/jacobkiers/atom-syndication", rev = "add7083b56b9d737f0fa1d3383aa82789b6c38ad" }
|
||||
|
|
39
README.md
39
README.md
|
@ -1,6 +1,41 @@
|
|||
# Newsletter 2 Web
|
||||
# Newsletter to Web
|
||||
|
||||
Converts a newsletter to static HTML files.
|
||||
Converts a newsletter to and Atom feed and static HTML files.
|
||||
|
||||
## Usage
|
||||
|
||||
Get the latest release [from the releases page](https://code.kiers.eu/newsletter-to-web/newsletter-to-web/releases/latest).
|
||||
|
||||
### Getting help
|
||||
|
||||
For help, use
|
||||
|
||||
* `newsletter-to-web help`
|
||||
* `newsletter-to-web help <subcommand>`.
|
||||
|
||||
### Basic usage
|
||||
|
||||
First, download all messages from the IMAP mail server
|
||||
and store them in the `data/` directory:
|
||||
|
||||
```sh
|
||||
newsletter-to-web fetch-from-imap -s <imap.example.com> -u <email@example.com> -p <password>
|
||||
```
|
||||
|
||||
Then, convert them to an Atom feed, using
|
||||
`newsletters.example.com` as the base domain:
|
||||
|
||||
```sh
|
||||
newsletter-to-web --include-html build-feed newsletters.example.org
|
||||
```
|
||||
|
||||
This will put the output in the `output/` directory. The Atom
|
||||
feed will be in `output/feed.xml`, together with a very simple
|
||||
`index.html` file pointing to the feed. It will also add an HTML
|
||||
file for every email with the HTML content.
|
||||
|
||||
The feed will already contain the full HTML, so it can easily be
|
||||
read from a feed reader.
|
||||
|
||||
## Features
|
||||
|
||||
|
|
|
@ -26,6 +26,7 @@
|
|||
</section>
|
||||
<section>
|
||||
<h2>Recent Items</h2>
|
||||
<p>Last updated on <xsl:apply-templates select="atom:feed/atom:updated" /></p>
|
||||
<xsl:apply-templates select="atom:feed/atom:entry" />
|
||||
</section>
|
||||
</body>
|
||||
|
|
19
src/feed.rs
19
src/feed.rs
|
@ -2,6 +2,7 @@ use crate::Message;
|
|||
|
||||
use atom_syndication::{
|
||||
ContentBuilder, Entry, EntryBuilder, Feed, FeedBuilder, Generator, LinkBuilder, Person,
|
||||
WriteConfig,
|
||||
};
|
||||
use chrono::{DateTime, TimeZone, Utc};
|
||||
|
||||
|
@ -37,10 +38,7 @@ pub(crate) fn add_entry_to_feed(
|
|||
email: from.address.as_ref().map(|e| e.to_string()),
|
||||
uri: None,
|
||||
},
|
||||
title: parsed
|
||||
.subject()
|
||||
.expect("Expected a subject")
|
||||
.to_string(),
|
||||
title: parsed.subject().expect("Expected a subject").to_string(),
|
||||
content: Some(processed_html.clone()),
|
||||
id: url.clone(),
|
||||
published: Utc.timestamp_opt(date.to_timestamp(), 0).unwrap(),
|
||||
|
@ -79,6 +77,19 @@ pub(crate) fn build_atom_feed(hostname: &String, feed_file: &str) -> Feed {
|
|||
.build()
|
||||
}
|
||||
|
||||
pub(crate) fn write_feed<W: std::io::Write>(
|
||||
feed: Feed,
|
||||
mut out: W,
|
||||
) -> Result<W, atom_syndication::Error> {
|
||||
let _ = writeln!(out, r#"<?xml version="1.0"?>"#);
|
||||
let _ = writeln!(out, r#"<?xml-stylesheet href="feed.xsl" type="text/xsl"?>"#);
|
||||
let config = WriteConfig {
|
||||
write_document_declaration: false,
|
||||
..Default::default()
|
||||
};
|
||||
feed.write(out, config)
|
||||
}
|
||||
|
||||
//#[derive(Serialize, Deserialize, Debug)]
|
||||
pub(crate) struct Newsletter {
|
||||
id: String,
|
||||
|
|
52
src/main.rs
52
src/main.rs
|
@ -41,7 +41,11 @@ fn main() -> Result<(), Box<dyn Error>> {
|
|||
username.to_owned(),
|
||||
password.to_owned(),
|
||||
),
|
||||
cli::Command::BuildFeed { filename, hostname, include_html } => build_feed(filename, hostname, *include_html),
|
||||
cli::Command::BuildFeed {
|
||||
filename,
|
||||
hostname,
|
||||
include_html,
|
||||
} => build_feed(filename, hostname, *include_html),
|
||||
_ => unimplemented!("This method is not yet implemented."),
|
||||
};
|
||||
|
||||
|
@ -56,7 +60,11 @@ fn create_directory<P: AsRef<Path>>(dir: P) -> Result<(), std::io::Error> {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
fn build_feed(filename: &PathBuf, hostname: &String, include_html: bool) -> Result<(), Box<dyn Error>> {
|
||||
fn build_feed(
|
||||
filename: &PathBuf,
|
||||
hostname: &String,
|
||||
include_html: bool,
|
||||
) -> Result<(), Box<dyn Error>> {
|
||||
let dir = filename.parent().ok_or(format!(
|
||||
"Could not get parent directory of {}",
|
||||
filename.display()
|
||||
|
@ -71,8 +79,10 @@ fn build_feed(filename: &PathBuf, hostname: &String, include_html: bool) -> Resu
|
|||
create_directory(dir)?;
|
||||
|
||||
let feed_file = filename
|
||||
.file_name().expect("Feed path should have a file name")
|
||||
.to_str().expect("Feed path should be printable.");
|
||||
.file_name()
|
||||
.expect("Feed path should have a file name")
|
||||
.to_str()
|
||||
.expect("Feed path should be printable.");
|
||||
|
||||
let mut feed = feed::build_atom_feed(&hostname, feed_file);
|
||||
|
||||
|
@ -99,7 +109,7 @@ fn build_feed(filename: &PathBuf, hostname: &String, include_html: bool) -> Resu
|
|||
let processed_html = process_html(&html_body).expect("Could not process the HTML");
|
||||
|
||||
if include_html {
|
||||
let path : PathBuf = [dir, Path::new(&get_path(&parsed, &msg))].iter().collect();
|
||||
let path: PathBuf = [dir, Path::new(&get_path(&parsed, &msg))].iter().collect();
|
||||
write_file(&path, processed_html.as_bytes())?;
|
||||
}
|
||||
|
||||
|
@ -110,10 +120,12 @@ fn build_feed(filename: &PathBuf, hostname: &String, include_html: bool) -> Resu
|
|||
feed.set_updated(Utc::now());
|
||||
println!("Writing feed to {}", filename.display());
|
||||
|
||||
feed.set_stylesheet(Some("feed.xsl".into()));
|
||||
write_file(filename, feed.to_string())?;
|
||||
feed::write_feed(feed, open_file(filename).unwrap())?;
|
||||
write_file(dir.join("feed.xsl"), FEED_STYLESHEET)?;
|
||||
write_file(dir.join("index.html"), INDEX_HTML.replace("{FEED}", feed_file))?;
|
||||
write_file(
|
||||
dir.join("index.html"),
|
||||
INDEX_HTML.replace("{FEED}", feed_file),
|
||||
)?;
|
||||
}
|
||||
|
||||
println!("Finished building the feed.");
|
||||
|
@ -132,12 +144,7 @@ fn fetch_from_imap(
|
|||
|
||||
print!("Getting mail from {} for mailbox {}", server, username);
|
||||
|
||||
let mut reader = ImapReader::new(
|
||||
server,
|
||||
port,
|
||||
username,
|
||||
password,
|
||||
);
|
||||
let mut reader = ImapReader::new(server, port, username, password);
|
||||
|
||||
for msg in reader.read_rfc822_messages() {
|
||||
let parsed = msg.get_parsed().ok_or(format!(
|
||||
|
@ -175,8 +182,6 @@ fn fetch_from_imap(
|
|||
Ok(())
|
||||
}
|
||||
|
||||
|
||||
|
||||
fn get_path(parsed: &ParsedMessage, msg: &Message) -> String {
|
||||
let date = parsed.date().expect("Could not extract date");
|
||||
let date_str = format!(
|
||||
|
@ -188,7 +193,8 @@ fn get_path(parsed: &ParsedMessage, msg: &Message) -> String {
|
|||
parsed.body_html(0).expect("Expected a body").as_bytes(),
|
||||
));
|
||||
|
||||
let uid: i32 = msg.get_uid()
|
||||
let uid: i32 = msg
|
||||
.get_uid()
|
||||
.parse()
|
||||
.unwrap_or_else(|_| panic!("Could not convert message id {} to an i32.", msg.get_uid()));
|
||||
|
||||
|
@ -199,13 +205,17 @@ fn process_html(input: &str) -> Result<String, ()> {
|
|||
Ok(input.replace("src", "data-source"))
|
||||
}
|
||||
|
||||
fn write_file<P: Into<PathBuf>, D: AsRef<[u8]>>(html_path: P, data: D) -> Result<(), std::io::Error> {
|
||||
let path : PathBuf = html_path.into();
|
||||
fn open_file<P: Into<PathBuf>>(path: P) -> std::io::Result<std::fs::File> {
|
||||
OpenOptions::new()
|
||||
.write(true)
|
||||
.truncate(true)
|
||||
.create(true)
|
||||
.open(&path)
|
||||
.open(path.into())
|
||||
}
|
||||
|
||||
fn write_file<P: Into<PathBuf>, D: AsRef<[u8]>>(path: P, data: D) -> Result<(), std::io::Error> {
|
||||
let path: PathBuf = path.into();
|
||||
open_file(path.clone())
|
||||
.unwrap_or_else(|_| panic!("Could not open file '{}' for writing", &path.display()))
|
||||
.write_all(data.as_ref())
|
||||
}
|
||||
}
|
||||
|
|
|
@ -21,4 +21,4 @@ impl Message {
|
|||
pub fn get_data(&self) -> &Vec<u8> {
|
||||
&self.data
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue