Repository: fujiapple852/trippy (language: rust)

Issue:
Generation of man pages



Discussion / hints:
[help2man](https://www.gnu.org/software/help2man/) creates a good man page:

```sh
help2man -s 1 -Nn "Network diagnostic tool inspired by mtr" `which trip` -o files/trip.1
```

[Here](https://gist.github.com/ehaupt/4e10f8db8bc1c7d1fe4dbfb3836b6092) is what it generated.

Maybe this would be a start?


Hi @ehaupt,

What I had in mind here was using https://github.com/clap-rs/clap/tree/master/clap_mangen since Trippy is already using `clap` (for command line parsing) and `clap_complete` (for generating shell completions).

I haven't given this much thought, but I was imaging this may work in a similar way to the completions, where there is a command such as:

```
trip --generate-man > /path/to/man/pages/trip.1
```

So the command outputs to stdout and the user can redirect it to a file in wherever their man pages are stored.  

Thoughts?
Clap would also suffice. Regardless, it would be beneficial if the man page were generated during the build process, enabling packagers (I maintain the FreeBSD port) to install it easily.
FYI: I've just added an option to the FreeBSD port for a `html2man` generated man page. I'll be more than happy to adjust the mechanism in the future.

https://github.com/freebsd/freebsd-ports/commit/0c470ad9c557d5e2b544816eabbde428835e49a8
> it would be beneficial if the man page were generated during the build process, enabling packagers (I maintain the FreeBSD port) to install it easily.

That is a valid point @ehaupt.  I think this could be created at build time during a release, i'm just not sure where the generated man page file could be stored, I guess the `Assets` section in the [release note](https://github.com/fujiapple852/trippy/releases/tag/0.10.0]) ?

Given the `help2man` runs _after_ the build then I guess it could (in the future if/when it exists) be modified to be something like:

```
post-build-MANPAGES-on:
	${CARGO_TARGET_DIR}/${CARGO_BUILD_TARGET}/*/trip --generate-man trip.1
```
I was searching for a method to install man pages using the cargo toolchain. Upon reading https://github.com/rust-lang/cargo/issues/2729, I discovered that this process is not as straightforward as it is with cmake or autotools.

In this context, your proposed solution of adding a `--generate-man` flag to enable man page generation at runtime appears to be the optimal approach. This allows me to generate the man pages in the post-build section and install them in the post-install section.

Fix patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 6cb46a767..5c4115112 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -10,6 +10,8 @@ to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
 ### Added
 
 - Added Ubuntu PPA package ([#859](https://github.com/fujiapple852/trippy/issues/859))
+- Added `--generate-man` flags for generating [ROFF](https://en.wikipedia.org/wiki/Roff_(software)) man
+  page ([#85](https://github.com/fujiapple852/trippy/issues/85))
 
 ### Changed
 
diff --git a/Cargo.lock b/Cargo.lock
index 718e89560..2649d1fae 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -306,6 +306,16 @@ version = "0.7.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "98cc8fbded0c607b7ba9dd60cd98df59af97e84d24e49c8557331cfc26d301ce"
 
+[[package]]
+name = "clap_mangen"
+version = "0.2.19"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8e35a078f3aae828c9b7ad58e1631dab87e9dac40da19418f2219bbf3198aa5c"
+dependencies = [
+ "clap",
+ "roff",
+]
+
 [[package]]
 name = "colorchoice"
 version = "1.0.0"
@@ -1397,6 +1407,12 @@ dependencies = [
  "quick-error",
 ]
 
+[[package]]
+name = "roff"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b833d8d034ea094b1ea68aa6d5c740e0d04bad9d16568d08ba6f76823a114316"
+
 [[package]]
 name = "rustc-demangle"
 version = "0.1.23"
@@ -1946,6 +1962,7 @@ dependencies = [
  "chrono",
  "clap",
  "clap_complete",
+ "clap_mangen",
  "comfy-table",
  "crossbeam",
  "crossterm",
diff --git a/Cargo.toml b/Cargo.toml
index b5e27bfff..88e4103f3 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -56,6 +56,7 @@ csv = "1.3.0"
 serde_with = "3.7.0"
 encoding_rs_io = "0.1.7"
 bitflags = "2.5.0"
+clap_mangen = "0.2.19"
 
 # Library dependencies (Linux)
 [target.'cfg(target_os = "linux")'.dependencies]
diff --git a/README.md b/README.md
index 8a890c213..d4b3feaea 100644
--- a/README.md
+++ b/README.md
@@ -455,6 +455,12 @@ Generate `bash` shell completions (or `fish`, `powershell`, `zsh`, `elvish`):
 trip --generate bash
 ```
 
+Generate `ROFF` man page:
+
+```shell
+trip --generate-man
+```
+
 Run in `silent` tracing mode and output `compact` trace logging with `full` span events:
 
 ```shell
@@ -692,6 +698,9 @@ Options:
 
           [possible values: bash, elvish, fish, powershell, zsh]
 
+      --generate-man
+          Generate ROFF man page
+
       --print-config-template
           Print a template toml config file and exit
 
diff --git a/src/config.rs b/src/config.rs
index 3ae9457a8..a04327668 100644
--- a/src/config.rs
+++ b/src/config.rs
@@ -254,6 +254,8 @@ pub enum TrippyAction {
     PrintConfigTemplate,
     /// Generate shell completion and exit.
     PrintShellCompletions(Shell),
+    /// Generate a man page and exit.
+    PrintManPage,
 }
 
 impl TrippyAction {
@@ -266,6 +268,8 @@ impl TrippyAction {
             Self::PrintConfigTemplate
         } else if let Some(shell) = args.generate {
             Self::PrintShellCompletions(shell)
+        } else if args.generate_man {
+            Self::PrintManPage
         } else {
             Self::Trippy(TrippyConfig::from(args, platform)?)
         })
@@ -1609,6 +1613,7 @@ mod tests {
     #[test_case("trip --generate zsh", Ok(TrippyAction::PrintShellCompletions(Shell::Zsh)); "generate zsh shell completions")]
     #[test_case("trip --generate bash", Ok(TrippyAction::PrintShellCompletions(Shell::Bash)); "generate bash shell completions")]
     #[test_case("trip --generate foo", Err(anyhow!("error: one of the values isn't valid for an argument")); "generate invalid shell completions")]
+    #[test_case("trip --generate-man", Ok(TrippyAction::PrintManPage); "generate man page")]
     fn test_action(cmd: &str, expected: anyhow::Result<TrippyAction>) {
         compare(parse_action(cmd), expected);
     }
diff --git a/src/config/cmd.rs b/src/config/cmd.rs
index c11decde4..d644a31cc 100644
--- a/src/config/cmd.rs
+++ b/src/config/cmd.rs
@@ -15,7 +15,7 @@ use clap_complete::Shell;
 #[command(name = "trip", author, version, about, long_about = None, arg_required_else_help(true), styles=Styles::styled())]
 pub struct Args {
     /// A space delimited list of hostnames and IPs to trace
-    #[arg(required_unless_present_any(["print_tui_theme_items", "print_tui_binding_commands", "print_config_template", "generate"]))]
+    #[arg(required_unless_present_any(["print_tui_theme_items", "print_tui_binding_commands", "print_config_template", "generate", "generate_man"]))]
     pub targets: Vec<String>,
 
     /// Config file
@@ -240,6 +240,10 @@ pub struct Args {
     #[arg(long)]
     pub generate: Option<Shell>,
 
+    /// Generate ROFF man page
+    #[arg(long)]
+    pub generate_man: bool,
+
     /// Print a template toml config file and exit
     #[arg(long)]
     pub print_config_template: bool,
diff --git a/src/main.rs b/src/main.rs
index 16ca2bf65..560e71098 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -60,6 +60,7 @@ fn main() -> anyhow::Result<()> {
         TrippyAction::PrintTuiThemeItems => print_tui_theme_items(),
         TrippyAction::PrintTuiBindingCommands => print_tui_binding_commands(),
         TrippyAction::PrintConfigTemplate => print_config_template(),
+        TrippyAction::PrintManPage => print_man_page()?,
         TrippyAction::PrintShellCompletions(shell) => print_shell_completions(shell)?,
     }
     Ok(())
@@ -102,6 +103,11 @@ fn print_shell_completions(shell: Shell) -> anyhow::Result<()> {
     process::exit(0);
 }
 
+fn print_man_page() -> anyhow::Result<()> {
+    println!("{}", man_page()?);
+    process::exit(0);
+}
+
 /// Start the DNS resolver.
 fn start_dns_resolver(cfg: &TrippyConfig) -> anyhow::Result<DnsResolver> {
     Ok(DnsResolver::start(trippy::dns::Config::new(
@@ -467,6 +473,13 @@ fn shell_completions(shell: Shell) -> anyhow::Result<String> {
     Ok(String::from_utf8(buffer)?)
 }
 
+fn man_page() -> anyhow::Result<String> {
+    let cmd = Args::command();
+    let mut buffer: Vec<u8> = vec![];
+    clap_mangen::Man::new(cmd).render(&mut buffer)?;
+    Ok(String::from_utf8(buffer)?)
+}
+
 #[cfg(test)]
 mod tests {
     use super::*;
@@ -479,6 +492,7 @@ mod tests {
     #[test_case(include_str!("../test_resources/config/completions_fish.txt"), &shell_completions(Shell::Fish).unwrap(); "generate fish shell completions")]
     #[test_case(include_str!("../test_resources/config/completions_powershell.txt"), &shell_completions(Shell::PowerShell).unwrap(); "generate powershell shell completions")]
     #[test_case(include_str!("../test_resources/config/completions_zsh.txt"), &shell_completions(Shell::Zsh).unwrap(); "generate zsh shell completions")]
+    #[test_case(include_str!("../test_resources/config/trip.1"), &man_page().unwrap(); "generate man page")]
     fn test_output(expected: &str, actual: &str) {
         if remove_whitespace(actual.to_string()) != remove_whitespace(expected.to_string()) {
             pretty_assertions::assert_eq!(actual, expected);


Test patch:
diff --git a/test_resources/config/completions_bash.txt b/test_resources/config/completions_bash.txt
index bcfe60f1d..8d433d9a3 100644
--- a/test_resources/config/completions_bash.txt
+++ b/test_resources/config/completions_bash.txt
@@ -19,7 +19,7 @@ _trip() {
 
     case "${cmd}" in
         trip)
-            opts="-c -m -u -p -F -4 -6 -P -S -A -I -i -T -g -R -U -f -t -Q -e -r -y -z -a -M -s -C -G -v -h -V --config-file --mode --unprivileged --protocol --udp --tcp --icmp --addr-family --ipv4 --ipv6 --target-port --source-port --source-address --interface --min-round-duration --max-round-duration --grace-duration --initial-sequence --multipath-strategy --max-inflight --first-ttl --max-ttl --packet-size --payload-pattern --tos --icmp-extensions --read-timeout --dns-resolve-method --dns-resolve-all --dns-timeout --dns-lookup-as-info --tui-address-mode --tui-as-mode --tui-custom-columns --tui-icmp-extension-mode --tui-geoip-mode --tui-max-addrs --tui-max-samples --tui-max-flows --tui-preserve-screen --tui-refresh-rate --tui-privacy-max-ttl --tui-theme-colors --print-tui-theme-items --tui-key-bindings --print-tui-binding-commands --report-cycles --geoip-mmdb-file --generate --print-config-template --log-format --log-filter --log-span-events --verbose --help --version [TARGETS]..."
+            opts="-c -m -u -p -F -4 -6 -P -S -A -I -i -T -g -R -U -f -t -Q -e -r -y -z -a -M -s -C -G -v -h -V --config-file --mode --unprivileged --protocol --udp --tcp --icmp --addr-family --ipv4 --ipv6 --target-port --source-port --source-address --interface --min-round-duration --max-round-duration --grace-duration --initial-sequence --multipath-strategy --max-inflight --first-ttl --max-ttl --packet-size --payload-pattern --tos --icmp-extensions --read-timeout --dns-resolve-method --dns-resolve-all --dns-timeout --dns-lookup-as-info --tui-address-mode --tui-as-mode --tui-custom-columns --tui-icmp-extension-mode --tui-geoip-mode --tui-max-addrs --tui-max-samples --tui-max-flows --tui-preserve-screen --tui-refresh-rate --tui-privacy-max-ttl --tui-theme-colors --print-tui-theme-items --tui-key-bindings --print-tui-binding-commands --report-cycles --geoip-mmdb-file --generate --generate-man --print-config-template --log-format --log-filter --log-span-events --verbose --help --version [TARGETS]..."
             if [[ ${cur} == -* || ${COMP_CWORD} -eq 1 ]] ; then
                 COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") )
                 return 0
diff --git a/test_resources/config/completions_elvish.txt b/test_resources/config/completions_elvish.txt
index 7fa744360..cbf335861 100644
--- a/test_resources/config/completions_elvish.txt
+++ b/test_resources/config/completions_elvish.txt
@@ -98,6 +98,7 @@ set edit:completion:arg-completer[trip] = {|@words|
             cand --tui-preserve-screen 'Preserve the screen on exit [default: false]'
             cand --print-tui-theme-items 'Print all TUI theme items and exit'
             cand --print-tui-binding-commands 'Print all TUI commands that can be bound and exit'
+            cand --generate-man 'Generate ROFF man page'
             cand --print-config-template 'Print a template toml config file and exit'
             cand -v 'Enable verbose debug logging'
             cand --verbose 'Enable verbose debug logging'
diff --git a/test_resources/config/completions_fish.txt b/test_resources/config/completions_fish.txt
index b291578c3..99b1e8a31 100644
--- a/test_resources/config/completions_fish.txt
+++ b/test_resources/config/completions_fish.txt
@@ -50,6 +50,7 @@ complete -c trip -s z -l dns-lookup-as-info -d 'Lookup autonomous system (AS) in
 complete -c trip -l tui-preserve-screen -d 'Preserve the screen on exit [default: false]'
 complete -c trip -l print-tui-theme-items -d 'Print all TUI theme items and exit'
 complete -c trip -l print-tui-binding-commands -d 'Print all TUI commands that can be bound and exit'
+complete -c trip -l generate-man -d 'Generate ROFF man page'
 complete -c trip -l print-config-template -d 'Print a template toml config file and exit'
 complete -c trip -s v -l verbose -d 'Enable verbose debug logging'
 complete -c trip -s h -l help -d 'Print help (see more with \'--help\')'
diff --git a/test_resources/config/completions_powershell.txt b/test_resources/config/completions_powershell.txt
index 9916b068a..fef90d9ba 100644
--- a/test_resources/config/completions_powershell.txt
+++ b/test_resources/config/completions_powershell.txt
@@ -101,6 +101,7 @@ Register-ArgumentCompleter -Native -CommandName 'trip' -ScriptBlock {
             [CompletionResult]::new('--tui-preserve-screen', 'tui-preserve-screen', [CompletionResultType]::ParameterName, 'Preserve the screen on exit [default: false]')
             [CompletionResult]::new('--print-tui-theme-items', 'print-tui-theme-items', [CompletionResultType]::ParameterName, 'Print all TUI theme items and exit')
             [CompletionResult]::new('--print-tui-binding-commands', 'print-tui-binding-commands', [CompletionResultType]::ParameterName, 'Print all TUI commands that can be bound and exit')
+            [CompletionResult]::new('--generate-man', 'generate-man', [CompletionResultType]::ParameterName, 'Generate ROFF man page')
             [CompletionResult]::new('--print-config-template', 'print-config-template', [CompletionResultType]::ParameterName, 'Print a template toml config file and exit')
             [CompletionResult]::new('-v', 'v', [CompletionResultType]::ParameterName, 'Enable verbose debug logging')
             [CompletionResult]::new('--verbose', 'verbose', [CompletionResultType]::ParameterName, 'Enable verbose debug logging')
diff --git a/test_resources/config/completions_zsh.txt b/test_resources/config/completions_zsh.txt
index b0bb2648a..56fe8b132 100644
--- a/test_resources/config/completions_zsh.txt
+++ b/test_resources/config/completions_zsh.txt
@@ -151,6 +151,7 @@ full\:"Display all event spans"))' \
 '--tui-preserve-screen[Preserve the screen on exit \[default\: false\]]' \
 '--print-tui-theme-items[Print all TUI theme items and exit]' \
 '--print-tui-binding-commands[Print all TUI commands that can be bound and exit]' \
+'--generate-man[Generate ROFF man page]' \
 '--print-config-template[Print a template toml config file and exit]' \
 '-v[Enable verbose debug logging]' \
 '--verbose[Enable verbose debug logging]' \
diff --git a/test_resources/config/trip.1 b/test_resources/config/trip.1
new file mode 100644
index 000000000..5d9c0a70a
--- /dev/null
+++ b/test_resources/config/trip.1
@@ -0,0 +1,353 @@
+.ie \n(.g .ds Aq \(aq
+.el .ds Aq '
+.TH trip 1  "trip 0.11.0-dev" 
+.SH NAME
+trip \- A network diagnostic tool
+.SH SYNOPSIS
+\fBtrip\fR [\fB\-c\fR|\fB\-\-config\-file\fR] [\fB\-m\fR|\fB\-\-mode\fR] [\fB\-u\fR|\fB\-\-unprivileged\fR] [\fB\-p\fR|\fB\-\-protocol\fR] [\fB\-\-udp\fR] [\fB\-\-tcp\fR] [\fB\-\-icmp\fR] [\fB\-F\fR|\fB\-\-addr\-family\fR] [\fB\-4\fR|\fB\-\-ipv4\fR] [\fB\-6\fR|\fB\-\-ipv6\fR] [\fB\-P\fR|\fB\-\-target\-port\fR] [\fB\-S\fR|\fB\-\-source\-port\fR] [\fB\-A\fR|\fB\-\-source\-address\fR] [\fB\-I\fR|\fB\-\-interface\fR] [\fB\-i\fR|\fB\-\-min\-round\-duration\fR] [\fB\-T\fR|\fB\-\-max\-round\-duration\fR] [\fB\-g\fR|\fB\-\-grace\-duration\fR] [\fB\-\-initial\-sequence\fR] [\fB\-R\fR|\fB\-\-multipath\-strategy\fR] [\fB\-U\fR|\fB\-\-max\-inflight\fR] [\fB\-f\fR|\fB\-\-first\-ttl\fR] [\fB\-t\fR|\fB\-\-max\-ttl\fR] [\fB\-\-packet\-size\fR] [\fB\-\-payload\-pattern\fR] [\fB\-Q\fR|\fB\-\-tos\fR] [\fB\-e\fR|\fB\-\-icmp\-extensions\fR] [\fB\-\-read\-timeout\fR] [\fB\-r\fR|\fB\-\-dns\-resolve\-method\fR] [\fB\-y\fR|\fB\-\-dns\-resolve\-all\fR] [\fB\-\-dns\-timeout\fR] [\fB\-z\fR|\fB\-\-dns\-lookup\-as\-info\fR] [\fB\-a\fR|\fB\-\-tui\-address\-mode\fR] [\fB\-\-tui\-as\-mode\fR] [\fB\-\-tui\-custom\-columns\fR] [\fB\-\-tui\-icmp\-extension\-mode\fR] [\fB\-\-tui\-geoip\-mode\fR] [\fB\-M\fR|\fB\-\-tui\-max\-addrs\fR] [\fB\-s\fR|\fB\-\-tui\-max\-samples\fR] [\fB\-\-tui\-max\-flows\fR] [\fB\-\-tui\-preserve\-screen\fR] [\fB\-\-tui\-refresh\-rate\fR] [\fB\-\-tui\-privacy\-max\-ttl\fR] [\fB\-\-tui\-theme\-colors\fR] [\fB\-\-print\-tui\-theme\-items\fR] [\fB\-\-tui\-key\-bindings\fR] [\fB\-\-print\-tui\-binding\-commands\fR] [\fB\-C\fR|\fB\-\-report\-cycles\fR] [\fB\-G\fR|\fB\-\-geoip\-mmdb\-file\fR] [\fB\-\-generate\fR] [\fB\-\-generate\-man\fR] [\fB\-\-print\-config\-template\fR] [\fB\-\-log\-format\fR] [\fB\-\-log\-filter\fR] [\fB\-\-log\-span\-events\fR] [\fB\-v\fR|\fB\-\-verbose\fR] [\fB\-h\fR|\fB\-\-help\fR] [\fB\-V\fR|\fB\-\-version\fR] [\fITARGETS\fR] 
+.SH DESCRIPTION
+A network diagnostic tool
+.SH OPTIONS
+.TP
+\fB\-c\fR, \fB\-\-config\-file\fR=\fICONFIG_FILE\fR
+Config file
+.TP
+\fB\-m\fR, \fB\-\-mode\fR=\fIMODE\fR
+Output mode [default: tui]
+.br
+
+.br
+\fIPossible values:\fR
+.RS 14
+.IP \(bu 2
+tui: Display interactive TUI
+.IP \(bu 2
+stream: Display a continuous stream of tracing data
+.IP \(bu 2
+pretty: Generate a pretty text table report for N cycles
+.IP \(bu 2
+markdown: Generate a Markdown text table report for N cycles
+.IP \(bu 2
+csv: Generate a CSV report for N cycles
+.IP \(bu 2
+json: Generate a JSON report for N cycles
+.IP \(bu 2
+dot: Generate a Graphviz DOT file for N cycles
+.IP \(bu 2
+flows: Display all flows for N cycles
+.IP \(bu 2
+silent: Do not generate any tracing output for N cycles
+.RE
+.TP
+\fB\-u\fR, \fB\-\-unprivileged\fR
+Trace without requiring elevated privileges on supported platforms [default: false]
+.TP
+\fB\-p\fR, \fB\-\-protocol\fR=\fIPROTOCOL\fR
+Tracing protocol [default: icmp]
+.br
+
+.br
+\fIPossible values:\fR
+.RS 14
+.IP \(bu 2
+icmp: Internet Control Message Protocol
+.IP \(bu 2
+udp: User Datagram Protocol
+.IP \(bu 2
+tcp: Transmission Control Protocol
+.RE
+.TP
+\fB\-\-udp\fR
+Trace using the UDP protocol
+.TP
+\fB\-\-tcp\fR
+Trace using the TCP protocol
+.TP
+\fB\-\-icmp\fR
+Trace using the ICMP protocol
+.TP
+\fB\-F\fR, \fB\-\-addr\-family\fR=\fIADDR_FAMILY\fR
+The address family [default: Ipv4thenIpv6]
+.br
+
+.br
+\fIPossible values:\fR
+.RS 14
+.IP \(bu 2
+ipv4: Ipv4 only
+.IP \(bu 2
+ipv6: Ipv6 only
+.IP \(bu 2
+ipv6\-then\-ipv4: Ipv6 with a fallback to Ipv4
+.IP \(bu 2
+ipv4\-then\-ipv6: Ipv4 with a fallback to Ipv6
+.RE
+.TP
+\fB\-4\fR, \fB\-\-ipv4\fR
+Use IPv4 only
+.TP
+\fB\-6\fR, \fB\-\-ipv6\fR
+Use IPv6 only
+.TP
+\fB\-P\fR, \fB\-\-target\-port\fR=\fITARGET_PORT\fR
+The target port (TCP & UDP only) [default: 80]
+.TP
+\fB\-S\fR, \fB\-\-source\-port\fR=\fISOURCE_PORT\fR
+The source port (TCP & UDP only) [default: auto]
+.TP
+\fB\-A\fR, \fB\-\-source\-address\fR=\fISOURCE_ADDRESS\fR
+The source IP address [default: auto]
+.TP
+\fB\-I\fR, \fB\-\-interface\fR=\fIINTERFACE\fR
+The network interface [default: auto]
+.TP
+\fB\-i\fR, \fB\-\-min\-round\-duration\fR=\fIMIN_ROUND_DURATION\fR
+The minimum duration of every round [default: 1s]
+.TP
+\fB\-T\fR, \fB\-\-max\-round\-duration\fR=\fIMAX_ROUND_DURATION\fR
+The maximum duration of every round [default: 1s]
+.TP
+\fB\-g\fR, \fB\-\-grace\-duration\fR=\fIGRACE_DURATION\fR
+The period of time to wait for additional ICMP responses after the target has responded [default: 100ms]
+.TP
+\fB\-\-initial\-sequence\fR=\fIINITIAL_SEQUENCE\fR
+The initial sequence number [default: 33000]
+.TP
+\fB\-R\fR, \fB\-\-multipath\-strategy\fR=\fIMULTIPATH_STRATEGY\fR
+The Equal\-cost Multi\-Path routing strategy (UDP only) [default: classic]
+.br
+
+.br
+\fIPossible values:\fR
+.RS 14
+.IP \(bu 2
+classic: The src or dest port is used to store the sequence number
+.IP \(bu 2
+paris: The UDP `checksum` field is used to store the sequence number
+.IP \(bu 2
+dublin: The IP `identifier` field is used to store the sequence number
+.RE
+.TP
+\fB\-U\fR, \fB\-\-max\-inflight\fR=\fIMAX_INFLIGHT\fR
+The maximum number of in\-flight ICMP echo requests [default: 24]
+.TP
+\fB\-f\fR, \fB\-\-first\-ttl\fR=\fIFIRST_TTL\fR
+The TTL to start from [default: 1]
+.TP
+\fB\-t\fR, \fB\-\-max\-ttl\fR=\fIMAX_TTL\fR
+The maximum number of TTL hops [default: 64]
+.TP
+\fB\-\-packet\-size\fR=\fIPACKET_SIZE\fR
+The size of IP packet to send (IP header + ICMP header + payload) [default: 84]
+.TP
+\fB\-\-payload\-pattern\fR=\fIPAYLOAD_PATTERN\fR
+The repeating pattern in the payload of the ICMP packet [default: 0]
+.TP
+\fB\-Q\fR, \fB\-\-tos\fR=\fITOS\fR
+The TOS (i.e. DSCP+ECN) IP header value (TCP and UDP only) [default: 0]
+.TP
+\fB\-e\fR, \fB\-\-icmp\-extensions\fR
+Parse ICMP extensions
+.TP
+\fB\-\-read\-timeout\fR=\fIREAD_TIMEOUT\fR
+The socket read timeout [default: 10ms]
+.TP
+\fB\-r\fR, \fB\-\-dns\-resolve\-method\fR=\fIDNS_RESOLVE_METHOD\fR
+How to perform DNS queries [default: system]
+.br
+
+.br
+\fIPossible values:\fR
+.RS 14
+.IP \(bu 2
+system: Resolve using the OS resolver
+.IP \(bu 2
+resolv: Resolve using the `/etc/resolv.conf` DNS configuration
+.IP \(bu 2
+google: Resolve using the Google `8.8.8.8` DNS service
+.IP \(bu 2
+cloudflare: Resolve using the Cloudflare `1.1.1.1` DNS service
+.RE
+.TP
+\fB\-y\fR, \fB\-\-dns\-resolve\-all\fR
+Trace to all IPs resolved from DNS lookup [default: false]
+.TP
+\fB\-\-dns\-timeout\fR=\fIDNS_TIMEOUT\fR
+The maximum time to wait to perform DNS queries [default: 5s]
+.TP
+\fB\-z\fR, \fB\-\-dns\-lookup\-as\-info\fR
+Lookup autonomous system (AS) information during DNS queries [default: false]
+.TP
+\fB\-a\fR, \fB\-\-tui\-address\-mode\fR=\fITUI_ADDRESS_MODE\fR
+How to render addresses [default: host]
+.br
+
+.br
+\fIPossible values:\fR
+.RS 14
+.IP \(bu 2
+ip: Show IP address only
+.IP \(bu 2
+host: Show reverse\-lookup DNS hostname only
+.IP \(bu 2
+both: Show both IP address and reverse\-lookup DNS hostname
+.RE
+.TP
+\fB\-\-tui\-as\-mode\fR=\fITUI_AS_MODE\fR
+How to render AS information [default: asn]
+.br
+
+.br
+\fIPossible values:\fR
+.RS 14
+.IP \(bu 2
+asn: Show the ASN
+.IP \(bu 2
+prefix: Display the AS prefix
+.IP \(bu 2
+country\-code: Display the country code
+.IP \(bu 2
+registry: Display the registry name
+.IP \(bu 2
+allocated: Display the allocated date
+.IP \(bu 2
+name: Display the AS name
+.RE
+.TP
+\fB\-\-tui\-custom\-columns\fR=\fITUI_CUSTOM_COLUMNS\fR
+Custom columns to be displayed in the TUI hops table [default: holsravbwdt]
+.TP
+\fB\-\-tui\-icmp\-extension\-mode\fR=\fITUI_ICMP_EXTENSION_MODE\fR
+How to render ICMP extensions [default: off]
+.br
+
+.br
+\fIPossible values:\fR
+.RS 14
+.IP \(bu 2
+off: Do not show `icmp` extensions
+.IP \(bu 2
+mpls: Show MPLS label(s) only
+.IP \(bu 2
+full: Show full `icmp` extension data for all known extensions
+.IP \(bu 2
+all: Show full `icmp` extension data for all classes
+.RE
+.TP
+\fB\-\-tui\-geoip\-mode\fR=\fITUI_GEOIP_MODE\fR
+How to render GeoIp information [default: short]
+.br
+
+.br
+\fIPossible values:\fR
+.RS 14
+.IP \(bu 2
+off: Do not display GeoIp data
+.IP \(bu 2
+short: Show short format
+.IP \(bu 2
+long: Show long format
+.IP \(bu 2
+location: Show latitude and Longitude format
+.RE
+.TP
+\fB\-M\fR, \fB\-\-tui\-max\-addrs\fR=\fITUI_MAX_ADDRS\fR
+The maximum number of addresses to show per hop [default: auto]
+.TP
+\fB\-s\fR, \fB\-\-tui\-max\-samples\fR=\fITUI_MAX_SAMPLES\fR
+The maximum number of samples to record per hop [default: 256]
+.TP
+\fB\-\-tui\-max\-flows\fR=\fITUI_MAX_FLOWS\fR
+The maximum number of flows to show [default: 64]
+.TP
+\fB\-\-tui\-preserve\-screen\fR
+Preserve the screen on exit [default: false]
+.TP
+\fB\-\-tui\-refresh\-rate\fR=\fITUI_REFRESH_RATE\fR
+The Tui refresh rate [default: 100ms]
+.TP
+\fB\-\-tui\-privacy\-max\-ttl\fR=\fITUI_PRIVACY_MAX_TTL\fR
+The maximum ttl of hops which will be masked for privacy [default: 0]
+.TP
+\fB\-\-tui\-theme\-colors\fR=\fITUI_THEME_COLORS\fR
+The TUI theme colors [item=color,item=color,..]
+.TP
+\fB\-\-print\-tui\-theme\-items\fR
+Print all TUI theme items and exit
+.TP
+\fB\-\-tui\-key\-bindings\fR=\fITUI_KEY_BINDINGS\fR
+The TUI key bindings [command=key,command=key,..]
+.TP
+\fB\-\-print\-tui\-binding\-commands\fR
+Print all TUI commands that can be bound and exit
+.TP
+\fB\-C\fR, \fB\-\-report\-cycles\fR=\fIREPORT_CYCLES\fR
+The number of report cycles to run [default: 10]
+.TP
+\fB\-G\fR, \fB\-\-geoip\-mmdb\-file\fR=\fIGEOIP_MMDB_FILE\fR
+The supported MaxMind or IPinfo GeoIp mmdb file
+.TP
+\fB\-\-generate\fR=\fIGENERATE\fR
+Generate shell completion
+.br
+
+.br
+[\fIpossible values: \fRbash, elvish, fish, powershell, zsh]
+.TP
+\fB\-\-generate\-man\fR
+Generate ROFF man page
+.TP
+\fB\-\-print\-config\-template\fR
+Print a template toml config file and exit
+.TP
+\fB\-\-log\-format\fR=\fILOG_FORMAT\fR
+The debug log format [default: pretty]
+.br
+
+.br
+\fIPossible values:\fR
+.RS 14
+.IP \(bu 2
+compact: Display log data in a compact format
+.IP \(bu 2
+pretty: Display log data in a pretty format
+.IP \(bu 2
+json: Display log data in a json format
+.IP \(bu 2
+chrome: Display log data in Chrome trace format
+.RE
+.TP
+\fB\-\-log\-filter\fR=\fILOG_FILTER\fR
+The debug log filter [default: trippy=debug]
+.TP
+\fB\-\-log\-span\-events\fR=\fILOG_SPAN_EVENTS\fR
+The debug log format [default: off]
+.br
+
+.br
+\fIPossible values:\fR
+.RS 14
+.IP \(bu 2
+off: Do not display event spans
+.IP \(bu 2
+active: Display enter and exit event spans
+.IP \(bu 2
+full: Display all event spans
+.RE
+.TP
+\fB\-v\fR, \fB\-\-verbose\fR
+Enable verbose debug logging
+.TP
+\fB\-h\fR, \fB\-\-help\fR
+Print help (see a summary with \*(Aq\-h\*(Aq)
+.TP
+\fB\-V\fR, \fB\-\-version\fR
+Print version
+.TP
+[\fITARGETS\fR]
+A space delimited list of hostnames and IPs to trace
+.SH VERSION
+v0.11.0\-dev
+.SH AUTHORS
+FujiApple <fujiapple852@gmail.com>
+
diff --git a/test_resources/config/usage_long.txt b/test_resources/config/usage_long.txt
index ebfa241af..00b18d2fb 100644
--- a/test_resources/config/usage_long.txt
+++ b/test_resources/config/usage_long.txt
@@ -214,6 +214,9 @@ Options:
           
           [possible values: bash, elvish, fish, powershell, zsh]
 
+      --generate-man
+          Generate ROFF man page
+
       --print-config-template
           Print a template toml config file and exit
 
diff --git a/test_resources/config/usage_short.txt b/test_resources/config/usage_short.txt
index 67771c55c..eb7f3fe85 100644
--- a/test_resources/config/usage_short.txt
+++ b/test_resources/config/usage_short.txt
@@ -106,6 +106,8 @@ Options:
           The supported MaxMind or IPinfo GeoIp mmdb file
       --generate <GENERATE>
           Generate shell completion [possible values: bash, elvish, fish, powershell, zsh]
+      --generate-man
+          Generate ROFF man page
       --print-config-template
           Print a template toml config file and exit
       --log-format <LOG_FORMAT>