64 个版本 (稳定版)

4.1.0 2024年4月28日
3.0.1 2023年11月29日
2.3.2 2023年11月29日
2.3.0 2023年3月22日
1.5.0 2016年11月23日

#19 in 文件系统

Download history 16909/week @ 2024-05-03 18287/week @ 2024-05-10 16210/week @ 2024-05-17 16391/week @ 2024-05-24 16200/week @ 2024-05-31 14379/week @ 2024-06-07 12565/week @ 2024-06-14 14185/week @ 2024-06-21 10533/week @ 2024-06-28 10894/week @ 2024-07-05 12223/week @ 2024-07-12 13046/week @ 2024-07-19 12079/week @ 2024-07-26 11634/week @ 2024-08-02 13855/week @ 2024-08-09 11009/week @ 2024-08-16

51,016 每月下载量
用于 46 个 Crates(直接使用21个)

Apache-2.0

275KB
5.5K SLoC

Crates.io page API Docs Crate license: Apache 2.0 CI status

Watchexec 库

Watchexec CLI 和其他工具提供动力的库。

示例

以下是一个完整的示例,展示了该库的一些功能

use miette::{IntoDiagnostic, Result};
use std::{
    sync::{Arc, Mutex},
    time::Duration,
};
use watchexec::{
    command::{Command, Program, Shell},
    job::CommandState,
    Watchexec,
};
use watchexec_events::{Event, Priority};
use watchexec_signals::Signal;

#[tokio::main]
async fn main() -> Result<()> {
    // this is okay to start with, but Watchexec logs a LOT of data,
    // even at error level. you will quickly want to filter it down.
    tracing_subscriber::fmt()
        .with_env_filter(tracing_subscriber::EnvFilter::from_default_env())
        .init();

    // initialise Watchexec with a simple initial action handler
    let job = Arc::new(Mutex::new(None));
    let wx = Watchexec::new({
        let outerjob = job.clone();
        move |mut action| {
            let (_, job) = action.create_job(Arc::new(Command {
                program: Program::Shell {
                    shell: Shell::new("bash"),
                    command: "
                        echo 'Hello world'
                        trap 'echo Not quitting yet!' TERM
                        read
                    "
                    .into(),
                    args: Vec::new(),
                },
                options: Default::default(),
            }));

            // store the job outside this closure too
            *outerjob.lock().unwrap() = Some(job.clone());

            // block SIGINT
            #[cfg(unix)]
            job.set_spawn_hook(|cmd, _| {
                use nix::sys::signal::{sigprocmask, SigSet, SigmaskHow, Signal};
                unsafe {
                    cmd.command_mut().pre_exec(|| {
                        let mut newset = SigSet::empty();
                        newset.add(Signal::SIGINT);
                        sigprocmask(SigmaskHow::SIG_BLOCK, Some(&newset), None)?;
                        Ok(())
                    });
                }
            });

            // start the command
            job.start();

            action
        }
    })?;

    // start the engine
    let main = wx.main();

    // send an event to start
    wx.send_event(Event::default(), Priority::Urgent)
        .await
        .unwrap();
    // ^ this will cause the action handler we've defined above to run,
    //   creating and starting our little bash program, and storing it in the mutex

    // spin until we've got the job
    while job.lock().unwrap().is_none() {
        tokio::task::yield_now().await;
    }

    // watch the job and restart it when it exits
    let job = job.lock().unwrap().clone().unwrap();
    let auto_restart = tokio::spawn(async move {
        loop {
            job.to_wait().await;
            job.run(|context| {
                if let CommandState::Finished {
                    status,
                    started,
                    finished,
                } = context.current
                {
                    let duration = *finished - *started;
                    eprintln!("[Program stopped with {status:?}; ran for {duration:?}]")
                }
            })
            .await;

            eprintln!("[Restarting...]");
            job.start().await;
        }
    });

    // now we change what the action does:
    let auto_restart_abort = auto_restart.abort_handle();
    wx.config.on_action(move |mut action| {
        // if we get Ctrl-C on the Watchexec instance, we quit
        if action.signals().any(|sig| sig == Signal::Interrupt) {
            eprintln!("[Quitting...]");
            auto_restart_abort.abort();
            action.quit_gracefully(Signal::ForceStop, Duration::ZERO);
            return action;
        }

        // if the action was triggered by file events, gracefully stop the program
        if action.paths().next().is_some() {
            // watchexec can manage ("supervise") more than one program;
            // here we only have one but we don't know its Id so we grab it out of the iterator
            if let Some(job) = action.list_jobs().next().map(|(_, job)| job.clone()) {
                eprintln!("[Asking program to stop...]");
                job.stop_with_signal(Signal::Terminate, Duration::from_secs(5));
            }
        }

        action
    });

    // and watch all files in the current directory:
    wx.config.pathset(["."]);

    // then keep running until Watchexec quits!
    let _ = main.await.into_diagnostic()?;
    auto_restart.abort();
    Ok(())
}

其他示例

大杂烩

虽然这不是其主要用例,但该库公开了其大部分相对独立的组件,可用于创建其他非 Watchexec 形状的工具

过滤器已分割到自己的 crates 中,以便可以独立发展

  • Globset 过滤器实现了基于正则表达式 crate 的忽略机制的默认 Watchexec CLI 过滤。

  • Tagged 过滤器 是创建更强大过滤解决方案的实验,它可以使用自定义语法在事件的所有部分上操作,而不仅仅是它们的路径。它不再维护。

  • 忽略 过滤器 实现了忽略文件语义,尤其支持 树状 忽略文件。它被用作上述两个主要过滤器中的子过滤器。

还有用于构建 Watchexec 的独立、独立的 crate,您可以使用它们。

  • 监控器 是 Watchexec 的进程监控器和命令抽象。

  • 清屏 默认以跨平台方式轻松清除终端屏幕,并提供高级选项以适应您的使用场景。

  • 命令组 在 Unix 和 Windows 之间增强了 std 和 tokio 的 Command,支持进程组。

  • 事件类型 包含 Watchexec 使用的所有事件类型,包括用于向子进程传递事件数据的 JSON 格式。

  • 信号类型 包含 Watchexec 使用的所有信号类型。

  • 忽略文件 找到、解析和解释忽略文件。

  • 项目起源 找到项目的起源(或根)路径以及它是什么类型的项目。

Rust 版本(MSRV)

由于依赖项更改 MSRV 的不可预测性,此库不再尝试在稳定版本背后保持最小支持的 Rust 版本。相反,假定开发者在所有时间都使用最新稳定的版本。

希望支持低于稳定 Rust 的应用程序(例如,Watchexec CLI 所做的那样)应

  • 使用锁文件
  • 在从源安装时推荐使用 --locked
  • 为非分发用户提供预构建的二进制文件(以及 Binstall 支持)
  • 避免使用新功能,直到一段时间过去,以便分发用户赶上
  • 考虑建议 distro-Rust 用户在可用时切换到 distro rustup

依赖项

~17–32MB
~482K SLoC