From 8cf6d29285702fd6eae0a87544a7120d95a6fac6 Mon Sep 17 00:00:00 2001 From: Yumacide Date: Fri, 12 Aug 2022 19:59:36 +0400 Subject: [PATCH 1/6] Implement `rojo open` --- src/cli/build.rs | 8 ++-- src/cli/mod.rs | 4 ++ src/cli/open.rs | 102 ++++++++++++++++++++++++++++++++++++++++++++++ src/cli/plugin.rs | 2 +- src/cli/serve.rs | 10 +++-- src/project.rs | 2 +- 6 files changed, 119 insertions(+), 9 deletions(-) create mode 100644 src/cli/open.rs diff --git a/src/cli/build.rs b/src/cli/build.rs index d6c3f3499..435c3bf6f 100644 --- a/src/cli/build.rs +++ b/src/cli/build.rs @@ -14,7 +14,7 @@ use crate::serve_session::ServeSession; use super::resolve_path; -const UNKNOWN_OUTPUT_KIND_ERR: &str = "Could not detect what kind of file to build. \ +pub(in crate::cli) const UNKNOWN_OUTPUT_KIND_ERR: &str = "Could not detect what kind of file to build. \ Expected output file to end in .rbxl, .rbxlx, .rbxm, or .rbxmx."; /// Generates a model or place file from the Rojo project. @@ -72,7 +72,7 @@ impl BuildCommand { /// The different kinds of output that Rojo can build to. #[derive(Debug, Clone, Copy, PartialEq, Eq)] -enum OutputKind { +pub(in crate::cli) enum OutputKind { /// An XML model file. Rbxmx, @@ -86,7 +86,7 @@ enum OutputKind { Rbxl, } -fn detect_output_kind(output: &Path) -> Option { +pub(in crate::cli) fn detect_output_kind(output: &Path) -> Option { let extension = output.extension()?.to_str()?; match extension { @@ -103,7 +103,7 @@ fn xml_encode_config() -> rbx_xml::EncodeOptions { } #[profiling::function] -fn write_model( +pub(in crate::cli) fn write_model( session: &ServeSession, output: &Path, output_kind: OutputKind, diff --git a/src/cli/mod.rs b/src/cli/mod.rs index 36b329404..7315c27a2 100644 --- a/src/cli/mod.rs +++ b/src/cli/mod.rs @@ -4,6 +4,7 @@ mod build; mod doc; mod fmt_project; mod init; +mod open; mod plugin; mod serve; mod sourcemap; @@ -18,6 +19,7 @@ pub use self::build::BuildCommand; pub use self::doc::DocCommand; pub use self::fmt_project::FmtProjectCommand; pub use self::init::{InitCommand, InitKind}; +use self::open::OpenCommand; pub use self::plugin::{PluginCommand, PluginSubcommand}; pub use self::serve::ServeCommand; pub use self::sourcemap::SourcemapCommand; @@ -46,6 +48,7 @@ impl Options { Subcommand::FmtProject(subcommand) => subcommand.run(), Subcommand::Doc(subcommand) => subcommand.run(), Subcommand::Plugin(subcommand) => subcommand.run(), + Subcommand::Open(subcommand) => subcommand.run(self.global), } } } @@ -119,6 +122,7 @@ pub enum Subcommand { FmtProject(FmtProjectCommand), Doc(DocCommand), Plugin(PluginCommand), + Open(OpenCommand), } pub(super) fn resolve_path(path: &Path) -> Cow<'_, Path> { diff --git a/src/cli/open.rs b/src/cli/open.rs new file mode 100644 index 000000000..27c9a52ee --- /dev/null +++ b/src/cli/open.rs @@ -0,0 +1,102 @@ +use anyhow::{Context, Ok}; +use clap::Parser; +use memofs::Vfs; +use roblox_install::RobloxStudio; + +use crate::{ + cli::{ + build::{detect_output_kind, write_model, UNKNOWN_OUTPUT_KIND_ERR}, + plugin::install_plugin, + serve::{show_start_message, DEFAULT_BIND_ADDRESS, DEFAULT_PORT}, + }, + serve_session::ServeSession, + web::LiveServer, + PROJECT_FILENAME, +}; +use std::{ + env, + net::IpAddr, + path::{Path, PathBuf}, + process::{Command, Stdio}, + sync::Arc, +}; + +use super::GlobalOptions; + +#[derive(Debug, Parser)] +pub struct OpenCommand { + /// Path to the project file to serve from. Defaults to default.project.json + #[clap(long)] + pub project: Option, + + // Path to an output place to build and serve to. Will be created automatically if it doesn't exist + #[clap(long)] + pub output: PathBuf, + + /// The IP address to listen on. Defaults to `127.0.0.1`. + #[clap(long)] + pub address: Option, + + /// The port to listen on. Defaults to the project's preference, or `34872` if + /// it has none. + #[clap(long)] + pub port: Option, +} + +impl OpenCommand { + pub fn run(self, global: GlobalOptions) -> anyhow::Result<()> { + let project = self + .project + .unwrap_or_else(|| env::current_dir().unwrap().join(PROJECT_FILENAME)); + let output_kind = detect_output_kind(&self.output).context(UNKNOWN_OUTPUT_KIND_ERR)?; + + log::trace!("Constructing in-memory filesystem"); + let vfs = Vfs::new_default(); + vfs.set_watch_enabled(false); + + let session = ServeSession::new(vfs, &project)?; + + let studio = RobloxStudio::locate()?; + + if !self.output.exists() { + write_model(&session, &self.output, output_kind)?; + } + + if !plugin_exists(&studio) { + install_plugin().unwrap(); + } + + open_place(&studio, &self.output).expect("Could not open place in Roblox Studio"); + + let ip = self + .address + .or_else(|| session.serve_address()) + .unwrap_or_else(|| DEFAULT_BIND_ADDRESS.into()); + + let port = self + .port + .or_else(|| session.project_port()) + .unwrap_or(DEFAULT_PORT); + + let server = LiveServer::new(Arc::new(session)); + + let _ = show_start_message(ip, port, global.color.into()); + server.start((ip, port).into()); + + Ok(()) + } +} + +fn plugin_exists(studio: &RobloxStudio) -> bool { + studio.plugins_path().join("rojo.rbxm").exists() +} + +fn open_place(studio: &RobloxStudio, place: &Path) -> anyhow::Result<()> { + Command::new(studio.application_path()) + .arg(format!("{}", place.display())) + .stdout(Stdio::null()) + .stderr(Stdio::null()) + .spawn()?; + + Ok(()) +} diff --git a/src/cli/plugin.rs b/src/cli/plugin.rs index aa8773b35..90d802689 100644 --- a/src/cli/plugin.rs +++ b/src/cli/plugin.rs @@ -46,7 +46,7 @@ impl PluginSubcommand { } } -fn install_plugin() -> anyhow::Result<()> { +pub(in crate::cli) fn install_plugin() -> anyhow::Result<()> { let plugin_snapshot: VfsSnapshot = bincode::deserialize(PLUGIN_BINCODE) .expect("Rojo's plugin was not properly packed into Rojo's binary"); diff --git a/src/cli/serve.rs b/src/cli/serve.rs index 0e35785e3..9b8ca73f6 100644 --- a/src/cli/serve.rs +++ b/src/cli/serve.rs @@ -13,8 +13,8 @@ use crate::{serve_session::ServeSession, web::LiveServer}; use super::{resolve_path, GlobalOptions}; -const DEFAULT_BIND_ADDRESS: Ipv4Addr = Ipv4Addr::new(127, 0, 0, 1); -const DEFAULT_PORT: u16 = 34872; +pub(in crate::cli) const DEFAULT_BIND_ADDRESS: Ipv4Addr = Ipv4Addr::new(127, 0, 0, 1); +pub(in crate::cli) const DEFAULT_PORT: u16 = 34872; /// Expose a Rojo project to the Rojo Studio plugin. #[derive(Debug, Parser)] @@ -60,7 +60,11 @@ impl ServeCommand { } } -fn show_start_message(bind_address: IpAddr, port: u16, color: ColorChoice) -> io::Result<()> { +pub(in crate::cli) fn show_start_message( + bind_address: IpAddr, + port: u16, + color: ColorChoice, +) -> io::Result<()> { let mut green = ColorSpec::new(); green.set_fg(Some(Color::Green)).set_bold(true); diff --git a/src/project.rs b/src/project.rs index 330cbd23a..f80d2ca4d 100644 --- a/src/project.rs +++ b/src/project.rs @@ -10,7 +10,7 @@ use thiserror::Error; use crate::{glob::Glob, resolution::UnresolvedValue}; -static PROJECT_FILENAME: &str = "default.project.json"; +pub(crate) static PROJECT_FILENAME: &str = "default.project.json"; /// Error type returned by any function that handles projects. #[derive(Debug, Error)] From ef246023cbb24cf00b24a492341b4ac157583108 Mon Sep 17 00:00:00 2001 From: Micah Date: Sat, 13 Aug 2022 22:13:03 +0200 Subject: [PATCH 2/6] Add periods to comments --- src/cli/open.rs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/cli/open.rs b/src/cli/open.rs index 27c9a52ee..a739e7811 100644 --- a/src/cli/open.rs +++ b/src/cli/open.rs @@ -25,11 +25,12 @@ use super::GlobalOptions; #[derive(Debug, Parser)] pub struct OpenCommand { - /// Path to the project file to serve from. Defaults to default.project.json + /// Path to the project file to serve from. Defaults to default.project.json. #[clap(long)] pub project: Option, - // Path to an output place to build and serve to. Will be created automatically if it doesn't exist + // Path to an output place to build and serve to. Will be created automatically + /// if it doesn't exist. #[clap(long)] pub output: PathBuf, From a530fad7a365552b8a10295aab329220b5945b72 Mon Sep 17 00:00:00 2001 From: yumacide Date: Sat, 13 Aug 2022 22:15:08 +0200 Subject: [PATCH 3/6] Make `project` a positional arg --- src/cli/open.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/cli/open.rs b/src/cli/open.rs index a739e7811..27ef1dbb2 100644 --- a/src/cli/open.rs +++ b/src/cli/open.rs @@ -26,7 +26,7 @@ use super::GlobalOptions; #[derive(Debug, Parser)] pub struct OpenCommand { /// Path to the project file to serve from. Defaults to default.project.json. - #[clap(long)] + #[clap(value_parser)] pub project: Option, // Path to an output place to build and serve to. Will be created automatically From 5c669490026621d4461c8a2c24e35745c8b9f686 Mon Sep 17 00:00:00 2001 From: yumacide Date: Sat, 13 Aug 2022 22:24:59 +0200 Subject: [PATCH 4/6] Use `opener` --- src/cli/open.rs | 24 ++---------------------- 1 file changed, 2 insertions(+), 22 deletions(-) diff --git a/src/cli/open.rs b/src/cli/open.rs index 27ef1dbb2..1fa03b47e 100644 --- a/src/cli/open.rs +++ b/src/cli/open.rs @@ -13,13 +13,7 @@ use crate::{ web::LiveServer, PROJECT_FILENAME, }; -use std::{ - env, - net::IpAddr, - path::{Path, PathBuf}, - process::{Command, Stdio}, - sync::Arc, -}; +use std::{env, net::IpAddr, path::PathBuf, sync::Arc}; use super::GlobalOptions; @@ -56,29 +50,25 @@ impl OpenCommand { vfs.set_watch_enabled(false); let session = ServeSession::new(vfs, &project)?; - let studio = RobloxStudio::locate()?; if !self.output.exists() { write_model(&session, &self.output, output_kind)?; } - if !plugin_exists(&studio) { install_plugin().unwrap(); } - open_place(&studio, &self.output).expect("Could not open place in Roblox Studio"); + opener::open(&self.output).expect("Could not open place in Roblox Studio"); let ip = self .address .or_else(|| session.serve_address()) .unwrap_or_else(|| DEFAULT_BIND_ADDRESS.into()); - let port = self .port .or_else(|| session.project_port()) .unwrap_or(DEFAULT_PORT); - let server = LiveServer::new(Arc::new(session)); let _ = show_start_message(ip, port, global.color.into()); @@ -91,13 +81,3 @@ impl OpenCommand { fn plugin_exists(studio: &RobloxStudio) -> bool { studio.plugins_path().join("rojo.rbxm").exists() } - -fn open_place(studio: &RobloxStudio, place: &Path) -> anyhow::Result<()> { - Command::new(studio.application_path()) - .arg(format!("{}", place.display())) - .stdout(Stdio::null()) - .stderr(Stdio::null()) - .spawn()?; - - Ok(()) -} From dca99d2ad7ae888ecfefbfae8ac03c62bbeff5a1 Mon Sep 17 00:00:00 2001 From: yumacide Date: Sat, 13 Aug 2022 22:31:44 +0200 Subject: [PATCH 5/6] Always install plugin --- src/cli/open.rs | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) diff --git a/src/cli/open.rs b/src/cli/open.rs index 1fa03b47e..7a4c733a4 100644 --- a/src/cli/open.rs +++ b/src/cli/open.rs @@ -48,16 +48,12 @@ impl OpenCommand { log::trace!("Constructing in-memory filesystem"); let vfs = Vfs::new_default(); vfs.set_watch_enabled(false); - let session = ServeSession::new(vfs, &project)?; - let studio = RobloxStudio::locate()?; if !self.output.exists() { write_model(&session, &self.output, output_kind)?; } - if !plugin_exists(&studio) { - install_plugin().unwrap(); - } + install_plugin().unwrap(); opener::open(&self.output).expect("Could not open place in Roblox Studio"); @@ -71,13 +67,9 @@ impl OpenCommand { .unwrap_or(DEFAULT_PORT); let server = LiveServer::new(Arc::new(session)); - let _ = show_start_message(ip, port, global.color.into()); + show_start_message(ip, port, global.color.into())?; server.start((ip, port).into()); Ok(()) } } - -fn plugin_exists(studio: &RobloxStudio) -> bool { - studio.plugins_path().join("rojo.rbxm").exists() -} From 4dc4395d5490c68ad03b2727aebdf20d6922ad00 Mon Sep 17 00:00:00 2001 From: yumacide Date: Sat, 13 Aug 2022 23:38:41 +0200 Subject: [PATCH 6/6] Enable VFS watch --- src/cli/open.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/cli/open.rs b/src/cli/open.rs index 7a4c733a4..4b522326a 100644 --- a/src/cli/open.rs +++ b/src/cli/open.rs @@ -47,7 +47,6 @@ impl OpenCommand { log::trace!("Constructing in-memory filesystem"); let vfs = Vfs::new_default(); - vfs.set_watch_enabled(false); let session = ServeSession::new(vfs, &project)?; if !self.output.exists() {