The words Under construction in black text on a yellow background with diagonal black stipes surrounding it
I'm in the process of moving my site. It's still a work in progress. Please excuse the mess and broken links.

Run Rust Code When File Change With watchexec

TODO: Pull subtitle into page object

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 `ps`` to find the pocess ID and then use `kill -9 PID`` 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)

code_start_default_section code_end_default_section

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.

Code
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

pre_full_default_section

Notes

Debugging Stuff

I'm moving stuff around right now. All this below is helping me figure out where to put stuff

        -- title

Run Rust Code When File Change With watchexec

-- note

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

-- warning

I wasn't able to use CTRL+c to stop a running 
app until I added the Signal::Interrupt check.
I had to use `ps`` to find the pocess ID and then
use `kill -9 PID`` to stop it. Turned out to 
not be a big deal but I would have preferred
to know that going in

-- h2

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)

-- code/
-- 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)
// }

-- /code

-- h2

Original Notes

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

-- p


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

-- code 
-- 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(())
}

-- h3

Dependencies


-- pre

cargo add watchexec miette tokio watchexec_signals


-- notes

- The `.action_throttle()`rust` 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()`rust` 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 <<kbd|CTRL>><<kbd|c>> to stop the
process

- Using the struct that's outside of the 
`.on_action()`rust` requires keeping it outside
of the `async`rust` 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.


-- todo

[] 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


-- ref
-- title: watchexec 
-- url: https://docs.rs/watchexec/latest/watchexec/

The Rust crate that powers all this and things
like <<em|cargo watch>> too


-- metadata
-- date: 2023-04-29 19:36:05
-- id: 2p7c45vj
-- site: aws
-- type: rust 
-- status: published