Home
Head's Up: I'm in the middle of upgrading my site. Most things are in place, but there are something missing and/or broken including image alt text. Please bear with me while I'm getting things fixed.

Run Rust Code When File Change With watchexec

This is more for running external commands than running internal code. Took me a while to figure that out. I'm looking at the notify crate now for watching the system and running code that way.

https : //docs.rs/notify/latest/notify/index.html

I wasn't able to use CTRL+c to stop a running app until I added the Signal : : Interrupt check. I had to use [TODO: Code shorthand span ] to find the pocess ID and then use [TODO: Code shorthand span ] to stop it. Turned out to not be a big deal but I would have preferred to know that going in

New Vervion

TODO : clean up and put this version in place for this post

TODO : Look at the neo process for a solution to only running one process at a time (assuming you solve for that)

rust
// use eyre::Error;
// use anyhow::Error;
// use anyhow::Result;
use miette::{IntoDiagnostic, Result};
// use std::path::PathBuf;
use core::fmt::Error;
use std::process::Command;
use watchexec::{
    action::{Action, Outcome},
    config::{InitConfig, RuntimeConfig},
    handler::PrintDebug,
    Watchexec,
};
use watchexec_signals::Signal;
use std::time::Duration;


Don't delte this until you've updated the post
-- id: 2p7c45vj


// NOTE: This is a temporary process. Eventually
// it'll be rolled into the main script....

// use tokio::process::Command;

// #[derive(Debug)]
// enum PointOfInterest {
//     Include(&str, Vec<&str>)
// }

#[tokio::main]
async fn main() -> Result<()> {
    let mut init = InitConfig::default();
    init.on_error(PrintDebug(std::io::stderr()));
    let mut runtime = RuntimeConfig::default();
    runtime.pathset(["/Users/alan/Desktop"]);
    let we = Watchexec::new(init, runtime.clone())?;


    Make sure to test the action throttle
     runtime.action_throttle(Duration::new(0, 100000000));
    // let w = we.clone();
    // let c = runtime.clone();
    runtime.on_action(move |action: Action| {
        // let mut c = c.clone();
        // let w = w.clone();
        async move {
            let mut stop_running = false;
            for event in action.events.iter() {
                event.signals().for_each(|sig| match sig {
                    Signal::Interrupt => {
                        stop_running = true;
                    }
                    _ => {}
                });
                if event
                    .paths()
                    .any(|(p, _)| p.starts_with("/Users/alan/Desktop"))
                {
                    dbg!("HEHRERE");
                }

                // if event.paths().any(|(p, _)| p.ends_with("txt")) {
                //     // let conf = YourConfigFormat::load_from_file("watchexec.conf").await?;
                //     // conf.apply(&mut c);
                //     // w.reconfigure(c.clone());
                //     // tada! self-reconfiguring watchexec on config file change!
                //     dbg!("HERERE");
                //     break;
                // }

                //
            }

            if stop_running {
                action.outcome(Outcome::Exit);
            }

            // action.outcome(Outcome::if_running(
            //     Outcome::DoNothing,
            //     Outcome::both(Outcome::Clear, Outcome::Start),
            // ));

            Ok::<(), Error>(())
        }
    });

    let _ = we.reconfigure(runtime);
    let _ = we.main().await.into_diagnostic()?;
    Ok(())
}

// use core::fmt::Error;
// use miette::Result;
// use std::path::PathBuf;
// use std::time::Duration;
// use watchexec::action::Action;
// use watchexec::action::Outcome;
// use watchexec::config::InitConfig;
// use watchexec::config::RuntimeConfig;
// use watchexec::Watchexec;
// use watchexec_signals::Signal;

//struct Widget {}

// impl Widget {
//     pub fn update(&mut self, path: PathBuf) {
//         dbg!(path);
//     }
// }

