Skip to content

Commit

Permalink
feat: Implement find_manpage
Browse files Browse the repository at this point in the history
  • Loading branch information
ysthakur committed Aug 6, 2023
1 parent 8387520 commit f55c162
Show file tree
Hide file tree
Showing 2 changed files with 58 additions and 25 deletions.
20 changes: 12 additions & 8 deletions cli/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ struct CLI {

/// Manpage sections to exclude (1-8)
#[arg(short = 'S', long, value_parser = section_num_parser, value_delimiter = ',')]
sections_exclude: Option<Vec<u8>>,
sections_exclude: Vec<u8>,

/// A particular command to generate completions for. If omitted, generates
/// completions for all found commands.
Expand Down Expand Up @@ -61,7 +61,6 @@ fn gen_shell(shell: Shell, manpages: HashMap<String, CommandInfo>, out_dir: &Pat
fn main() -> Result<()> {
let args = CLI::parse();

println!("{:?}", &args);
match get_manpath() {
Some(manpath) => {
let exclude_dirs = args.dirs_exclude.unwrap_or_default();
Expand All @@ -72,12 +71,17 @@ fn main() -> Result<()> {
.collect();

if let Some(cmd) = &args.cmd {
let manpage = man_completions::find_manpage(cmd, included)?;
let parsed = parse_manpage_at_path(cmd, manpage)?;
let mut map = HashMap::new();
map.insert(cmd.to_string(), parsed);
gen_shell(args.shell, map, &args.out);
Ok(())
if let Some(manpage) = man_completions::find_manpage(cmd, included) {
let parsed = parse_manpage_at_path(cmd, manpage)?;
let mut map = HashMap::new();
map.insert(cmd.to_string(), parsed);
gen_shell(args.shell, map, &args.out);
Ok(())
} else {
Err(Error::Other {
msg: format!("No manpage found for {cmd}"),
})
}
} else {
let all_manpages = man_completions::enumerate_manpages(included, args.sections_exclude);
let all_parsed = parse_all_manpages(all_manpages);
Expand Down
63 changes: 46 additions & 17 deletions lib/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,17 @@ pub type Result<T> = std::result::Result<T, Error>;

#[derive(Debug, Error)]
pub enum Error {
#[error("Could not parse manpage")]
#[error("could not parse manpage")]
ParseError(String),

#[error(transparent)]
IoError(#[from] std::io::Error),

#[error("No manpages found. Please set the MANPATH environment variable.")]
#[error("no manpages found. Please set the MANPATH environment variable.")]
NoManPages,

#[error("{msg:?}")]
Other { msg: String },
}

/// Find the manpath (search path for man)
Expand Down Expand Up @@ -53,20 +56,14 @@ pub fn get_manpath() -> Option<HashSet<PathBuf>> {
}
}

pub fn find_manpage<P: AsRef<Path>, I>(cmd: &str, manpath: I) -> Result<P>
where
I: IntoIterator<Item = P>,
{
todo!()
}

/// Enumerate all manpages given a list of directories to search in
///
/// * manpath - Directories that man searches in (`$MANPATH/manpath/man --path`).
/// ## Arguments
/// * `manpath` - Directories that man searches in (`$MANPATH/manpath/man --path`).
/// Inside each of these directories should be `man1`, `man2`, etc. folders.
/// The paths should be canonical.
/// * exclude_sections - Man sections to exclude, if any (1-8)
pub fn enumerate_manpages<I, P, S>(manpath: I, exclude_sections: Option<S>) -> Vec<PathBuf>
/// * `exclude_sections` - Man sections to exclude, if any (1-8)
pub fn enumerate_manpages<I, P, S>(manpath: I, exclude_sections: S) -> Vec<PathBuf>
where
I: IntoIterator<Item = P>,
P: AsRef<Path>,
Expand All @@ -75,11 +72,7 @@ where
let mut res = vec![];

// TODO figure out why fish only seems to use man1, man6, and man8
let exclude: Vec<u8> = if let Some(sections) = exclude_sections {
sections.into_iter().collect()
} else {
Vec::new()
};
let exclude: Vec<u8> = exclude_sections.into_iter().collect();
let section_names: Vec<_> = (1u8..8u8)
.filter(|n| !exclude.contains(&n))
.map(|n| format!("man{n}"))
Expand All @@ -101,6 +94,42 @@ where
res
}

/// Get the command that a manpage is for, given its path
fn manpage_cmd(manpage_path: &Path) -> String {
let file_name = manpage_path
.file_name()
.unwrap()
.to_string_lossy()
.replace(std::char::REPLACEMENT_CHARACTER, "");
// The file name will be something like foo.1.gz, we only want foo
file_name
.split(".")
.nth(0)
.unwrap_or_else(|| &file_name)
.to_string()
}

/// Find the manpage for a specific command
///
/// ## Arguments
/// * `cmd` - The command to find the manpage for
/// * `manpath` - Directories that man searches in (`$MANPATH/manpath/man --path`).
/// Inside each of these directories should be `man1`, `man2`, etc. folders.
/// The paths should be canonical.
pub fn find_manpage<P, I>(cmd: &str, manpath: I) -> Option<PathBuf>
where
P: AsRef<Path>,
I: IntoIterator<Item = P>,
{
for manpage in enumerate_manpages(manpath, vec![]) {
if cmd == manpage_cmd(&manpage) {
return Some(manpage);
}
}

None
}

pub fn parse_all_manpages<I, P>(manpages: I) -> HashMap<String, CommandInfo>
where
I: IntoIterator<Item = P>,
Expand Down

0 comments on commit f55c162

Please sign in to comment.