marky 0.9.0

Markdown Magician 🧙
use anyhow::Result;
use colored::Colorize;
use notify::Watcher;

use crate::{document, error, info, ioutil};
use std::{
    io, net,
    path::{Path, PathBuf},
};

fn recompile(path: &PathBuf, options: &document::RenderOptions) -> io::Result<document::Document> {
    match ioutil::read_path(path) {
        Ok(contents) => Ok(document::Document {
            text: contents,
            options: options.clone(),
        }),
        Err(e) => Err(e),
    }
}

macro_rules! watch {
    ($path: ident, $options:ident, $on_update:ident$(.$field:ident)*$( $arg:ident)*) => {{
        info!("waiting for changes on {}", $path.display().to_string().cyan());

        let (tx, rx) = std::sync::mpsc::channel();
        let mut watcher = notify::RecommendedWatcher::new(tx, notify::Config::default())?;

        watcher.watch($path.as_path(), notify::RecursiveMode::NonRecursive)?;

        if let Ok(compiled) = recompile($path, $options) {
            $on_update$(.$field)*($($arg,)* &compiled).await;
        }

        for res in rx {
            match res {
                Ok(event) => {
                    if event.kind.is_modify() {
                        match recompile($path, $options) {
                            Ok(compiled) => {
                                $on_update$(.$field)*($($arg,)* &compiled).await;
                                info!("updated")
                            },
                            Err(e) => error!("compilation failed: {}", e)
                        }
                    }
                }
                Err(e) => error!("{}", e.to_string()),
            }
        }

        Ok(())
    }};
}

pub async fn watch_live(
    path: &PathBuf,
    options: &document::RenderOptions,
    port: u16,
) -> Result<(), Box<dyn std::error::Error>> {
    let addr = net::SocketAddr::V4(net::SocketAddrV4::new(
        net::Ipv4Addr::new(127, 0, 0, 1),
        port,
    ));

    let config = crate::server::Config {
        root_dir: path
            .clone()
            .parent()
            .unwrap_or(Path::new("."))
            .to_path_buf(),
        render_options: options.clone(),
    };

    let server = crate::server::Server::bind(&addr, config);

    watch!(path, options, server.send)
}

pub async fn watch_file(
    path: &PathBuf,
    output: &PathBuf,
    options: &document::RenderOptions,
) -> Result<(), Box<dyn std::error::Error>> {
    watch!(path, options, write_to_file output)
}

async fn write_to_file(path: &PathBuf, document: &document::Document) {
    match document.render() {
        Ok(rendered) => {
            if let Err(e) = std::fs::write(path, rendered) {
                error!("{}", e);
            }
        }
        Err(e) => error!("{}", e),
    }
}