Skip to content

Commit

Permalink
Install completions scripts instead printing to the stdout
Browse files Browse the repository at this point in the history
  • Loading branch information
crodas committed Feb 15, 2024
1 parent cc8f743 commit c7015d0
Show file tree
Hide file tree
Showing 3 changed files with 134 additions and 58 deletions.
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions forc/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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" }
Expand Down
190 changes: 132 additions & 58 deletions forc/src/cli/commands/completions.rs
Original file line number Diff line number Diff line change
@@ -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<Self, Self::Err> {
match s {
"fig" => Ok(Target::Fig),
other => Ok(Target::BuiltIn(
<clap_complete::Shell as FromStr>::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<clap::PossibleValue<'a>> {
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(),
}
}
}
Expand All @@ -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<Target>,
}

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());
Expand All @@ -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);
}
}
}
});
Expand All @@ -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<P: AsRef<Path>>(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::<Vec<_>>();
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<G: Generator>(gen: G, cmd: &mut ClapCommand) {
generate(gen, cmd, cmd.get_name().to_string(), &mut std::io::stdout());
#[inline]
fn print_completions<G: Generator>(gen: G, cmd: &mut ClapCommand, writer: &mut dyn Write) {
generate(gen, cmd, cmd.get_name().to_string(), writer);
}

0 comments on commit c7015d0

Please sign in to comment.