diff --git a/Cargo.lock b/Cargo.lock index 02c27b8..53b9655 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -11,6 +11,12 @@ dependencies = [ "memchr", ] +[[package]] +name = "anyhow" +version = "1.0.66" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "216261ddc8289130e551ddcd5ce8a064710c0d064a4d2895c67151c92b5443f6" + [[package]] name = "arrayref" version = "0.3.6" @@ -445,6 +451,7 @@ dependencies = [ name = "fission" version = "0.1.0" dependencies = [ + "anyhow", "base64 0.13.0", "bs58", "clap", diff --git a/Cargo.toml b/Cargo.toml index b1e2e4c..8d05ff0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,6 +7,7 @@ edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] +anyhow = "1.0.66" bs58 = "0.4.0" base64 = "0.13.0" clap = { version = "3.2.12", features = ["derive"] } diff --git a/src/cmd/app.rs b/src/cmd/app.rs index 6cfb98d..1d68360 100644 --- a/src/cmd/app.rs +++ b/src/cmd/app.rs @@ -1,4 +1,7 @@ +use crate::legacy::{prepare_args, prepare_flags}; +use anyhow::Result; use clap::{ArgEnum, Args, Subcommand}; +use std::{collections::HashMap, process::Command}; #[derive(Args)] pub struct App { @@ -10,15 +13,14 @@ pub struct App { pub enum AppCommands { #[clap(about = "Delegate capability to an audience DID")] Delegate { - #[clap(short, long, value_parser, help = "The target app")] + #[clap(short, long, value_name = "NAME", help = "The target app")] app_name: Option, - #[clap(short, long, value_parser, help = "An audience DID")] + #[clap(short, long, help = "An audience DID")] did: Option, #[clap( short, long, - arg_enum, - value_parser, + value_enum, default_value = "append", help = "The potency to delegate" )] @@ -26,23 +28,104 @@ pub enum AppCommands { #[clap( short, long, - value_parser, default_value_t = 300, help = "Lifetime in seconds before UCAN expires" )] lifetime: u16, #[clap(short, long, help = "Only output the UCAN on success")] quiet: bool, + #[clap(from_global)] + verbose: bool, + #[clap(from_global)] + remote: Option, + }, + #[clap(about = "Detail about the current app")] + Info { + #[clap(from_global)] + verbose: bool, + #[clap(from_global)] + remote: Option, }, #[clap(about = "Upload the working directory")] Publish { + #[clap( + help = "The file path of the assets or directory to sync", + default_value = "./" + )] + path: String, #[clap(short, long, help = "Open your default browser after publish")] open: bool, #[clap(short, long, help = "Watch for changes & automatically trigger upload")] watch: bool, + #[clap( + long = "ipfs-bin", + help = "Path to IPFS binary [default: `which ipfs`]", + value_name = "BIN_PATH" + )] + ipfs_bin: Option, + #[clap( + long = "ipfs-timeout", + help = "IPFS timeout", + default_value = "1800", + value_name = "SECONDS" + )] + ipfs_timeout: String, + #[clap( + long = "update-data", + help = "Upload the data", + default_value = "True", + value_name = "ARG" + )] + update_data: String, + #[clap( + long = "update-dns", + help = "Update DNS", + default_value = "True", + value_name = "ARG" + )] + update_dns: String, + #[clap(from_global)] + verbose: bool, + #[clap(from_global)] + remote: Option, }, #[clap(about = "Initialize an existing app")] - Register, + Register { + #[clap( + short, + long = "app-dir", + help = "The file path to initialize the app in (app config, etc.)", + default_value = ".", + value_name = "PATH" + )] + app_dir: String, + #[clap( + short, + long = "build-dir", + help = "The file path of the assets or directory to sync", + value_name = "PATH" + )] + build_dir: Option, + #[clap(short, long = "name", help = "Optional app name")] + name: Option, + #[clap( + long = "ipfs-bin", + help = "Path to IPFS binary [default: `which ipfs`]", + value_name = "BIN_PATH" + )] + ipfs_bin: Option, + #[clap( + long = "ipfs-timeout", + help = "IPFS timeout", + default_value = "1800", + value_name = "SECONDS" + )] + ipfs_timeout: String, + #[clap(from_global)] + verbose: bool, + #[clap(from_global)] + remote: Option, + }, } #[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, ArgEnum)] @@ -51,22 +134,125 @@ pub enum Potency { Destroy, SuperUser, } -pub fn run_command(a: App) { + +impl Potency { + fn to_string(&self) -> String { + match self { + Potency::Append => "Append".to_string(), + Potency::Destroy => "Destroy".to_string(), + Potency::SuperUser => "Super_User".to_string(), + } + } +} + +pub fn run_command(a: App) -> Result<()> { match a.command { AppCommands::Delegate { - app_name: _, - did: _, - potency: _, - lifetime: _, - quiet: _, + app_name, + did, + lifetime, + potency, + quiet, + verbose, + remote, } => { - todo!("delegate") + let args = prepare_args(&HashMap::from([ + ("-a", app_name.as_ref()), + ("-d", did.as_ref()), + ("-l", Some(lifetime.to_string()).as_ref()), + ("-p", Some(potency.to_string()).as_ref()), + ])); + let remote = prepare_args(&HashMap::from([("-R", remote.as_ref())])); + + // N.B. The wrapped app delegate command does not accept verbose + let flags = prepare_flags(&HashMap::from([("-q", quiet)])); + + Command::new("fission") + .args(["app", "delegate"]) + .args(args) + .args(remote) + .args(flags) + .spawn()? + .wait()?; + + Ok(()) } - AppCommands::Publish { open: _, watch: _ } => { - todo!("publish") + AppCommands::Info { verbose, remote } => { + let remote = prepare_args(&HashMap::from([("-R", remote.as_ref())])); + let flags = prepare_flags(&HashMap::from([("-v", verbose)])); + + Command::new("fission") + .args(["app", "info"]) + .args(remote) + .args(flags) + .spawn()? + .wait()?; + + Ok(()) } - AppCommands::Register => { - todo!("register") + AppCommands::Publish { + path, + open, + watch, + ipfs_bin, + ipfs_timeout, + update_data, + update_dns, + verbose, + remote, + } => { + let args = prepare_args(&HashMap::from([ + ("--ipfs-bin", ipfs_bin.as_ref()), + ("--ipfs-timeout", Some(ipfs_timeout).as_ref()), + ("--update-data", Some(update_data).as_ref()), + ("--update-dns", Some(update_dns).as_ref()), + ])); + let remote = prepare_args(&HashMap::from([("-R", remote.as_ref())])); + let flags = prepare_flags(&HashMap::from([ + ("-o", open), + ("-w", watch), + ("-v", verbose), + ])); + + Command::new("fission") + .args(["app", "publish"]) + .arg(path) + .args(args) + .args(remote) + .args(flags) + .spawn()? + .wait()?; + + Ok(()) + } + AppCommands::Register { + app_dir, + build_dir, + name, + ipfs_bin, + ipfs_timeout, + verbose, + remote, + } => { + let args = prepare_args(&HashMap::from([ + ("-a", Some(app_dir).as_ref()), + ("-b", build_dir.as_ref()), + ("-n", name.as_ref()), + ("--ipfs-bin", ipfs_bin.as_ref()), + ("--ipfs-timeout", Some(ipfs_timeout).as_ref()), + ])); + let remote = prepare_args(&HashMap::from([("-R", remote.as_ref())])); + let flags = prepare_flags(&HashMap::from([("-v", verbose)])); + + Command::new("fission") + .args(["app", "register"]) + .args(args) + .args(remote) + .args(flags) + .spawn()? + .wait()?; + + Ok(()) } } } diff --git a/src/cmd/setup.rs b/src/cmd/setup.rs index 9f9eebf..4168e46 100644 --- a/src/cmd/setup.rs +++ b/src/cmd/setup.rs @@ -1,8 +1,35 @@ +use crate::legacy::{prepare_args, prepare_flags}; +use anyhow::Result; use clap::Args; +use std::{collections::HashMap, process::Command}; #[derive(Args)] struct Setup {} -pub fn run_command(username: Option) { - todo!("setup --username={:?}", username) +pub fn run_command( + username: Option, + email: Option, + keyfile: Option, + os: Option, + verbose: bool, + remote: Option, +) -> Result<()> { + let args = prepare_args(&HashMap::from([ + ("-u", username.as_ref()), + ("-e", email.as_ref()), + ("-k", keyfile.as_ref()), + ("--os", os.as_ref()), + ])); + let remote = prepare_args(&HashMap::from([("-R", remote.as_ref())])); + let flags = prepare_flags(&HashMap::from([("-v", verbose)])); + + Command::new("fission") + .arg("setup") + .args(args) + .args(remote) + .args(flags) + .spawn()? + .wait()?; + + Ok(()) } diff --git a/src/cmd/user.rs b/src/cmd/user.rs index 6999707..4db1c16 100644 --- a/src/cmd/user.rs +++ b/src/cmd/user.rs @@ -1,9 +1,12 @@ +use crate::legacy::{prepare_args, prepare_flags}; +use anyhow::Result; use clap::{Args, Subcommand}; +use std::{collections::HashMap, process::Command}; #[derive(Args)] pub struct User { #[clap(subcommand)] - command: UserCommands, + pub command: UserCommands, } #[derive(Subcommand)] @@ -12,18 +15,53 @@ pub enum UserCommands { Login { #[clap(short, long, value_parser, help = "Username")] username: Option, + #[clap(from_global)] + verbose: bool, + #[clap(from_global)] + remote: Option, }, #[clap(about = "Display current user")] - Whoami, + Whoami { + #[clap(from_global)] + verbose: bool, + #[clap(from_global)] + remote: Option, + }, } -pub fn run_command(u: User) { +pub fn run_command(u: User) -> Result<()> { match u.command { - UserCommands::Login { username: _ } => { - todo!("login") + UserCommands::Login { + username, + verbose, + remote, + } => { + let args = prepare_args(&HashMap::from([("-u", username.as_ref())])); + let remote = prepare_args(&HashMap::from([("-R", remote.as_ref())])); + let flags = prepare_flags(&HashMap::from([("-v", verbose)])); + + Command::new("fission") + .args(["user", "login"]) + .args(args) + .args(remote) + .args(flags) + .spawn()? + .wait()?; + + Ok(()) } - UserCommands::Whoami => { - todo!("whoami") + UserCommands::Whoami { verbose, remote } => { + let remote = prepare_args(&HashMap::from([("-R", remote.as_ref())])); + let flags = prepare_flags(&HashMap::from([("-v", verbose)])); + + Command::new("fission") + .args(["user", "whoami"]) + .args(remote) + .args(flags) + .spawn()? + .wait()?; + + Ok(()) } } } diff --git a/src/legacy.rs b/src/legacy.rs new file mode 100644 index 0000000..6a07ecd --- /dev/null +++ b/src/legacy.rs @@ -0,0 +1,28 @@ +use std::{collections::HashMap, iter::once}; + +/// Convert flags to a vector of flag strings +/// +/// This function is glue to reformat flags parsed by CLAP for +/// Command::args (https://doc.rust-lang.org/std/process/struct.Command.html#method.args). +/// +/// It keeps flags CLAP parsed as True and drops False flags. +pub fn prepare_flags(flags: &HashMap<&str, bool>) -> Vec { + flags + .iter() + .filter(|tup| *tup.1) + .flat_map(|tup| once(tup.0.to_string())) + .collect() +} + +/// Convert arguments to a vector of argument strings. +/// +/// This function is glue to reformat args parsed by CLAP for +/// Command::args (https://doc.rust-lang.org/std/process/struct.Command.html#method.args). +/// +/// It keeps optional arguments CLAP parsed as Some(arg) and drops None arguments. +pub fn prepare_args(args: &HashMap<&str, Option<&String>>) -> Vec { + args.iter() + .filter(|tup| tup.1.is_some()) + .flat_map(|tup| once(tup.0.to_string()).chain(once(tup.1.unwrap().to_string()))) + .collect() +} diff --git a/src/lib.rs b/src/lib.rs index 52958ec..dac0599 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1 +1,3 @@ pub mod cmd; +pub mod legacy; +pub mod utils; diff --git a/src/main.rs b/src/main.rs index a56fa27..baa6261 100644 --- a/src/main.rs +++ b/src/main.rs @@ -3,12 +3,16 @@ use fission::cmd::{ app::{run_command as run_app_command, App}, generate::{run_command as run_generate_command, Generate}, setup::run_command as run_setup_command, - user::{run_command as run_user_command, User}, + user::{run_command as run_user_command, User, UserCommands}, }; #[derive(Parser)] #[clap(author, version, about="Fission makes developing, deploying, updating, and iterating on web apps quick and easy.", long_about = None)] struct Cli { + #[clap(short, long, global = true, help = "Print detailed output")] + verbose: bool, + #[clap(short = 'R', long, global = true, hide = true)] + remote: Option, #[clap(subcommand)] command: Commands, } @@ -23,17 +27,65 @@ enum Commands { Setup { #[clap(short, long, value_parser, help = "The username to register")] username: Option, + #[clap(short, long, value_parser, help = "The email address for the account")] + email: Option, + #[clap( + short, + long = "with-key", + value_parser, + help = "A root keyfile to import" + )] + keyfile: Option, + #[clap(short, long, value_parser, help = "Override OS detection")] + os: Option, + #[clap(from_global)] + verbose: bool, + #[clap(short = 'R', long, global = true, hide = true)] + remote: Option, }, #[clap(about = "User application management")] User(User), + + // Shortcuts + #[clap(about = "Display current user")] + Whoami { + #[clap(from_global)] + verbose: bool, + #[clap(short = 'R', long, global = true, hide = true)] + remote: Option, + }, } fn main() { let cli = Cli::parse(); match cli.command { - Commands::App(a) => run_app_command(a), + Commands::App(a) => match run_app_command(a) { + Ok(()) => (), + Err(_err) => eprintln!("💥 Failed to execute app command."), + }, Commands::Generate(g) => run_generate_command(g), - Commands::Setup { username } => run_setup_command(username), - Commands::User(u) => run_user_command(u), + Commands::Setup { + username, + email, + keyfile, + os, + verbose, + remote + } => match run_setup_command(username, email, keyfile, os, verbose, remote) { + Ok(()) => (), + Err(_err) => eprintln!("💥 Failed to execute setup command."), + }, + Commands::User(u) => match run_user_command(u) { + Ok(()) => (), + Err(_err) => eprintln!("💥 Failed to execute user command.",), + }, + + // Shortcuts + Commands::Whoami { verbose, remote } => match run_user_command(User { + command: UserCommands::Whoami { verbose, remote }, + }) { + Ok(()) => (), + Err(_err) => eprintln!("💥 Failed to execute whoami command.",), + }, } } diff --git a/src/utils.rs b/src/utils.rs new file mode 100644 index 0000000..7cd3105 --- /dev/null +++ b/src/utils.rs @@ -0,0 +1,53 @@ +use anyhow::{anyhow, Result}; +use std::io::{self, Write}; +use std::process::Output; + +pub struct OutputOptions { + verbose: bool, + quiet: bool, + error: bool, +} + +impl OutputOptions { + fn verbose() -> OutputOptions { + OutputOptions { + verbose: true, + quiet: false, + error: false, + } + } + + fn default() -> OutputOptions { + OutputOptions { + verbose: false, + quiet: false, + error: false, + } + } + + fn quiet() -> OutputOptions { + OutputOptions { + verbose: false, + quiet: true, + error: false, + } + } +} + +pub fn write_output(output: &Output) -> Result<()> { + let mut options = OutputOptions::verbose(); + + if output.stdout != [] { + print_output(std::str::from_utf8(&output.stdout)?, &options)?; + } + if output.stderr != [] { + options.error = true; + print_output(std::str::from_utf8(&output.stdout)?, &options)?; + } + Ok(()) +} + +pub fn print_output(output: &str, options: &OutputOptions) -> Result<()> { + println!("{}", output); + Ok(()) +}