Fix exports and move help in place (#1166)

Depends on https://github.com/apache/maven/pull/1799
Help output https://gist.github.com/cstamas/b53333945eb2d7721e0aa5f03329c4a6
This commit is contained in:
Tamas Cservenak
2024-10-17 09:24:29 +02:00
committed by GitHub
parent 910675d8b2
commit 9734734140
4 changed files with 192 additions and 208 deletions

View File

@@ -18,18 +18,31 @@
*/ */
package org.apache.maven.cli; package org.apache.maven.cli;
import java.io.ByteArrayOutputStream;
import java.io.PrintStream;
import java.io.PrintWriter;
import java.nio.charset.StandardCharsets;
import java.util.Collection; import java.util.Collection;
import java.util.List; import java.util.List;
import java.util.ListIterator; import java.util.ListIterator;
import java.util.Locale;
import java.util.Map; import java.util.Map;
import java.util.Set;
import java.util.StringTokenizer;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.apache.commons.cli.CommandLine; import org.apache.commons.cli.CommandLine;
import org.apache.commons.cli.HelpFormatter;
import org.apache.commons.cli.Option; import org.apache.commons.cli.Option;
import org.apache.commons.cli.Options; import org.apache.commons.cli.Options;
import org.apache.commons.cli.ParseException; import org.apache.commons.cli.ParseException;
import org.apache.maven.cling.invoker.mvn.CommonsCliMavenOptions; import org.apache.maven.cling.invoker.mvn.CommonsCliMavenOptions;
import org.apache.maven.jline.MessageUtils;
import org.codehaus.plexus.interpolation.BasicInterpolator; import org.codehaus.plexus.interpolation.BasicInterpolator;
import org.codehaus.plexus.interpolation.InterpolationException; import org.codehaus.plexus.interpolation.InterpolationException;
import org.mvndaemon.mvnd.common.Environment;
import org.mvndaemon.mvnd.common.OptionType;
import static org.apache.maven.cling.invoker.Utils.createInterpolator; import static org.apache.maven.cling.invoker.Utils.createInterpolator;
@@ -81,6 +94,14 @@ public class CommonsCliDaemonMavenOptions extends CommonsCliMavenOptions impleme
protected static class CLIManager extends CommonsCliMavenOptions.CLIManager { protected static class CLIManager extends CommonsCliMavenOptions.CLIManager {
public static final String RAW_STREAMS = "ras"; public static final String RAW_STREAMS = "ras";
private static final Pattern HTML_TAGS_PATTERN = Pattern.compile("<[^>]*>");
private static final Pattern COLUMNS_DETECTOR_PATTERN = Pattern.compile("^[ ]+[^s]");
private static final Pattern WS_PATTERN = Pattern.compile("\\s+");
static String toPlainText(String javadocText) {
return HTML_TAGS_PATTERN.matcher(javadocText).replaceAll("");
}
@Override @Override
protected void prepareOptions(Options options) { protected void prepareOptions(Options options) {
super.prepareOptions(options); super.prepareOptions(options);
@@ -89,5 +110,149 @@ public class CommonsCliDaemonMavenOptions extends CommonsCliMavenOptions impleme
.desc("Use raw-streams for daemon communication") .desc("Use raw-streams for daemon communication")
.build()); .build());
} }
@Override
public void displayHelp(String command, PrintWriter pw) {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
try (PrintWriter pww = new PrintWriter(new PrintStream(baos), true, StandardCharsets.UTF_8)) {
super.displayHelp(command, pww);
}
final String mvnHelp = baos.toString(StandardCharsets.UTF_8);
final Matcher m = COLUMNS_DETECTOR_PATTERN.matcher(mvnHelp);
final String indent = m.find() ? m.group() : " ";
final int terminalWidth = getTerminalWidth();
final String lineSeparator = System.lineSeparator();
final StringBuilder help =
new StringBuilder(mvnHelp).append(lineSeparator).append("mvnd specific options:");
Environment.documentedEntries().forEach(entry -> {
final Environment env = entry.getEntry();
help.append(lineSeparator);
int indentPos = help.length() + indent.length();
int lineEnd = help.length() + terminalWidth;
spaces(help, HelpFormatter.DEFAULT_LEFT_PAD);
final String property = env.getProperty();
if (property != null) {
help.append("-D").append(property);
if (env.getType() != OptionType.VOID) {
help.append("=<")
.append(env.getType().name().toLowerCase(Locale.ROOT))
.append('>');
}
}
final Set<String> opts = env.getOptions();
if (!opts.isEmpty()) {
if (property != null) {
help.append(';');
}
boolean first = true;
for (String opt : opts) {
if (first) {
first = false;
} else {
help.append(',');
}
help.append(opt);
}
if (env.getType() != OptionType.VOID) {
help.append(" <")
.append(env.getType().name().toLowerCase(Locale.ROOT))
.append('>');
}
}
help.append(' ');
spaces(help, indentPos - help.length());
wrap(help, toPlainText(entry.getJavaDoc()), terminalWidth, lineEnd, indent);
if (env.isDocumentedAsDiscriminating()) {
indentedLine(help, terminalWidth, "This is a discriminating start parameter.", indent);
}
if (env.getDefault() != null) {
indentedLine(help, terminalWidth, "Default: " + env.getDefault(), indent);
}
if (env.getEnvironmentVariable() != null) {
indentedLine(help, terminalWidth, "Env. variable: " + env.getEnvironmentVariable(), indent);
}
});
help.append(lineSeparator).append(lineSeparator).append("mvnd value types:");
OptionType.documentedEntries().forEach(entry -> {
final OptionType type = entry.getEntry();
help.append(lineSeparator);
int indentPos = help.length() + indent.length();
int lineEnd = help.length() + terminalWidth;
spaces(help, HelpFormatter.DEFAULT_LEFT_PAD);
help.append(type.name().toLowerCase(Locale.ROOT));
spaces(help, indentPos - help.length());
wrap(help, toPlainText(entry.getJavaDoc()), terminalWidth, lineEnd, indent);
});
pw.print(help);
pw.flush();
}
private static int getTerminalWidth() {
int terminalWidth = MessageUtils.getTerminalWidth();
if (terminalWidth <= 0) {
terminalWidth = HelpFormatter.DEFAULT_WIDTH;
}
return terminalWidth;
}
private static void indentedLine(StringBuilder stringBuilder, int terminalWidth, String text, String indent) {
final int lineEnd = stringBuilder.length() + terminalWidth;
stringBuilder.append(System.lineSeparator()).append(indent);
wrap(stringBuilder, text, terminalWidth, lineEnd, indent);
}
/**
* Word-wrap the given {@code text} to the given {@link StringBuilder}
*
* @param stringBuilder the {@link StringBuilder} to append to
* @param text the text to wrap and append
* @param lineLength the preferred line length
* @param nextLineEnd the length of the {@code stringBuilder} at which the current line should end
* @param indent the indentation string
*/
static void wrap(StringBuilder stringBuilder, String text, int lineLength, int nextLineEnd, String indent) {
final StringTokenizer st = new StringTokenizer(text, " \t\n\r", true);
String lastWs = null;
while (st.hasMoreTokens()) {
final String token = st.nextToken();
if (WS_PATTERN.matcher(token).matches()) {
lastWs = token;
} else {
if (stringBuilder.length() + token.length() + (lastWs != null ? lastWs.length() : 0)
< nextLineEnd) {
if (lastWs != null) {
stringBuilder.append(lastWs);
}
stringBuilder.append(token);
} else {
nextLineEnd = stringBuilder.length() + lineLength;
stringBuilder
.append(System.lineSeparator())
.append(indent)
.append(token);
}
lastWs = null;
}
}
}
/**
* Append {@code count} spaces to the given {@code stringBuilder}
*
* @param stringBuilder the {@link StringBuilder} to append to
* @param count the number of spaces to append
*/
static void spaces(StringBuilder stringBuilder, int count) {
stringBuilder.append(" ".repeat(Math.max(0, count)));
}
} }
} }

