Skip to content

Commit

Permalink
feat(zkstack_cli): Autocompletion support (#3097)
Browse files Browse the repository at this point in the history
## What ❔

<!-- What are the changes this PR brings about? -->
<!-- Example: This PR adds a PR template to the repo. -->
<!-- (For bigger PRs adding more context is appreciated) -->

Add autocomplete feature to zkstack:

```bash
❯ zkstack
chain              dev     -- Chain related commands                                                                                                                   
consensus                  -- Update ZKsync                                                                                                                            
containers                 -- Run containers for local development                                                                                                     
contract-verifier          -- Run contract verifier                                                                                                                    
ecosystem                  -- Ecosystem related commands                                                                                                               
explorer                   -- Run block-explorer                                                                                                                       
external-node              -- External Node related commands                                                                                                           
help                       -- Print this message or the help of the given subcommand(s)                                                                                
markdown           update  --                                                                                                                                          
portal                     -- Run dapp-portal                                                                                                                          
prover                     -- Prover related commands                                                                                                                  
server                     -- Run server
```

```bash
❯ zkstack ecosystem                   
build-transactions    -- Create transactions to build ecosystem contracts
change-default-chain  -- Change the default chain
create                -- Create a new ecosystem and chain, setting necessary configurations for later initialization
help                  -- Print this message or the help of the given subcommand(s)
init                  -- Initialize ecosystem and chain, deploying necessary contracts and performing on-chain operations
setup-observability   -- Setup observability for the ecosystem, downloading Grafana dashboards from the era-observability repo
```

## Why ❔

<!-- Why are these changes done? What goal do they contribute to? What
are the principles behind them? -->
<!-- Example: PR templates ensure PR reviewers, observers, and future
iterators are in context about the evolution of repos. -->

## Checklist

<!-- Check your PR fulfills the following items. -->
<!-- For draft PRs check the boxes as you complete them. -->

- [x] PR title corresponds to the body of PR (we generate changelog
entries from PRs).
- [x] Tests for the changes have been added / updated.
- [x] Documentation comments have been added / updated.
- [x] Code has been formatted via `zkstack dev fmt` and `zkstack dev
lint`.

---------

Co-authored-by: Danil <deniallugo@gmail.com>
  • Loading branch information
manuelmauro and Deniallugo authored Oct 22, 2024
1 parent abeee81 commit b29be7d
Show file tree
Hide file tree
Showing 24 changed files with 13,021 additions and 59 deletions.
1 change: 1 addition & 0 deletions .github/workflows/ci-core-lint-reusable.yml
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ jobs:
ci_run zkstack dev lint -t js --check
ci_run zkstack dev lint -t ts --check
ci_run zkstack dev lint -t rs --check
ci_run zkstack dev lint -t autocompletion --check
- name: Check Database
run: |
Expand Down
12 changes: 11 additions & 1 deletion zkstack_cli/Cargo.lock

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

3 changes: 2 additions & 1 deletion zkstack_cli/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -40,11 +40,12 @@ zksync_protobuf_build = "=0.5.0"
# External dependencies
anyhow = "1.0.82"
clap = { version = "4.4", features = ["derive", "wrap_help", "string"] }
clap_complete = "4.5.33"
dirs = "5.0.1"
slugify-rs = "0.0.3"
cliclack = "0.2.5"
console = "0.15.8"
chrono = "0.4.38"
eyre = "0.6.12"
ethers = "2.0"
futures = "0.3.30"
human-panic = "2.0"
Expand Down
6 changes: 5 additions & 1 deletion zkstack_cli/crates/zkstack/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,12 @@ keywords.workspace = true
anyhow.workspace = true
chrono.workspace = true
clap.workspace = true
clap_complete.workspace = true
clap-markdown.workspace = true
cliclack.workspace = true
common.workspace = true
config.workspace = true
dirs.workspace = true
ethers.workspace = true
futures.workspace = true
human-panic.workspace = true
Expand Down Expand Up @@ -49,6 +51,8 @@ rand.workspace = true
zksync_consensus_utils.workspace = true

[build-dependencies]
eyre.workspace = true
anyhow.workspace = true
clap_complete.workspace = true
dirs.workspace = true
ethers.workspace = true
zksync_protobuf_build.workspace = true
121 changes: 116 additions & 5 deletions zkstack_cli/crates/zkstack/build.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,23 @@
use std::path::PathBuf;
use std::path::{Path, PathBuf};

use anyhow::{anyhow, Context};
use ethers::contract::Abigen;

fn main() -> eyre::Result<()> {
const COMPLETION_DIR: &str = "completion";

fn main() -> anyhow::Result<()> {
let outdir = PathBuf::from(std::env::var("OUT_DIR")?).canonicalize()?;
Abigen::new("ConsensusRegistry", "abi/ConsensusRegistry.json")?
.generate()?
.write_to_file(outdir.join("consensus_registry_abi.rs"))?;
Abigen::new("ConsensusRegistry", "abi/ConsensusRegistry.json")
.map_err(|_| anyhow!("Failed ABI deserialization"))?
.generate()
.map_err(|_| anyhow!("Failed ABI generation"))?
.write_to_file(outdir.join("consensus_registry_abi.rs"))
.context("Failed to write ABI to file")?;

if let Err(e) = configure_shell_autocompletion() {
println!("cargo:warning=It was not possible to install autocomplete scripts. Please generate them manually with `zkstack autocomplete`");
println!("cargo:error={}", e);
};

zksync_protobuf_build::Config {
input_root: "src/commands/consensus/proto".into(),
Expand All @@ -19,3 +30,103 @@ fn main() -> eyre::Result<()> {
.unwrap();
Ok(())
}

fn configure_shell_autocompletion() -> anyhow::Result<()> {
// Array of supported shells
let shells = [
clap_complete::Shell::Bash,
clap_complete::Shell::Fish,
clap_complete::Shell::Zsh,
];

for shell in shells {
std::fs::create_dir_all(&shell.autocomplete_folder()?)
.context("it was impossible to create the configuration directory")?;

let src = Path::new(COMPLETION_DIR).join(shell.autocomplete_file_name()?);
let dst = shell
.autocomplete_folder()?
.join(shell.autocomplete_file_name()?);

std::fs::copy(src, dst)?;

shell
.configure_autocomplete()
.context("failed to run extra configuration requirements")?;
}

Ok(())
}

pub trait ShellAutocomplete {
fn autocomplete_folder(&self) -> anyhow::Result<PathBuf>;
fn autocomplete_file_name(&self) -> anyhow::Result<String>;
/// Extra steps required for shells enable command autocomplete.
fn configure_autocomplete(&self) -> anyhow::Result<()>;
}

impl ShellAutocomplete for clap_complete::Shell {
fn autocomplete_folder(&self) -> anyhow::Result<PathBuf> {
let home_dir = dirs::home_dir().context("missing home folder")?;

match self {
clap_complete::Shell::Bash => Ok(home_dir.join(".bash_completion.d")),
clap_complete::Shell::Fish => Ok(home_dir.join(".config/fish/completions")),
clap_complete::Shell::Zsh => Ok(home_dir.join(".zsh/completion")),
_ => anyhow::bail!("unsupported shell"),
}
}

fn autocomplete_file_name(&self) -> anyhow::Result<String> {
let crate_name = env!("CARGO_PKG_NAME");

match self {
clap_complete::Shell::Bash => Ok(format!("{}.sh", crate_name)),
clap_complete::Shell::Fish => Ok(format!("{}.fish", crate_name)),
clap_complete::Shell::Zsh => Ok(format!("_{}.zsh", crate_name)),
_ => anyhow::bail!("unsupported shell"),
}
}

fn configure_autocomplete(&self) -> anyhow::Result<()> {
match self {
clap_complete::Shell::Bash | clap_complete::Shell::Zsh => {
let shell = &self.to_string().to_lowercase();
let completion_file = self
.autocomplete_folder()?
.join(self.autocomplete_file_name()?);

// Source the completion file inside .{shell}rc
let shell_rc = dirs::home_dir()
.context("missing home directory")?
.join(format!(".{}rc", shell));

if shell_rc.exists() {
let shell_rc_content = std::fs::read_to_string(&shell_rc)
.context(format!("could not read .{}rc", shell))?;

if !shell_rc_content.contains("# zkstack completion") {
std::fs::write(
shell_rc,
format!(
"{}\n# zkstack completion\nsource \"{}\"\n",
shell_rc_content,
completion_file.to_str().unwrap()
),
)
.context(format!("could not write .{}rc", shell))?;
}
} else {
println!(
"cargo:warning=Please add the following line to your .{}rc:",
shell
);
println!("cargo:warning=source {}", completion_file.to_str().unwrap());
}
}
_ => (),
}

Ok(())
}
}
Loading

0 comments on commit b29be7d

Please sign in to comment.