diff --git a/Cargo.lock b/Cargo.lock index b7d9903..5944903 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -845,6 +845,15 @@ version = "2.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f518f335dce6725a761382244631d86cf0ccb2863413590b31338feb467f9c3" +[[package]] +name = "is-docker" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "928bae27f42bc99b60d9ac7334e3a21d10ad8f1835a4e12ec3ec0464765ed1b3" +dependencies = [ + "once_cell", +] + [[package]] name = "is-terminal" version = "0.4.3" @@ -857,6 +866,16 @@ dependencies = [ "windows-sys 0.45.0", ] +[[package]] +name = "is-wsl" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "173609498df190136aa7dea1a91db051746d339e18476eed5ca40521f02d7aa5" +dependencies = [ + "is-docker", + "once_cell", +] + [[package]] name = "itoa" version = "1.0.10" @@ -929,6 +948,7 @@ dependencies = [ "mime_guess", "notify", "notify-debouncer-full", + "open", "reqwest", "tokio", ] @@ -1114,6 +1134,17 @@ version = "1.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" +[[package]] +name = "open" +version = "5.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90878fb664448b54c4e592455ad02831e23a3f7e157374a8b95654731aac7349" +dependencies = [ + "is-wsl", + "libc", + "pathdiff", +] + [[package]] name = "openssl" version = "0.10.61" @@ -1181,6 +1212,12 @@ dependencies = [ "windows-targets 0.48.0", ] +[[package]] +name = "pathdiff" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8835116a5c179084a830efb3adc117ab007512b535bc1a21c991d3b32a6b44dd" + [[package]] name = "percent-encoding" version = "2.3.1" diff --git a/Cargo.toml b/Cargo.toml index 6dcd69d..eabd5fb 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -22,6 +22,7 @@ tokio = { version = "1.35.0", features = ["full"] } axum = { version = "0.7.2", features = ["ws"]} futures = "0.3.29" mime_guess = "2.0.3" +open = "5.0.1" [dev-dependencies] reqwest = "0.11.23" diff --git a/README.md b/README.md index 69d4ae9..769dfdd 100644 --- a/README.md +++ b/README.md @@ -35,6 +35,7 @@ Arguments: Options: -H, --host Set the listener host [default: 0.0.0.0] -p, --port Set the listener port [default: 0] + -o, --open Open the page in browser automatically -h, --help Print help -V, --version Print version ``` diff --git a/src/lib.rs b/src/lib.rs index 5598bcb..4d6e797 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -17,10 +17,11 @@ mod server; mod watcher; -use std::{error::Error, path::PathBuf}; +use std::{error::Error, net::IpAddr, path::PathBuf}; use axum::Router; -use notify::ReadDirectoryChangesWatcher; +use local_ip_address::local_ip; +use notify::RecommendedWatcher; use notify_debouncer_full::{DebouncedEvent, Debouncer, FileIdMap}; use server::{create_listener, create_server}; use tokio::{ @@ -37,13 +38,13 @@ pub struct Listener { tcp_listener: TcpListener, router: Router, root_path: PathBuf, - debouncer: Debouncer, + debouncer: Debouncer, rx: Receiver, Vec>>, } impl Listener { /// Start live-server - /// + /// /// ``` /// use live_server::listen; /// @@ -52,9 +53,9 @@ impl Listener { /// } /// ``` pub async fn start(self) -> Result<(), Box> { - ROOT.set(self.root_path.clone()).unwrap(); + ROOT.set(self.root_path.clone())?; let (tx, _) = broadcast::channel(16); - TX.set(tx).unwrap(); + TX.set(tx)?; let watcher_future = tokio::spawn(watcher::watch(self.root_path, self.debouncer, self.rx)); let server_future = tokio::spawn(server::serve(self.tcp_listener, self.router)); @@ -63,11 +64,25 @@ impl Listener { Ok(()) } -} + pub fn link(&self) -> Result> { + let addr = self.tcp_listener.local_addr()?; + let port = addr.port(); + let host = addr.ip(); + let host = match host.is_unspecified() { + true => local_ip()?, + false => host, + }; + + Ok(match host { + IpAddr::V4(host) => format!("http://{host}:{port}"), + IpAddr::V6(host) => format!("http://[{host}]:{port}"), + }) + } +} /// Create live-server listener -/// +/// /// ``` /// use live_server::listen; /// diff --git a/src/main.rs b/src/main.rs index 3e09c53..a8ca397 100644 --- a/src/main.rs +++ b/src/main.rs @@ -15,6 +15,9 @@ struct Args { /// Set the listener port #[clap(short, long, default_value = "0")] port: u16, + /// Open the page in browser automatically + #[clap(short, long)] + open: bool, } #[tokio::main] @@ -22,9 +25,20 @@ async fn main() { let env = Env::new().default_filter_or("info"); env_logger::init_from_env(env); - let Args { host, port, root } = Args::parse(); + let Args { + host, + port, + root, + open, + } = Args::parse(); let addr = format!("{}:{}", host, port); + let listener = listen(addr, root).await.unwrap(); - listen(addr, root).await.unwrap().start().await.unwrap(); + if open { + let link = listener.link().unwrap(); + open::that(link).unwrap(); + } + + listener.start().await.unwrap(); } diff --git a/src/watcher.rs b/src/watcher.rs index 5eda5f8..dc21e27 100644 --- a/src/watcher.rs +++ b/src/watcher.rs @@ -1,6 +1,6 @@ use std::{path::PathBuf, time::Duration}; -use notify::{Error, ReadDirectoryChangesWatcher, RecursiveMode, Watcher}; +use notify::{Error, RecommendedWatcher, RecursiveMode, Watcher}; use notify_debouncer_full::{ new_debouncer, DebounceEventResult, DebouncedEvent, Debouncer, FileIdMap, }; @@ -21,7 +21,7 @@ pub(crate) async fn create_watcher( root: PathBuf, ) -> Result< ( - Debouncer, + Debouncer, PathBuf, Receiver, Vec>>, ), @@ -65,7 +65,7 @@ pub(crate) async fn create_watcher( pub async fn watch( root_path: PathBuf, - mut debouncer: Debouncer, + mut debouncer: Debouncer, mut rx: Receiver, Vec>>, ) { debouncer