You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

212 lines
5.8 KiB

use std::collections::{HashMap, HashSet};
use std::path::{Path, PathBuf};
3 years ago
use std::sync::mpsc::channel;
use std::time::Duration;
use std::{fs, io};
use markdown;
3 years ago
use notify::{
DebouncedEvent::{Create, NoticeRemove, NoticeWrite, Remove},
RecommendedWatcher, RecursiveMode, Watcher,
};
use serde::{Deserialize, Serialize};
use toml;
3 years ago
type TagName = String;
type TagId = String;
type TagMap = HashMap<TagName, TagId>;
// Maps field to value.
type NoteMap = HashMap<String, String>;
type NotesMap = HashMap<PathBuf, NoteMap>;
const TAG_TYPE_ID: &'static str = "5";
#[derive(Serialize, Deserialize)]
struct Configuration {
path_in: String,
path_out: String,
}
struct Context {
config: Configuration,
tags: Option<TagMap>,
public_tag_id: Option<String>,
3 years ago
}
impl Context {
fn new(config: Configuration) -> Self {
Self {
config,
tags: None,
public_tag_id: None,
}
}
fn update_tags(&mut self, notes: &NotesMap) {
let tags = analyze_tags(notes);
self.public_tag_id = tags.get("public").map(|x| x.clone());
self.tags = Some(tags);
}
fn publish_public(&self, notes: &NotesMap) -> Result<(), io::Error> {
let public_tag_id = if let Some(id) = &self.public_tag_id {
id
} else {
return Ok(());
};
let mut tagged_notes = HashSet::new();
for note in notes.values() {
if let Some(tag_id) = note.get("tag_id") {
if tag_id == public_tag_id {
tagged_notes.insert(note.get("note_id").unwrap());
}
}
}
let tagged_notes = tagged_notes.iter().map(|&note_id| {
notes
.iter()
.find(|(_, note)| &note["id"] == note_id)
.map(|(path, _)| path)
.unwrap()
});
for note_id in tagged_notes {
let note = &notes[note_id];
if let Some(content) = note.get("markdown_content") {
println!("Gonna publish this content: {}", content);
// TODO crash in markdown!
let content = markdown::to_html(content);
// TODO do a better sanitize of the title.
let title = content.split("\n").next().unwrap().replace(" ", "_");
let path = self.config.path_out.clone() + &format!("/{}.html", title);
fs::write(path, content)?;
}
}
Ok(())
}
}
fn analyze_tags(notes: &NotesMap) -> TagMap {
let mut tags = TagMap::new();
for note in notes.values() {
if let Some(type_id) = note.get("type_") {
if type_id == TAG_TYPE_ID {
tags.insert(note["markdown_content"].clone(), note["id"].clone());
}
}
}
tags
}
fn run_hooks(context: &mut Context, notes: &NotesMap) -> Result<(), io::Error> {
context.update_tags(notes);
context.publish_public(notes)
}
fn read_file(path: &Path) -> Result<NoteMap, io::Error> {
3 years ago
let content = fs::read_to_string(path)?;
let content = content.trim().split("\n").collect::<Vec<_>>();
let mut fields = NoteMap::new();
3 years ago
for j in 0..content.len() {
let i = content.len() - 1 - j;
let line = content[i];
if let Some(index) = line.find(":") {
let (key, value) = line.split_at(index);
// Remove the ":" character.
let value = value[1..].trim();
if key.len() > 0 && value.len() > 0 {
fields.insert(key.to_string(), value.to_string());
}
} else {
// First line without a metadata field! Reverse content, it's the markdown block.
let markdown = content[0..i]
.iter()
.map(|s| s.to_string())
.collect::<Vec<_>>()
.join("\n");
let markdown = markdown.trim();
fields.insert("markdown_content".to_string(), markdown.to_string());
break;
}
}
println!(
"Extracted content of {} ({} fields)",
fields["id"],
fields.len()
);
Ok(fields)
}
fn main() -> Result<(), io::Error> {
let config_content = fs::read_to_string("./config.toml")?;
let config: Configuration = toml::from_str(&config_content).unwrap();
match fs::metadata(&config.path_out) {
Ok(_) => {}
Err(_) => {
fs::create_dir(&config.path_out)?;
}
}
3 years ago
let mut num_files = 0;
let mut files = NotesMap::new();
3 years ago
for entry in fs::read_dir(&config.path_in)? {
3 years ago
let dir_entry = entry?;
if !dir_entry.file_type()?.is_file() {
continue;
}
let path = dir_entry.path();
let read_file = read_file(&path)?;
files.insert(path, read_file);
num_files += 1;
}
println!("Found {} files.", num_files);
let mut context = Context::new(config);
run_hooks(&mut context, &files)?;
3 years ago
println!("Now listening to disk events...");
let (tx, rx) = channel();
let mut watcher: RecommendedWatcher = Watcher::new(tx, Duration::from_secs(2)).unwrap();
watcher
.watch("./assets/", RecursiveMode::Recursive)
.unwrap();
loop {
match rx.recv() {
Ok(evt) => {
match &evt {
NoticeWrite(path) | Create(path) => {
files.insert(path.clone(), read_file(path)?);
}
NoticeRemove(path) | Remove(path) => {
files.remove(path);
}
_ => {}
}
run_hooks(&mut context, &files)?;
3 years ago
println!("{:?}", evt);
}
Err(e) => println!("watch error: {:?}", e),
}
}
}