livereload-server 0.2.2

A server to serve static content and automatically reload it one a file in a directory has been changed
Documentation
use std::path::PathBuf;
use std::sync::Arc;

use clap::Parser;
use hyper::{Body, StatusCode};
use livereload_server::{handle_websocket, serve_file};
use warp::path::{peek, Peek};
use warp::Filter;

const INJECTED_SCRIPT: &str = include_str!("./injected_script.js");

lazy_static::lazy_static! {
    static ref INJECTION_PAYLOAD: String = {
        format!(r#"<script type="text/javascript">{INJECTED_SCRIPT}</script>"#)
    };
}

#[derive(Debug, Clone, Parser)]
#[clap(rename_all = "kebab-case")]
struct Args {
    #[clap(short, long)]
    pub target_dir: PathBuf,

    #[clap(short, long, default_value = "5500")]
    pub port: u16,
}

#[tokio::main]
async fn main() -> anyhow::Result<()> {
    dotenv::dotenv()?;
    pretty_env_logger::try_init()?;

    let args = Arc::new(Args::parse());

    let args_filter = {
        let args = args.clone();
        warp::any().map(move || args.clone())
    };

    let ws_route = warp::ws().and(args_filter.clone()).map(
        move |ws: warp::ws::Ws, args: Arc<Args>| {
            ws.on_upgrade(move |websocket| async move {
                if let Err(err) =
                    handle_websocket(&args.as_ref().target_dir, websocket).await
                {
                    log::error!(
                        "An error occurred during websocket connection: {err}"
                    );
                }
            })
        },
    );

    let default_route = peek().and(args_filter).then(
        |peek: Peek, args: Arc<Args>| async move {
            let builder = warp::http::Response::builder();

            match serve_file(
                &args.as_ref().target_dir,
                peek.as_str(),
                INJECTION_PAYLOAD.as_str(),
            )
            .await
            {
                Ok(Some(res)) => {
                    builder.status(StatusCode::OK).body(Body::from(res))
                }
                Ok(None) => {
                    builder.status(StatusCode::NOT_FOUND).body(Body::empty())
                }
                Err(err) => {
                    let err_msg = err.to_string();
                    builder
                        .status(StatusCode::INTERNAL_SERVER_ERROR)
                        .body(Body::from(err_msg))
                }
            }
        },
    );

    let routes = ws_route.or(default_route);

    let port = args.port;
    log::info!("Serving on localhost:{port}");

    warp::serve(routes).run(([127, 0, 0, 1], port)).await;

    Ok(())
}