1
1
use clap:: Parser ;
2
2
use fmterr:: fmt_err;
3
+ use notify:: { watcher, RecursiveMode , Watcher } ;
3
4
use perseus_cli:: parse:: SnoopSubcommand ;
4
5
use perseus_cli:: {
5
6
build, check_env, delete_artifacts, delete_bad_dir, deploy, eject, export, has_ejected,
@@ -10,6 +11,8 @@ use perseus_cli::{errors::*, snoop_build, snoop_server, snoop_wasm_build};
10
11
use std:: env;
11
12
use std:: io:: Write ;
12
13
use std:: path:: PathBuf ;
14
+ use std:: sync:: mpsc:: channel;
15
+ use std:: time:: Duration ;
13
16
14
17
// All this does is run the program and terminate with the acquired exit code
15
18
#[ tokio:: main]
@@ -24,6 +27,8 @@ async fn main() {
24
27
std:: process:: exit ( exit_code)
25
28
}
26
29
30
+ // IDEA Watch files at the `core()` level and then panic, catching the unwind in the watcher loop
31
+
27
32
// This manages error handling and returns a definite exit code to terminate with
28
33
async fn real_main ( ) -> i32 {
29
34
// Get the working directory
@@ -86,27 +91,148 @@ async fn core(dir: PathBuf) -> Result<i32, Error> {
86
91
build ( dir, build_opts) ?
87
92
}
88
93
Subcommand :: Export ( export_opts) => {
89
- // Delete old build/exportation artifacts
94
+ // Delete old build/export artifacts
90
95
delete_artifacts ( dir. clone ( ) , "static" ) ?;
91
96
delete_artifacts ( dir. clone ( ) , "exported" ) ?;
92
97
let exit_code = export ( dir. clone ( ) , export_opts. clone ( ) ) ?;
93
98
if exit_code != 0 {
94
99
return Ok ( exit_code) ;
95
100
}
96
- // Start a server for those files if requested
97
- if export_opts. serve {
98
- serve_exported ( dir, export_opts. host , export_opts. port ) . await ;
99
- }
100
101
101
- 0
102
+ if export_opts. watch {
103
+ let dir_2 = dir. clone ( ) ;
104
+ let export_opts_2 = export_opts. clone ( ) ;
105
+ if export_opts. serve {
106
+ tokio:: spawn ( async move {
107
+ serve_exported ( dir_2, export_opts_2. host , export_opts_2. port ) . await
108
+ } ) ;
109
+ }
110
+ // Now watch for changes
111
+ let ( tx, rx) = channel ( ) ;
112
+ let mut watcher = watcher ( tx, Duration :: from_secs ( 2 ) )
113
+ . map_err ( |err| WatchError :: WatcherSetupFailed { source : err } ) ?;
114
+ // Watch the current directory
115
+ for entry in std:: fs:: read_dir ( "." )
116
+ . map_err ( |err| WatchError :: ReadCurrentDirFailed { source : err } ) ?
117
+ {
118
+ // We want to exclude `target/` and `.perseus/`, otherwise we should watch everything
119
+ let entry =
120
+ entry. map_err ( |err| WatchError :: ReadDirEntryFailed { source : err } ) ?;
121
+ let name = entry. file_name ( ) ;
122
+ if name != "target" && name != ".perseus" {
123
+ watcher
124
+ . watch ( entry. path ( ) , RecursiveMode :: Recursive )
125
+ . map_err ( |err| WatchError :: WatchFileFailed {
126
+ filename : entry. path ( ) . to_str ( ) . unwrap ( ) . to_string ( ) ,
127
+ source : err,
128
+ } ) ?;
129
+ }
130
+ }
131
+
132
+ let res: Result < i32 , Error > = loop {
133
+ match rx. recv ( ) {
134
+ Ok ( _) => {
135
+ // Delete old build/exportation artifacts
136
+ delete_artifacts ( dir. clone ( ) , "static" ) ?;
137
+ delete_artifacts ( dir. clone ( ) , "exported" ) ?;
138
+ let dir_2 = dir. clone ( ) ;
139
+ let opts = export_opts. clone ( ) ;
140
+ match export ( dir_2. clone ( ) , opts. clone ( ) ) {
141
+ // We'l let the user know if there's a non-zero exit code
142
+ Ok ( exit_code) => {
143
+ if exit_code != 0 {
144
+ eprintln ! ( "Non-zero exit code returned from exporting process: {}." , exit_code)
145
+ }
146
+ }
147
+ // Because we're watching for changes, we can manage errors here
148
+ // We won't actually terminate unless the user tells us to
149
+ Err ( err) => eprintln ! ( "{}" , fmt_err( & err) ) ,
150
+ }
151
+ // TODO Reload the browser automatically
152
+ }
153
+ Err ( err) => break Err ( WatchError :: WatcherError { source : err } . into ( ) ) ,
154
+ }
155
+ } ;
156
+ return res;
157
+ } else {
158
+ if export_opts. serve {
159
+ serve_exported ( dir, export_opts. host , export_opts. port ) . await ;
160
+ }
161
+ 0
162
+ }
102
163
}
103
164
Subcommand :: Serve ( serve_opts) => {
104
- // Delete old build artifacts if `--no-build` wasn't specified
105
165
if !serve_opts. no_build {
106
166
delete_artifacts ( dir. clone ( ) , "static" ) ?;
107
167
}
108
- let ( exit_code, _server_path) = serve ( dir, serve_opts) ?;
109
- exit_code
168
+ if serve_opts. watch {
169
+ match serve ( dir. clone ( ) , serve_opts. clone ( ) ) {
170
+ // We'll let the user know if there's a non-zero exit code
171
+ Ok ( ( exit_code, _server_path) ) => {
172
+ if exit_code != 0 {
173
+ eprintln ! (
174
+ "Non-zero exit code returned from serving process: {}." ,
175
+ exit_code
176
+ )
177
+ }
178
+ }
179
+ // Because we're watching for changes, we can manage errors here
180
+ // We won't actually terminate unless the user tells us to
181
+ Err ( err) => eprintln ! ( "{}" , fmt_err( & err) ) ,
182
+ } ;
183
+ // Now watch for changes
184
+ let ( tx, rx) = channel ( ) ;
185
+ let mut watcher = watcher ( tx, Duration :: from_secs ( 2 ) )
186
+ . map_err ( |err| WatchError :: WatcherSetupFailed { source : err } ) ?;
187
+ // Watch the current directory
188
+ for entry in std:: fs:: read_dir ( "." )
189
+ . map_err ( |err| WatchError :: ReadCurrentDirFailed { source : err } ) ?
190
+ {
191
+ // We want to exclude `target/` and `.perseus/`, otherwise we should watch everything
192
+ let entry =
193
+ entry. map_err ( |err| WatchError :: ReadDirEntryFailed { source : err } ) ?;
194
+ let name = entry. file_name ( ) ;
195
+ if name != "target" && name != ".perseus" {
196
+ watcher
197
+ . watch ( entry. path ( ) , RecursiveMode :: Recursive )
198
+ . map_err ( |err| WatchError :: WatchFileFailed {
199
+ filename : entry. path ( ) . to_str ( ) . unwrap ( ) . to_string ( ) ,
200
+ source : err,
201
+ } ) ?;
202
+ }
203
+ }
204
+
205
+ let res: Result < i32 , Error > = loop {
206
+ match rx. recv ( ) {
207
+ Ok ( _) => {
208
+ // Delete old build artifacts if `--no-build` wasn't specified
209
+ if !serve_opts. no_build {
210
+ delete_artifacts ( dir. clone ( ) , "static" ) ?;
211
+ }
212
+ match serve ( dir. clone ( ) , serve_opts. clone ( ) ) {
213
+ // We'll let the user know if there's a non-zero exit code
214
+ Ok ( ( exit_code, _server_path) ) => {
215
+ if exit_code != 0 {
216
+ eprintln ! (
217
+ "Non-zero exit code returned from serving process: {}." ,
218
+ exit_code
219
+ )
220
+ }
221
+ }
222
+ // Because we're watching for changes, we can manage errors here
223
+ // We won't actually terminate unless the user tells us to
224
+ Err ( err) => eprintln ! ( "{}" , fmt_err( & err) ) ,
225
+ } ;
226
+ // TODO Reload the browser automatically
227
+ }
228
+ Err ( err) => break Err ( WatchError :: WatcherError { source : err } . into ( ) ) ,
229
+ }
230
+ } ;
231
+ return res;
232
+ } else {
233
+ let ( exit_code, _server_path) = serve ( dir, serve_opts) ?;
234
+ exit_code
235
+ }
110
236
}
111
237
Subcommand :: Test ( test_opts) => {
112
238
// This will be used by the subcrates
0 commit comments