View File

@@ -18,9 +18,11 @@
*/ */
package org.apache.maven.cli; package org.apache.maven.cli;
import java.io.ByteArrayOutputStream;
import java.io.IOException; import java.io.IOException;
import java.io.OutputStream; import java.io.OutputStream;
import java.io.PrintStream; import java.io.PrintStream;
import java.io.PrintWriter;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import org.apache.maven.api.cli.Options; import org.apache.maven.api.cli.Options;
@@ -92,9 +94,11 @@ public class DaemonMavenInvoker extends DefaultResidentMavenInvoker {
BuildEventListener buildEventListener = BuildEventListener buildEventListener =
context.invokerRequest.parserRequest().lookup().lookup(BuildEventListener.class); context.invokerRequest.parserRequest().lookup().lookup(BuildEventListener.class);
if (invokerRequest.options().help().isPresent()) { if (invokerRequest.options().help().isPresent()) {
// TODO: ugly, cleanup ByteArrayOutputStream baos = new ByteArrayOutputStream();
buildEventListener.log( try (PrintWriter pw = new PrintWriter(new PrintStream(baos), true, StandardCharsets.UTF_8)) {
MvndHelpFormatter.displayHelp((CommonsCliDaemonMavenOptions) context.invokerRequest.options())); context.invokerRequest.options().displayHelp(invokerRequest.parserRequest(), pw);
}
buildEventListener.log(baos.toString(StandardCharsets.UTF_8));
throw new ExitException(0); throw new ExitException(0);
} }
if (invokerRequest.options().showVersionAndExit().isPresent()) { if (invokerRequest.options().showVersionAndExit().isPresent()) {

View File

@@ -20,19 +20,39 @@ package org.apache.maven.cli;
import java.nio.file.Path; import java.nio.file.Path;
import java.nio.file.Paths; import java.nio.file.Paths;
import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.Set;
import java.util.stream.Stream; import java.util.stream.Stream;
import org.apache.maven.api.cli.mvn.MavenInvokerRequest; import org.apache.maven.api.cli.mvn.MavenInvokerRequest;
import org.apache.maven.api.cli.mvn.MavenOptions; import org.apache.maven.api.cli.mvn.MavenOptions;
import org.apache.maven.cling.invoker.PlexusContainerCapsuleFactory; import org.apache.maven.cling.invoker.PlexusContainerCapsuleFactory;
import org.apache.maven.cling.invoker.mvn.resident.DefaultResidentMavenInvoker; import org.apache.maven.cling.invoker.mvn.resident.DefaultResidentMavenInvoker;
import org.apache.maven.extension.internal.CoreExtensionEntry;
import org.mvndaemon.mvnd.common.Environment; import org.mvndaemon.mvnd.common.Environment;
public class DaemonPlexusContainerCapsuleFactory public class DaemonPlexusContainerCapsuleFactory
extends PlexusContainerCapsuleFactory< extends PlexusContainerCapsuleFactory<
MavenOptions, MavenInvokerRequest<MavenOptions>, DefaultResidentMavenInvoker.LocalContext> { MavenOptions, MavenInvokerRequest<MavenOptions>, DefaultResidentMavenInvoker.LocalContext> {
@Override
protected Set<String> collectExportedArtifacts(
CoreExtensionEntry coreEntry, List<CoreExtensionEntry> extensionEntries) {
HashSet<String> result = new HashSet<>(super.collectExportedArtifacts(coreEntry, extensionEntries));
result.add("org.codehaus.plexus:plexus-interactivity-api");
return result;
}
@Override
protected Set<String> collectExportedPackages(
CoreExtensionEntry coreEntry, List<CoreExtensionEntry> extensionEntries) {
HashSet<String> result = new HashSet<>(super.collectExportedPackages(coreEntry, extensionEntries));
result.add("org.mvndaemon.mvnd.interactivity");
result.add("org.codehaus.plexus.components.interactivity");
return result;
}
@Override @Override
protected List<Path> parseExtClasspath(DefaultResidentMavenInvoker.LocalContext context) throws Exception { protected List<Path> parseExtClasspath(DefaultResidentMavenInvoker.LocalContext context) throws Exception {
return Stream.of(Environment.MVND_EXT_CLASSPATH.asString().split(",")) return Stream.of(Environment.MVND_EXT_CLASSPATH.asString().split(","))

View File

@@ -1,205 +0,0 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.maven.cli;
import java.io.ByteArrayOutputStream;
import java.io.PrintStream;
import java.io.PrintWriter;
import java.nio.charset.StandardCharsets;
import java.util.Locale;
import java.util.Set;
import java.util.StringTokenizer;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.apache.commons.cli.HelpFormatter;
import org.mvndaemon.mvnd.common.Environment;
import org.mvndaemon.mvnd.common.OptionType;
/**
* Combines the help message from the stock Maven with {@code mvnd}'s help message.
*/
public class MvndHelpFormatter {
private static final Pattern HTML_TAGS_PATTERN = Pattern.compile("<[^>]*>");
private static final Pattern COLUMNS_DETECTOR_PATTERN = Pattern.compile("^[ ]+[^s]");
private static final Pattern WS_PATTERN = Pattern.compile("\\s+");
static String toPlainText(String javadocText) {
return HTML_TAGS_PATTERN.matcher(javadocText).replaceAll("");
}
/**
* Returns Maven option descriptions combined with mvnd options descriptions
*
* @param cliManager The cli manager
* @return the string containing the help message
*/
public static String displayHelp(CommonsCliDaemonMavenOptions cliManager) {
int terminalWidth = getTerminalWidth();
final ByteArrayOutputStream baos = new ByteArrayOutputStream();
try (PrintStream out = new PrintStream(baos, false, StandardCharsets.UTF_8)) {
out.println();
PrintWriter pw = new PrintWriter(out);
HelpFormatter formatter = new HelpFormatter();
formatter.printHelp(
pw,
terminalWidth,
"mvnd [options] [<goal(s)>] [<phase(s)>]",
"\nOptions:",
cliManager.getOptions(),
1,
3,
"\n",
false);
pw.flush();
}
final String mvnHelp = baos.toString(StandardCharsets.UTF_8);
final Matcher m = COLUMNS_DETECTOR_PATTERN.matcher(mvnHelp);
final String indent = m.find() ? m.group() : " ";
final String lineSeparator = System.lineSeparator();
final StringBuilder help =
new StringBuilder(mvnHelp).append(lineSeparator).append("mvnd specific options:");
Environment.documentedEntries().forEach(entry -> {
final Environment env = entry.getEntry();
help.append(lineSeparator);
int indentPos = help.length() + indent.length();
int lineEnd = help.length() + terminalWidth;
spaces(help, HelpFormatter.DEFAULT_LEFT_PAD);
final String property = env.getProperty();
if (property != null) {
help.append("-D").append(property);
if (env.getType() != OptionType.VOID) {
help.append("=<")
.append(env.getType().name().toLowerCase(Locale.ROOT))
.append('>');
}
}
final Set<String> opts = env.getOptions();
if (!opts.isEmpty()) {
if (property != null) {
help.append(';');
}
boolean first = true;
for (String opt : opts) {
if (first) {
first = false;
} else {
help.append(',');
}
help.append(opt);
}
if (env.getType() != OptionType.VOID) {
help.append(" <")
.append(env.getType().name().toLowerCase(Locale.ROOT))
.append('>');
}
}
help.append(' ');
spaces(help, indentPos - help.length());
wrap(help, toPlainText(entry.getJavaDoc()), terminalWidth, lineEnd, indent);
if (env.isDocumentedAsDiscriminating()) {
indentedLine(help, terminalWidth, "This is a discriminating start parameter.", indent);
}
if (env.getDefault() != null) {
indentedLine(help, terminalWidth, "Default: " + env.getDefault(), indent);
}
if (env.getEnvironmentVariable() != null) {
indentedLine(help, terminalWidth, "Env. variable: " + env.getEnvironmentVariable(), indent);
}
});
help.append(lineSeparator).append(lineSeparator).append("mvnd value types:");
OptionType.documentedEntries().forEach(entry -> {
final OptionType type = entry.getEntry();
help.append(lineSeparator);
int indentPos = help.length() + indent.length();
int lineEnd = help.length() + terminalWidth;
spaces(help, HelpFormatter.DEFAULT_LEFT_PAD);
help.append(type.name().toLowerCase(Locale.ROOT));
spaces(help, indentPos - help.length());
wrap(help, toPlainText(entry.getJavaDoc()), terminalWidth, lineEnd, indent);
});
return help.toString();
}
private static int getTerminalWidth() {
int terminalWidth;
try {
terminalWidth = Environment.MVND_TERMINAL_WIDTH.asInt();
} catch (Exception e) {
terminalWidth = 80;
}
return terminalWidth;
}
private static void indentedLine(StringBuilder stringBuilder, int terminalWidth, String text, String indent) {
final int lineEnd = stringBuilder.length() + terminalWidth;
stringBuilder.append(System.lineSeparator()).append(indent);
wrap(stringBuilder, text, terminalWidth, lineEnd, indent);
}
/**
* Word-wrap the given {@code text} to the given {@link StringBuilder}
*
* @param stringBuilder the {@link StringBuilder} to append to
* @param text the text to wrap and append
* @param lineLength the preferred line length
* @param nextLineEnd the length of the {@code stringBuilder} at which the current line should end
* @param indent the indentation string
*/
static void wrap(StringBuilder stringBuilder, String text, int lineLength, int nextLineEnd, String indent) {
final StringTokenizer st = new StringTokenizer(text, " \t\n\r", true);
String lastWs = null;
while (st.hasMoreTokens()) {
final String token = st.nextToken();
if (WS_PATTERN.matcher(token).matches()) {
lastWs = token;
} else {
if (stringBuilder.length() + token.length() + (lastWs != null ? lastWs.length() : 0) < nextLineEnd) {
if (lastWs != null) {
stringBuilder.append(lastWs);
}
stringBuilder.append(token);
} else {
nextLineEnd = stringBuilder.length() + lineLength;
stringBuilder.append(System.lineSeparator()).append(indent).append(token);
}
lastWs = null;
}
}
}
/**
* Append {@code count} spaces to the given {@code stringBuilder}
*
* @param stringBuilder the {@link StringBuilder} to append to
* @param count the number of spaces to append
*/
static void spaces(StringBuilder stringBuilder, int count) {
stringBuilder.append(" ".repeat(Math.max(0, count)));
}
}