diff --git a/Cargo.lock b/Cargo.lock index ae259aee..a08937cc 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -602,6 +602,14 @@ dependencies = [ "tokio-io 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "heck" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "unicode-segmentation 1.6.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "hermit-abi" version = "0.1.3" @@ -1169,6 +1177,16 @@ dependencies = [ "output_vt100 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "proc-macro-error" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "proc-macro2 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)", + "quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", + "syn 1.0.11 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "proc-macro-hack" version = "0.5.11" @@ -1588,7 +1606,6 @@ dependencies = [ name = "rojo" version = "0.6.0-dev" dependencies = [ - "clap 2.33.0 (registry+https://github.com/rust-lang/crates.io-index)", "criterion 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", "crossbeam-channel 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)", "csv 1.1.1 (registry+https://github.com/rust-lang/crates.io-index)", @@ -1617,6 +1634,7 @@ dependencies = [ "serde 1.0.103 (registry+https://github.com/rust-lang/crates.io-index)", "serde_json 1.0.44 (registry+https://github.com/rust-lang/crates.io-index)", "serde_yaml 0.8.11 (registry+https://github.com/rust-lang/crates.io-index)", + "structopt 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)", "tempfile 3.1.0 (registry+https://github.com/rust-lang/crates.io-index)", "termcolor 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)", "tokio 0.1.22 (registry+https://github.com/rust-lang/crates.io-index)", @@ -1850,6 +1868,27 @@ name = "strsim" version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "structopt" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "clap 2.33.0 (registry+https://github.com/rust-lang/crates.io-index)", + "structopt-derive 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "structopt-derive" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "heck 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", + "proc-macro-error 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)", + "proc-macro2 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)", + "quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", + "syn 1.0.11 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "syn" version = "1.0.11" @@ -2170,6 +2209,11 @@ dependencies = [ "smallvec 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "unicode-segmentation" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "unicode-width" version = "0.1.7" @@ -2414,6 +2458,7 @@ dependencies = [ "checksum getrandom 0.1.13 (registry+https://github.com/rust-lang/crates.io-index)" = "e7db7ca94ed4cd01190ceee0d8a8052f08a247aa1b469a7f68c6a3b71afcf407" "checksum glob 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)" = "8be18de09a56b60ed0edf84bc9df007e30040691af7acd1c41874faac5895bfb" "checksum h2 0.1.26 (registry+https://github.com/rust-lang/crates.io-index)" = "a5b34c246847f938a410a03c5458c7fee2274436675e76d8b903c08efc29c462" +"checksum heck 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "20564e78d53d2bb135c343b3f47714a56af2061f1c928fdb541dc7b9fdd94205" "checksum hermit-abi 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "307c3c9f937f38e3534b1d6447ecf090cafcc9744e4a6360e8b037b2cf5af120" "checksum htmlescape 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "e9025058dae765dee5070ec375f591e2ba14638c63feff74f13805a72e523163" "checksum http 0.1.21 (registry+https://github.com/rust-lang/crates.io-index)" = "d6ccf5ede3a895d8856620237b2f02972c1bbc78d2965ad7fe8838d4a0ed41f0" @@ -2477,6 +2522,7 @@ dependencies = [ "checksum pkg-config 0.3.17 (registry+https://github.com/rust-lang/crates.io-index)" = "05da548ad6865900e60eaba7f589cc0783590a92e940c26953ff81ddbab2d677" "checksum ppv-lite86 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)" = "74490b50b9fbe561ac330df47c08f3f33073d2d00c150f719147d7c54522fa1b" "checksum pretty_assertions 0.6.1 (registry+https://github.com/rust-lang/crates.io-index)" = "3f81e1644e1b54f5a68959a29aa86cde704219254669da328ecfdf6a1f09d427" +"checksum proc-macro-error 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)" = "aeccfe4d5d8ea175d5f0e4a2ad0637e0f4121d63bd99d356fb1f39ab2e7c6097" "checksum proc-macro-hack 0.5.11 (registry+https://github.com/rust-lang/crates.io-index)" = "ecd45702f76d6d3c75a80564378ae228a85f0b59d2f3ed43c91b4a69eb2ebfc5" "checksum proc-macro-nested 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "369a6ed065f249a159e06c45752c780bda2fb53c995718f9e484d08daa9eb42e" "checksum proc-macro2 0.4.30 (registry+https://github.com/rust-lang/crates.io-index)" = "cf3d2011ab5c909338f7887f4fc896d35932e29146c12c8d01da6b22a80ba759" @@ -2543,6 +2589,8 @@ dependencies = [ "checksum snax 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "7ef82be0baf3ae45701ab992230f1f4889c15984736d87f37558aea3e4e321af" "checksum string 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "d24114bfcceb867ca7f71a0d3fe45d45619ec47a6fbfa98cb14e14250bfa5d6d" "checksum strsim 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a" +"checksum structopt 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)" = "30b3a3e93f5ad553c38b3301c8a0a0cec829a36783f6a0c467fc4bf553a5f5bf" +"checksum structopt-derive 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)" = "ea692d40005b3ceba90a9fe7a78fa8d4b82b0ce627eebbffc329aab850f3410e" "checksum syn 1.0.11 (registry+https://github.com/rust-lang/crates.io-index)" = "dff0acdb207ae2fe6d5976617f887eb1e35a2ba52c13c7234c790960cdad9238" "checksum synstructure 0.12.3 (registry+https://github.com/rust-lang/crates.io-index)" = "67656ea1dc1b41b1451851562ea232ec2e5a80242139f7e679ceccfb5d61f545" "checksum tempdir 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)" = "15f2b5fb00ccdf689e0149d1b1b3c03fead81c2b37735d812fa8bddbbf41b6d8" @@ -2574,6 +2622,7 @@ dependencies = [ "checksum unicase 2.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "50f37be617794602aabbeee0be4f259dc1778fabe05e2d67ee8f79326d5cb4f6" "checksum unicode-bidi 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "49f2bd0c6468a8230e1db229cff8029217cf623c767ea5d60bfbd42729ea54d5" "checksum unicode-normalization 0.1.11 (registry+https://github.com/rust-lang/crates.io-index)" = "b561e267b2326bb4cebfc0ef9e68355c7abe6c6f522aeac2f5bf95d56c59bdcf" +"checksum unicode-segmentation 1.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e83e153d1053cbb5a118eeff7fd5be06ed99153f00dbcd8ae310c5fb2b22edc0" "checksum unicode-width 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)" = "caaa9d531767d1ff2150b9332433f32a24622147e5ebb1f26409d5da67afd479" "checksum unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "fc72304796d0818e357ead4e000d19c9c174ab23dc11093ac919054d20a6a7fc" "checksum unicode-xid 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "826e7639553986605ec5979c7dd957c7895e93eabed50ab2ffa7f6128a75097c" diff --git a/Cargo.toml b/Cargo.toml index 4e2c2742..9e203466 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -48,7 +48,6 @@ name = "build" harness = false [dependencies] -clap = "2.27" crossbeam-channel = "0.3.9" csv = "1.0" env_logger = "0.6" @@ -57,6 +56,7 @@ futures = "0.1" humantime = "1.3.0" hyper = "0.12" jod-thread = "0.1.0" +lazy_static = "1.4.0" log = "0.4" maplit = "1.0.1" notify = "4.0" @@ -70,6 +70,7 @@ ritz = "0.1.0" rlua = "0.16.3" serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" +structopt = "0.3" termcolor = "1.0.5" uuid = { version = "0.7", features = ["v4", "serde"] } diff --git a/src/bin.rs b/src/bin.rs index f49dc2bb..0616517c 100644 --- a/src/bin.rs +++ b/src/bin.rs @@ -1,63 +1,19 @@ -use std::{ - env, panic, - path::{Path, PathBuf}, - process, +use std::{panic, process}; + +use failure::Error; +use log::error; +use structopt::StructOpt; + +use librojo::{ + cli::{Options, Subcommand}, + commands, }; -use clap::{clap_app, ArgMatches}; -use log::error; - -use librojo::commands; - -fn make_path_absolute(value: &Path) -> PathBuf { - if value.is_absolute() { - PathBuf::from(value) - } else { - let current_dir = env::current_dir().unwrap(); - current_dir.join(value) - } -} - fn main() { - let app = clap_app!(Rojo => - (version: env!("CARGO_PKG_VERSION")) - (author: env!("CARGO_PKG_AUTHORS")) - (about: env!("CARGO_PKG_DESCRIPTION")) - - (@arg verbose: --verbose -v +multiple +global "Sets verbosity level. Can be specified multiple times.") - - (@subcommand init => - (about: "Creates a new Rojo project.") - (@arg PATH: "Path to the place to create the project. Defaults to the current directory.") - (@arg kind: --kind +takes_value "The kind of project to create, 'place' or 'model'. Defaults to place.") - ) - - (@subcommand serve => - (about: "Serves the project's files for use with the Rojo Studio plugin.") - (@arg PROJECT: "Path to the project to serve. Defaults to the current directory.") - (@arg port: --port +takes_value "The port to listen on. Defaults to 34872.") - ) - - (@subcommand build => - (about: "Generates a model or place file from the project.") - (@arg PROJECT: "Path to the project to serve. Defaults to the current directory.") - (@arg output: --output -o +takes_value +required "Where to output the result.") - ) - - (@subcommand upload => - (about: "Generates a place or model file out of the project and uploads it to Roblox.") - (@arg PROJECT: "Path to the project to upload. Defaults to the current directory.") - (@arg kind: --kind +takes_value "The kind of asset to generate, 'place', or 'model'. Defaults to place.") - (@arg cookie: --cookie +takes_value "Authenication cookie to use. If not specified, Rojo will attempt to find one from the system automatically.") - (@arg asset_id: --asset_id +takes_value +required "Asset ID to upload to.") - ) - ); - - let matches = app.get_matches(); + let options = Options::from_args(); { - let verbosity = matches.occurrences_of("verbose"); - let log_filter = match verbosity { + let log_filter = match options.verbosity { 0 => "warn", 1 => "warn,librojo=info", 2 => "warn,librojo=trace", @@ -71,15 +27,14 @@ fn main() { .init(); } - let result = panic::catch_unwind(|| match matches.subcommand() { - ("init", Some(sub_matches)) => start_init(sub_matches), - ("serve", Some(sub_matches)) => start_serve(sub_matches), - ("build", Some(sub_matches)) => start_build(sub_matches), - ("upload", Some(sub_matches)) => start_upload(sub_matches), - _ => eprintln!("Usage: rojo \nUse 'rojo help' for more help."), + let panic_result = panic::catch_unwind(|| { + if let Err(err) = run(options.subcommand) { + log::error!("{}", err); + process::exit(1); + } }); - if let Err(error) = result { + if let Err(error) = panic_result { let message = match error.downcast_ref::<&str>() { Some(message) => message.to_string(), None => match error.downcast_ref::() { @@ -93,6 +48,17 @@ fn main() { } } +fn run(subcommand: Subcommand) -> Result<(), Error> { + match subcommand { + Subcommand::Init(init_options) => commands::init(init_options)?, + Subcommand::Serve(serve_options) => commands::serve(serve_options)?, + Subcommand::Build(build_options) => commands::build(build_options)?, + Subcommand::Upload(upload_options) => commands::upload(upload_options)?, + } + + Ok(()) +} + fn show_crash_message(message: &str) { error!("Rojo crashed!"); error!("This is a bug in Rojo."); @@ -101,113 +67,3 @@ fn show_crash_message(message: &str) { error!(""); error!("Details: {}", message); } - -fn start_init(sub_matches: &ArgMatches) { - let fuzzy_project_path = - make_path_absolute(Path::new(sub_matches.value_of("PATH").unwrap_or(""))); - let kind = sub_matches.value_of("kind"); - - let options = commands::InitOptions { - fuzzy_project_path, - kind, - }; - - match commands::init(&options) { - Ok(_) => {} - Err(e) => { - error!("{}", e); - process::exit(1); - } - } -} - -fn start_serve(sub_matches: &ArgMatches) { - let fuzzy_project_path = match sub_matches.value_of("PROJECT") { - Some(v) => make_path_absolute(Path::new(v)), - None => std::env::current_dir().unwrap(), - }; - - let port = match sub_matches.value_of("port") { - Some(v) => match v.parse::() { - Ok(port) => Some(port), - Err(_) => { - error!("Invalid port {}", v); - process::exit(1); - } - }, - None => None, - }; - - let options = commands::ServeOptions { - fuzzy_project_path, - port, - }; - - match commands::serve(&options) { - Ok(_) => {} - Err(e) => { - error!("{}", e); - process::exit(1); - } - } -} - -fn start_build(sub_matches: &ArgMatches) { - let fuzzy_project_path = match sub_matches.value_of("PROJECT") { - Some(v) => make_path_absolute(Path::new(v)), - None => std::env::current_dir().unwrap(), - }; - - let output_file = make_path_absolute(Path::new(sub_matches.value_of("output").unwrap())); - - let options = commands::BuildOptions { - fuzzy_project_path, - output_file, - output_kind: None, // TODO: Accept from argument - }; - - match commands::build(&options) { - Ok(_) => {} - Err(e) => { - error!("{}", e); - process::exit(1); - } - } -} - -fn start_upload(sub_matches: &ArgMatches) { - let fuzzy_project_path = match sub_matches.value_of("PROJECT") { - Some(v) => make_path_absolute(Path::new(v)), - None => std::env::current_dir().unwrap(), - }; - - let kind = sub_matches.value_of("kind"); - let auth_cookie = sub_matches.value_of("cookie").map(Into::into); - - let asset_id: u64 = { - let arg = sub_matches.value_of("asset_id").unwrap(); - - match arg.parse() { - Ok(v) => v, - Err(_) => { - error!("Invalid place ID {}", arg); - process::exit(1); - } - } - }; - - let options = commands::UploadOptions { - fuzzy_project_path, - auth_cookie, - asset_id, - kind, - }; - - match commands::upload(options) { - Ok(_) => {} - Err(e) => { - error!("{}", e); - process::exit(1); - } - } -} diff --git a/src/cli.rs b/src/cli.rs new file mode 100644 index 00000000..4f721b49 --- /dev/null +++ b/src/cli.rs @@ -0,0 +1,191 @@ +//! Defines Rojo's CLI through structopt types. + +#![deny(missing_docs)] + +use std::{env, error::Error, fmt, path::PathBuf, str::FromStr}; + +use structopt::StructOpt; + +/// Trick used with structopt to get the initial working directory of the +/// process and store it for use in default values. +fn working_dir() -> &'static str { + lazy_static::lazy_static! { + static ref INITIAL_WORKING_DIR: String = { + env::current_dir().unwrap().display().to_string() + }; + } + + &INITIAL_WORKING_DIR +} + +/// Command line options that Rojo accepts, defined using the structopt crate. +#[derive(Debug, StructOpt)] +#[structopt(name = "Rojo", about, author)] +pub struct Options { + /// Sets verbosity level. Can be specified multiple times. + #[structopt(long = "verbose", short, parse(from_occurrences))] + pub verbosity: u8, + + /// Subcommand to run in this invocation. + #[structopt(subcommand)] + pub subcommand: Subcommand, +} + +/// All of Rojo's subcommands. +#[derive(Debug, StructOpt)] +pub enum Subcommand { + /// Creates a new Rojo project. + Init(InitCommand), + + /// Serves the project's files for use with the Rojo Studio plugin. + Serve(ServeCommand), + + /// Generates a model or place file from the project. + Build(BuildCommand), + + /// Generates a place or model file out of the project and uploads it to Roblox. + Upload(UploadCommand), +} + +/// Initializes a new Rojo project. +#[derive(Debug, StructOpt)] +pub struct InitCommand { + /// Path to the place to create the project. Defaults to the current directory. + #[structopt(default_value = &working_dir())] + pub path: PathBuf, + + /// The kind of project to create, 'place' or 'model'. Defaults to place. + #[structopt(long, default_value = "place")] + pub kind: InitKind, +} + +/// The templates we support for initializing a Rojo project. +#[derive(Debug, Clone, Copy)] +pub enum InitKind { + /// A place that matches what File -> New does in Roblox Studio. + Place, + + /// An empty model, suitable for a library or plugin. + Model, +} + +impl FromStr for InitKind { + type Err = InitKindParseError; + + fn from_str(source: &str) -> Result { + match source { + "place" => Ok(InitKind::Place), + "model" => Ok(InitKind::Model), + _ => Err(InitKindParseError { + attempted: source.to_owned(), + }), + } + } +} + +/// Error type for failing to parse an `InitKind`. +#[derive(Debug)] +pub struct InitKindParseError { + attempted: String, +} + +impl Error for InitKindParseError {} + +impl fmt::Display for InitKindParseError { + fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + write!( + formatter, + "Invalid init kind '{}'. Valid kinds are: place, model", + self.attempted + ) + } +} + +/// Expose a Rojo project through a web server that can communicate with the +/// Rojo Roblox Studio plugin, or be visited by the user in the browser. +#[derive(Debug, StructOpt)] +pub struct ServeCommand { + /// Path to the project to serve. Defaults to the current directory. + #[structopt(default_value = &working_dir())] + pub project: PathBuf, + + /// The port to listen on. Defaults to the project's preference, or 34872 if + /// it has none. + #[structopt(long)] + pub port: Option, +} + +/// Build a Rojo project into a file. +#[derive(Debug, StructOpt)] +pub struct BuildCommand { + /// Path to the project to serve. Defaults to the current directory. + #[structopt(default_value = &working_dir())] + pub project: PathBuf, + + /// Where to output the result. + #[structopt(long, short)] + pub output: PathBuf, +} + +/// Build and upload a Rojo project to Roblox.com. +#[derive(Debug, StructOpt)] +pub struct UploadCommand { + /// Path to the project to upload. Defaults to the current directory. + #[structopt(default_value = &working_dir())] + pub project: PathBuf, + + /// The kind of asset to generate, 'place', or 'model'. Defaults to place. + #[structopt(long, default_value = "place")] + pub kind: UploadKind, + + /// Authenication cookie to use. If not specified, Rojo will attempt to find one from the system automatically. + #[structopt(long)] + pub cookie: Option, + + /// Asset ID to upload to. + #[structopt(long = "asset_id")] + pub asset_id: u64, +} + +/// The kind of asset to upload to the website. Affects what endpoints Rojo uses +/// and changes how the asset is built. +#[derive(Debug, Clone, Copy)] +pub enum UploadKind { + /// Upload to a place. + Place, + + /// Upload to a model-like asset, like a Model, Plugin, or Package. + Model, +} + +impl FromStr for UploadKind { + type Err = UploadKindParseError; + + fn from_str(source: &str) -> Result { + match source { + "place" => Ok(UploadKind::Place), + "model" => Ok(UploadKind::Model), + _ => Err(UploadKindParseError { + attempted: source.to_owned(), + }), + } + } +} + +/// Error type for failing to parse an `UploadKind`. +#[derive(Debug)] +pub struct UploadKindParseError { + attempted: String, +} + +impl Error for UploadKindParseError {} + +impl fmt::Display for UploadKindParseError { + fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + write!( + formatter, + "Invalid upload kind '{}'. Valid kinds are: place, model", + self.attempted + ) + } +} diff --git a/src/commands/build.rs b/src/commands/build.rs index 8f9fed5c..d5021dc1 100644 --- a/src/commands/build.rs +++ b/src/commands/build.rs @@ -1,27 +1,27 @@ use std::{ fs::File, io::{self, BufWriter, Write}, - path::PathBuf, }; use failure::Fail; use crate::{ + cli::BuildCommand, common_setup, project::ProjectLoadError, vfs::{FsError, RealFetcher, Vfs, WatchMode}, }; #[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub enum OutputKind { +enum OutputKind { Rbxmx, Rbxlx, Rbxm, Rbxl, } -fn detect_output_kind(options: &BuildOptions) -> Option { - let extension = options.output_file.extension()?.to_str()?; +fn detect_output_kind(options: &BuildCommand) -> Option { + let extension = options.output.extension()?.to_str()?; match extension { "rbxlx" => Some(OutputKind::Rbxlx), @@ -32,13 +32,6 @@ fn detect_output_kind(options: &BuildOptions) -> Option { } } -#[derive(Debug)] -pub struct BuildOptions { - pub fuzzy_project_path: PathBuf, - pub output_file: PathBuf, - pub output_kind: Option, -} - #[derive(Debug, Fail)] pub enum BuildError { #[fail(display = "Could not detect what kind of file to create")] @@ -72,22 +65,19 @@ fn xml_encode_config() -> rbx_xml::EncodeOptions { rbx_xml::EncodeOptions::new().property_behavior(rbx_xml::EncodePropertyBehavior::WriteUnknown) } -pub fn build(options: &BuildOptions) -> Result<(), BuildError> { - let output_kind = options - .output_kind - .or_else(|| detect_output_kind(options)) - .ok_or(BuildError::UnknownOutputKind)?; +pub fn build(options: BuildCommand) -> Result<(), BuildError> { + let output_kind = detect_output_kind(&options).ok_or(BuildError::UnknownOutputKind)?; log::debug!("Hoping to generate file of type {:?}", output_kind); log::trace!("Constructing in-memory filesystem"); let vfs = Vfs::new(RealFetcher::new(WatchMode::Disabled)); - let (_maybe_project, tree) = common_setup::start(&options.fuzzy_project_path, &vfs); + let (_maybe_project, tree) = common_setup::start(&options.project, &vfs); let root_id = tree.get_root_id(); log::trace!("Opening output file for write"); - let mut file = BufWriter::new(File::create(&options.output_file)?); + let mut file = BufWriter::new(File::create(&options.output)?); match output_kind { OutputKind::Rbxmx => { diff --git a/src/commands/init.rs b/src/commands/init.rs index e17ba6dd..c4fe437f 100644 --- a/src/commands/init.rs +++ b/src/commands/init.rs @@ -1,17 +1,12 @@ -use std::path::PathBuf; - use failure::Fail; -use crate::project::{Project, ProjectInitError}; +use crate::{ + cli::{InitCommand, InitKind}, + project::{Project, ProjectInitError}, +}; #[derive(Debug, Fail)] pub enum InitError { - #[fail( - display = "Invalid project kind '{}', valid kinds are 'place' and 'model'", - _0 - )] - InvalidKind(String), - #[fail(display = "Project init error: {}", _0)] ProjectInitError(#[fail(cause)] ProjectInitError), } @@ -20,23 +15,16 @@ impl_from!(InitError { ProjectInitError => ProjectInitError, }); -#[derive(Debug)] -pub struct InitOptions<'a> { - pub fuzzy_project_path: PathBuf, - pub kind: Option<&'a str>, -} - -pub fn init(options: &InitOptions) -> Result<(), InitError> { +pub fn init(options: InitCommand) -> Result<(), InitError> { let (project_path, project_kind) = match options.kind { - Some("place") | None => { - let path = Project::init_place(&options.fuzzy_project_path)?; + InitKind::Place => { + let path = Project::init_place(&options.path)?; (path, "place") } - Some("model") => { - let path = Project::init_model(&options.fuzzy_project_path)?; + InitKind::Model => { + let path = Project::init_model(&options.path)?; (path, "model") } - Some(invalid) => return Err(InitError::InvalidKind(invalid.to_string())), }; println!( diff --git a/src/commands/serve.rs b/src/commands/serve.rs index 26a35b39..06c83614 100644 --- a/src/commands/serve.rs +++ b/src/commands/serve.rs @@ -1,6 +1,5 @@ use std::{ io::{self, Write}, - path::PathBuf, sync::Arc, }; @@ -8,6 +7,7 @@ use failure::Fail; use termcolor::{BufferWriter, Color, ColorChoice, ColorSpec, WriteColor}; use crate::{ + cli::ServeCommand, project::ProjectLoadError, serve_session::ServeSession, vfs::{RealFetcher, Vfs, WatchMode}, @@ -16,12 +16,6 @@ use crate::{ const DEFAULT_PORT: u16 = 34872; -#[derive(Debug)] -pub struct ServeOptions { - pub fuzzy_project_path: PathBuf, - pub port: Option, -} - #[derive(Debug, Fail)] pub enum ServeError { #[fail(display = "Couldn't load project: {}", _0)] @@ -32,10 +26,10 @@ impl_from!(ServeError { ProjectLoadError => ProjectLoad, }); -pub fn serve(options: &ServeOptions) -> Result<(), ServeError> { +pub fn serve(options: ServeCommand) -> Result<(), ServeError> { let vfs = Vfs::new(RealFetcher::new(WatchMode::Enabled)); - let session = Arc::new(ServeSession::new(vfs, &options.fuzzy_project_path)); + let session = Arc::new(ServeSession::new(vfs, &options.project)); let port = options .port diff --git a/src/commands/upload.rs b/src/commands/upload.rs index 68054591..df6f8429 100644 --- a/src/commands/upload.rs +++ b/src/commands/upload.rs @@ -1,10 +1,9 @@ -use std::path::PathBuf; - use failure::Fail; use reqwest::header::{ACCEPT, CONTENT_TYPE, COOKIE, USER_AGENT}; use crate::{ auth_cookie::get_auth_cookie, + cli::UploadCommand, common_setup, vfs::{RealFetcher, Vfs, WatchMode}, }; @@ -29,24 +28,16 @@ impl_from!(UploadError { reqwest::Error => Http, }); -#[derive(Debug)] -pub struct UploadOptions<'a> { - pub fuzzy_project_path: PathBuf, - pub auth_cookie: Option, - pub asset_id: u64, - pub kind: Option<&'a str>, -} - -pub fn upload(options: UploadOptions) -> Result<(), UploadError> { +pub fn upload(options: UploadCommand) -> Result<(), UploadError> { let cookie = options - .auth_cookie + .cookie .or_else(get_auth_cookie) .ok_or(UploadError::NeedAuthCookie)?; log::trace!("Constructing in-memory filesystem"); let vfs = Vfs::new(RealFetcher::new(WatchMode::Disabled)); - let (_maybe_project, tree) = common_setup::start(&options.fuzzy_project_path, &vfs); + let (_maybe_project, tree) = common_setup::start(&options.project, &vfs); let root_id = tree.get_root_id(); let mut buffer = Vec::new(); diff --git a/src/lib.rs b/src/lib.rs index 12953c3a..6e4f8e42 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -5,6 +5,7 @@ #[macro_use] mod impl_from; +pub mod cli; pub mod commands; // This module is only public for testing right now, and won't be