rust-cli / man Goto Github PK
View Code? Open in Web Editor NEWGenerate structured man pages
License: Apache License 2.0
Generate structured man pages
License: Apache License 2.0
The current API is more of a sketch of what this could look like. On chat there seems to be a sentiment that the current API might not suffice for more complex man pages.
I'm opening this issue to gather examples of complex man pages, and to help shape the design of the crate. This should hopefully allow us to get a deeper understanding of which API we can, and should expose.
The usage example in the README does not currently compile. Even if the issues that prevent compilation are fixed, it does not result in any output.
Running the usage example should result in the output described in the README
Running the usage example results in compiler errors or no output.
I plan to submit a PR with a fix to this issue.
I'm not sure how we should be handling subcommands. Should we create separate man pages? Should we create a single man page? It'd be super useful to gather prior art so we can figure out some of the options we have!
Ideally we could find a default that works for most people, and start by implementing that.
Let's start by looking at how git(1)
does it.
Git create separate man pages for its subcommands. The git
man page has GIT COMMANDS
and HIGH LEVEL COMMANDS
sections listing these commands.
Each subcommand has its own page, like you'd expect from any other man page.
git(1)
commands sectiongit-add(1)
man page🐛 bug report
The man
crate depends on roff
0.1, but roff
has been at 0.2 since the end of last year, which is an API break.
It would be useful to bring man
up to date with the latest version of roff
, so that other build processes that depend on modern roff
could use the same roff
crate as man
uses.
Applies to #10. Per https://github.com/killercup/clap-man-demo/blob/master/build.rs#L17-L19, we should have a method to generate a file name.
for man in gen_manuals(&app) {
let name = "clap-man-demo.1"; // FIXME: Extract this from man!
let path = PathBuf::from(&outdir).join(name);
let mut out = fs::File::create(&path).unwrap();
out.write_all(man.render().as_bytes()).unwrap();
}
for man in gen_manuals(&app) {
let path = PathBuf::from(env!("CARGO_PKG_NAME")).push(man.path());
fs::write(path, man.render())?;
}
In discussing #31, @yoshuawuyts mentioned that we might be calling man
differently. That made me realize that we don't have any documentation about the typical way to call man
(e.g., at run time, in a build.rs
script, or using an external tool. I propose adding something to the README that outlines calling man
from a build.rs
file, saving the generated file to the OUT_DIR
and then (separately) installing the generated man page with make. I'm suggesting something like this be added to the README:
First, generate a man page and save it to disk as part of your compilation using a build.rs
file.
// build.rs
use man::prelude::*;
use std::env;
use std::fs::File;
use std::io::prelude::*;
include!("src/cli.rs");
fn main() {
let mut out_dir = env::var("OUT_DIR").unwrap();
let page = Manual::new("basic")
.about("A basic example")
.author(Author::new("Alice Person").email("[email protected]"))
.author(Author::new("Bob Human").email("[email protected]"))
.flag(
Flag::new()
.short("-d")
.long("--debug")
.help("Enable debug mode"),
)
.flag(
Flag::new()
.short("-v")
.long("--verbose")
.help("Enable verbose mode"),
)
.option(
Opt::new("output")
.short("-o")
.long("--output")
.help("The file path to write output to"),
)
.example(
Example::new()
.text("run basic in debug mode")
.command("basic -d")
.output("Debug Mode: basic will print errors to the console")
)
.custom(
Section::new("usage note")
.paragraph("This program will overwrite any file currently stored at the output path")
)
.render();
out_dir.push_str("/MY_APP.1");
let mut file = File::create(out_dir).expect("Should be able to open file in project directory");
file.write_all(page.as_bytes()).expect("Should be able to write to file in project directory");
}
And then add a makefile
to install the generated man page
# makefile
OUT_DIR := $(shell find -type d -name out | grep MY_APP)
.PHONY : install
install :
cp $(OUT_DIR)/MY_APP.1 /usr/local/share/man/man1/MY_APP.1
# any other install commands for MY_APP (the bin, shell completions, etc.)
.PHONY : uninstall
uninstall :
rm -f /usr/local/share/man/man1/MY_APP.1
# any other uninstall commands for MY_APP
Two questions on this:
build.rs
+ makefile
the procedure we want to recommend?https://github.com/rust-clique/man/blob/77e75bb820bff51de3ff2c3472282740b7654579/src/man/mod.rs#L110
Allow accepting not only required
positional arguments, also allow optional
, and vectors
.
The output should be something like:
SYNOPSIS
my_cmd [OPTIONS] arg1 arg2? arg3...
🙋 feature request
Hi, I just started experimenting with this crate in my configure_me
crate and I must say it's pretty neat.
However, I'm missing DESCRIPTION
section. I tried to put my documentation into about, but it was long and the result was very ugly.
I am including version and date in the same issue because I've checked several man(1) pages and noticed that the formatting of the two is related: specifically, the version will be displayed in the bottom center of the page if the man page doesn't have a date. If the man page has a date, the date is displayed in the bottom center of the page and the version is moved to the left.
I also note that we can use the env!("CARGO_PACKAGE_VERSION") macro to read the version from the Cargo.toml file and that version is a required field. Based on the above, I propose the following API:
Default (nothing specified; assume the Cargo.toml version is 0.2.0
)
Manual::new("basic");
prints at the bottom:
0.2.0 BASIC(1)
Date
Manual::new("basic")
.date("August 2017");
0.2.0 August 2017 BASIC(1)
Date & Custom version
Manual::new("basic")
.date("January 1, 2019")
.version("0.1.0");
0.1.0 January 1, 2017 BASIC(1)
Custom date & no version
Manual::new("basic")
.date("2015-05-23")
.version("");
2015-05-23 BASIC(1)
Note: the API I suggest takes a str for the date and leaves the exact formatting to the user. It could, of course, take a date string or something (perhaps using Chrono) and format the date for the user. But, based on looking at a few man pages, there doesn't seem to be consensus on how to format dates and it seems better to leave that to the users.
Any thoughts on this API before I work on a PR?
I'm thinking almost all sections should be optional, but we want to somewhat restrict which sections are possible. Which sections should we have?
From what I've seen, we should probably support the following sections:
This should probably be part of the ::new()
constructor?
-h
in CLIs~/.ifconfig
)As noted in the comments, the exit_status
section is currently hard-coded, but should be revised to accept arguments. Specifically, the exit status section will currently always be:
EXIT STATUS
0 Successful program execution.
1 Unsuccessful program execution.
101 The program panicked.
Looking at a variety of man pages, I've noticed that not all of them have an "exit status" section (including some that I trust to be good, including git
, bash
, zsh
, and rg
. Thus, I think that we should make the exit status section optional.
Of course, as noted in the comment, we should make it possible to pass app-specific exit codes. However, it seems like the current implementation (0, 1, and 101) will be a very common default, so it would be nice if we keep that as fairly easy behavior.
Given all of the above, I propose the following API
.exit_status(ExitStatus::default())
produces current behavior
.exit_status(ExitStatus::default_plus([(1, "invalid input"), (3, "could not read file")])
Produces an exit status section with 0 and 101 as defaults plus whatever custom codes and messages the user supplies in an vec of tuples.
.exit_status(ExitStatus::custom([(0, "success")]))
Produces a fully custom exit status section with only the user-supplied codes/messages.
I'm happy to implement this API; please let me know if you think we should go in a different direction.
As discussed in #1, a man page traditionally has several different sections beyond the few that man
currently supports. I agree that, long-term, it would be best for man
to support a larger number of those sections. Supporting a list section directly would allow man
to provide a higher abstraction over that section and would guide crate authors towards including relevant/helpful/traditional sections.
However, I believe man
should also support custom sections. In the short term, this would allow users to include any of the sections listed in #1, even if they need to operate at a bit lower level of abstraction/don't have access to the same degree of prewritten formatting. Longer term, including custom sections would still be useful to users who want to include sections beyond those we chose to support.
As an example of the later, I have seen many man pages that include a copying
section, which isn't one of the sections listed in #1. Many users might want to include that section, and a custom section command would let them do so.
If no one objects or has a different suggestion, I will work on a PR for custom sections.
As per clap-rs/clap#552 (comment), the clap team would be happy to add the man page generation code to their new clap_derive
crate. That would mean we could move all clap-specific code there, and only keep the man page generation code in this repo.
cc/ @spacekookie how would you feel about moving code there? I also tagged you in the issue above about joining the clap org.
This furthers the https://github.com/rust-lang-nursery/cli-wg/issues/42 milestone issue.
The example in the README currently shows the output in plain text, when means it cannot show which text is bold. However, GitHub supports using the <pre>
tag, which would alow us to use html markup inside the output. The result would look like this:
BASIC(1) General Commands Manual BASIC(1) NAME basic - A basic example SYNOPSIS basic [FLAGS] [OPTIONS] FLAGS -d, --debug Enable debug mode -v, --verbose Enable verbose mode OPTIONS -o, --output=output The file path to write output to USAGE NOTE This file will overwrite any file currently stored at the output path. EXIT STATUS 0 Successful program execution. 1 Unsuccessful program execution. 101 The program panicked. AUTHORS Alice Person <[email protected]> Bob Human <[email protected]>
I think this would be pretty helpful in letting users see the actual formatting that man
is doing—much of what we're doing is adding bold, so showing the output without that makes the crate look a lot less useful.
My only hesitation about this is crates.io. Does anyone know if it respects the <pre>
tag in the same way GitHub does?
Found myself debugging a tool today, and turned out I had 2 tools installed with duplicate names.
The way I found out was thanks to the "source" section in the man page, providing a key differentiator between the tools.
I propose we add a "source" section to the man page, which ideally can be populated by consumers using the repository
value from the Cargo.toml
in upstream consumers of man
.
Thanks!
I've taken a look at the examples provided in various man pages and they're pretty diverse (including many that don't have any, but I think we should make it easy for Rust CLIs to provide examples).
I've noticed that example sections seem to have multiple parts, which leads me to propose the following API.
.text_before()
default, none
.prompt()
default "$"
.command()
.output()
.text_after()`
Here's how this API would look with a simple example (taken from the watchexec
man page
Manual::new("watchexec")
.example(
Example::new()
.text_before("Watch all HTML, CSS, and JavaScript files for changes:")
.command("watchexec -e html,css,js make")
);
which would print
EXAMPLES Watch all HTML, CSS, and JavaScript files for changes: $ watchexec -e html,css,js make
And here's a more slightly complex example (from the pass man page)
Manual::new("pass")
.example(
Example::new()
.text_before("Initialize password store")
.prompt("zx2c4@laptop ~ $")
.command(" pass init [email protected]")
.output("mkdir: created directory ‘/home/zx2c4/.password-store’")
.output("Password store initialized for [email protected].")
);
Which would produce
EXAMPLES Initialize password store zx2c4@laptop ~ $ pass init [email protected] mkdir: created directory ‘/home/zx2c4/.password-store’ Password store initialized for [email protected].
The .prompt()
method could also take an empty string to produce examples without a leading prompt (the style used in the git man/help pages).
Any thoughts on that API?
The Opt::new
function forces items to labeled --long=NEW
. Many programs man pages have display options as --long NEW
or --long NEW1 NEW2
. Would it be possible in the future to not force the user to have the =
? Then the user could add the =
to the begining whent they want to use it?
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.