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

feat: fill options and improve resolving option #52

Merged
merged 5 commits into from
Jul 22, 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
5 changes: 5 additions & 0 deletions .changeset/curly-planes-lie.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'archons': patch
---

Support more options for command option
5 changes: 5 additions & 0 deletions .changeset/empty-jars-cough.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'archons': patch
---

Support action option and resolve by str
5 changes: 5 additions & 0 deletions .changeset/hip-walls-remain.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'archons': patch
---

Support `conflicts_with` in command option definition
5 changes: 5 additions & 0 deletions .changeset/new-candles-tickle.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'archons': patch
---

Improvements of type annotations for command option
5 changes: 5 additions & 0 deletions .changeset/young-kids-sell.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'archons': patch
---

Add `default_missing` arg for option
2 changes: 1 addition & 1 deletion examples/simple.cts
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ const main = defineCommand({
},
options: {
verbose: {
type: 'flag',
type: 'option',
parser: 'boolean',
help: 'Enable verbose output',
},
Expand Down
109 changes: 101 additions & 8 deletions index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,21 +60,114 @@ export interface CommandMeta {
styled?: boolean
}
export interface CommandOption {
type?: 'positional' | 'flag' | 'option'
parser?:
| 'string'
| 'str'
| 'number'
| 'boolean'
| 'bool'
short?: string
/**
* Option type for argument
*
* `type` option and `action` option are used to specify how to parse the argument.
*
* - `option` and (`store` or `store_false`): Boolean flag
* - `option` and `count`: Counter flag
* - `option` and `set`: Option flag
* - `option` and `append`: Multiple option flag
* - `positional` and `set`: Positional argument
* - `positional` and `append`: Multiple positional argument
*
* Defaults to `option` if not specified.
*/
type?: 'positional' | 'option'
/** Specify the value type for the argument. */
parser?: 'string' | 'number' | 'boolean'
/**
* Specify how to react to an argument when parsing it.
*
* - `set`: Overwrite previous values with new ones
* - `append`: Append new values to all previous ones
* - `count`: Count how many times a flag occurs
* - `store`: Store the value as a boolean flag
* - `store_false`: Store the value as a boolean flag with opposite meaning
*
* Defaults to `set` if not specified.
*/
action?: 'set' | 'append' | 'count' | 'store' | 'store_false'
/**
* Short option name
*
* This is a single character that can be used to represent the option
* in the command line. For example, `-v` for the `--verbose` option.
* Panics if the length of the string is empty. If the size of string
* is greater than 1, the first character will be used as the short option.
*
* This option will be ignored if option `type` is not `option`.
*
* Defaults to the first character of the long option name.
*/
short?: string & { length: 1 }
/**
* Long option name
*
* This is the name of the option that will be used to represent the option,
* preceded by two dashes. For example, `--verbose` option.
*
* This option will be ignored if option `type` is not `option`.
*
* Defaults to the name of the argument.
*/
long?: string
/** Option aliases */
alias?: Array<string>
/** Hidden option aliases */
hiddenAlias?: Array<string>
/** Short option aliases */
shortAlias?: Array<string & { length: 1 }>
/** Hidden short option aliases */
hiddenShortAlias?: Array<string & { length: 1 }>
/** Option description */
help?: string
/**
* Required argument
*
* If true, the argument is required and the command will fail without it.
*/
required?: boolean
/** Value for the argument when not present */
default?: string
/**
* Value for the argument when the flag is present but no value is specified.
*
* This configuration option is often used to give the user a shortcut and
* allow them to efficiently specify an option argument without requiring an
* explicitly value. The `--color` argument is a common example. By supplying
* a default, such as `default_missing_value("always")`, the user can quickly
* just add `--color` to the command line to produce the desired color output.
*/
defaultMissing?: string
/**
* Hide argument in help output
*
* Do not display the argument in the help message.
*/
hidden?: boolean
/**
* Global argument
*
* Specifies that an argument can be matched to all child subcommands
*/
global?: boolean
/**
* Options that conflict with this argument
*
* This argument is mutually exclusive with the specified arguments.
*/
conflictsWith?: Array<string>
/**
* Hide default value in help output
*
* Do not display the default value of the argument in the help message.
*
* This is useful when default behavior of an arg is explained elsewhere
* in the help text.
*/
hideDefaultValue?: boolean
}
/**
* Command definition
Expand Down
64 changes: 45 additions & 19 deletions src/resolver.rs
Original file line number Diff line number Diff line change
Expand Up @@ -40,21 +40,20 @@ pub(crate) fn resolve_command_meta(
clap
}

pub(crate) fn resolve_action(_type: &str, parser: Option<&str>) -> Option<clap::ArgAction> {
match _type {
"positional" | "option" => {
if parser.is_some() && parser.unwrap().ends_with("[]") {
Some(clap::ArgAction::Append)
} else {
None
}
}
"flag" => match parser {
Some("bool" | "boolean") | None => Some(clap::ArgAction::SetTrue),
Some("number") => Some(clap::ArgAction::Count),
_ => panic!("Invalid flag parser: `{:?}`", parser),
pub(crate) fn resolve_action(action: &Option<String>, type_: &Option<String>) -> clap::ArgAction {
let type_ = type_.as_deref().unwrap_or("option");
match action.as_deref() {
Some("set") => clap::ArgAction::Set,
Some("append") => clap::ArgAction::Append,
Some("count") => clap::ArgAction::Count,
Some("store") => clap::ArgAction::SetTrue,
Some("store_false") => clap::ArgAction::SetFalse,
None => match type_ {
"option" => clap::ArgAction::SetTrue,
"positional" => clap::ArgAction::Set,
_ => panic!("Unsupported type: {:?}", type_),
},
_ => panic!("Unsupported option type: `{}`", _type),
_ => panic!("Unsupported action: {:?}", action),
}
}

Expand All @@ -67,11 +66,8 @@ pub(crate) fn resolve_command_options(
.iter()
.map(|(name, opt)| {
let mut arg = clap::Arg::new(leak_borrowed_str(name));
arg = arg.action(resolve_action(
opt._type.as_deref().unwrap_or("option"),
opt.parser.as_deref(),
));
if opt._type.as_deref() != Some("positional") {
arg = arg.action(resolve_action(&opt.action, &opt.type_));
if opt.type_.as_deref() != Some("positional") {
let long = leak_borrowed_str_or_default(opt.long.as_ref(), name);
arg = arg.long(long).short(
leak_borrowed_str_or_default(opt.short.as_ref(), long)
Expand All @@ -90,6 +86,20 @@ pub(crate) fn resolve_command_options(
.collect::<Vec<&str>>();
arg = arg.aliases(hidden_alias);
}
if let Some(short_alias) = &opt.short_alias {
let short_alias = short_alias
.iter()
.map(|s| s.chars().next().unwrap())
.collect::<Vec<char>>();
arg = arg.visible_short_aliases(short_alias);
}
if let Some(hidden_short_alias) = &opt.hidden_short_alias {
let hidden_short_alias = hidden_short_alias
.iter()
.map(|s| s.chars().next().unwrap())
.collect::<Vec<char>>();
arg = arg.short_aliases(hidden_short_alias);
}
if let Some(help) = &opt.help {
arg = arg.help(leak_borrowed_str(help));
}
Expand All @@ -99,9 +109,25 @@ pub(crate) fn resolve_command_options(
if let Some(default) = &opt.default {
arg = arg.default_value(leak_borrowed_str(default));
}
if let Some(default_missing) = opt.default_missing {
arg = arg.default_missing_value(default_missing);
}
if let Some(hidden) = opt.hidden {
arg = arg.hide(hidden);
}
if let Some(global) = opt.global {
arg = arg.global(global);
}
if let Some(conflicts_with) = &opt.conflicts_with {
let conflicts_with = conflicts_with
.iter()
.map(leak_borrowed_str)
.collect::<Vec<&str>>();
arg = arg.conflicts_with_all(conflicts_with);
}
if let Some(hide_default_value) = opt.hide_default_value {
arg = arg.hide_default_value(hide_default_value);
}
arg
})
.collect::<Vec<clap::Arg>>(),
Expand Down
93 changes: 85 additions & 8 deletions src/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -51,23 +51,100 @@ pub struct CommandMeta {
#[napi(object)]
#[derive(Clone)]
pub struct CommandOption {
#[napi(js_name = "type", ts_type = "'positional' | 'flag' | 'option'")]
pub _type: Option<String>,
#[napi(ts_type = r#"
| 'string'
| 'str'
| 'number'
| 'boolean'
| 'bool'"#)]
/// Option type for argument
///
/// `type` option and `action` option are used to specify how to parse the argument.
///
/// - `option` and (`store` or `store_false`): Boolean flag
/// - `option` and `count`: Counter flag
/// - `option` and `set`: Option flag
/// - `option` and `append`: Multiple option flag
/// - `positional` and `set`: Positional argument
/// - `positional` and `append`: Multiple positional argument
///
/// Defaults to `option` if not specified.
#[napi(js_name = "type", ts_type = "'positional' | 'option'")]
pub type_: Option<String>,
/// Specify the value type for the argument.
#[napi(ts_type = "'string' | 'number' | 'boolean'")]
pub parser: Option<String>,
/// Specify how to react to an argument when parsing it.
///
/// - `set`: Overwrite previous values with new ones
/// - `append`: Append new values to all previous ones
/// - `count`: Count how many times a flag occurs
/// - `store`: Store the value as a boolean flag
/// - `store_false`: Store the value as a boolean flag with opposite meaning
///
/// Defaults to `set` if not specified.
#[napi(ts_type = "'set' | 'append' | 'count' | 'store' | 'store_false'")]
pub action: Option<String>,
/// Short option name
///
/// This is a single character that can be used to represent the option
/// in the command line. For example, `-v` for the `--verbose` option.
/// Panics if the length of the string is empty. If the size of string
/// is greater than 1, the first character will be used as the short option.
///
/// This option will be ignored if option `type` is not `option`.
///
/// Defaults to the first character of the long option name.
#[napi(ts_type = "string & { length: 1 }")]
pub short: Option<String>,
/// Long option name
///
/// This is the name of the option that will be used to represent the option,
/// preceded by two dashes. For example, `--verbose` option.
///
/// This option will be ignored if option `type` is not `option`.
///
/// Defaults to the name of the argument.
pub long: Option<String>,
/// Option aliases
pub alias: Option<Vec<String>>,
/// Hidden option aliases
pub hidden_alias: Option<Vec<String>>,
/// Short option aliases
#[napi(ts_type = "Array<string & { length: 1 }>")]
pub short_alias: Option<Vec<String>>,
/// Hidden short option aliases
#[napi(ts_type = "Array<string & { length: 1 }>")]
pub hidden_short_alias: Option<Vec<String>>,
/// Option description
pub help: Option<String>,
/// Required argument
///
/// If true, the argument is required and the command will fail without it.
pub required: Option<bool>,
/// Value for the argument when not present
pub default: Option<String>,
/// Value for the argument when the flag is present but no value is specified.
///
/// This configuration option is often used to give the user a shortcut and
/// allow them to efficiently specify an option argument without requiring an
/// explicitly value. The `--color` argument is a common example. By supplying
/// a default, such as `default_missing_value("always")`, the user can quickly
/// just add `--color` to the command line to produce the desired color output.
pub default_missing: Option<&'static str>,
/// Hide argument in help output
///
/// Do not display the argument in the help message.
pub hidden: Option<bool>,
/// Global argument
///
/// Specifies that an argument can be matched to all child subcommands
pub global: Option<bool>,
/// Options that conflict with this argument
///
/// This argument is mutually exclusive with the specified arguments.
pub conflicts_with: Option<Vec<String>>,
/// Hide default value in help output
///
/// Do not display the default value of the argument in the help message.
///
/// This is useful when default behavior of an arg is explained elsewhere
/// in the help text.
pub hide_default_value: Option<bool>,
}

/// Command definition
Expand Down
2 changes: 1 addition & 1 deletion src/utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ pub(crate) fn merge_args_matches(
.find(|&(name, _)| name == id)
.map(|(_, t)| t)
.unwrap();
match opts._type.as_deref().unwrap_or("option") {
match opts.type_.as_deref().unwrap_or("option") {
"option" => {
parsed_args.set(id, matches.get_one::<String>(id.as_str()))?;
}
Expand Down