[#1066] improved usage help; added tests

This commit is contained in:
Remko Popma
2020-06-06 13:25:08 +09:00
parent 9c9bf4374d
commit e7250d66d1
10 changed files with 416 additions and 8 deletions

View File

@@ -25,6 +25,7 @@ import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.io.PrintWriter;
import java.lang.reflect.Method;
import java.util.*;
import java.util.concurrent.Callable;
@@ -46,7 +47,7 @@ import static java.lang.String.format;
sortOptions = false,
usageHelpAutoWidth = true,
usageHelpWidth = 100,
description = {"Generates one or more AsciiDoc files with doctype 'manpage' in the specified directory."},
description = {"Generates man pages for all commands in the specified directory."},
//exitCodeListHeading = "%nExit Codes (if enabled with `--exit`)%n",
//exitCodeList = {
// "0:Successful program execution.",
@@ -66,7 +67,7 @@ import static java.lang.String.format;
"See http://man7.org/linux/man-pages/man7/roff.7.html",
}
)
public class ManPageGenerator {
public class ManPageGenerator implements Callable<Integer> {
static final int EXIT_CODE_TEMPLATE_EXISTS = 4;
static final IStyle BOLD = new IStyle() {
@@ -103,8 +104,7 @@ public class ManPageGenerator {
* @throws IOException if a problem occurred writing files.
*/
public Integer call() throws IOException {
List<CommandSpec> specs = Util.flattenHierarchy(spec.root());
return generateManPage(config, specs.toArray(new CommandSpec[0]));
return generateManPage(config, spec.root());
}
private static Map<String, IStyle> createMarkupMap() {
@@ -279,6 +279,8 @@ public class ManPageGenerator {
return CommandLine.ExitCode.USAGE;
}
traceAllSpecs(specs, config);
for (CommandSpec spec : specs) {
int result = generateSingleManPage(config, spec);
if (result != CommandLine.ExitCode.OK) {
@@ -301,6 +303,21 @@ public class ManPageGenerator {
return CommandLine.ExitCode.OK;
}
private static void traceAllSpecs(CommandSpec[] specs, Config config) {
List<String> all = new ArrayList<String>();
for (CommandSpec spec: specs) {
Object obj = spec.userObject();
if (obj == null) {
all.add(spec.name() + " (no user object)");
} else if (obj instanceof Method) {
all.add(spec.name() + " (" + ((Method) obj).toGenericString() + ")");
} else {
all.add(obj.getClass().getName());
}
}
config.verbose("Generating man pages for %s and all subcommands%n", all);
}
private static int generateSingleManPage(Config config, CommandSpec spec) throws IOException {
if (!mkdirs(config, config.directory)) {
return CommandLine.ExitCode.SOFTWARE;

View File

@@ -1,19 +1,22 @@
package picocli.codegen.docgen.manpage;
import org.junit.Ignore;
import org.junit.Test;
import picocli.CommandLine;
import picocli.CommandLine.ArgGroup;
import picocli.CommandLine.Command;
import picocli.CommandLine.Option;
import picocli.CommandLine.Parameters;
import picocli.codegen.util.Assert;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.net.URL;
import java.nio.charset.Charset;
import java.util.Arrays;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
@@ -259,7 +262,9 @@ public class ManPageGeneratorTest {
}
private String read(String resource) throws IOException {
return readAndClose(getClass().getResourceAsStream(resource));
URL url = ManPageGenerator.class.getResource(resource);
Assert.notNull(url, "resource '" + resource + "'");
return readAndClose(url.openStream());
}
private String readAndClose(InputStream in) throws IOException {
@@ -322,7 +327,7 @@ public class ManPageGeneratorTest {
String expected = String.format("" +
"Usage: top-level-command subcommand gen-manpage [-fhVv] [-d=<outdir>] [-t=<template-dir>]%n" +
" [@<filename>...]%n" +
"Generates one or more AsciiDoc files with doctype 'manpage' in the specified directory.%n" +
"Generates man pages for all commands in the specified directory.%n" +
" [@<filename>...] One or more argument files containing options.%n" +
" -d, --outdir=<outdir> Output directory to write the generated AsciiDoc files to. If not%n" +
" specified, files are written to the current directory.%n" +
@@ -357,4 +362,70 @@ public class ManPageGeneratorTest {
"See http://man7.org/linux/man-pages/man7/roff.7.html%n");
assertEquals(expected, sw.toString());
}
}
@Test
public void testManPageGeneratorAsSubcommandParentHelp() {
StringWriter sw = new StringWriter();
new CommandLine(new Top()).setOut(new PrintWriter(sw, true))
.execute("subcommand", "--help");
String expected = String.format("" +
"Usage: top-level-command subcommand [-hV] [COMMAND]%n" +
"Example subcommand%n" +
" -h, --help Show this help message and exit.%n" +
" -V, --version Print version information and exit.%n" +
"Commands:%n" +
" gen-manpage Generates man pages for all commands in the specified directory.%n");
assertEquals(expected, sw.toString());
}
@Test
public void testManPageGeneratorAsSubcommand() throws IOException {
File outdir = new File(System.getProperty("java.io.tmpdir"), "manpage" + System.currentTimeMillis());
outdir.mkdir();
File templateDir = new File(outdir, "templates");
int exitCode = new CommandLine(new Top())
.execute("subcommand", "gen-manpage", /*"-vv",*/ "--outdir=" + outdir, "--template-dir=" + templateDir);
try {
assertEquals(0, exitCode);
//System.out.println(Arrays.asList(templateDir.listFiles()));
//System.out.println(Arrays.asList(outdir.listFiles()));
String[] files = new String[] {
"top-level-command.adoc", //
"top-level-command-visible.adoc", //
"top-level-command-subcommand.adoc", //
"top-level-command-subcommand-gen-manpage.adoc"
};
for (String f : files) {
String expected = read("/manpagegenerator/templates/" + f);
String actual = readAndClose(new FileInputStream(new File(templateDir, f)));
expected = expected.replace("$OUTDIR", outdir.getAbsolutePath().replace('\\', '/'));
assertEquals("/manpagegenerator/templates/" + f, expected, actual);
}
for (String f : files) {
String expected = read("/manpagegenerator/" + f);
String actual = readAndClose(new FileInputStream(new File(outdir, f)));
expected = expected.replace("$VERSION", CommandLine.VERSION);
assertEquals("/manpagegenerator/" + f, expected, actual);
}
} finally {
for (File f : templateDir.listFiles()) {
f.delete();
}
try {
assertTrue(templateDir.getAbsolutePath(), templateDir.delete());
} finally {
for (File f : outdir.listFiles()) {
f.delete();
}
assertTrue(outdir.getAbsolutePath(), outdir.delete());
}
}
}
}

View File

@@ -0,0 +1,20 @@
:includedir: $OUTDIR
//include::{includedir}/top-level-command-subcommand-gen-manpage.adoc[tag=picocli-generated-full-manpage]
include::{includedir}/top-level-command-subcommand-gen-manpage.adoc[tag=picocli-generated-man-section-header]
include::{includedir}/top-level-command-subcommand-gen-manpage.adoc[tag=picocli-generated-man-section-name]
include::{includedir}/top-level-command-subcommand-gen-manpage.adoc[tag=picocli-generated-man-section-synopsis]
include::{includedir}/top-level-command-subcommand-gen-manpage.adoc[tag=picocli-generated-man-section-description]
include::{includedir}/top-level-command-subcommand-gen-manpage.adoc[tag=picocli-generated-man-section-options]
include::{includedir}/top-level-command-subcommand-gen-manpage.adoc[tag=picocli-generated-man-section-arguments]
include::{includedir}/top-level-command-subcommand-gen-manpage.adoc[tag=picocli-generated-man-section-commands]
include::{includedir}/top-level-command-subcommand-gen-manpage.adoc[tag=picocli-generated-man-section-exit-status]
include::{includedir}/top-level-command-subcommand-gen-manpage.adoc[tag=picocli-generated-man-section-footer]

View File

@@ -0,0 +1,20 @@
:includedir: $OUTDIR
//include::{includedir}/top-level-command-subcommand.adoc[tag=picocli-generated-full-manpage]
include::{includedir}/top-level-command-subcommand.adoc[tag=picocli-generated-man-section-header]
include::{includedir}/top-level-command-subcommand.adoc[tag=picocli-generated-man-section-name]
include::{includedir}/top-level-command-subcommand.adoc[tag=picocli-generated-man-section-synopsis]
include::{includedir}/top-level-command-subcommand.adoc[tag=picocli-generated-man-section-description]
include::{includedir}/top-level-command-subcommand.adoc[tag=picocli-generated-man-section-options]
include::{includedir}/top-level-command-subcommand.adoc[tag=picocli-generated-man-section-arguments]
include::{includedir}/top-level-command-subcommand.adoc[tag=picocli-generated-man-section-commands]
include::{includedir}/top-level-command-subcommand.adoc[tag=picocli-generated-man-section-exit-status]
include::{includedir}/top-level-command-subcommand.adoc[tag=picocli-generated-man-section-footer]

View File

@@ -0,0 +1,20 @@
:includedir: $OUTDIR
//include::{includedir}/top-level-command-visible.adoc[tag=picocli-generated-full-manpage]
include::{includedir}/top-level-command-visible.adoc[tag=picocli-generated-man-section-header]
include::{includedir}/top-level-command-visible.adoc[tag=picocli-generated-man-section-name]
include::{includedir}/top-level-command-visible.adoc[tag=picocli-generated-man-section-synopsis]
include::{includedir}/top-level-command-visible.adoc[tag=picocli-generated-man-section-description]
include::{includedir}/top-level-command-visible.adoc[tag=picocli-generated-man-section-options]
include::{includedir}/top-level-command-visible.adoc[tag=picocli-generated-man-section-arguments]
include::{includedir}/top-level-command-visible.adoc[tag=picocli-generated-man-section-commands]
include::{includedir}/top-level-command-visible.adoc[tag=picocli-generated-man-section-exit-status]
include::{includedir}/top-level-command-visible.adoc[tag=picocli-generated-man-section-footer]

View File

@@ -0,0 +1,20 @@
:includedir: $OUTDIR
//include::{includedir}/top-level-command.adoc[tag=picocli-generated-full-manpage]
include::{includedir}/top-level-command.adoc[tag=picocli-generated-man-section-header]
include::{includedir}/top-level-command.adoc[tag=picocli-generated-man-section-name]
include::{includedir}/top-level-command.adoc[tag=picocli-generated-man-section-synopsis]
include::{includedir}/top-level-command.adoc[tag=picocli-generated-man-section-description]
include::{includedir}/top-level-command.adoc[tag=picocli-generated-man-section-options]
include::{includedir}/top-level-command.adoc[tag=picocli-generated-man-section-arguments]
include::{includedir}/top-level-command.adoc[tag=picocli-generated-man-section-commands]
include::{includedir}/top-level-command.adoc[tag=picocli-generated-man-section-exit-status]
include::{includedir}/top-level-command.adoc[tag=picocli-generated-man-section-footer]

View File

@@ -0,0 +1,86 @@
// tag::picocli-generated-full-manpage[]
// tag::picocli-generated-man-section-header[]
:doctype: manpage
:revnumber: top-level-command subcommand gen-manpage $VERSION
:manmanual: Top-level-command Manual
:mansource: top-level-command subcommand gen-manpage $VERSION
:man-linkstyle: pass:[blue R < >]
= top-level-command-subcommand-gen-manpage(1)
// end::picocli-generated-man-section-header[]
// tag::picocli-generated-man-section-name[]
== Name
top-level-command-subcommand-gen-manpage - Generates man pages for all commands in the specified directory.
// end::picocli-generated-man-section-name[]
// tag::picocli-generated-man-section-synopsis[]
== Synopsis
*top-level-command subcommand gen-manpage* [*-fhVv*] [*-d*=_<outdir>_] [*-t*=_<template-dir>_] [_@<filename>_...]
// end::picocli-generated-man-section-synopsis[]
// tag::picocli-generated-man-section-description[]
== Description
Generates man pages for all commands in the specified directory.
// end::picocli-generated-man-section-description[]
// tag::picocli-generated-man-section-options[]
== Options
*-d*, *--outdir*=_<outdir>_::
Output directory to write the generated AsciiDoc files to. If not specified, files are written to the current directory.
*-t*, *--template-dir*=_<template-dir>_::
Optional directory to write customizable man page template files. If specified, an additional "template" file is created here for each generated manpage AsciiDoc file.
+
Each template file contains `include` directives that import content from the corresponding generated manpage AsciiDoc file in the `--outdir` directory. Text can be added after each include to customize the resulting man page. The resulting man page will be a mixture of generated and manually edited text.
+
These customizable templates are intended to be generated once, and afterwards be manually updated and maintained.
*-v*, *--verbose*::
Specify multiple -v options to increase verbosity.
+
For example, `-v -v -v` or `-vvv`
*-f*, *--[no-]force*::
Overwrite existing man page templates. The default is `--no-force`, meaning processing is aborted and the process exits with status code 4 if a man page template file already exists.
*-h*, *--help*::
Show this help message and exit.
*-V*, *--version*::
Print version information and exit.
// end::picocli-generated-man-section-options[]
// tag::picocli-generated-man-section-arguments[]
== Arguments
[_@<filename>_...]::
One or more argument files containing options.
// end::picocli-generated-man-section-arguments[]
// tag::picocli-generated-man-section-footer[]
== Converting to Man Page Format
[%hardbreaks]
Use the `asciidoctor` tool to convert the generated AsciiDoc files to man pages in roff format:
[%hardbreaks]
`asciidoctor --backend=manpage --source-dir=SOURCE_DIR --destination-dir=DESTINATION *.adoc`
[%hardbreaks]
Point the SOURCE_DIR to either the `--outdir` directory or the `--template-dir` directory. Use some other directory as the DESTINATION.
See https://asciidoctor.org/docs/user-manual/#man-pages
See http://man7.org/linux/man-pages/man7/roff.7.html
// end::picocli-generated-man-section-footer[]
// end::picocli-generated-full-manpage[]

View File

@@ -0,0 +1,52 @@
// tag::picocli-generated-full-manpage[]
// tag::picocli-generated-man-section-header[]
:doctype: manpage
:revnumber:
:manmanual: Top-level-command Manual
:mansource:
:man-linkstyle: pass:[blue R < >]
= top-level-command-subcommand(1)
// end::picocli-generated-man-section-header[]
// tag::picocli-generated-man-section-name[]
== Name
top-level-command-subcommand - Example subcommand
// end::picocli-generated-man-section-name[]
// tag::picocli-generated-man-section-synopsis[]
== Synopsis
*top-level-command subcommand* [*-hV*] [COMMAND]
// end::picocli-generated-man-section-synopsis[]
// tag::picocli-generated-man-section-description[]
== Description
Example subcommand
// end::picocli-generated-man-section-description[]
// tag::picocli-generated-man-section-options[]
== Options
*-h*, *--help*::
Show this help message and exit.
*-V*, *--version*::
Print version information and exit.
// end::picocli-generated-man-section-options[]
// tag::picocli-generated-man-section-commands[]
== Commands
*gen-manpage*::
Generates man pages for all commands in the specified directory.
// end::picocli-generated-man-section-commands[]
// end::picocli-generated-full-manpage[]

View File

@@ -0,0 +1,44 @@
// tag::picocli-generated-full-manpage[]
// tag::picocli-generated-man-section-header[]
:doctype: manpage
:revnumber:
:manmanual: Top-level-command Manual
:mansource:
:man-linkstyle: pass:[blue R < >]
= top-level-command-visible(1)
// end::picocli-generated-man-section-header[]
// tag::picocli-generated-man-section-name[]
== Name
top-level-command-visible - Example visible subcommand
// end::picocli-generated-man-section-name[]
// tag::picocli-generated-man-section-synopsis[]
== Synopsis
*top-level-command visible* [*-hV*]
// end::picocli-generated-man-section-synopsis[]
// tag::picocli-generated-man-section-description[]
== Description
Example visible subcommand
// end::picocli-generated-man-section-description[]
// tag::picocli-generated-man-section-options[]
== Options
*-h*, *--help*::
Show this help message and exit.
*-V*, *--version*::
Print version information and exit.
// end::picocli-generated-man-section-options[]
// end::picocli-generated-full-manpage[]

View File

@@ -0,0 +1,58 @@
// tag::picocli-generated-full-manpage[]
// tag::picocli-generated-man-section-header[]
:doctype: manpage
:revnumber:
:manmanual: Top-level-command Manual
:mansource:
:man-linkstyle: pass:[blue R < >]
= top-level-command(1)
// end::picocli-generated-man-section-header[]
// tag::picocli-generated-man-section-name[]
== Name
top-level-command - example top-level command
// end::picocli-generated-man-section-name[]
// tag::picocli-generated-man-section-synopsis[]
== Synopsis
*top-level-command* [*-hV*] [*-x*=_<x>_] [COMMAND]
// end::picocli-generated-man-section-synopsis[]
// tag::picocli-generated-man-section-description[]
== Description
example top-level command
// end::picocli-generated-man-section-description[]
// tag::picocli-generated-man-section-options[]
== Options
*-h*, *--help*::
Show this help message and exit.
*-V*, *--version*::
Print version information and exit.
*-x*, *--long-option*=_<x>_::
Some example option
// end::picocli-generated-man-section-options[]
// tag::picocli-generated-man-section-commands[]
== Commands
*subcommand*::
Example subcommand
*visible*::
Example visible subcommand
// end::picocli-generated-man-section-commands[]
// end::picocli-generated-full-manpage[]