Skip to content

Commit

Permalink
refactor: compgen for --argc-* (#324)
Browse files Browse the repository at this point in the history
  • Loading branch information
sigoden authored Apr 27, 2024
1 parent c6c5489 commit fda9a64
Show file tree
Hide file tree
Showing 8 changed files with 144 additions and 90 deletions.
14 changes: 14 additions & 0 deletions src/bin/argc/completion.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
# @option --argc-eval~ <FILE> <ARGS> Use `eval "$(argc --argc-eval "$0" "$@")"`
# @option --argc-create~ <RECIPES> Create a boilerplate argcfile
# @option --argc-build <FILE> <OUTPATH?> Generate bashscript without argc dependency
# @option --argc-mangen <FILE> <OUTDIR> Generate man pages
# @option --argc-completions <SHELL> <CMDS> Generate shell completion scripts
# @option --argc-compgen <SHELL> <FILE> <ARGS> Generate completion candidates
# @option --argc-export <FILE> Export command line definitions as json
# @option --argc-parallel~ <FILE> <ARGS> Run functions in parallel
# @flag --argc-script-path Print current argcfile path
# @flag --argc-shell-path Print current shell path
# @flag --argc-help Print help information
# @flag --argc-version Print version information

eval "$(argc --argc-eval "$0" "$@")"
112 changes: 47 additions & 65 deletions src/bin/argc/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,9 @@ mod parallel;

use anyhow::{anyhow, bail, Context, Result};
use argc::{
utils::{escape_shell_words, is_true_value, ARGC_COMPLETION_SCRIPT_PATH},
NativeRuntime, Runtime, Shell,
compgen_kind,
utils::{escape_shell_words, is_true_value},
CompKind, NativeRuntime, Runtime, Shell, COMPGEN_KIND_SYMBOL,
};
use base64::{engine::general_purpose, Engine as _};
use std::{
Expand All @@ -22,6 +23,8 @@ const ARGC_SCRIPT_NAMES: [&str; 6] = [
"ARGCFILE",
];

const ARGC_COMPLETION_SCRIPT: &str = include_str!("completion.sh");

fn main() {
match run() {
Ok(code) => {
Expand Down Expand Up @@ -52,7 +55,7 @@ fn run() -> Result<i32> {
let (source, cmd_args) = parse_script_args(&args[2..])?;
if cmd_args
.get(1)
.map(|v| v == parallel::PARALLEL_MODE)
.map(|v| v == parallel::PARALLEL_SYMBOL)
.unwrap_or_default()
{
let cmd_args_len = cmd_args.len();
Expand Down Expand Up @@ -162,7 +165,7 @@ fn run() -> Result<i32> {
println!("{}", shell);
}
"--argc-help" => {
println!("{}", get_argc_help())
println!("{}", get_argc_help(runtime)?)
}
"--argc-version" => {
println!("{}", get_argc_version())
Expand Down Expand Up @@ -221,21 +224,29 @@ fn run_compgen(runtime: NativeRuntime, mut args: Vec<String>) -> Option<()> {
let no_color = std::env::var("NO_COLOR")
.map(|v| is_true_value(&v))
.unwrap_or_default();
let last_arg = args.last().map(|v| v.as_str()).unwrap_or_default();
let output = if &args[4] == "argc" && (args[3].is_empty() || args[5].starts_with("--argc")) {
let cmd_args = &args[4..];
let script = get_argc_script_code();
argc::compgen(
runtime,
shell,
ARGC_COMPLETION_SCRIPT_PATH,
&script,
cmd_args,
no_color,
)
.ok()?
if (args[5] == "--argc-completions" || args[5] == "--argc-compgen") && args.len() == 7 {
compgen_kind(runtime, shell, CompKind::Shell, last_arg, no_color).ok()?
} else if args[5] == "--argc-compgen" {
compgen_kind(runtime, shell, CompKind::Path, last_arg, no_color).ok()?
} else {
argc::compgen(
runtime,
shell,
"",
ARGC_COMPLETION_SCRIPT,
&args[4..],
no_color,
)
.ok()?
}
} else if args[3] == COMPGEN_KIND_SYMBOL {
let kind = CompKind::new(&args[4]);
compgen_kind(runtime, shell, kind, last_arg, no_color).ok()?
} else if args[3].is_empty() {
let cmd_args = &args[4..];
argc::compgen(runtime, shell, "", "# @arg path*", cmd_args, no_color).ok()?
let last_arg = args.last().map(|v| v.as_str()).unwrap_or_default();
compgen_kind(runtime, shell, CompKind::Path, last_arg, no_color).ok()?
} else {
let (source, cmd_args) = parse_script_args(&args[3..]).ok()?;
argc::compgen(runtime, shell, &args[3], &source, &cmd_args[1..], no_color).ok()?
Expand Down Expand Up @@ -270,63 +281,34 @@ fn retrive_argc_variables() -> Option<String> {
String::from_utf8(value).ok()
}

fn get_argc_help() -> String {
fn get_argc_help(runtime: NativeRuntime) -> Result<String> {
let about = concat!(
env!("CARGO_PKG_DESCRIPTION"),
" - ",
env!("CARGO_PKG_REPOSITORY")
);
format!(
let values = argc::eval(
runtime,
ARGC_COMPLETION_SCRIPT,
&["argc".into(), "--help".into()],
None,
None,
)?;
let argc_options = argc::ArgcValue::to_bash(&values);
let argc_options = argc_options
.lines()
.map(|v| v.trim())
.filter(|v| v.starts_with("--argc-"))
.map(|v| format!(" argc {v}\n"))
.collect::<Vec<String>>()
.join("");
let output = format!(
r###"{about}
USAGE:
argc --argc-eval <SCRIPT> [ARGS]... Use `eval "$(argc --argc-eval "$0" "$@")"`
argc --argc-create [RECIPES]... Create a boilerplate argcfile
argc --argc-build <SCRIPT> [OUTPATH] Generate bashscript without argc dependency
argc --argc-mangen <SCRIPT> <OUTDIR> Generate man pages
argc --argc-completions <SHELL> [CMDS]... Generate shell completion scripts
argc --argc-compgen <SHELL> <SCRIPT> <ARGS>... Generate completion candidates
argc --argc-export <SCRIPT> Export command line definitions as json
argc --argc-parallel <SCRIPT> <ARGS>... Run functions in parallel
argc --argc-script-path Print current argcfile path
argc --argc-shell-path Print current shell path
argc --argc-help Print help information
argc --argc-version Print version information
"###
)
}

fn get_argc_script_code() -> String {
let about = concat!(
env!("CARGO_PKG_DESCRIPTION"),
" - ",
env!("CARGO_PKG_REPOSITORY")
{argc_options}"###
);
format!(
r###"#!/usr/bin/env bash
# @describe {about}
# @option --argc-eval~ <FILE> <ARGS> Use `eval "$(argc --argc-eval "$0" "$@")"`
# @option --argc-create~ <RECIPES> Create a boilerplate argcfile
# @option --argc-build <FILE> <OUTPATH?> Generate bashscript without argc dependency
# @option --argc-mangen <FILE> <OUTDIR> Generate man pages
# @option --argc-completions~[`_choice_completion`] <SHELL> <CMDS> Generate shell completion scripts
# @option --argc-compgen~[`_choice_compgen`] <SHELL> <FILE> <ARGS> Generate completion candidates
# @option --argc-export <FILE> Export command line definitions as json
# @option --argc-parallel~ <FILE> <ARGS> Run functions in parallel
# @flag --argc-script-path Print current argcfile path
# @flag --argc-shell-path Print current shell path
# @flag --argc-help Print help information
# @flag --argc-version Print version information
_choice_completion() {{ :; }}
_choice_compgen() {{ :; }}
eval "$(argc --argc-eval "$0" "$@")"
"###
)
Ok(output)
}

fn get_argc_version() -> String {
Expand Down
4 changes: 2 additions & 2 deletions src/bin/argc/parallel.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ use std::process::{self, Command};
use std::sync::mpsc::channel;
use threadpool::ThreadPool;

pub const PARALLEL_MODE: &str = "___parallel___";
pub const PARALLEL_SYMBOL: &str = "___parallel___";

pub fn parallel(
runtime: NativeRuntime,
Expand All @@ -20,7 +20,7 @@ pub fn parallel(
let path_env = runtime.path_env_with_current_exe();
let mut shell_extra_args = runtime.shell_args(shell);
shell_extra_args.push(script_file.to_string());
shell_extra_args.push(PARALLEL_MODE.to_string());
shell_extra_args.push(PARALLEL_SYMBOL.to_string());
for (i, job_args) in jobs.into_iter().enumerate() {
let tx = tx.clone();
let shell = shell.to_string();
Expand Down
8 changes: 4 additions & 4 deletions src/command/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,9 @@ use crate::param::{EnvValue, FlagOptionValue, PositionalValue};
use crate::parser::{parse, parse_symbol, Event, EventData, EventScope, Position};
use crate::runtime::Runtime;
use crate::utils::{
AFTER_HOOK, BEFORE_HOOK, INTERNAL_SYMBOL, MAIN_NAME, META_AUTHOR, META_COMBINE_SHORTS,
META_DEFAULT_SUBCOMMAND, META_DOTENV, META_INHERIT_FLAG_OPTIONS, META_REQUIRE_TOOLS,
META_SYMBOL, META_VERSION, ROOT_NAME,
AFTER_HOOK, BEFORE_HOOK, MAIN_NAME, META_AUTHOR, META_COMBINE_SHORTS, META_DEFAULT_SUBCOMMAND,
META_DOTENV, META_INHERIT_FLAG_OPTIONS, META_REQUIRE_TOOLS, META_SYMBOL, META_VERSION,
ROOT_NAME,
};
use crate::Result;

Expand Down Expand Up @@ -77,7 +77,7 @@ impl Command {
if args.is_empty() {
bail!("Invalid args");
}
if args.len() >= 3 && args[1] == INTERNAL_SYMBOL {
if args.len() >= 3 && args[1] == T::INTERNAL_SYMBOL {
let fallback_args = vec![ROOT_NAME.to_string()];
let new_args = if args.len() == 3 {
&fallback_args
Expand Down
74 changes: 60 additions & 14 deletions src/compgen.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use crate::command::Command;
use crate::matcher::Matcher;
use crate::runtime::Runtime;
use crate::utils::{is_quote_char, is_windows_path, unbalance_quote, ARGC_COMPLETION_SCRIPT_PATH};
use crate::utils::{is_quote_char, is_windows_path, unbalance_quote};
use crate::Shell;

use anyhow::{bail, Result};
Expand All @@ -10,6 +10,8 @@ use std::collections::{HashMap, HashSet};
use std::fmt;
use std::str::FromStr;

pub const COMPGEN_KIND_SYMBOL: &str = "___compgen_kind___";

const FILE_PROTO: &str = "file://";

pub fn compgen<T: Runtime>(
Expand All @@ -33,7 +35,17 @@ pub fn compgen<T: Runtime>(
(last_arg.to_string(), None)
}
};
let cmd = Command::new(script_content, &args[0])?;
let cmd = if script_path == COMPGEN_KIND_SYMBOL {
let comp_kind = &args[0];
let script_content = format!(
r#"# @arg args~[`{comp_kind}`]
{comp_kind} () {{ :; }}
"#
);
Command::new(&script_content, &args[0])?
} else {
Command::new(script_content, &args[0])?
};
let new_args: Vec<String> = if cmd.delegated() {
args.to_vec()
} else {
Expand Down Expand Up @@ -96,19 +108,14 @@ pub fn compgen<T: Runtime>(
let mut argc_suffix = String::new();
let mut argc_cd = None;
if let Some(func) = argc_fn {
let output = if script_path == ARGC_COMPLETION_SCRIPT_PATH {
// complete for argc
let output = if script_path == COMPGEN_KIND_SYMBOL {
let mut values = vec![];
if func == "_choice_completion" {
if new_args.len() == 3 {
values.extend(Shell::list().map(|v| v.name().to_string()))
}
} else if func == "_choice_compgen" {
if new_args.len() == 3 {
values.extend(Shell::list().map(|v| v.name().to_string()))
} else {
values.push("__argc_value=path".to_string());
}
let comp_kind = CompKind::new(&func);
match comp_kind {
CompKind::Shell => values.extend(Shell::list().map(|v| v.name().to_string())),
CompKind::Path => values.push("__argc_value=path".to_string()),
CompKind::Dir => values.push("__argc_value=dir".to_string()),
CompKind::Unknown => {}
}
Some(values.join("\n"))
} else if !script_path.is_empty() {
Expand Down Expand Up @@ -255,6 +262,45 @@ pub fn compgen<T: Runtime>(
Ok(values.join("\n"))
}

pub fn compgen_kind<T: Runtime>(
runtime: T,
shell: Shell,
kind: CompKind,
last_arg: &str,
no_color: bool,
) -> Result<String> {
let args = [kind.name().to_string(), last_arg.to_string()];
compgen(runtime, shell, COMPGEN_KIND_SYMBOL, "", &args, no_color)
}

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum CompKind {
Shell,
Path,
Dir,
Unknown,
}

impl CompKind {
pub fn new(value: &str) -> Self {
match value {
"shell" => CompKind::Shell,
"path" => CompKind::Path,
"dir" => CompKind::Dir,
_ => CompKind::Unknown,
}
}

pub fn name(&self) -> &str {
match self {
CompKind::Shell => "shell",
CompKind::Path => "path",
CompKind::Dir => "dir",
CompKind::Unknown => "unknown",
}
}
}

pub(crate) type CandidateValue = (String, String, bool, CompColor); // value, description, nospace, comp_color

/// (value, description, nospace, color)
Expand Down
2 changes: 1 addition & 1 deletion src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ pub use build::build;
#[cfg(feature = "export")]
pub use command::CommandValue;
#[cfg(feature = "compgen")]
pub use compgen::compgen;
pub use compgen::{compgen, compgen_kind, CompKind, COMPGEN_KIND_SYMBOL};
#[cfg(feature = "completions")]
pub use completions::generate_completions;
#[cfg(feature = "mangen")]
Expand Down
4 changes: 0 additions & 4 deletions src/utils.rs
Original file line number Diff line number Diff line change
@@ -1,15 +1,11 @@
use convert_case::{Boundary, Converter, Pattern};

pub const INTERNAL_SYMBOL: &str = "___internal___";
pub const VARIABLE_PREFIX: &str = "argc_";
pub const BEFORE_HOOK: &str = "_argc_before";
pub const AFTER_HOOK: &str = "_argc_after";
pub const ROOT_NAME: &str = "prog";
pub const MAIN_NAME: &str = "main";

#[cfg(feature = "compgen")]
pub const ARGC_COMPLETION_SCRIPT_PATH: &str = "___argc_completion_script_path___";

pub(crate) const META_VERSION: &str = "version";
pub(crate) const META_AUTHOR: &str = "author";
pub(crate) const META_DOTENV: &str = "dotenv";
Expand Down
16 changes: 16 additions & 0 deletions tests/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,22 @@ fn compgen_argc() {
.success();
}

#[test]
fn compgen_kind() {
Command::cargo_bin("argc")
.unwrap()
.args([
"--argc-compgen",
"fish",
argc::COMPGEN_KIND_SYMBOL,
"shell",
"",
])
.assert()
.stdout(predicates::str::contains("zsh"))
.success();
}

#[test]
fn export() {
let path = locate_script("examples/options.sh");
Expand Down

0 comments on commit fda9a64

Please sign in to comment.