// #[tokio::main]
// async fn main() -> Result<()> {
//     println!("Starting process");
//     let mut widget = Widget {};
//     let init = InitConfig::default();
//     let mut runtime = RuntimeConfig::default();
//     runtime.pathset([
//         "/Users/alan/workshop/alanwsmith.com/templates",
//         "/Users/alan/Grimoire",
//         "/Users/alan/workshop/neopolengine/target/release",
//     ]);
//     runtime.action_throttle(Duration::new(0, 3000000000));
//     runtime.on_action(move |action: Action| {
//         let mut stop: bool = false;
//         // let mut paths: Vec<PathBuf> = vec![];
//         action.events.iter().for_each(|event| {
//             event.signals().for_each(|sig| match sig {
//                 Signal::Interrupt => {
//                     println!("Caught Interrupt: Stopping");
//                     stop = true;
//                 }
//                 _ => {}
//             });
//              event
//                  .paths()
//             //     .for_each(|path| paths.push(path.0.to_path_buf()));
//         });
//         if stop {
//             action.outcome(Outcome::Exit);
//         }
//         // paths.dedup();
//         // paths
//         //     .iter()
//         //     .filter(|p| is_visible(p.to_path_buf()))
//         //     .for_each(|path| {
//         //         widget.update(path.to_path_buf());
//         //     });
//         async move { Ok::<(), Error>(()) }
//     });
//     let we = Watchexec::new(init, runtime)?;
//     we.main().await.unwrap().unwrap();
//     Ok(())
// }

// fn is_visible(entry: PathBuf) -> bool {
//     entry
//         .file_name()
//         .unwrap()
//         .to_str()
//         .map(|s| !s.starts_with(".") && !s.ends_with("~"))
//         .unwrap_or(false)
// }

Original Notes

TODO : Remove this prior version of the code when the new version is polished

This is what I'm doing to automatically run my site built process when content files change.

rust
use core::fmt::Error;
use miette::Result;
use std::path::PathBuf;
use std::time::Duration;
use watchexec::action::Action;
use watchexec::action::Outcome;
use watchexec::config::InitConfig;
use watchexec::config::RuntimeConfig;
use watchexec::Watchexec;
use watchexec_signals::Signal;

struct Widget {}

impl Widget {
    pub fn update(&mut self, path: PathBuf) {
        dbg!(path);
    }
}

#[tokio::main]
async fn main() -> Result<()> {
    println!("Starting process");
    let mut widget = Widget {};
    let init = InitConfig::default();
    let mut runtime = RuntimeConfig::default();
    runtime.pathset(["/Users/alan/Desktop"]);
    runtime.action_throttle(Duration::new(0, 100000000));
    runtime.on_action(move |action: Action| {
        let mut stop: bool = false;
        let mut paths: Vec<PathBuf> = vec![];
        action.events.iter().for_each(|event| {
            event.signals().for_each(|sig| match sig {
                Signal::Interrupt => {
                    println!("Caught Interrupt: Stopping");
                    stop = true;
                }
                _ => {}
            });
            event
                .paths()
                .for_each(|path| paths.push(path.0.to_path_buf()));
        });
        if stop {
            action.outcome(Outcome::Exit);
        }
        paths.dedup();
        paths.iter().for_each(|path| {
            widget.update(path.to_path_buf());
        });
        async move { Ok::<(), Error>(()) }
    });
    let we = Watchexec::new(init, runtime)?;
    we.main().await.unwrap().unwrap();
    Ok(())
}

Dependencies

code full

- The ` .action _ throttle() [TODO: Code shorthand span ] value of 100000000 is nanoseconds and translates to 0.1 seconds for debouncing. I thought the method was broken at first becuase I was using a value based on milliseconds which made it seem like there was no delay at all.

- I've got a dedupe in place to try to work alongside the debounce throttle, but it's not working yet

- The ` .action _ throttle() [TODO: Code shorthand span ] is there in an effort to debounce but it's not doing what I want. Needs more investigation.

- There are often several file events that fire for one file change. I'm pushing all the paths for any batch into a vec then deduping it as a partial measure to debounce

- You can use CTRL c to stop the process

- Using the struct that's outside of the ` .on _ action() [TODO: Code shorthand span ] requires keeping it outside of the ` async [TODO: Code shorthand span ] call. It took a while to figure that part out. It seems like there should be a way to get it in there, but this is fine for my purpose because the build files should probably go synchronously anyway.

[] Link over to the other piece that points to just catching Control - C.

[] Refine this post as an alternative and then cross link it with this one : 2p1t9jxc5gmb

Footnotes And References

  • watchexec

    The Rust crate that powers all this and things like cargo watch too