163 lines
3.2 KiB
Rust
163 lines
3.2 KiB
Rust
|
use crate::common::*;
|
||
|
|
||
|
#[derive(Debug, Ord, PartialOrd, Eq, PartialEq)]
|
||
|
pub(crate) struct Subcommand {
|
||
|
pub(crate) bin: PathBuf,
|
||
|
pub(crate) command: Vec<String>,
|
||
|
pub(crate) subcommands: Vec<String>,
|
||
|
}
|
||
|
|
||
|
impl Subcommand {
|
||
|
#[throws]
|
||
|
pub(crate) fn new(bin: &Path, command: Vec<String>) -> Self {
|
||
|
let wide_help = Command::new(bin)
|
||
|
.args(command.as_slice())
|
||
|
.env("IMDL_TERM_WIDTH", "200")
|
||
|
.arg("--help")
|
||
|
.out()?;
|
||
|
|
||
|
const MARKER: &str = "\nSUBCOMMANDS:\n";
|
||
|
|
||
|
let mut subcommands = Vec::new();
|
||
|
|
||
|
if let Some(marker) = wide_help.find(MARKER) {
|
||
|
let block = &wide_help[marker + MARKER.len()..];
|
||
|
|
||
|
for line in block.lines() {
|
||
|
let name = line.trim().split_whitespace().next().unwrap();
|
||
|
subcommands.push(name.into());
|
||
|
}
|
||
|
}
|
||
|
|
||
|
Self {
|
||
|
bin: bin.into(),
|
||
|
command,
|
||
|
subcommands,
|
||
|
}
|
||
|
}
|
||
|
|
||
|
#[throws]
|
||
|
fn help(&self) -> String {
|
||
|
eprintln!("Getting help for `{}`", self.command_line());
|
||
|
|
||
|
Command::new(&self.bin)
|
||
|
.args(self.command.as_slice())
|
||
|
.env("IMDL_TERM_WIDTH", "80")
|
||
|
.arg("--help")
|
||
|
.out()?
|
||
|
}
|
||
|
|
||
|
#[throws]
|
||
|
pub(crate) fn man(&self) -> String {
|
||
|
let command_line = self.command_line();
|
||
|
|
||
|
eprintln!("Generating man page for `{}`", command_line);
|
||
|
|
||
|
let name = command_line.replace(" ", "\\ ");
|
||
|
|
||
|
let help = self.help()?;
|
||
|
|
||
|
let description = if self.command.is_empty() {
|
||
|
"A 40' shipping container for the Internet".to_string()
|
||
|
} else {
|
||
|
help.lines().nth(1).unwrap().into()
|
||
|
};
|
||
|
|
||
|
let include = format!(
|
||
|
"\
|
||
|
[NAME]
|
||
|
\\fB{}\\fR
|
||
|
- {}
|
||
|
",
|
||
|
name, description
|
||
|
);
|
||
|
|
||
|
let tmp = tempfile::tempdir()?;
|
||
|
|
||
|
let include_path = tmp.path().join("include");
|
||
|
|
||
|
fs::write(&include_path, include)?;
|
||
|
|
||
|
let version = cmd!(&self.bin, "--version")
|
||
|
.out()?
|
||
|
.split_whitespace()
|
||
|
.nth(1)
|
||
|
.unwrap()
|
||
|
.to_owned();
|
||
|
|
||
|
eprintln!("Running help2man for `{}`", command_line);
|
||
|
|
||
|
let mut command = self.bin.as_os_str().to_owned();
|
||
|
for arg in &self.command {
|
||
|
command.push(" ");
|
||
|
command.push(arg);
|
||
|
}
|
||
|
|
||
|
let output = cmd!(
|
||
|
"help2man",
|
||
|
"--include",
|
||
|
&include_path,
|
||
|
"--manual",
|
||
|
"Intermodal Manual",
|
||
|
"--no-info",
|
||
|
"--source",
|
||
|
&format!("Intermodal {}", version),
|
||
|
command
|
||
|
)
|
||
|
.out()?;
|
||
|
|
||
|
let man = output
|
||
|
.replace("📦 ", "\n")
|
||
|
.replace("\n.SS ", "\n.SH ")
|
||
|
.replace("\"USAGE:\"", "\"SYNOPSIS:\"");
|
||
|
|
||
|
let re = Regex::new(r"(?ms).SH DESCRIPTION.*?.SH").unwrap();
|
||
|
|
||
|
let man = re.replace(&man, ".SH").into_owned();
|
||
|
|
||
|
man
|
||
|
}
|
||
|
|
||
|
pub(crate) fn slug(&self) -> String {
|
||
|
let mut slug = self
|
||
|
.bin
|
||
|
.display()
|
||
|
.to_string()
|
||
|
.split('/')
|
||
|
.last()
|
||
|
.unwrap()
|
||
|
.to_owned();
|
||
|
|
||
|
for name in &self.command {
|
||
|
slug.push('-');
|
||
|
slug.push_str(&name);
|
||
|
}
|
||
|
|
||
|
slug
|
||
|
}
|
||
|
|
||
|
pub(crate) fn command_line(&self) -> String {
|
||
|
let mut line = self
|
||
|
.bin
|
||
|
.display()
|
||
|
.to_string()
|
||
|
.split('/')
|
||
|
.last()
|
||
|
.unwrap()
|
||
|
.to_owned();
|
||
|
|
||
|
for name in &self.command {
|
||
|
line.push(' ');
|
||
|
line.push_str(&name);
|
||
|
}
|
||
|
|
||
|
line
|
||
|
}
|
||
|
|
||
|
#[throws]
|
||
|
pub(crate) fn page(&self) -> String {
|
||
|
let help = self.help()?;
|
||
|
format!("# `{}`\n```\n{}\n```", self.command_line(), help)
|
||
|
}
|
||
|
}
|