Skip to content

Commit

Permalink
refactor!: Rename Arg to Flag
Browse files Browse the repository at this point in the history
  • Loading branch information
ysthakur committed Aug 12, 2023
1 parent 7f5ae26 commit ec2dcce
Show file tree
Hide file tree
Showing 10 changed files with 73 additions and 50 deletions.
27 changes: 18 additions & 9 deletions src/gen/json.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,11 @@ impl Completions for JsonCompletions {
/// Generate JSON representing the parsed options
///
/// This should probably use a real JSON library but whatever
fn generate<P>(cmd_name: &str, cmd_info: &CommandInfo, out_dir: P) -> Result<()>
fn generate<P>(
cmd_name: &str,
cmd_info: &CommandInfo,
out_dir: P,
) -> Result<()>
where
P: AsRef<Path>,
{
Expand All @@ -37,39 +41,44 @@ impl Completions for JsonCompletions {
/// * `indent` - The indentation level (how many subcommands in we are)
/// * `last` - Whether this is the last command at this level. Used for deciding
/// whether or not to put a trailing comma
fn generate_cmd(cmd: &str, cmd_info: &CommandInfo, last: bool, out: &mut Output) {
fn generate_cmd(
cmd: &str,
cmd_info: &CommandInfo,
last: bool,
out: &mut Output,
) {
let cmd = quote(cmd);
// Avoid trailing commas
let end = if last { "}" } else { "}," };
let mut args = cmd_info.args.iter();
if let Some(mut arg) = args.next() {
let mut flags = cmd_info.flags.iter();
if let Some(mut flag) = flags.next() {
out.writeln(format!("{cmd}: {{"));
out.indent();
out.writeln("\"args\": [");
out.writeln("\"flags\": [");
out.indent();

loop {
out.writeln("{");
out.indent();

let forms = arg
let forms = flag
.forms
.iter()
.map(|a| quote(a))
.collect::<Vec<_>>()
.join(", ");
out.write(format!("\"forms\": [{}]", forms));
if let Some(desc) = &arg.desc {
if let Some(desc) = &flag.desc {
out.writeln(",");
out.writeln(format!("\"description\": {}", quote(desc)));
} else {
out.writeln("");
}

out.dedent();
if let Some(next) = args.next() {
if let Some(next) = flags.next() {
out.writeln("},");
arg = next;
flag = next;
} else {
// Avoid trailing comma
out.writeln("}");
Expand Down
6 changes: 3 additions & 3 deletions src/gen/nu.rs
Original file line number Diff line number Diff line change
Expand Up @@ -42,11 +42,11 @@ fn generate_cmd(
out.writeln(format!("export extern \"{}\" [", cmd_name));
out.indent();

for arg in cmd_info.args.iter() {
for flag in cmd_info.flags.iter() {
let (mut short, mut long): (Vec<_>, Vec<_>) =
arg.forms.iter().partition(|f| f.len() == 2);
flag.forms.iter().partition(|f| f.len() == 2);

let desc_str = if let Some(desc) = &arg.desc {
let desc_str = if let Some(desc) = &flag.desc {
format!(" # {}", desc)
} else {
String::from("")
Expand Down
16 changes: 12 additions & 4 deletions src/gen/zsh.rs
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,11 @@ impl Completions for ZshCompletions {
/// '-b[Make new branch]'
/// }
/// ```
fn generate<P>(cmd_name: &str, cmd_info: &CommandInfo, out_dir: P) -> Result<()>
fn generate<P>(
cmd_name: &str,
cmd_info: &CommandInfo,
out_dir: P,
) -> Result<()>
where
P: AsRef<Path>,
{
Expand Down Expand Up @@ -88,9 +92,13 @@ fn generate_fn(
}

out.indent();
for opt in cmd_info.args.iter() {
let desc = if let Some(desc) = &opt.desc { desc } else { "" };
for form in opt.forms.iter() {
for flag in cmd_info.flags.iter() {
let desc = if let Some(desc) = &flag.desc {
desc
} else {
""
};
for form in flag.forms.iter() {
let text = util::quote_bash(format!("{}[{}]", form, desc));
out.writeln(" \\");
out.write(text);
Expand Down
12 changes: 6 additions & 6 deletions src/parse/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,17 +15,17 @@ use log::{debug, trace};

#[derive(Debug)]
pub struct CommandInfo {
pub args: Vec<Arg>,
pub flags: Vec<Flag>,
pub subcommands: HashMap<String, CommandInfo>,
}

#[derive(Debug)]
pub struct Arg {
pub struct Flag {
pub forms: Vec<String>,
pub desc: Option<String>,
}

pub fn parse_manpage_text<S>(text: S) -> Option<Vec<Arg>>
pub fn parse_manpage_text<S>(text: S) -> Option<Vec<Flag>>
where
S: AsRef<str>,
{
Expand All @@ -46,15 +46,15 @@ pub fn parse_from(
cmd_name: &str,
pre_info: CmdPreInfo,
) -> (CommandInfo, Vec<Error>) {
let mut args = Vec::new();
let mut flags = Vec::new();
let mut subcommands = HashMap::new();
let mut errors = Vec::new();

if let Some(path) = pre_info.path {
match read_manpage(path) {
Ok(text) => {
if let Some(mut parsed) = parse_manpage_text(text) {
args.append(&mut parsed);
flags.append(&mut parsed);
} else {
errors.push(anyhow!("Could not parse man page for '{}'", cmd_name))
}
Expand All @@ -77,7 +77,7 @@ pub fn parse_from(
errors.append(&mut sub_errors);
}

(CommandInfo { args, subcommands }, errors)
(CommandInfo { flags, subcommands }, errors)
}

/// Insert a subcommand into a tree of subcommands
Expand Down
12 changes: 6 additions & 6 deletions src/parse/type1.rs
Original file line number Diff line number Diff line change
@@ -1,16 +1,16 @@
use log::debug;

use super::{util, Arg};
use super::{util, Flag};

/// Ported from Fish's `Type1ManParser`
///
/// todo implement fallback and fallback2 like the Fish script
pub fn parse(page_text: &str) -> Option<Vec<Arg>> {
pub fn parse(page_text: &str) -> Option<Vec<Flag>> {
let re = util::regex_for_section(r#""OPTIONS""#);
match re.captures(page_text) {
Some(captures) => {
let content = captures.get(1).unwrap().as_str();
let mut args = Vec::new();
let mut flags = Vec::new();

let mut paras = content.split(".PP");
paras.next(); // Discard the part before the first option
Expand All @@ -21,8 +21,8 @@ pub fn parse(page_text: &str) -> Option<Vec<Arg>> {
let mut data = data.split(".RS 4");
let options = data.next().unwrap();
let desc = data.next();
if let Some(arg) = util::make_arg(options, desc) {
args.push(arg);
if let Some(flag) = util::make_flag(options, desc) {
flags.push(flag);
}
} else {
debug!(
Expand All @@ -32,7 +32,7 @@ pub fn parse(page_text: &str) -> Option<Vec<Arg>> {
}
}

Some(args)
Some(flags)
}
None => None,
}
Expand Down
21 changes: 11 additions & 10 deletions src/parse/type2.rs
Original file line number Diff line number Diff line change
@@ -1,21 +1,22 @@
use log::debug;
use regex::Regex;

use super::{util, Arg};
use super::{util, Flag};

/// Ported from Fish's `Type2ManParser`
///
/// TODO actually test this
pub fn parse(page_text: &str) -> Option<Vec<Arg>> {
pub fn parse(page_text: &str) -> Option<Vec<Flag>> {
let re = util::regex_for_section("OPTIONS");
match re.captures(page_text) {
Some(captures) => {
let content = captures.get(1).unwrap().as_str();
let mut args = Vec::new();
let mut flags = Vec::new();

// todo this diverges from the Fish impl for splitting, check if it's okay
// need to see more samples of manpages of this kind
let para_re = Regex::new(&format!(r"\.[IT]P( {}i?)?", util::NUM_RE)).unwrap();
let para_re =
Regex::new(&format!(r"\.[IT]P( {}i?)?", util::NUM_RE)).unwrap();
let para_end = Regex::new(r"\.(IP|TP|UNINDENT|UN|SH)").unwrap();

let mut paras = para_re.split(content);
Expand All @@ -29,19 +30,19 @@ pub fn parse(page_text: &str) -> Option<Vec<Arg>> {
};
let data = util::remove_groff_formatting(data);
let data = data.trim();
let arg = if let Some((options, desc)) = data.split_once('\n') {
util::make_arg(options, Some(desc))
let flag = if let Some((options, desc)) = data.split_once('\n') {
util::make_flag(options, Some(desc))
} else {
// todo should this be an error instead?
debug!("No description, data: {}", util::truncate(data, 40));
util::make_arg(data, None)
util::make_flag(data, None)
};
if let Some(arg) = arg {
args.push(arg);
if let Some(flag) = flag {
flags.push(flag);
}
}

Some(args)
Some(flags)
}
None => None,
}
Expand Down
15 changes: 8 additions & 7 deletions src/parse/util.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
use log::debug;
use regex::{Regex, RegexBuilder};

use super::Arg;
use super::Flag;

/// Maximum length of a description
///
Expand Down Expand Up @@ -50,7 +50,8 @@ pub fn remove_groff_formatting(data: &str) -> String {
.replace(r"\fR", "")
.replace(r"\e", "");
// TODO check if this one is necessary
// also, fish uses a slightly different regex: `.PD( \d+)`, check if that's fine
// also, fish uses a slightly different regex: `.PD( \d+)`, check if that's
// fine
let re = Regex::new(r"\.PD \d+").unwrap();
let data = re.replace_all(&data, "");
let data = data
Expand Down Expand Up @@ -89,7 +90,7 @@ pub fn truncate(s: &str, len: usize) -> String {
/// Parse the line of options after .PP and the description after it
///
/// Ported from Fish's `built_command`
pub fn make_arg(options: &str, desc: Option<&str>) -> Option<Arg> {
pub fn make_flag(options: &str, desc: Option<&str>) -> Option<Flag> {
// Unquote the options string
let options = options.trim();
let options = if options.len() < 2 {
Expand All @@ -109,8 +110,8 @@ pub fn make_arg(options: &str, desc: Option<&str>) -> Option<Arg> {
// todo Fish doesn't replace <.*> so maybe this is wrong
let option = Regex::new(r"<.*").unwrap().replace(&option, "");
// todo this is ridiculously verbose
let option =
option.trim_matches(" \t\r\n[](){}.:!".chars().collect::<Vec<_>>().as_slice());
let option = option
.trim_matches(" \t\r\n[](){}.:!".chars().collect::<Vec<_>>().as_slice());
if !option.starts_with('-') || option == "-" || option == "--" {
continue;
}
Expand Down Expand Up @@ -139,8 +140,8 @@ pub fn make_arg(options: &str, desc: Option<&str>) -> Option<Arg> {

let desc = trim_desc(desc);
let desc = if desc.is_empty() { None } else { Some(desc) };
Some(Arg { forms, desc })
Some(Flag { forms, desc })
}
None => Some(Arg { forms, desc: None }),
None => Some(Flag { forms, desc: None }),
}
}
8 changes: 6 additions & 2 deletions tests/integration_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,7 @@ const BIN_NAME: &str = "man-completions";
/// Shells to test
const SHELLS: [&str; 4] = ["zsh", "json", "bash", "nu"];

#[test]
fn test() {
fn run_test() {
// The project's root directory
let root = env::var("CARGO_MANIFEST_DIR").unwrap();

Expand Down Expand Up @@ -92,3 +91,8 @@ fn test() {
assert!(false);
}
}

#[test]
fn test1() {
run_test();
}
4 changes: 2 additions & 2 deletions tests/resources/expected/git.json

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

2 changes: 1 addition & 1 deletion tests/resources/expected/rfcomm.json

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

0 comments on commit ec2dcce

Please sign in to comment.