44//! The "v2cli" command-line interface -- a "multitool" interface resembling
55//! Cargo, as compared to the classic "rustc-like" CLI.
66
7- use std:: { env, ffi:: OsString , io:: Write , path:: PathBuf , process, str:: FromStr } ;
7+ use std:: {
8+ convert:: Infallible , env, ffi:: OsString , io:: Write , path:: PathBuf , process, str:: FromStr ,
9+ sync:: Arc ,
10+ } ;
811use structopt:: { clap:: AppSettings , StructOpt } ;
912use tectonic:: {
1013 self ,
@@ -19,9 +22,16 @@ use tectonic::{
1922use tectonic_bridge_core:: { SecuritySettings , SecurityStance } ;
2023use tectonic_bundles:: Bundle ;
2124use tectonic_docmodel:: workspace:: { Workspace , WorkspaceCreator } ;
22- use tectonic_errors:: Error as NewError ;
2325use tectonic_status_base:: plain:: PlainStatusBackend ;
24- use watchexec:: run:: OnBusyUpdate ;
26+ use tokio:: runtime;
27+ use watchexec:: {
28+ action:: { Action , Outcome , PreSpawn } ,
29+ command:: { Command , Shell } ,
30+ config:: InitConfig ,
31+ Watchexec ,
32+ } ;
33+ use watchexec_filterer_globset:: GlobsetFilterer ;
34+ use watchexec_signals:: Signal ;
2535
2636/// The main options for the "V2" command-line interface.
2737#[ derive( Debug , StructOpt ) ]
@@ -521,46 +531,100 @@ pub struct WatchCommand {
521531impl WatchCommand {
522532 fn customize ( & self , _cc : & mut CommandCustomizations ) { }
523533
524- fn execute ( self , _config : PersistentConfig , status : & mut dyn StatusBackend ) -> Result < i32 > {
534+ async fn execute_inner ( self , status : & mut dyn StatusBackend ) -> Result < i32 > {
525535 let exe_name = crate :: watch:: get_trimmed_exe_name ( )
526536 . into_os_string ( )
527537 . into_string ( )
528538 . expect ( "Executable path wasn't valid UTF-8" ) ;
529539 let mut cmds = Vec :: new ( ) ;
530540 for x in self . execute . iter ( ) {
531- let mut cmd = format ! ( "{exe_name} -X " ) ;
532541 let x = x. trim ( ) ;
533542 if !x. is_empty ( ) {
534- cmd. push_str ( x) ;
543+ let cmd = Command :: Exec {
544+ prog : exe_name. clone ( ) ,
545+ args : vec ! [ "-X" . to_string( ) , x. to_string( ) ] ,
546+ } ;
535547 cmds. push ( cmd)
536548 }
537549 }
538550
539551 if cmds. is_empty ( ) {
540- cmds. push ( format ! ( "{exe_name} -X build" ) )
552+ cmds. push ( Command :: Exec {
553+ prog : exe_name,
554+ args : vec ! [ "-X" . to_string( ) , "build" . to_string( ) ] ,
555+ } ) ;
541556 }
542557
543- let command = cmds. join ( " && " ) ;
544-
545- let mut args = watchexec:: config:: ConfigBuilder :: default ( ) ;
546- let mut final_command = command. clone ( ) ;
547- #[ cfg( unix) ]
548- final_command. push_str ( "; echo [Finished running. Exit status: $?]" ) ;
549558 #[ cfg( windows) ]
550- {
551- final_command. push_str ( " & echo [Finished running. Exit status: %ERRORLEVEL%]" ) ;
552- args. shell ( watchexec:: Shell :: Cmd ) ;
553- }
554- #[ cfg( not( any( unix, windows) ) ) ]
555- final_command. push_str ( " ; echo [Finished running]" ) ;
559+ let ( shell, command) = (
560+ Shell :: Cmd ,
561+ "echo [Finished running. Exit status: %ERRORLEVEL%]" ,
562+ ) ;
563+ #[ cfg( unix) ]
564+ let ( shell, command) = (
565+ Shell :: Unix ( "bash" . to_string ( ) ) ,
566+ "echo [Finished running. Exit status: $?]" ,
567+ ) ;
568+
569+ cmds. push ( Command :: Shell {
570+ shell,
571+ args : vec ! [ ] ,
572+ command : command. to_string ( ) ,
573+ } ) ;
574+
575+ let mut runtime_config = watchexec:: config:: RuntimeConfig :: default ( ) ;
576+ runtime_config. commands ( cmds) ;
577+
578+ let current_dir = env:: current_dir ( ) ?;
579+
580+ let filter = GlobsetFilterer :: new (
581+ & current_dir,
582+ [ ] ,
583+ // Ignore build directory, and things like vim swap files
584+ [ ( "build/**" . to_string ( ) , None ) , ( "*.swp" . to_string ( ) , None ) ] ,
585+ [ ] ,
586+ [ ] ,
587+ )
588+ . await
589+ . unwrap ( ) ;
590+
591+ runtime_config
592+ . pathset ( [ & current_dir] )
593+ . filterer ( Arc :: new ( filter) )
594+ . on_pre_spawn ( |pre_spawn : PreSpawn | async move {
595+ println ! ( "[Running `{}`]" , pre_spawn. command) ;
596+ Ok :: < _ , Infallible > ( ( ) )
597+ } )
598+ . on_action ( |action : Action | async move {
599+ for event in & * action. events {
600+ let is_kill = event. signals ( ) . any ( |signal| {
601+ matches ! (
602+ signal,
603+ Signal :: Interrupt
604+ | Signal :: Quit
605+ | Signal :: Terminate
606+ | Signal :: ForceStop
607+ )
608+ } ) ;
609+ if is_kill {
610+ action. outcome ( Outcome :: Exit ) ;
611+ return Ok :: < _ , Infallible > ( ( ) ) ;
612+ }
613+
614+ let paths = event. paths ( ) . collect :: < Vec < _ > > ( ) ;
615+ if !paths. is_empty ( ) {
616+ action. outcome ( Outcome :: IfRunning (
617+ Box :: new ( Outcome :: DoNothing ) ,
618+ Box :: new ( Outcome :: Start ) ,
619+ ) ) ;
620+ return Ok ( ( ) ) ;
621+ }
622+ }
623+ Ok ( ( ) )
624+ } ) ;
556625
557- args. cmd ( vec ! [ final_command] )
558- . paths ( vec ! [ env:: current_dir( ) ?] )
559- . ignores ( vec ! [ "build" . to_owned( ) ] )
560- . on_busy_update ( OnBusyUpdate :: Queue ) ;
561- let args = args. build ( ) . map_err ( NewError :: from) ?;
626+ let exec_handler = Watchexec :: new ( InitConfig :: default ( ) , runtime_config) ;
562627
563- let exec_handler = watchexec:: run:: ExecHandler :: new ( args) ;
564628 match exec_handler {
565629 Err ( e) => {
566630 tt_error ! (
@@ -571,19 +635,19 @@ impl WatchCommand {
571635 Ok ( 1 )
572636 }
573637 Ok ( exec_handler) => {
574- let handler = crate :: watch:: Watcher {
575- command,
576- inner : exec_handler,
577- } ;
578- if let Err ( e) = watchexec:: watch ( & handler) {
579- tt_error ! ( status, "failed to execute watch" ; e. into( ) ) ;
580- Ok ( 1 )
581- } else {
582- Ok ( 0 )
583- }
638+ exec_handler. main ( ) . await . unwrap ( ) . unwrap ( ) ;
639+ Ok ( 0 )
584640 }
585641 }
586642 }
643+
644+ fn execute ( self , _config : PersistentConfig , status : & mut dyn StatusBackend ) -> Result < i32 > {
645+ let rt = runtime:: Builder :: new_multi_thread ( )
646+ . enable_all ( )
647+ . build ( )
648+ . unwrap ( ) ;
649+ rt. block_on ( self . execute_inner ( status) )
650+ }
587651}
588652
589653/// `new`: Create a new document project
0 commit comments