Repository: casey/just
Pull request #2041 (2024-05-15 07:23:02)

Add --man subcommand


Patch:
diff --git a/Cargo.lock b/Cargo.lock
index 4b90853a..a34c6a5d 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -215,6 +215,16 @@ version = "0.7.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "98cc8fbded0c607b7ba9dd60cd98df59af97e84d24e49c8557331cfc26d301ce"
 
+[[package]]
+name = "clap_mangen"
+version = "0.2.20"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e1dd95b5ebb5c1c54581dd6346f3ed6a79a3eef95dd372fc2ac13d535535300e"
+dependencies = [
+ "clap 4.5.4",
+ "roff",
+]
+
 [[package]]
 name = "colorchoice"
 version = "1.0.1"
@@ -512,6 +522,7 @@ dependencies = [
  "camino",
  "clap 4.5.4",
  "clap_complete",
+ "clap_mangen",
  "cradle",
  "ctrlc",
  "derivative",
@@ -774,6 +785,12 @@ version = "0.8.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "adad44e29e4c806119491a7f06f03de4d1af22c3a680dd47f1e6e179439d1f56"
 
+[[package]]
+name = "roff"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b833d8d034ea094b1ea68aa6d5c740e0d04bad9d16568d08ba6f76823a114316"
+
 [[package]]
 name = "rustix"
 version = "0.38.34"
diff --git a/Cargo.toml b/Cargo.toml
index cbbb1e1e..5ac4ca8a 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -15,7 +15,7 @@ repository = "https://github.com/casey/just"
 rust-version = "1.63"
 
 [workspace]
-members = [".", "bin/ref-type", "bin/generate-book", "bin/update-contributors"]
+members = [".", "crates/*"]
 
 [dependencies]
 ansi_term = "0.12.0"
@@ -24,6 +24,7 @@ blake3 = { version = "1.5.0", features = ["rayon", "mmap"] }
 camino = "1.0.4"
 clap = { version = "4.0.0", features = ["env", "wrap_help"] }
 clap_complete = "4.0.0"
+clap_mangen = "0.2.20"
 ctrlc = { version = "3.1.1", features = ["termination"] }
 derivative = "2.0.0"
 dirs = "5.0.1"
@@ -64,13 +65,6 @@ path = "src/main.rs"
 name = "just"
 test = false
 
-[features]
-# No features are active by default.
-default = []
-# The `help4help2man` feature modifies the message produced by `--help`
-# so that `help2man` produces a reasonable man page.
-help4help2man = []
-
 # The public documentation is minimal and doesn't change between
 # platforms, so we only build them for linux on docs.rs to save
 # their build machines some cycles.
diff --git a/README.md b/README.md
index a6d8c7d4..8937fbef 100644
--- a/README.md
+++ b/README.md
@@ -3158,6 +3158,14 @@ fpath=($HOMEBREW_PREFIX/share/zsh/site-functions $fpath)
 # compinit
 ```
 
+### Man Page
+
+`just` can print its own man page with `just --man`. Man pages are written in
+[`roff`](https://en.wikipedia.org/wiki/Roff_%28software%29), a venerable markup
+language and one of the first practical applications of Unix. If you have
+[`groff`](https://www.gnu.org/software/groff/) installed you can view the man
+page with  `just --man | groff -mandoc -Tascii | less`.
+
 ### Grammar
 
 A non-normative grammar of `justfile`s can be found in
diff --git a/completions/just.bash b/completions/just.bash
index 0cd0880c..7006dba8 100644
--- a/completions/just.bash
+++ b/completions/just.bash
@@ -30,7 +30,7 @@ _just() {
 
     case "${cmd}" in
         "$1")
-            opts="-n -f -q -u -v -d -c -e -l -s -E -h -V --check --chooser --color --command-color --yes --dry-run --dump-format --highlight --list-heading --list-prefix --no-aliases --no-deps --no-dotenv --no-highlight --justfile --quiet --set --shell --shell-arg --shell-command --clear-shell-args --unsorted --unstable --verbose --working-directory --changelog --choose --command --completions --dump --edit --evaluate --fmt --init --list --show --summary --variables --dotenv-filename --dotenv-path --help --version [ARGUMENTS]..."
+            opts="-n -f -q -u -v -d -c -e -l -s -E -h -V --check --chooser --color --command-color --yes --dry-run --dump-format --highlight --list-heading --list-prefix --no-aliases --no-deps --no-dotenv --no-highlight --justfile --quiet --set --shell --shell-arg --shell-command --clear-shell-args --unsorted --unstable --verbose --working-directory --changelog --choose --command --completions --dump --edit --evaluate --fmt --init --list --man --show --summary --variables --dotenv-filename --dotenv-path --help --version [ARGUMENTS]..."
                 if [[ ${cur} == -* ]] ; then
                     COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") )
                     return 0
diff --git a/completions/just.elvish b/completions/just.elvish
index 86882d50..461b75ae 100644
--- a/completions/just.elvish
+++ b/completions/just.elvish
@@ -66,6 +66,7 @@ set edit:completion:arg-completer[just] = {|@words|
             cand --init 'Initialize new justfile in project root'
             cand -l 'List available recipes and their arguments'
             cand --list 'List available recipes and their arguments'
+            cand --man 'Print man page'
             cand --summary 'List names of available recipes'
             cand --variables 'List names of variables'
             cand -h 'Print help'
diff --git a/completions/just.fish b/completions/just.fish
index 23da1045..073820af 100644
--- a/completions/just.fish
+++ b/completions/just.fish
@@ -73,6 +73,7 @@ complete -c just -l evaluate -d 'Evaluate and print all variables. If a variable
 complete -c just -l fmt -d 'Format and overwrite justfile'
 complete -c just -l init -d 'Initialize new justfile in project root'
 complete -c just -s l -l list -d 'List available recipes and their arguments'
+complete -c just -l man -d 'Print man page'
 complete -c just -l summary -d 'List names of available recipes'
 complete -c just -l variables -d 'List names of variables'
 complete -c just -s h -l help -d 'Print help'
diff --git a/completions/just.powershell b/completions/just.powershell
index f46cea1a..2b02718f 100644
--- a/completions/just.powershell
+++ b/completions/just.powershell
@@ -69,6 +69,7 @@ Register-ArgumentCompleter -Native -CommandName 'just' -ScriptBlock {
             [CompletionResult]::new('--init', 'init', [CompletionResultType]::ParameterName, 'Initialize new justfile in project root')
             [CompletionResult]::new('-l', 'l', [CompletionResultType]::ParameterName, 'List available recipes and their arguments')
             [CompletionResult]::new('--list', 'list', [CompletionResultType]::ParameterName, 'List available recipes and their arguments')
+            [CompletionResult]::new('--man', 'man', [CompletionResultType]::ParameterName, 'Print man page')
             [CompletionResult]::new('--summary', 'summary', [CompletionResultType]::ParameterName, 'List names of available recipes')
             [CompletionResult]::new('--variables', 'variables', [CompletionResultType]::ParameterName, 'List names of variables')
             [CompletionResult]::new('-h', 'h', [CompletionResultType]::ParameterName, 'Print help')
diff --git a/completions/just.zsh b/completions/just.zsh
index 4550daa7..0b79adba 100644
--- a/completions/just.zsh
+++ b/completions/just.zsh
@@ -64,6 +64,7 @@ _just() {
 '--init[Initialize new justfile in project root]' \
 '-l[List available recipes and their arguments]' \
 '--list[List available recipes and their arguments]' \
+'--man[Print man page]' \
 '--summary[List names of available recipes]' \
 '--variables[List names of variables]' \
 '-h[Print help]' \
diff --git a/bin/generate-book/Cargo.toml b/crates/generate-book/Cargo.toml
similarity index 100%
rename from bin/generate-book/Cargo.toml
rename to crates/generate-book/Cargo.toml
diff --git a/bin/generate-book/src/main.rs b/crates/generate-book/src/main.rs
similarity index 100%
rename from bin/generate-book/src/main.rs
rename to crates/generate-book/src/main.rs
diff --git a/bin/ref-type/Cargo.toml b/crates/ref-type/Cargo.toml
similarity index 100%
rename from bin/ref-type/Cargo.toml
rename to crates/ref-type/Cargo.toml
diff --git a/bin/ref-type/src/main.rs b/crates/ref-type/src/main.rs
similarity index 100%
rename from bin/ref-type/src/main.rs
rename to crates/ref-type/src/main.rs
diff --git a/bin/ref-type/tests/integration.rs b/crates/ref-type/tests/integration.rs
similarity index 100%
rename from bin/ref-type/tests/integration.rs
rename to crates/ref-type/tests/integration.rs
diff --git a/bin/update-contributors/Cargo.toml b/crates/update-contributors/Cargo.toml
similarity index 100%
rename from bin/update-contributors/Cargo.toml
rename to crates/update-contributors/Cargo.toml
diff --git a/bin/update-contributors/src/main.rs b/crates/update-contributors/src/main.rs
similarity index 100%
rename from bin/update-contributors/src/main.rs
rename to crates/update-contributors/src/main.rs
diff --git a/justfile b/justfile
index 660e3dae..98fc4226 100755
--- a/justfile
+++ b/justfile
@@ -43,13 +43,7 @@ shellcheck:
   shellcheck www/install.sh
 
 man:
-  cargo build --features help4help2man
-  help2man \
-    --name 'save and run commands' \
-    --manual 'Just Manual' \
-    --no-info \
-    target/debug/just \
-    > man/just.1
+  cargo run -- --man > man/just.1
 
 view-man: man
   man man/just.1
@@ -115,10 +109,6 @@ install-dev-deps:
   cargo install cargo-watch
   cargo install mdbook mdbook-linkcheck
 
-# install system development dependencies with homebrew
-install-dev-deps-homebrew:
-  brew install help2man
-
 # everyone's favorite animate paper clip
 clippy:
   cargo clippy --all --all-targets --all-features
diff --git a/man/just.1 b/man/just.1
index 5021ce57..dca12304 100644
--- a/man/just.1
+++ b/man/just.1
@@ -1,159 +1,168 @@
-.\" DO NOT MODIFY THIS FILE!  It was generated by help2man 1.49.3.
-.TH JUST "1" "May 2024" "just 1.26.0" "Just Manual"
+.ie \n(.g .ds Aq \(aq
+.el .ds Aq '
+.TH just 1  "just 1.26.0" 
 .SH NAME
-just \- save and run commands
+just \- 🤖 Just a command runner \- https://github.com/casey/just
+.SH SYNOPSIS
+\fBjust\fR [\fB\-\-check\fR] [\fB\-\-chooser\fR] [\fB\-\-color\fR] [\fB\-\-command\-color\fR] [\fB\-\-yes\fR] [\fB\-n\fR|\fB\-\-dry\-run\fR] [\fB\-\-dump\-format\fR] [\fB\-\-highlight\fR] [\fB\-\-list\-heading\fR] [\fB\-\-list\-prefix\fR] [\fB\-\-no\-aliases\fR] [\fB\-\-no\-deps\fR] [\fB\-\-no\-dotenv\fR] [\fB\-\-no\-highlight\fR] [\fB\-f\fR|\fB\-\-justfile\fR] [\fB\-q\fR|\fB\-\-quiet\fR] [\fB\-\-set\fR] [\fB\-\-shell\fR] [\fB\-\-shell\-arg\fR] [\fB\-\-shell\-command\fR] [\fB\-\-clear\-shell\-args\fR] [\fB\-u\fR|\fB\-\-unsorted\fR] [\fB\-\-unstable\fR] [\fB\-v\fR|\fB\-\-verbose\fR]... [\fB\-d\fR|\fB\-\-working\-directory\fR] [\fB\-\-changelog\fR] [\fB\-\-choose\fR] [\fB\-c\fR|\fB\-\-command\fR] [\fB\-\-completions\fR] [\fB\-\-dump\fR] [\fB\-e\fR|\fB\-\-edit\fR] [\fB\-\-evaluate\fR] [\fB\-\-fmt\fR] [\fB\-\-init\fR] [\fB\-l\fR|\fB\-\-list\fR] [\fB\-\-man\fR] [\fB\-s\fR|\fB\-\-show\fR] [\fB\-\-summary\fR] [\fB\-\-variables\fR] [\fB\-\-dotenv\-filename\fR] [\fB\-E\fR|\fB\-\-dotenv\-path\fR] [\fB\-h\fR|\fB\-\-help\fR] [\fB\-V\fR|\fB\-\-version\fR] [\fIARGUMENTS\fR] 
 .SH DESCRIPTION
-just 1.26.0
-\- Please see https://github.com/casey/just for more information.
-.SS "USAGE:"
-.IP
-just [FLAGS] [OPTIONS] [\-\-] [ARGUMENTS]...
-.SS "FLAGS:"
-.TP
-\fB\-\-changelog\fR
-Print changelog
+🤖 Just a command runner \- https://github.com/casey/just
+.SH OPTIONS
 .TP
 \fB\-\-check\fR
-Run `\-\-fmt` in 'check' mode. Exits with 0 if justfile is formatted
-correctly. Exits with 1 and prints a diff if formatting is required.
+Run `\-\-fmt` in \*(Aqcheck\*(Aq mode. Exits with 0 if justfile is formatted correctly. Exits with 1 and prints a diff if formatting is required.
 .TP
-\fB\-\-choose\fR
-Select one or more recipes to run using a binary chooser. If `\-\-chooser` is
-not passed the chooser defaults to the value of $JUST_CHOOSER, falling back
-to `fzf`
+\fB\-\-chooser\fR
+Override binary invoked by `\-\-choose`
+.RS
+May also be specified with the \fBJUST_CHOOSER\fR environment variable. 
+.RE
+.TP
+\fB\-\-color\fR [default: auto]
+Print colorful output
+.br
+
+.br
+[\fIpossible values: \fRauto, always, never]
+.TP
+\fB\-\-command\-color\fR
+Echo recipe lines in <COMMAND\-COLOR>
+.br
+
+.br
+[\fIpossible values: \fRblack, blue, cyan, green, purple, red, yellow]
 .TP
-\fB\-\-clear\-shell\-args\fR
-Clear shell arguments
+\fB\-\-yes\fR
+Automatically confirm all recipes.
 .TP
 \fB\-n\fR, \fB\-\-dry\-run\fR
 Print what just would do without doing it
 .TP
-\fB\-\-dump\fR
-Print justfile
-.TP
-\fB\-e\fR, \fB\-\-edit\fR
-Edit justfile with editor given by $VISUAL or $EDITOR, falling back to
-`vim`
-.TP
-\fB\-\-evaluate\fR
-Evaluate and print all variables. If a variable name is given as an
-argument, only print that variable's value.
-.TP
-\fB\-\-fmt\fR
-Format and overwrite justfile
+\fB\-\-dump\-format\fR=\fIFORMAT\fR [default: just]
+Dump justfile as <FORMAT>
+.br
+
+.br
+[\fIpossible values: \fRjust, json]
 .TP
 \fB\-\-highlight\fR
 Highlight echoed recipe lines in bold
 .TP
-\fB\-\-init\fR
-Initialize new justfile in project root
+\fB\-\-list\-heading\fR=\fITEXT\fR
+Print <TEXT> before list
 .TP
-\fB\-l\fR, \fB\-\-list\fR
-List available recipes and their arguments
+\fB\-\-list\-prefix\fR=\fITEXT\fR
+Print <TEXT> before each list item
 .TP
 \fB\-\-no\-aliases\fR
-Don't show aliases in list
+Don\*(Aqt show aliases in list
 .TP
 \fB\-\-no\-deps\fR
-Don't run recipe dependencies
+Don\*(Aqt run recipe dependencies
 .TP
 \fB\-\-no\-dotenv\fR
-Don't load `.env` file
+Don\*(Aqt load `.env` file
 .TP
 \fB\-\-no\-highlight\fR
-Don't highlight echoed recipe lines in bold
+Don\*(Aqt highlight echoed recipe lines in bold
+.TP
+\fB\-f\fR, \fB\-\-justfile\fR
+Use <JUSTFILE> as justfile
 .TP
 \fB\-q\fR, \fB\-\-quiet\fR
 Suppress all output
 .TP
+\fB\-\-set\fR=\fIVARIABLE VALUE\fR
+Override <VARIABLE> with <VALUE>
+.TP
+\fB\-\-shell\fR
+Invoke <SHELL> to run recipes
+.TP
+\fB\-\-shell\-arg\fR
+Invoke shell with <SHELL\-ARG> as an argument
+.TP
 \fB\-\-shell\-command\fR
 Invoke <COMMAND> with the shell used to run recipe lines and backticks
 .TP
-\fB\-\-summary\fR
-List names of available recipes
+\fB\-\-clear\-shell\-args\fR
+Clear shell arguments
 .TP
 \fB\-u\fR, \fB\-\-unsorted\fR
 Return list and summary entries in source order
 .TP
 \fB\-\-unstable\fR
 Enable unstable features
-.TP
-\fB\-\-variables\fR
-List names of variables
+.RS
+May also be specified with the \fBJUST_UNSTABLE\fR environment variable. 
+.RE
 .TP
 \fB\-v\fR, \fB\-\-verbose\fR
 Use verbose output
 .TP
-\fB\-\-yes\fR
-Automatically confirm all recipes.
+\fB\-d\fR, \fB\-\-working\-directory\fR
+Use <WORKING\-DIRECTORY> as working directory. \-\-justfile must also be set
 .TP
-\fB\-h\fR, \fB\-\-help\fR
-Print help information
+\fB\-\-changelog\fR
+Print changelog
 .TP
-\fB\-V\fR, \fB\-\-version\fR
-Print version information
-.SS "OPTIONS:"
+\fB\-\-choose\fR
+Select one or more recipes to run using a binary chooser. If `\-\-chooser` is not passed the chooser defaults to the value of $JUST_CHOOSER, falling back to `fzf`
 .TP
-\fB\-\-chooser\fR <CHOOSER>
-Override binary invoked by `\-\-choose`
-.HP
-\fB\-\-color\fR <COLOR>
-.TP
-Print colorful output [default: auto]
-[possible values: auto, always, never]
-.HP
-\fB\-c\fR, \fB\-\-command\fR <COMMAND>
-.IP
+\fB\-c\fR, \fB\-\-command\fR
 Run an arbitrary command with the working directory, `.env`, overrides, and exports set
-.HP
-\fB\-\-command\-color\fR <COMMAND\-COLOR>
-.IP
-Echo recipe lines in <COMMAND\-COLOR> [possible values: black, blue, cyan, green, purple, red,
-yellow]
-.HP
-\fB\-\-completions\fR <SHELL>
-.IP
-Print shell completion script for <SHELL> [possible values: zsh, bash, fish, powershell,
-elvish]
-.HP
-\fB\-\-dotenv\-filename\fR <DOTENV\-FILENAME>
-.IP
-Search for environment file named <DOTENV\-FILENAME> instead of `.env`
-.HP
-\fB\-E\fR, \fB\-\-dotenv\-path\fR <DOTENV\-PATH>
-.IP
-Load <DOTENV\-PATH> as environment file instead of searching for one
-.HP
-\fB\-\-dump\-format\fR <FORMAT>
 .TP
-Dump justfile as <FORMAT> [default: just]
-[possible values: just, json]
+\fB\-\-completions\fR=\fISHELL\fR
+Print shell completion script for <SHELL>
+.br
+
+.br
+[\fIpossible values: \fRbash, elvish, fish, powershell, zsh]
 .TP
-\fB\-f\fR, \fB\-\-justfile\fR <JUSTFILE>
-Use <JUSTFILE> as justfile
+\fB\-\-dump\fR
+Print justfile
 .TP
-\fB\-\-list\-heading\fR <TEXT>
-Print <TEXT> before list
+\fB\-e\fR, \fB\-\-edit\fR
+Edit justfile with editor given by $VISUAL or $EDITOR, falling back to `vim`
 .TP
-\fB\-\-list\-prefix\fR <TEXT>
-Print <TEXT> before each list item
+\fB\-\-evaluate\fR
+Evaluate and print all variables. If a variable name is given as an argument, only print that variable\*(Aqs value.
 .TP
-\fB\-\-set\fR <VARIABLE> <VALUE>
-Override <VARIABLE> with <VALUE>
+\fB\-\-fmt\fR
+Format and overwrite justfile
 .TP
-\fB\-\-shell\fR <SHELL>
-Invoke <SHELL> to run recipes
+\fB\-\-init\fR
+Initialize new justfile in project root
 .TP
-\fB\-\-shell\-arg\fR <SHELL\-ARG>...
-Invoke shell with <SHELL\-ARG> as an argument
+\fB\-l\fR, \fB\-\-list\fR
+List available recipes and their arguments
 .TP
-\fB\-s\fR, \fB\-\-show\fR <RECIPE>
+\fB\-\-man\fR
+Print man page
+.TP
+\fB\-s\fR, \fB\-\-show\fR=\fIRECIPE\fR
 Show information about <RECIPE>
-.HP
-\fB\-d\fR, \fB\-\-working\-directory\fR <WORKING\-DIRECTORY>
-.IP
-Use <WORKING\-DIRECTORY> as working directory. \fB\-\-justfile\fR must also be set
-.SS "ARGS:"
 .TP
-<ARGUMENTS>...
+\fB\-\-summary\fR
+List names of available recipes
+.TP
+\fB\-\-variables\fR
+List names of variables
+.TP
+\fB\-\-dotenv\-filename\fR
+Search for environment file named <DOTENV\-FILENAME> instead of `.env`
+.TP
+\fB\-E\fR, \fB\-\-dotenv\-path\fR
+Load <DOTENV\-PATH> as environment file instead of searching for one
+.TP
+\fB\-h\fR, \fB\-\-help\fR
+Print help
+.TP
+\fB\-V\fR, \fB\-\-version\fR
+Print version
+.TP
+[\fIARGUMENTS\fR]
 Overrides and recipe(s) to run, defaulting to the first recipe in the justfile
+.SH VERSION
+v1.26.0
+.SH AUTHORS
+Casey Rodarmor <casey@rodarmor.com>
diff --git a/src/config.rs b/src/config.rs
index 10a15caa..e47fe7f9 100644
--- a/src/config.rs
+++ b/src/config.rs
@@ -56,6 +56,7 @@ mod cmd {
   pub(crate) const FORMAT: &str = "FORMAT";
   pub(crate) const INIT: &str = "INIT";
   pub(crate) const LIST: &str = "LIST";
+  pub(crate) const MAN: &str = "MAN";
   pub(crate) const SHOW: &str = "SHOW";
   pub(crate) const SUMMARY: &str = "SUMMARY";
   pub(crate) const VARIABLES: &str = "VARIABLES";
@@ -71,13 +72,14 @@ mod cmd {
     FORMAT,
     INIT,
     LIST,
+    MAN,
     SHOW,
     SUMMARY,
     VARIABLES,
   ];
 
   pub(crate) const ARGLESS: &[&str] = &[
-    CHANGELOG, DUMP, EDIT, FORMAT, INIT, LIST, SUMMARY, VARIABLES,
+    CHANGELOG, DUMP, EDIT, FORMAT, INIT, LIST, MAN, SUMMARY, VARIABLES,
   ];
 }
 
@@ -140,8 +142,15 @@ mod arg {
 
 impl Config {
   pub(crate) fn app() -> Command {
-    let app = Command::new(env!("CARGO_PKG_NAME"))
+    Command::new(env!("CARGO_PKG_NAME"))
       .bin_name(env!("CARGO_PKG_NAME"))
+      .version(env!("CARGO_PKG_VERSION"))
+      .author(env!("CARGO_PKG_AUTHORS"))
+      .about(concat!(
+        env!("CARGO_PKG_DESCRIPTION"),
+        " - ",
+        env!("CARGO_PKG_HOMEPAGE")
+      ))
       .trailing_var_arg(true)
       .styles(
         Styles::styled()
@@ -400,6 +409,12 @@ impl Config {
           .action(ArgAction::SetTrue)
           .help("List available recipes and their arguments"),
       )
+      .arg(
+        Arg::new(cmd::MAN)
+          .long("man")
+          .action(ArgAction::SetTrue)
+          .help("Print man page"),
+      )
       .arg(
         Arg::new(cmd::SHOW)
           .short('s')
@@ -442,24 +457,7 @@ impl Config {
           .num_args(1..)
           .action(ArgAction::Append)
           .help("Overrides and recipe(s) to run, defaulting to the first recipe in the justfile"),
-      );
-
-    if cfg!(feature = "help4help2man") {
-      app.version(env!("CARGO_PKG_VERSION")).about(concat!(
-        "- Please see ",
-        env!("CARGO_PKG_HOMEPAGE"),
-        " for more information."
-      ))
-    } else {
-      app
-        .version(env!("CARGO_PKG_VERSION"))
-        .author(env!("CARGO_PKG_AUTHORS"))
-        .about(concat!(
-          env!("CARGO_PKG_DESCRIPTION"),
-          " - ",
-          env!("CARGO_PKG_HOMEPAGE")
-        ))
-    }
+      )
   }
 
   fn color_from_matches(matches: &ArgMatches) -> ConfigResult<Color> {
@@ -629,6 +627,8 @@ impl Config {
       Subcommand::Init
     } else if matches.get_flag(cmd::LIST) {
       Subcommand::List
+    } else if matches.get_flag(cmd::MAN) {
+      Subcommand::Man
     } else if let Some(name) = matches.get_one::<String>(cmd::SHOW).map(Into::into) {
       Subcommand::Show { name }
     } else if matches.get_flag(cmd::EVALUATE) {
diff --git a/src/error.rs b/src/error.rs
index 9bcf2719..e18d7e9c 100644
--- a/src/error.rs
+++ b/src/error.rs
@@ -142,6 +142,9 @@ pub(crate) enum Error<'src> {
     line_number: Option<usize>,
     signal: i32,
   },
+  StdoutIo {
+    io_error: io::Error,
+  },
   TempdirIo {
     recipe: &'src str,
     io_error: io::Error,
@@ -406,6 +409,9 @@ impl<'src> ColorDisplay for Error<'src> {
           write!(f, "Recipe `{recipe}` was terminated by signal {signal}")?;
         }
       }
+      StdoutIo { io_error } => {
+        write!(f, "I/O error writing to stdout: {io_error}?")?;
+      }
       TempdirIo { recipe, io_error } => {
         write!(f, "Recipe `{recipe}` could not be run because of an IO error while trying to create a temporary \
                    directory or write a file to that directory: {io_error}")?;
diff --git a/src/subcommand.rs b/src/subcommand.rs
index 30417e02..3d0346b9 100644
--- a/src/subcommand.rs
+++ b/src/subcommand.rs
@@ -1,5 +1,6 @@
 use {
   super::*,
+  clap_mangen::Man,
   std::io::{Read, Seek},
   tempfile::tempfile,
 };
@@ -30,6 +31,7 @@ pub(crate) enum Subcommand {
   Format,
   Init,
   List,
+  Man,
   Run {
     arguments: Vec<String>,
     overrides: BTreeMap<String, String>,
@@ -56,6 +58,7 @@ impl Subcommand {
       }
       Completions { shell } => return Self::completions(*shell),
       Init => return Self::init(config),
+      Man => return Self::man(),
       Run {
         arguments,
         overrides,
@@ -87,7 +90,7 @@ impl Subcommand {
       Show { ref name } => Self::show(config, name, justfile)?,
       Summary => Self::summary(config, justfile),
       Variables => Self::variables(justfile),
-      Changelog | Completions { .. } | Edit | Init | Run { .. } => unreachable!(),
+      Changelog | Completions { .. } | Edit | Init | Man | Run { .. } => unreachable!(),
     }
 
     Ok(())
@@ -440,6 +443,26 @@ impl Subcommand {
     }
   }
 
+  fn man() -> Result<(), Error<'static>> {
+    let mut buffer = Vec::<u8>::new();
+
+    Man::new(Config::app())
+      .render(&mut buffer)
+      .expect("writing to buffer cannot fail");
+
+    let mut stdout = io::stdout().lock();
+
+    stdout
+      .write_all(&buffer)
+      .map_err(|io_error| Error::StdoutIo { io_error })?;
+
+    stdout
+      .flush()
+      .map_err(|io_error| Error::StdoutIo { io_error })?;
+
+    Ok(())
+  }
+
   fn list(config: &Config, level: usize, justfile: &Justfile) {
     const MAX_WIDTH: usize = 50;
 


Test patch:
diff --git a/tests/lib.rs b/tests/lib.rs
index abb3c46c..26c9d125 100644
--- a/tests/lib.rs
+++ b/tests/lib.rs
@@ -66,6 +66,7 @@ mod interrupts;
 mod invocation_directory;
 mod json;
 mod line_prefixes;
+mod man;
 mod misc;
 mod modules;
 mod multibyte_char;
diff --git a/tests/man.rs b/tests/man.rs
new file mode 100644
index 00000000..e957414c
--- /dev/null
+++ b/tests/man.rs
@@ -0,0 +1,9 @@
+use super::*;
+
+#[test]
+fn output() {
+  Test::new()
+    .arg("--man")
+    .stdout_regex("(?s).*.TH just 1.*")
+    .run();
+}