From c7015d0c0ad13cd0fd9a88ea2ef8c4b19ad38f26 Mon Sep 17 00:00:00 2001 From: Cesar Rodas Date: Thu, 15 Feb 2024 17:14:01 -0300 Subject: [PATCH] Install completions scripts instead printing to the stdout --- Cargo.lock | 1 + forc/Cargo.toml | 1 + forc/src/cli/commands/completions.rs | 190 +++++++++++++++++++-------- 3 files changed, 134 insertions(+), 58 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index f19705551c4..72869093f68 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1983,6 +1983,7 @@ dependencies = [ "fs_extra", "fuel-asm", "hex", + "home", "serde", "serde_json", "sway-core", diff --git a/forc/Cargo.toml b/forc/Cargo.toml index 8d09034869c..62ba434abc7 100644 --- a/forc/Cargo.toml +++ b/forc/Cargo.toml @@ -30,6 +30,7 @@ forc-util = { version = "0.50.0", path = "../forc-util" } fs_extra = "1.2" fuel-asm = { workspace = true } hex = "0.4.3" +home = "0.5.9" serde = { version = "1.0", features = ["derive"] } serde_json = "1" sway-core = { version = "0.50.0", path = "../sway-core" } diff --git a/forc/src/cli/commands/completions.rs b/forc/src/cli/commands/completions.rs index 95b49424206..39ce52ea108 100644 --- a/forc/src/cli/commands/completions.rs +++ b/forc/src/cli/commands/completions.rs @@ -1,58 +1,39 @@ -use clap::{Command as ClapCommand, ValueEnum}; -use clap::{CommandFactory, Parser}; -use clap_complete::{generate, Generator, Shell as BuiltInShell}; -use forc_util::cli::CommandInfo; -use forc_util::ForcResult; -use std::collections::HashMap; -use std::{fmt::Display, str::FromStr}; - use crate::cli::plugin::find_all; +use clap::{Command as ClapCommand, CommandFactory, Parser}; +use clap_complete::{generate, Generator}; +use forc_util::{cli::CommandInfo, ForcResult}; +use std::{ + collections::HashMap, + fs::{metadata, File, OpenOptions}, + io::{BufRead, BufReader, Write}, + path::Path, +}; -#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] -#[non_exhaustive] +#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, clap::ValueEnum)] enum Target { - BuiltIn(BuiltInShell), + /// Bourne Again SHell (bash) + Bash, + /// Elvish shell + Elvish, + /// Friendly Interactive SHell (fish) + Fish, + /// PowerShell + PowerShell, + /// Z SHell (zsh) + Zsh, + /// Fig Fig, } -impl Display for Target { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - self.to_possible_value() - .expect("no values are skipped") - .get_name() - .fmt(f) - } -} - -impl FromStr for Target { - type Err = String; - - fn from_str(s: &str) -> Result { - match s { - "fig" => Ok(Target::Fig), - other => Ok(Target::BuiltIn( - ::from_str(other)?, - )), - } - } -} - -impl ValueEnum for Target { - fn value_variants<'a>() -> &'a [Self] { - &[ - Target::BuiltIn(BuiltInShell::Bash), - Target::BuiltIn(BuiltInShell::Elvish), - Target::BuiltIn(BuiltInShell::Fish), - Target::BuiltIn(BuiltInShell::PowerShell), - Target::BuiltIn(BuiltInShell::Zsh), - Target::Fig, - ] - } - - fn to_possible_value<'a>(&self) -> Option> { +impl ToString for Target { + fn to_string(&self) -> String { match self { - Target::BuiltIn(shell) => shell.to_possible_value(), - Target::Fig => Some(clap::PossibleValue::new("fig")), + Target::Bash => "bash".to_string(), + Target::Elvish => "elvish".to_string(), + Target::Fish => "fish".to_string(), + Target::PowerShell => "powershell".to_string(), + Target::Zsh => "zsh".to_string(), + Target::Fig => "fig".to_string(), } } } @@ -65,13 +46,12 @@ pub struct Command { /// [possible values: zsh, bash, fish, powershell, elvish] /// /// For more info: https://fuellabs.github.io/sway/latest/forc/commands/forc_completions.html - #[clap(short = 'T', long)] - target: Target, + #[clap(short = 'T', long, value_enum)] + target: Option, } -pub(crate) fn exec(command: Command) -> ForcResult<()> { +fn generate_autocomplete_script(target: Target, writer: &mut dyn Write) { let mut cmd = CommandInfo::new(&super::super::Opt::command()); - let mut plugins = HashMap::new(); find_all().for_each(|path| { let mut proc = std::process::Command::new(path.clone()); @@ -83,7 +63,9 @@ pub(crate) fn exec(command: Command) -> ForcResult<()> { } else { command_info.name }; - plugins.insert(command_info.name.to_owned(), command_info); + if !plugins.contains_key(&command_info.name) { + plugins.insert(command_info.name.to_owned(), command_info); + } } } }); @@ -92,13 +74,105 @@ pub(crate) fn exec(command: Command) -> ForcResult<()> { cmd.subcommands.append(&mut plugins); let mut cmd = cmd.to_clap(); - match command.target { - Target::Fig => print_completions(clap_complete_fig::Fig, &mut cmd), - Target::BuiltIn(shell) => print_completions(shell, &mut cmd), + match target { + Target::Bash => print_completions(clap_complete::Shell::Bash, &mut cmd, writer), + Target::Zsh => print_completions(clap_complete::Shell::Zsh, &mut cmd, writer), + Target::Fish => print_completions(clap_complete::Shell::Fish, &mut cmd, writer), + Target::Elvish => print_completions(clap_complete::Shell::Elvish, &mut cmd, writer), + Target::PowerShell => print_completions(clap_complete::Shell::PowerShell, &mut cmd, writer), + Target::Fig => print_completions(clap_complete_fig::Fig, &mut cmd, writer), + } +} + +fn is_writable>(path: P) -> bool { + if let Ok(metadata) = metadata(path) { + return !metadata.permissions().readonly(); + } + false +} + +pub(crate) fn exec(command: Command) -> ForcResult<()> { + let target = command.target.unwrap_or_else(|| { + if let Ok(shell) = std::env::var("SHELL") { + match Path::new(shell.as_str()) + .file_name() + .unwrap() + .to_str() + .unwrap() + { + "bash" => Target::Bash, + "zsh" => Target::Zsh, + "fish" => Target::Fish, + "pwsh" => Target::PowerShell, + "elvish" => Target::Elvish, + _ => Target::Bash, + } + } else { + Target::Bash + } + }); + + let dir = home::home_dir().map(|p| p.display().to_string()).unwrap(); + let forc_autocomplete_path = match target { + Target::Zsh => { + let x = std::process::Command::new("zsh") + .arg("-c") + .arg("echo $fpath") + .output() + .expect("Cannot read $FPATH env variable") + .stdout; + let paths = String::from_utf8_lossy(&x) + .split(' ') + .filter(|path| is_writable(Path::new(path))) + .map(|x| x.to_owned()) + .collect::>(); + format!( + "{}/_forc", + paths.get(0).expect("No writable path found for zsh") + ) + } + _ => format!("{}/.forc.autocomplete", dir), + }; + + let mut file = File::create(&forc_autocomplete_path).expect("Open the shell config file"); + generate_autocomplete_script(target, &mut file); + + let user_shell_config = match target { + Target::Fish => Some(format!("{}/.config/fish/config.fish", dir)), + Target::Elvish => Some(format!("{}/.elvish/rc.elv", dir)), + Target::PowerShell => Some(format!( + "{}/.config/powershell/Microsoft.PowerShell_profile.ps1", + dir + )), + Target::Bash => Some(format!("{}/.bashrc", dir)), + Target::Fig => Some(format!("{}/.config/fig/fig.fish", dir)), + _ => None, + }; + + if let Some(file_path) = user_shell_config { + let file = File::open(&file_path).expect("Open the shell config file"); + let reader = BufReader::new(file); + + for line in reader.lines() { + if let Ok(line) = line { + if line.contains(&forc_autocomplete_path) { + println!("Forc completions is already installed"); + return Ok(()); + } + } + } + + let mut file = OpenOptions::new().append(true).open(&file_path)?; + writeln!(file, "source {}", forc_autocomplete_path,).unwrap(); } + + println!("Forc completions is installed successfully"); + println!("\t The script is stored in {}", forc_autocomplete_path); + Ok(()) } -fn print_completions(gen: G, cmd: &mut ClapCommand) { - generate(gen, cmd, cmd.get_name().to_string(), &mut std::io::stdout()); +#[inline] +fn print_completions(gen: G, cmd: &mut ClapCommand, writer: &mut dyn Write) { + generate(gen, cmd, cmd.get_name().to_string(), writer); }