command_stream/development/
cli.rs1use super::{dev, start_repl, DevEvent, DevOptions, ReplOptions};
2use std::io::{self, IsTerminal};
3use std::path::Path;
4
5pub async fn cli(args: &[String]) -> io::Result<Option<i32>> {
7 match args.first().map(String::as_str) {
8 Some("--help" | "-h") => {
9 println!("Usage: command-stream <command> [args...]\n repl [--crate PATH] [--disable-readline] Rust REPL (requires Evcxr)\n dev Cargo.toml [watch-glob ...] [--repl] Restart Cargo on changes\n fy input.sh output.mjs Translate a shell script");
10 Ok(Some(0))
11 }
12 Some("repl") => {
13 let mut options = ReplOptions {
14 disable_readline: !io::stdin().is_terminal(),
15 ..Default::default()
16 };
17 let mut arguments = args[1..].iter();
18 while let Some(argument) = arguments.next() {
19 match argument.as_str() {
20 "--crate" => {
21 options.crate_path = Some(arguments.next().ok_or_else(usage)?.into())
22 }
23 "--disable-readline" => options.disable_readline = true,
24 _ => return Err(usage()),
25 }
26 }
27 let piped = options.disable_readline;
28 let mut repl = start_repl(options)?;
29 loop {
30 tokio::select! {
31 result = repl.wait() => return Ok(Some(result?.code().unwrap_or(1))),
32 signal = tokio::signal::ctrl_c() => {
33 signal?;
34 if piped { repl.close().await?; return Ok(Some(0)); }
35 },
36 result = terminated() => { result?; repl.close().await?; return Ok(Some(0)); },
37 }
38 }
39 }
40 Some("dev") => {
41 let manifest = Path::new(args.get(1).ok_or_else(usage)?).canonicalize()?;
42 if !manifest.is_file() {
43 return Err(usage());
44 }
45 let cwd = manifest.parent().ok_or_else(usage)?.to_path_buf();
46 let watch = args[2..]
47 .iter()
48 .filter(|argument| argument.as_str() != "--repl")
49 .cloned()
50 .collect::<Vec<_>>();
51 let mut options = DevOptions {
52 cwd: cwd.clone(),
53 command: vec![
54 "cargo".into(),
55 "run".into(),
56 "--manifest-path".into(),
57 manifest.to_string_lossy().into_owned(),
58 ],
59 ..Default::default()
60 };
61 if !watch.is_empty() {
62 options.watch = watch;
63 }
64 let mut repl = if args.iter().any(|argument| argument == "--repl") {
65 Some(start_repl(ReplOptions {
66 disable_readline: !io::stdin().is_terminal(),
67 ..Default::default()
68 })?)
69 } else {
70 None
71 };
72 println!(
73 "command-stream Development Mode\nWatching patterns: {}",
74 options.watch.join(", ")
75 );
76 let mut controller = dev(options).await?;
77 loop {
78 tokio::select! {
79 result = tokio::signal::ctrl_c() => { result?; break; }
80 result = terminated() => { result?; break; }
81 event = controller.next_event() => match event {
82 Some(DevEvent::Error(message)) => eprintln!("Development: {message}"),
83 Some(DevEvent::Restarted { .. }) => {},
84 None => break,
85 },
86 _ = async { repl.as_mut().unwrap().wait().await }, if repl.is_some() => break,
87 }
88 }
89 if let Some(mut repl) = repl {
90 repl.close().await?;
91 }
92 controller.close().await?;
93 Ok(Some(0))
94 }
95 _ => Ok(None),
96 }
97}
98
99fn usage() -> io::Error {
100 io::Error::new(io::ErrorKind::InvalidInput, "Usage: command-stream repl [--crate PATH] [--disable-readline] | dev Cargo.toml [watch-glob ...] [--repl]")
101}
102
103async fn terminated() -> io::Result<()> {
104 #[cfg(unix)]
105 {
106 tokio::signal::unix::signal(tokio::signal::unix::SignalKind::terminate())?
107 .recv()
108 .await;
109 Ok(())
110 }
111 #[cfg(not(unix))]
112 std::future::pending().await
113}