Skip to content

Commit

Permalink
feat: Generate descriptions for strings arg type for nu
Browse files Browse the repository at this point in the history
  • Loading branch information
ysthakur committed Dec 30, 2023
1 parent 898537b commit c70d189
Show file tree
Hide file tree
Showing 5 changed files with 107 additions and 33 deletions.
33 changes: 30 additions & 3 deletions src/gen/nu.rs
Original file line number Diff line number Diff line change
Expand Up @@ -103,17 +103,35 @@ fn generate_cmd(cmd_name: &str, cmd: &CommandInfo, out: &mut Output) {
}
}

/// Generate Nu code to provide completions for a particular type
/// Generate Nu code to provide completions for a particular type.
///
/// Generates a list of records with a value field and possibly a description
/// field
fn complete_type(typ: &ArgType) -> String {
match typ {
ArgType::Unknown => complete_type(&ArgType::Path),
ArgType::Run(cmd) => format!("({})", cmd),
ArgType::Run { cmd, sep: desc_sep } => {
if let Some(sep) = desc_sep {
format!(
r#"(({}) | each {{ |it| $it | split row -n 2 '{}' | {{value: $in.0, description: $in.1}} }})"#,
cmd, sep
)
} else {
format!("(({}) | each {{ |it| {{value: $it}} }})", cmd)
}
}
ArgType::Strings(strs) => {
format!(
"[{}]",
strs
.iter()
.map(|s| format!("'{}'", s))
.map(|(s, desc)| {
if let Some(desc) = desc {
format!("{{value: '{}', description: '{}'}}", s, desc)
} else {
format!("{{value: '{}'}}", s)
}
})
.collect::<Vec<_>>()
.join(", ")
)
Expand All @@ -129,3 +147,12 @@ fn complete_type(typ: &ArgType) -> String {
_ => "[]".to_owned(), // todo implement
}
}

/// Whether the completions for this type will include descriptions
fn generates_descriptions(typ: ArgType) -> bool {
match typ {
ArgType::Strings(strs) => strs.iter().any(|(_, desc)| desc.is_some()),
ArgType::Run { sep: desc_sep, .. } => desc_sep.is_some(),
_ => false,
}
}
14 changes: 10 additions & 4 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -41,10 +41,16 @@ pub enum ArgType {
Dir,

/// Complete by running a command
Run(String),

/// Only these strings are allowed
Strings(Vec<String>),
Run {
/// The command to run
cmd: String,
/// The separator to split on to get the value (first) and description
/// (second). If none, assumed to only return values
sep: Option<String>,
},

/// Only these strings are allowed. The second part of each tuple is an optional description
Strings(Vec<(String, Option<String>)>),

/// Complete with the name of a command
CommandName,
Expand Down
68 changes: 45 additions & 23 deletions src/parse_deser/kdl.rs
Original file line number Diff line number Diff line change
Expand Up @@ -294,39 +294,51 @@ fn parse_type(node: &KdlNode) -> ParseResult<ArgType> {
"unknown" => ArgType::Unknown,
"command" => ArgType::CommandName,
"strings" => {
if let Some(children) = node.children() {
let help = if node.entries().is_empty() {
Some(r#"Write out the strings like 'strings "foo" "bar"' instead of 'strings {...}'"#.to_owned())
} else {
None
};
if !node.entries().is_empty() {
return Err(ParseError::Generic {
error: "'strings' type should have no child nodes".to_owned(),
span: *children.span(),
error: "'strings' type should have no entries".to_owned(),
span: *node.span(),
label: "this stuff shouldn't be here".to_owned(),
help,
help: Some(r#"Write out the strings like 'strings {...}' instead of 'strings ...'"#.to_owned()),
});
}
ArgType::Strings(
node
.entries()
.iter()
.map(|entry| strip_quotes(&entry.to_string()))
.collect::<Vec<_>>(),
)

if let Some(children) = node.children() {
let mut strings = vec![];
for child in children.nodes() {
let value = strip_quotes(child.name().to_string());
let desc = if child.entries().is_empty() {
None
} else if child.entries().len() == 1 {
Some(strip_quotes(child.entries()[0].value().to_string()))
} else {
return Err(ParseError::Generic {
error: "Too many entries".to_owned(),
span: *child.span(),
label: "You just need one entry for the description".to_owned(),
help: None,
});
};
strings.push((value, desc));
}
ArgType::Strings(strings)
} else {
ArgType::Strings(vec![])
}
}
"run" => {
if node.entries().is_empty() {
return Err(ParseError::MissingCommand(*node.name().span()));
}
ArgType::Run(
node
ArgType::Run {
cmd: node
.entries()
.iter()
.map(|entry| strip_quotes(&entry.to_string()))
.collect::<Vec<_>>()
.join(" "),
)
sep: None,
}
}
// todo handle other variants
typ => {
Expand Down Expand Up @@ -373,9 +385,10 @@ fn get_nodes<'a>(
}

/// KDL returns values with quotes around them, so remove those
fn strip_quotes(flag: &str) -> String {
fn strip_quotes(flag: impl AsRef<str>) -> String {
// todo check if strip_prefix/suffix is the right way to remove the quotes
// might need to unescape characters within string
let flag = flag.as_ref();
flag
.trim()
.strip_prefix('"')
Expand Down Expand Up @@ -437,8 +450,14 @@ mod tests {
typ: Some(ArgType::Any(vec![
ArgType::Path,
ArgType::Dir,
ArgType::Strings(vec!["foo".to_owned(), "bar".to_owned()]),
ArgType::Run("ls -al".to_owned()),
ArgType::Strings(vec![
("foo".to_owned(), None),
("bar".to_owned(), None)
]),
ArgType::Run {
cmd: "ls -al".to_owned(),
sep: None
},
ArgType::Unknown,
])),
}],
Expand All @@ -453,7 +472,10 @@ mod tests {
type {
path
dir
strings "foo" "bar"
strings {
"foo"
"bar"
}
run "ls -al"
unknown
}
Expand Down
8 changes: 6 additions & 2 deletions tests/resources/gen/expected/test-types-completions.nu

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

17 changes: 16 additions & 1 deletion tests/resources/gen/in/test-types.kdl

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

0 comments on commit c70d189

Please sign in to comment.