Skip to content

Commit

Permalink
wip
Browse files Browse the repository at this point in the history
  • Loading branch information
jdx committed Feb 10, 2024
1 parent b0b75f8 commit 42f086e
Show file tree
Hide file tree
Showing 14 changed files with 123 additions and 102 deletions.
4 changes: 2 additions & 2 deletions .mise.toml
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ outputs = ['target/debug/rtx']
run = 'cargo build --all'

[tasks.cli]
alias = ['x']
depends = ['build']
run = 'usage'
raw = true
Expand All @@ -18,8 +19,7 @@ depends = ['build']
run = 'usage cw'
raw = true

[tasks.run]
alias = ['x']
[tasks.run-example]
depends = ['build']
run = './examples/example.sh'
raw = true
Expand Down
2 changes: 2 additions & 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 Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ shell-escape = "0.1"
strum = { version = "0.26", features = ["derive"] }
thiserror = "1"
xx = "0.2"
heck = "0.4.1"

[features]
default = ["clap"]
Expand Down
1 change: 1 addition & 0 deletions cli/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ clap = { version = "4", features = ["derive", "string"] }
contracts = "0.6"
env_logger = "0.11"
exec = "0.3"
heck = "0.4"
indexmap = "2"
itertools = "0.12"
kdl = "4"
Expand Down
3 changes: 2 additions & 1 deletion cli/src/cli/complete_word.rs
Original file line number Diff line number Diff line change
Expand Up @@ -114,10 +114,11 @@ impl Display for ParseValue {
fn parse(spec: &Spec, mut words: VecDeque<String>) -> miette::Result<ParseOutput> {
let mut cmds = vec![];
let mut cmd = &spec.cmd;
words.pop_front();
cmds.push(cmd);

while !words.is_empty() {
if let Some(subcommand) = cmd.subcommands.get(&words[0]) {
if let Some(subcommand) = cmd.find_subcommand(&words[0]) {
words.pop_front();
cmds.push(subcommand);
cmd = subcommand;
Expand Down
46 changes: 28 additions & 18 deletions cli/src/cli/generate/completion.rs
Original file line number Diff line number Diff line change
@@ -1,34 +1,44 @@
use std::path::PathBuf;

use clap::Args;

use usage::Spec;

#[derive(Args)]
#[clap(visible_alias = "c", aliases=["complete", "completions"])]
#[clap(visible_alias = "c", aliases = ["complete", "completions"])]
pub struct Completion {
#[clap(value_parser = ["bash", "fish", "zsh"])]
shell: String,

#[clap(short, long)]
file: Option<PathBuf>,
/// The CLI which we're generates completions for
bin: String,

#[clap(short, long, required_unless_present = "file", overrides_with = "file")]
spec: Option<String>,
/// A command which generates a usage spec
/// e.g.: `mycli --usage` or `mycli completion usage`
/// Defaults to "$bin --usage"
#[clap(long)]
usage_cmd: Option<String>,
// #[clap(short, long)]
// file: Option<PathBuf>,
//
// #[clap(short, long, required_unless_present = "file", overrides_with = "file")]
// spec: Option<String>,
}

impl Completion {
pub fn run(&self) -> miette::Result<()> {
let spec = if let Some(file) = &self.file {
let (spec, _) = Spec::parse_file(file)?;
spec
} else {
Spec::parse_spec(self.spec.as_ref().unwrap())?
};
// let spec = if let Some(file) = &self.file {
// let (spec, _) = Spec::parse_file(file)?;
// spec
// } else {
// Spec::parse_spec(self.spec.as_ref().unwrap())?
// };
let bin = &self.bin;
let usage_cmd = self
.usage_cmd
.clone()
.unwrap_or_else(|| format!("{bin} --usage"));

let script = match self.shell.as_str() {
"bash" => usage::complete::bash::complete_bash(&spec),
"fish" => usage::complete::fish::complete_fish(&spec),
"zsh" => usage::complete::zsh::complete_zsh(&spec),
"bash" => usage::complete::bash::complete_bash(bin, &usage_cmd),
"fish" => usage::complete::fish::complete_fish(bin, &usage_cmd),
"zsh" => usage::complete::zsh::complete_zsh(bin, &usage_cmd),
_ => unreachable!(),
};
println!("{}", script.trim());
Expand Down
4 changes: 2 additions & 2 deletions cli/tests/complete_word.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ fn complete_word_subcommands() {

#[test]
fn complete_word_cword() {
assert_cmd(&["--cword=2", "plugins", "install"]).stdout(contains("plugin-2"));
assert_cmd(&["--cword=3", "plugins", "install"]).stdout(contains("plugin-2"));
}

#[test]
Expand All @@ -42,7 +42,7 @@ fn complete_word_short_flag() {

fn cmd() -> Command {
let mut cmd = Command::cargo_bin("usage").unwrap();
cmd.args(["cw", "-f", "../examples/basic.usage.kdl"]);
cmd.args(["cw", "-f", "../examples/basic.usage.kdl", "mycli"]);
cmd
}

Expand Down
1 change: 0 additions & 1 deletion examples/mise.usage.kdl
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
name "mise"
bin "mise"
version "2024.2.9-DEBUG macos-arm64 (494aafd 2024-02-10)"
about "The front-end to your dev env"
long_about r"mise is a tool for managing runtime versions. https://github.com/jdx/mise

Expand Down
54 changes: 27 additions & 27 deletions src/complete/bash.rs
Original file line number Diff line number Diff line change
@@ -1,17 +1,23 @@
use crate::{env, Spec};
use heck::ToShoutySnakeCase;

pub fn complete_bash(spec: &Spec) -> String {
let usage = &*env::USAGE_CMD;
let bin = &spec.bin;
let raw = shell_escape::unix::escape(spec.to_string().into());
use crate::env;

pub fn complete_bash(bin: &str, usage_cmd: &str) -> String {
let usage = env::USAGE_BIN.display();
let bin_up = bin.to_shouty_snake_case();
// let bin = &spec.bin;
// let raw = shell_escape::unix::escape(spec.to_string().into());
format!(
r#"
_{bin}() {{
local raw
spec={raw}
COMPREPLY=($({usage} complete-word -s "$spec" --cword="$COMP_CWORD" -- "${{COMP_WORDS[@]}}"))
#COMPREPLY=($(compgen -W "${{COMPREPLY[*]}}" -- "${{COMP_WORDS[$COMP_CWORD]}}"))
if [[ -z ${{_USAGE_SPEC_{bin_up}:-}} ]]; then
_USAGE_SPEC_{bin_up}="$({usage_cmd})"
fi
COMPREPLY=( $({usage} complete-word -s "${{_USAGE_SPEC_{bin_up}}}" --cword="$COMP_CWORD" -- "${{COMP_WORDS[@]}}") )
if [[ $? -ne 0 ]]; then
unset COMPREPLY
fi
return 0
}}
Expand All @@ -21,32 +27,26 @@ complete -F _{bin} {bin}
)
}

// fn render_args(cmds: &[&SchemaCmd]) -> String {
// format!("XX")
// }

#[cfg(test)]
mod tests {
use crate::parse::spec::Spec;

use super::*;

#[test]
fn test_complete_bash() {
let spec = r#"
"#;
let spec = Spec::parse(&Default::default(), spec).unwrap();
assert_snapshot!(complete_bash(&spec).trim(), @r###"
_() {
local raw
spec=''

COMPREPLY=($(usage complete-word -s "$spec" --cword="$COMP_CWORD" -- "${COMP_WORDS[@]}"))
#COMPREPLY=($(compgen -W "${COMPREPLY[*]}" -- "${COMP_WORDS[$COMP_CWORD]}"))
assert_snapshot!(complete_bash("mycli", "mycli complete --usage").trim(), @r###"
_mycli() {
if [[ -z ${_USAGE_SPEC_MYCLI:-} ]]; then
_USAGE_SPEC_MYCLI="$(mycli complete --usage)"
fi

COMPREPLY=( $(/Users/jdx/src/usage/target/debug/deps/usage-6b6342071eb3064a complete-word -s "${_USAGE_SPEC_MYCLI}" --cword="$COMP_CWORD" -- "${COMP_WORDS[@]}") )
if [[ $? -ne 0 ]]; then
unset COMPREPLY
fi
return 0
}

complete -F _
complete -F _mycli mycli
# vim: noet ci pi sts=0 sw=4 ts=4 ft=sh
"###);
}
Expand Down
26 changes: 12 additions & 14 deletions src/complete/fish.rs
Original file line number Diff line number Diff line change
@@ -1,31 +1,29 @@
use crate::{env, Spec};
use crate::env;

pub fn complete_fish(spec: &Spec) -> String {
let usage = &*env::USAGE_CMD;
let bin = &spec.bin;
let raw = spec.to_string().replace('\'', r"\'").to_string();
pub fn complete_fish(bin: &str, usage_cmd: &str) -> String {
let usage = env::USAGE_BIN.display();
// let bin = &spec.bin;
// let raw = spec.to_string().replace('\'', r"\'").to_string();
format!(
r#"
set _usage_spec_{bin} '{raw}'
set _usage_spec_{bin} ({usage_cmd})
complete -xc {bin} -a '({usage} complete-word -s "$_usage_spec_{bin}" --ctoken=(commandline -t) -- (commandline -op))'
"#
)
}

#[cfg(test)]
mod tests {
use crate::parse::spec::Spec;

use super::*;

#[test]
fn test_complete_fish() {
let spec = r#"
"#;
let spec = Spec::parse(&Default::default(), spec).unwrap();
assert_snapshot!(complete_fish(&spec).trim(), @r###"
set _usage_spec_ ''
complete -xc -a '(usage complete-word -s "$_usage_spec_" --ctoken=(commandline -t) -- (commandline -op))'
// let spec = r#"
// "#;
// let spec = Spec::parse(&Default::default(), spec).unwrap();
assert_snapshot!(complete_fish("mycli", "mycli complete --usage").trim(), @r###"
set _usage_spec_mycli (mycli complete --usage)
complete -xc mycli -a '(/Users/jdx/src/usage/target/debug/deps/usage-6b6342071eb3064a complete-word -s "$_usage_spec_mycli" --ctoken=(commandline -t) -- (commandline -op))'
"###);
}
}
31 changes: 14 additions & 17 deletions src/complete/zsh.rs
Original file line number Diff line number Diff line change
@@ -1,17 +1,14 @@
use crate::Spec;

pub fn complete_zsh(spec: &Spec) -> String {
pub fn complete_zsh(bin: &str, usage_cmd: &str) -> String {
// let cmds = vec![&spec.cmd];
// let args = render_args(&cmds);
let bin = &spec.bin;
let raw = spec.to_string();
format!(
r#"
#compdef {bin}
_{bin}() {{
typeset -A opt_args
local context state line curcontext=$curcontext
local spec='{raw}'
local spec
spec="$({usage_cmd})"
_arguments -s -S \
'-h[Show help information]' \
Expand All @@ -37,30 +34,30 @@ fi
#[cfg(test)]
mod tests {
use super::*;
use crate::parse::spec::Spec;

#[test]
fn test_complete_zsh() {
let spec = r#"
"#;
let spec = Spec::parse(&Default::default(), spec).unwrap();
assert_snapshot!(complete_zsh(&spec).trim(), @r###"
#compdef
_() {
// let spec = r#"
// "#;
// let spec = Spec::parse(&Default::default(), spec).unwrap();
assert_snapshot!(complete_zsh("mycli", "mycli complete --usage").trim(), @r###"
#compdef mycli
_mycli() {
typeset -A opt_args
local context state line curcontext=$curcontext
local spec=''
local spec
spec="$(mycli complete --usage)"
_arguments -s -S \
'-h[Show help information]' \
'-V[Show version information]' \
'*:: :->command' && return
}
if [ "$funcstack[1]" = "_" ]; then
_ "$@"
if [ "$funcstack[1]" = "_mycli" ]; then
_mycli "$@"
else
compdef _
compdef _mycli mycli
fi
# vim: noet ci pi sts=0 sw=4 ts=4
Expand Down
10 changes: 8 additions & 2 deletions src/env.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,12 @@
pub use std::env::*;
use std::path::PathBuf;

use once_cell::sync::Lazy;

pub static USAGE_CMD: Lazy<String> =
Lazy::new(|| var("USAGE_CMD").unwrap_or_else(|_| "usage".to_string()));
#[cfg(target_os = "macos")]
pub static USAGE_BIN: Lazy<PathBuf> = Lazy::new(|| {
var_os("USAGE_BIN")
.map(PathBuf::from)
.or_else(|| current_exe().ok())
.unwrap_or_else(|| "usage".into())
});
24 changes: 24 additions & 0 deletions src/parse/cmd.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
use std::collections::HashMap;
use std::sync::OnceLock;

use indexmap::IndexMap;
use kdl::{KdlDocument, KdlEntry, KdlNode, KdlValue};
use serde::Serialize;
Expand Down Expand Up @@ -27,6 +30,9 @@ pub struct SpecCommand {
pub after_help: Option<String>,
pub after_long_help: Option<String>,
pub examples: Vec<SpecExample>,

#[serde(skip)]
pub subcommand_lookup: OnceLock<HashMap<String, String>>,
}

#[derive(Debug, Default, Serialize, Clone)]
Expand Down Expand Up @@ -208,6 +214,24 @@ impl SpecCommand {
}
}

pub fn find_subcommand(&self, name: &str) -> Option<&SpecCommand> {
let sl = self.subcommand_lookup.get_or_init(|| {
let mut map = HashMap::new();
for (name, cmd) in &self.subcommands {
map.insert(name.clone(), name.clone());
for alias in &cmd.aliases {
map.insert(alias.clone(), name.clone());
}
for alias in &cmd.hidden_aliases {
map.insert(alias.clone(), name.clone());
}
}
map
});
let name = sl.get(name)?;
self.subcommands.get(name)
}

pub fn list_visible_long_flags(&self) -> Vec<String> {
self.flags
.iter()
Expand Down
Loading

0 comments on commit 42f086e

Please sign in to comment.