diff --git a/CHANGELOG.md b/CHANGELOG.md index 4a50adb2..5ede8240 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,7 @@ * Empty `.model.json` files will no longer cause errors. ([#420][pr-420]) * When specifying `$path` on a service, Rojo now keeps the correct class name. ([#331][issue-331]) * Improved error messages for misconfigured projects. +* Improved error output for many subcommands. [issue-331]: https://github.com/rojo-rbx/rojo/issues/331 [issue-369]: https://github.com/rojo-rbx/rojo/issues/369 diff --git a/src/bin.rs b/src/bin.rs index 0b67c214..020cca34 100644 --- a/src/bin.rs +++ b/src/bin.rs @@ -7,11 +7,11 @@ use librojo::cli::{self, GlobalOptions, Options, Subcommand}; fn run(global: GlobalOptions, subcommand: Subcommand) -> anyhow::Result<()> { match subcommand { - Subcommand::Init(init_options) => cli::init(init_options)?, + Subcommand::Init(subcommand) => subcommand.run()?, Subcommand::Serve(serve_options) => cli::serve(global, serve_options)?, Subcommand::Build(build_options) => cli::build(build_options)?, Subcommand::Upload(upload_options) => cli::upload(upload_options)?, - Subcommand::FmtProject(fmt_options) => fmt_options.run()?, + Subcommand::FmtProject(subcommand) => subcommand.run()?, Subcommand::Doc => cli::doc()?, Subcommand::Plugin(plugin_options) => cli::plugin(plugin_options)?, } diff --git a/src/cli/init.rs b/src/cli/init.rs index b5ff49dc..3117b61f 100644 --- a/src/cli/init.rs +++ b/src/cli/init.rs @@ -1,13 +1,14 @@ -use std::{ - fs::{self, OpenOptions}, - io::{self, Write}, - path::Path, - process::{Command, Stdio}, -}; +use std::io::{self, Write}; +use std::path::{Path, PathBuf}; +use std::process::{Command, Stdio}; +use std::str::FromStr; -use thiserror::Error; +use anyhow::{bail, format_err}; +use fs_err as fs; +use fs_err::OpenOptions; +use structopt::StructOpt; -use crate::cli::{InitCommand, InitKind}; +use super::resolve_path; static MODEL_PROJECT: &str = include_str!("../../assets/default-model-project/default.project.json"); @@ -20,37 +21,71 @@ static PLACE_PROJECT: &str = static PLACE_README: &str = include_str!("../../assets/default-place-project/README.md"); static PLACE_GIT_IGNORE: &str = include_str!("../../assets/default-place-project/gitignore.txt"); -#[derive(Debug, Error)] -enum Error { - #[error("A project file named default.project.json already exists in this folder")] - AlreadyExists, +/// 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 = "")] + pub path: PathBuf, - #[error("git init failed")] - GitInit, + /// The kind of project to create, 'place' or 'model'. Defaults to place. + #[structopt(long, default_value = "place")] + pub kind: InitKind, } -pub fn init(options: InitCommand) -> Result<(), anyhow::Error> { - let base_path = options.absolute_path(); - fs::create_dir_all(&base_path)?; +impl InitCommand { + pub fn run(self) -> anyhow::Result<()> { + let base_path = resolve_path(&self.path); + fs::create_dir_all(&base_path)?; - let canonical = fs::canonicalize(&base_path)?; - let project_name = canonical - .file_name() - .and_then(|name| name.to_str()) - .unwrap_or("new-project"); + let canonical = fs::canonicalize(&base_path)?; + let project_name = canonical + .file_name() + .and_then(|name| name.to_str()) + .unwrap_or("new-project"); - let project_params = ProjectParams { - name: project_name.to_owned(), - }; + let project_params = ProjectParams { + name: project_name.to_owned(), + }; - match options.kind { - InitKind::Place => init_place(&base_path, project_params), - InitKind::Model => init_model(&base_path, project_params), + match self.kind { + InitKind::Place => init_place(&base_path, project_params)?, + InitKind::Model => init_model(&base_path, project_params)?, + } + + println!("Created project successfully."); + + Ok(()) } } -fn init_place(base_path: &Path, project_params: ProjectParams) -> Result<(), anyhow::Error> { - eprintln!("Creating new place project '{}'", project_params.name); +/// The templates we support for initializing a Rojo project. +#[derive(Debug, Clone, Copy)] +pub enum InitKind { + /// A place that contains a baseplate. + Place, + + /// An empty model, suitable for a library or plugin. + Model, +} + +impl FromStr for InitKind { + type Err = anyhow::Error; + + fn from_str(source: &str) -> Result { + match source { + "place" => Ok(InitKind::Place), + "model" => Ok(InitKind::Model), + _ => Err(format_err!( + "Invalid init kind '{}'. Valid kinds are: place, model", + source + )), + } + } +} + +fn init_place(base_path: &Path, project_params: ProjectParams) -> anyhow::Result<()> { + println!("Creating new place project '{}'", project_params.name); let project_file = project_params.render_template(PLACE_PROJECT); try_create_project(base_path, &project_file)?; @@ -88,13 +123,11 @@ fn init_place(base_path: &Path, project_params: ProjectParams) -> Result<(), any let git_ignore = project_params.render_template(PLACE_GIT_IGNORE); try_git_init(base_path, &git_ignore)?; - eprintln!("Created project successfully."); - Ok(()) } -fn init_model(base_path: &Path, project_params: ProjectParams) -> Result<(), anyhow::Error> { - eprintln!("Creating new model project '{}'", project_params.name); +fn init_model(base_path: &Path, project_params: ProjectParams) -> anyhow::Result<()> { + println!("Creating new model project '{}'", project_params.name); let project_file = project_params.render_template(MODEL_PROJECT); try_create_project(base_path, &project_file)?; @@ -111,8 +144,6 @@ fn init_model(base_path: &Path, project_params: ProjectParams) -> Result<(), any let git_ignore = project_params.render_template(MODEL_GIT_IGNORE); try_git_init(base_path, &git_ignore)?; - eprintln!("Created project successfully."); - Ok(()) } @@ -138,7 +169,7 @@ fn try_git_init(path: &Path, git_ignore: &str) -> Result<(), anyhow::Error> { let status = Command::new("git").arg("init").current_dir(path).status()?; if !status.success() { - return Err(Error::GitInit.into()); + bail!("git init failed: status code {:?}", status.code()); } } @@ -195,13 +226,15 @@ fn try_create_project(base_path: &Path, contents: &str) -> Result<(), anyhow::Er let file_res = OpenOptions::new() .write(true) .create_new(true) - .open(project_path); + .open(&project_path); let mut file = match file_res { Ok(file) => file, Err(err) => { return match err.kind() { - io::ErrorKind::AlreadyExists => Err(Error::AlreadyExists.into()), + io::ErrorKind::AlreadyExists => { + bail!("Project file already exists: {}", project_path.display()) + } _ => Err(err.into()), } } diff --git a/src/cli/mod.rs b/src/cli/mod.rs index 81cc5087..c9e32bda 100644 --- a/src/cli/mod.rs +++ b/src/cli/mod.rs @@ -24,7 +24,7 @@ use thiserror::Error; pub use self::build::*; pub use self::doc::*; pub use self::fmt_project::FmtProjectCommand; -pub use self::init::*; +pub use self::init::{InitCommand, InitKind}; pub use self::plugin::*; pub use self::serve::*; pub use self::upload::*; @@ -102,7 +102,6 @@ pub struct ColorChoiceParseError { #[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. @@ -123,66 +122,6 @@ pub enum Subcommand { Plugin(PluginCommand), } -/// 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 = "")] - pub path: PathBuf, - - /// The kind of project to create, 'place' or 'model'. Defaults to place. - #[structopt(long, default_value = "place")] - pub kind: InitKind, -} - -impl InitCommand { - pub fn absolute_path(&self) -> Cow<'_, Path> { - resolve_path(&self.path) - } -} - -/// 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)]