diff --git a/daemon/src/main/java/org/apache/maven/cli/CommonsCliDaemonMavenOptions.java b/daemon/src/main/java/org/apache/maven/cli/CommonsCliDaemonMavenOptions.java index b66ad5e0..eed140a6 100644 --- a/daemon/src/main/java/org/apache/maven/cli/CommonsCliDaemonMavenOptions.java +++ b/daemon/src/main/java/org/apache/maven/cli/CommonsCliDaemonMavenOptions.java @@ -18,18 +18,31 @@ */ 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.List; import java.util.ListIterator; +import java.util.Locale; 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.HelpFormatter; import org.apache.commons.cli.Option; import org.apache.commons.cli.Options; import org.apache.commons.cli.ParseException; 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.InterpolationException; +import org.mvndaemon.mvnd.common.Environment; +import org.mvndaemon.mvnd.common.OptionType; 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 { 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 protected void prepareOptions(Options options) { super.prepareOptions(options); @@ -89,5 +110,149 @@ public class CommonsCliDaemonMavenOptions extends CommonsCliMavenOptions impleme .desc("Use raw-streams for daemon communication") .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 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))); + } } } diff --git a/daemon/src/main/java/org/apache/maven/cli/DaemonMavenInvoker.java b/daemon/src/main/java/org/apache/maven/cli/DaemonMavenInvoker.java index 7afe41de..a13f463d 100644 --- a/daemon/src/main/java/org/apache/maven/cli/DaemonMavenInvoker.java +++ b/daemon/src/main/java/org/apache/maven/cli/DaemonMavenInvoker.java @@ -18,9 +18,11 @@ */ package org.apache.maven.cli; +import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.OutputStream; import java.io.PrintStream; +import java.io.PrintWriter; import java.nio.charset.StandardCharsets; import org.apache.maven.api.cli.Options; @@ -92,9 +94,11 @@ public class DaemonMavenInvoker extends DefaultResidentMavenInvoker { BuildEventListener buildEventListener = context.invokerRequest.parserRequest().lookup().lookup(BuildEventListener.class); if (invokerRequest.options().help().isPresent()) { - // TODO: ugly, cleanup - buildEventListener.log( - MvndHelpFormatter.displayHelp((CommonsCliDaemonMavenOptions) context.invokerRequest.options())); + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + try (PrintWriter pw = new PrintWriter(new PrintStream(baos), true, StandardCharsets.UTF_8)) { + context.invokerRequest.options().displayHelp(invokerRequest.parserRequest(), pw); + } + buildEventListener.log(baos.toString(StandardCharsets.UTF_8)); throw new ExitException(0); } if (invokerRequest.options().showVersionAndExit().isPresent()) { diff --git a/daemon/src/main/java/org/apache/maven/cli/DaemonPlexusContainerCapsuleFactory.java b/daemon/src/main/java/org/apache/maven/cli/DaemonPlexusContainerCapsuleFactory.java index 3c213e08..346faff6 100644 --- a/daemon/src/main/java/org/apache/maven/cli/DaemonPlexusContainerCapsuleFactory.java +++ b/daemon/src/main/java/org/apache/maven/cli/DaemonPlexusContainerCapsuleFactory.java @@ -20,19 +20,39 @@ package org.apache.maven.cli; import java.nio.file.Path; import java.nio.file.Paths; +import java.util.HashSet; import java.util.List; +import java.util.Set; import java.util.stream.Stream; import org.apache.maven.api.cli.mvn.MavenInvokerRequest; import org.apache.maven.api.cli.mvn.MavenOptions; import org.apache.maven.cling.invoker.PlexusContainerCapsuleFactory; import org.apache.maven.cling.invoker.mvn.resident.DefaultResidentMavenInvoker; +import org.apache.maven.extension.internal.CoreExtensionEntry; import org.mvndaemon.mvnd.common.Environment; public class DaemonPlexusContainerCapsuleFactory extends PlexusContainerCapsuleFactory< MavenOptions, MavenInvokerRequest, DefaultResidentMavenInvoker.LocalContext> { + @Override + protected Set collectExportedArtifacts( + CoreExtensionEntry coreEntry, List extensionEntries) { + HashSet result = new HashSet<>(super.collectExportedArtifacts(coreEntry, extensionEntries)); + result.add("org.codehaus.plexus:plexus-interactivity-api"); + return result; + } + + @Override + protected Set collectExportedPackages( + CoreExtensionEntry coreEntry, List extensionEntries) { + HashSet result = new HashSet<>(super.collectExportedPackages(coreEntry, extensionEntries)); + result.add("org.mvndaemon.mvnd.interactivity"); + result.add("org.codehaus.plexus.components.interactivity"); + return result; + } + @Override protected List parseExtClasspath(DefaultResidentMavenInvoker.LocalContext context) throws Exception { return Stream.of(Environment.MVND_EXT_CLASSPATH.asString().split(",")) diff --git a/daemon/src/main/java/org/apache/maven/cli/MvndHelpFormatter.java b/daemon/src/main/java/org/apache/maven/cli/MvndHelpFormatter.java deleted file mode 100644 index 6e898089..00000000 --- a/daemon/src/main/java/org/apache/maven/cli/MvndHelpFormatter.java +++ /dev/null @@ -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] [] []", - "\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 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))); - } -}