Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Document full use-case (e.g., build.rs script) #33

Open
codesections opened this issue Jan 23, 2019 · 5 comments
Open

Document full use-case (e.g., build.rs script) #33

codesections opened this issue Jan 23, 2019 · 5 comments

Comments

@codesections
Copy link
Member

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:

Example

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("alice@person.com"))
        .author(Author::new("Bob Human").email("bob@human.com"))
        .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

end example

Two questions on this:

  1. Is build.rs + makefile the procedure we want to recommend?
  2. If so, should an example like this go under the current example in the README (which prints to stdout) or replace that example?
@codesections
Copy link
Member Author

codesections commented Jan 25, 2019

After experimenting a bit, I think that the method I suggested above (with the build.rs file) isn't a great approach and isn't really using build.rs the way it is intended to be used. It also runs into cross-platform difficulties due to the non-portable nature of the find command. After discussing the issue with @epage a bit, I now think that the best strategy is to use a separate binary and invoke it with cargo run --bin NAME.

Specifically, I think we should suggest that users have a file like this in src/bin/ (basically the build.rs script from above):

// /src/bin/generate-docs.rs
use man::prelude::*;
use std::fs::File;
use std::io::prelude::*;

fn main() {
    let page = Manual::new("basic")
        .about("A basic example")
        .author(Author::new("Alice Person").email("alice@person.com"))
        .author(Author::new("Bob Human").email("bob@human.com"))
        .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();
 
    let mut file = File::create('./docs/MY_APP.1)
         .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 have something like this in their makefile:

.PHONY : install
install : 
	cargo build --release
	cargo run --bin generate-docs
	sudo install -m 0755 -v ./target/release/MY_APP /usr/local/bin/MY_APP
	# man page
	sudo cp ./docs/MY_APP.1 /usr/local/share/man/man1/MY_APP.1
	# Other install commands (Zsh completions/etc.)


.PHONY : uninstall
uninstall :
	sudo rm -f /usr/local/bin/MY_APP
	sudo rm -f /usr/local/share/man/man1/MY_APP.1

Unless someone thinks this is a bad idea, I'll write it up up as a PR soon.

@yoshuawuyts
Copy link
Collaborator

@codesections the way I thought of using this is by auto-generating man pages from CLI options (e.g. as per clap-rs/clap_generate#1, example). I guess with fewer steps, and no makefile needed.

There's certainly some open questions about packaging things up, but that also applies to other files such as autocomplete definitions. I think those fall within the scope of the rust-clique packaging issue.

@codesections
Copy link
Member Author

@yoshuawuyts Yeah, totally agreed that integrating with Clap is the long-term goal. My understanding, though, it that clap integration is blocked pending Clap v3.0.0.

My thought was that we should have some documentation about how to actually go from "I've downloaded man and written my man page using it" to "I've successfully installed the man page so that a user can type man MY_APP and see the man page." Right now, that isn't clear at all (at least not to me), so I was wondering if some documentation would be helpful for the time between now and whenever we can integrate more fully with clap.

But if you don't think that's useful, I'm happy to close this issue.

@alerque
Copy link

alerque commented Jan 6, 2022

Where is Clap integration at now that v3 is out?

BTW I'd be happy to provide docs for how to package a man page using autotools for Rust projects — an arrangement I think provides a lot that cargo can't provide but I realize it's a bit unorthodox.

@epage
Copy link

epage commented Jan 6, 2022

In clap-rs/clap#3174,. we are leaning towards calling roff-rs directly rather than going through man .

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

4 participants