Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor: compgen for --argc-* #324

Merged
merged 1 commit into from
Apr 27, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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