diff --git a/Cargo.lock b/Cargo.lock index 200cda5..9146e42 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -47,6 +47,7 @@ dependencies = [ name = "android-cli" version = "0.1.0" dependencies = [ + "anyhow", "clap", "dialoguer", "env_logger", @@ -72,6 +73,12 @@ dependencies = [ "sha2 0.9.9", ] +[[package]] +name = "anyhow" +version = "1.0.72" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b13c32d80ecc7ab747b80c3784bce54ee8a7a0cc4fbda9bf4cda2cf6fe90854" + [[package]] name = "atty" version = "0.2.14" diff --git a/Cargo.toml b/Cargo.toml index e706619..9868213 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -22,3 +22,4 @@ which = "4.4.0" itertools = "0.11.0" ron = "0.8.0" serde = { version = "1.0.183", features = ["derive"] } +anyhow = "1.0.72" diff --git a/src/commands/build.rs b/src/commands/build.rs index c9a9069..a2bc8da 100644 --- a/src/commands/build.rs +++ b/src/commands/build.rs @@ -1,3 +1,4 @@ +use anyhow::{Context, Result}; use clap::{ArgAction, Parser}; #[derive(Parser, Debug)] @@ -7,7 +8,7 @@ pub struct Build { release: bool, } -pub fn handle(args: Build) { +pub fn handle(args: Build) -> Result<()> { // Decide gradle subcommand to use let cmd = match args.release { true => "assembleRelease", @@ -15,10 +16,13 @@ pub fn handle(args: Build) { }; // Invoke gradle as child process - let status = android_cli::invoke_gradle_command(cmd).unwrap(); + let status = + android_cli::invoke_gradle_command(cmd).context("failed to invoke gradle command")?; match status.success() { true => println!("Success!"), false => println!("Failed while executing Gradle."), } + + Ok(()) } diff --git a/src/commands/create.rs b/src/commands/create.rs index e5f2098..d951f6a 100644 --- a/src/commands/create.rs +++ b/src/commands/create.rs @@ -1,7 +1,8 @@ -use std::{collections::BTreeMap, path::PathBuf}; - +use anyhow::{Context, Result, anyhow}; use clap::Parser; +use std::{collections::BTreeMap, path::PathBuf}; + use crate::utils::prompt_for_input; const DEFAULT_COMPILE_SDK_VERSION: &str = "33"; @@ -42,17 +43,17 @@ pub struct Create { min_sdk_version: u32, } -fn get_vars(args: &Create) -> BTreeMap { +fn get_vars(args: &Create) -> Result> { let mut map = BTreeMap::::new(); // FIXME: this is a hack, we should use a proper parser let get_package_id = |package_id: String| { let mut parts = package_id.split('.'); - let domain = parts.next().unwrap(); - let org = parts.next().unwrap(); - let name = parts.next().unwrap(); + let domain = parts.next().ok_or_else(|| anyhow!("domain part missing in package"))?; + let org = parts.next().ok_or_else(|| anyhow!("org part missing in package"))?; + let name = parts.next().ok_or_else(|| anyhow!("name part missing in package"))?; - (domain.to_owned(), org.to_owned(), name.to_owned()) + anyhow::Ok((domain.to_owned(), org.to_owned(), name.to_owned())) }; // Metadata @@ -63,7 +64,8 @@ fn get_vars(args: &Create) -> BTreeMap { map.insert("app_name".into(), args.name.as_ref().unwrap().to_owned()); // Package identifiers - let (domain, org, name) = get_package_id(args.package_id.as_ref().unwrap().to_owned()); + let (domain, org, name) = get_package_id(args.package_id.as_ref().unwrap().to_owned()) + .context("failed to parse package id")?; map.insert("package_id_domain".into(), domain); map.insert("package_id_org".into(), org); map.insert("package_id_name".into(), name); @@ -79,30 +81,34 @@ fn get_vars(args: &Create) -> BTreeMap { ); map.insert("min_sdk_version".into(), args.min_sdk_version.to_string()); - map + Ok(map) } -fn post_create(args: Create) { +fn post_create(args: Create) -> Result<()> { let dest = args.dest.clone().unwrap(); - android_cli::create_local_properties_file(&dest, &args.sdk_path.unwrap()); - android_cli::create_dot_file(&dest, args.package_id.unwrap()); + android_cli::create_local_properties_file(&dest, &args.sdk_path.unwrap())?; + android_cli::create_dot_file(&dest, args.package_id.unwrap())?; + + Ok(()) } -fn ensure_valid_args(args: Create) -> Create { +fn ensure_valid_args(args: Create) -> Result { let dest = args .dest - .unwrap_or_else(|| prompt_for_input("Enter destination path").into()); + .unwrap_or_else(|| prompt_for_input("Enter destination path").unwrap().into()); let dest_folder_name = dest.file_name().unwrap().to_str().unwrap().to_owned(); // FIXME: this could fail on non-unicode paths - let sdk_path = args - .sdk_path - .unwrap_or_else(|| std::env::var("ANDROID_SDK_ROOT").expect("ANDROID_SDK_ROOT not set")); + let sdk_path = args.sdk_path.unwrap_or_else(|| { + std::env::var("ANDROID_SDK_ROOT") + .context("ANDROID_SDK_ROOT not set") + .unwrap() + }); let project_name = args .project_name - .unwrap_or_else(|| dest_folder_name) + .unwrap_or(dest_folder_name) // defaults to dest folder name .to_lowercase() .replace(" ", "_"); // Perform some cleanup @@ -110,32 +116,35 @@ fn ensure_valid_args(args: Create) -> Create { let package_id = args.package_id.unwrap_or_else(|| { prompt_for_input("Enter package identifier [example: com.example.demo]") + .unwrap() .to_lowercase() .replace(" ", "_") }); - Create { + Ok(Create { dest: Some(dest), sdk_path: Some(sdk_path), project_name: Some(project_name), name: Some(app_name), package_id: Some(package_id), ..args - } + }) } -pub fn handle(args: Create) { - let args = ensure_valid_args(args); +pub fn handle(args: Create) -> Result<()> { + let args = ensure_valid_args(args)?; // Prepare variables to substitute - let vars = get_vars(&args); + let vars = get_vars(&args)?; // Copy template let dest = args.dest.as_ref().unwrap(); - android_cli::copy_template(dest, vars); + android_cli::copy_template(dest, vars)?; // Perform post init tasks - post_create(args); + post_create(args)?; println!("Project created successfully"); + + Ok(()) } diff --git a/src/commands/install.rs b/src/commands/install.rs index 5651bce..b76d9a7 100644 --- a/src/commands/install.rs +++ b/src/commands/install.rs @@ -1,6 +1,7 @@ -use std::path::PathBuf; - use clap::{ArgAction, Parser}; +use anyhow::{Result, Context}; + +use std::path::PathBuf; #[derive(Parser, Debug)] pub struct Install { @@ -9,7 +10,7 @@ pub struct Install { release: bool, } -pub fn handle(args: Install) { +pub fn handle(args: Install) -> Result<()> { let output_dir = PathBuf::from("app/build/outputs/apk"); let apk_path = match args.release { @@ -21,10 +22,12 @@ pub fn handle(args: Install) { "install", apk_path.to_str().unwrap() ]) - .unwrap(); + .context("failed to run adb command")?; match status.success() { true => println!("Successfully installed APK."), false => eprintln!("Failed to install APK"), }; + + Ok(()) } diff --git a/src/lib.rs b/src/lib.rs index 40499d6..f5903ee 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -3,6 +3,7 @@ mod utils; use guidon::{GitOptions, Guidon, TryNew}; use itertools::Itertools; use serde::{Deserialize, Serialize}; +use anyhow::{Result, Context}; use std::{ collections::BTreeMap, @@ -25,28 +26,32 @@ struct DotAndroid { pub gen_at_version: String, } -pub fn copy_template(dest: &Path, vars: BTreeMap) { +pub fn copy_template(dest: &Path, vars: BTreeMap) -> Result<()> { let git_options = GitOptions::builder() .repo(DEFAULT_TEMPLATE_REPO) .rev(TEMPLATE_REV) .build() .unwrap(); - let mut guidon = Guidon::try_new(git_options).unwrap(); + let mut guidon = Guidon::try_new(git_options)?; guidon.variables(vars); - guidon.apply_template(dest).unwrap(); + guidon.apply_template(dest)?; + + Ok(()) } -pub fn create_local_properties_file(root: &Path, sdk_path: &str) { +pub fn create_local_properties_file(root: &Path, sdk_path: &str) -> Result<()> { let prop_file_path = PathBuf::new().join(root).join("local.properties"); let content = format!("sdk.dir={}", sdk_path); - std::fs::write(prop_file_path, content).expect("Unable to write local.properties file") + std::fs::write(prop_file_path, content).context("Unable to write local.properties file")?; + + Ok(()) } -pub fn invoke_gradle_command(cmd: &str) -> Result> { - let gradle_path = find_gradle().expect("ERROR: Gradle not found on system"); +pub fn invoke_gradle_command(cmd: &str) -> Result { + let gradle_path = find_gradle().context("ERROR: Gradle not found on system")?; let mut run = Command::new(gradle_path); run.arg(cmd); @@ -60,8 +65,8 @@ pub fn invoke_gradle_command(cmd: &str) -> Result Result> { - let adb_path = find_adb().expect("ERROR: ADB not found on system"); +pub fn invoke_adb_command(args: &[&str]) -> Result { + let adb_path = find_adb().context("ERROR: ADB not found on system")?; let mut run = Command::new(adb_path); run.args(args); @@ -75,7 +80,7 @@ pub fn invoke_adb_command(args: &[&str]) -> Result Result<()> { // Construct the structure let dot_android = DotAndroid { package_id, @@ -84,12 +89,14 @@ pub fn create_dot_file(dest: &Path, package_id: String) { // Serialize into Ron let mut ron_contents = - ron::ser::to_string_pretty(&dot_android, ron::ser::PrettyConfig::default()).unwrap(); + ron::ser::to_string_pretty(&dot_android, ron::ser::PrettyConfig::default())?; // Add a comment at top ron_contents.insert_str(0, DOTFILE_COMMENT); // Write to file let path = PathBuf::from(dest).join(".android"); - std::fs::write(path, ron_contents).expect("failed to write .android file"); + std::fs::write(path, ron_contents).context("failed to write .android file")?; + + Ok(()) } diff --git a/src/main.rs b/src/main.rs index 6de0ed6..0f3bba1 100644 --- a/src/main.rs +++ b/src/main.rs @@ -26,9 +26,13 @@ fn main() { let args = Cli::parse(); - match args.command { + let result = match args.command { SubCommand::Create(args) => commands::create::handle(args), SubCommand::Build(args) => commands::build::handle(args), SubCommand::Install(args) => commands::install::handle(args) + }; + + if result.is_err() { + eprintln!("ERROR: {:?}", result.unwrap_err()); } } diff --git a/src/utils.rs b/src/utils.rs index 0c30a3f..a7224df 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -1,12 +1,13 @@ #![allow(dead_code)] +use anyhow::{Result, Context}; use which::which; -pub fn prompt_for_input(prompt: &str) -> String { - dialoguer::Input::::with_theme(&dialoguer::theme::ColorfulTheme::default()) +pub fn prompt_for_input(prompt: &str) -> Result { + Ok(dialoguer::Input::::with_theme(&dialoguer::theme::ColorfulTheme::default()) .with_prompt(prompt) .interact_text() - .unwrap() + .context("failed to prompt user")?) } pub fn find_gradle() -> Option {