瀏覽代碼

feat(cli.rs) Shell completions (#4537)

Co-authored-by: Lucas Nogueira <lucas@tauri.studio>
Co-authored-by: Simon Hyll <hyllsimon@gmail.com>
Co-authored-by: Lucas Nogueira <lucas@tauri.app>
Jonas Kruckenberg 2 年之前
父節點
當前提交
6c5ade08d9

+ 6 - 0
.changes/feat-shell-completions.md

@@ -0,0 +1,6 @@
+---
+'tauri-cli': 'minor:feat'
+'@tauri-apps/cli': 'minor:feat'
+---
+
+Added `tauri completions` to generate shell completions scripts.

+ 10 - 0
tooling/cli/Cargo.lock

@@ -449,6 +449,15 @@ dependencies = [
  "strsim",
 ]
 
+[[package]]
+name = "clap_complete"
+version = "4.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a04ddfaacc3bc9e6ea67d024575fafc2a813027cf374b8f24f7bc233c6b6be12"
+dependencies = [
+ "clap",
+]
+
 [[package]]
 name = "clap_derive"
 version = "4.3.0"
@@ -3313,6 +3322,7 @@ dependencies = [
  "base64 0.21.1",
  "cc",
  "clap",
+ "clap_complete",
  "colored",
  "common-path",
  "ctrlc",

+ 1 - 0
tooling/cli/Cargo.toml

@@ -39,6 +39,7 @@ name = "cargo-tauri"
 path = "src/main.rs"
 
 [dependencies]
+clap_complete = "4"
 clap = { version = "4.0", features = [ "derive" ] }
 anyhow = "1.0"
 tauri-bundler = { version = "1.2.1", path = "../bundler", default-features = false }

+ 97 - 0
tooling/cli/src/completions.rs

@@ -0,0 +1,97 @@
+// Copyright 2019-2023 Tauri Programme within The Commons Conservancy
+// SPDX-License-Identifier: Apache-2.0
+// SPDX-License-Identifier: MIT
+
+use crate::Result;
+use anyhow::Context;
+use clap::{Command, Parser};
+use clap_complete::{generate, Shell};
+use log::info;
+
+use std::{fs::write, path::PathBuf};
+
+const PKG_MANAGERS: &[&str] = &["cargo", "pnpm", "npm", "yarn"];
+
+#[derive(Debug, Clone, Parser)]
+#[clap(about = "Shell completions")]
+pub struct Options {
+  /// Shell to generate a completion script for.
+  #[clap(short, long, verbatim_doc_comment)]
+  shell: Shell,
+  /// Output file for the shell completions. By default the completions are printed to stdout.
+  #[clap(short, long)]
+  output: Option<PathBuf>,
+}
+
+fn completions_for(shell: Shell, manager: &'static str, cmd: Command) -> Vec<u8> {
+  let tauri = cmd.name("tauri");
+  let mut command = if manager == "npm" {
+    Command::new(manager)
+      .bin_name(manager)
+      .subcommand(Command::new("run").subcommand(tauri))
+  } else {
+    Command::new(manager).bin_name(manager).subcommand(tauri)
+  };
+
+  let mut buf = Vec::new();
+  generate(shell, &mut command, manager, &mut buf);
+  buf
+}
+
+fn get_completions(shell: Shell, cmd: Command) -> Result<String> {
+  let completions = if shell == Shell::Bash {
+    let mut completions =
+      String::from_utf8_lossy(&completions_for(shell, "cargo", cmd)).into_owned();
+    for manager in PKG_MANAGERS {
+      completions.push_str(&format!(
+        "complete -F _cargo -o bashdefault -o default {} tauri\n",
+        if manager == &"npm" {
+          "npm run"
+        } else {
+          manager
+        }
+      ));
+    }
+    completions
+  } else {
+    let mut buffer = String::new();
+
+    for (i, manager) in PKG_MANAGERS.iter().enumerate() {
+      let buf = String::from_utf8_lossy(&completions_for(shell, manager, cmd.clone())).into_owned();
+
+      let completions = match shell {
+        Shell::PowerShell => {
+          if i != 0 {
+            // namespaces have already been imported
+            buf
+              .replace("using namespace System.Management.Automation.Language", "")
+              .replace("using namespace System.Management.Automation", "")
+          } else {
+            buf
+          }
+        }
+        _ => buf,
+      };
+
+      buffer.push_str(&completions);
+      buffer.push('\n');
+    }
+
+    buffer
+  };
+
+  Ok(completions)
+}
+
+pub fn command(options: Options, cmd: Command) -> Result<()> {
+  info!("Generating completion file for {}...", options.shell);
+
+  let completions = get_completions(options.shell, cmd)?;
+  if let Some(output) = options.output {
+    write(output, completions).context("failed to write to output path")?;
+  } else {
+    print!("{completions}");
+  }
+
+  Ok(())
+}

+ 8 - 4
tooling/cli/src/lib.rs

@@ -5,6 +5,7 @@
 pub use anyhow::Result;
 
 mod build;
+mod completions;
 mod dev;
 mod helpers;
 mod icon;
@@ -51,7 +52,7 @@ pub struct PackageJson {
   propagate_version(true),
   no_binary_name(true)
 )]
-struct Cli {
+pub(crate) struct Cli {
   /// Enables verbose logging
   #[clap(short, long, global = true, action = ArgAction::Count)]
   verbose: u8,
@@ -68,6 +69,7 @@ enum Commands {
   Init(init::Options),
   Plugin(plugin::Cli),
   Signer(signer::Cli),
+  Completions(completions::Options),
 }
 
 fn format_error<I: CommandFactory>(err: clap::Error) -> clap::Error {
@@ -106,11 +108,12 @@ where
   I: IntoIterator<Item = A>,
   A: Into<OsString> + Clone,
 {
-  let matches = match bin_name {
+  let cli = match bin_name {
     Some(bin_name) => Cli::command().bin_name(bin_name),
     None => Cli::command(),
-  }
-  .get_matches_from(args);
+  };
+  let cli_ = cli.clone();
+  let matches = cli.get_matches_from(args);
 
   let res = Cli::from_arg_matches(&matches).map_err(format_error::<Cli>);
   let cli = match res {
@@ -167,6 +170,7 @@ where
     Commands::Init(options) => init::command(options)?,
     Commands::Plugin(cli) => plugin::command(cli)?,
     Commands::Signer(cli) => signer::command(cli)?,
+    Commands::Completions(options) => completions::command(options, cli_)?,
   }
 
   Ok(())

+ 2 - 2
tooling/cli/src/signer/sign.rs

@@ -17,10 +17,10 @@ use tauri_utils::display_path;
 #[clap(about = "Sign a file")]
 pub struct Options {
   /// Load the private key from a file
-  #[clap(short = 'k', long, conflicts_with("private-key-path"))]
+  #[clap(short = 'k', long, conflicts_with("private_key_path"))]
   private_key: Option<String>,
   /// Load the private key from a string
-  #[clap(short = 'f', long, conflicts_with("private-key"))]
+  #[clap(short = 'f', long, conflicts_with("private_key"))]
   private_key_path: Option<PathBuf>,
   /// Set private key password when signing
   #[clap(short, long)]