This is an automated email from the ASF dual-hosted git repository. gnodet pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/maven.git
The following commit(s) were added to refs/heads/master by this push: new 782e8679bf [MNG-7995] Switch to JLine to provide line editing (#1279) 782e8679bf is described below commit 782e8679bf487bcd6ad6ef36d1d1e92b00d418a4 Author: Guillaume Nodet <gno...@gmail.com> AuthorDate: Mon Jan 8 11:37:09 2024 +0100 [MNG-7995] Switch to JLine to provide line editing (#1279) --- apache-maven/pom.xml | 4 +- .../licenses/unrecognized-jline-3.25.0.txt | 35 + .../apache/maven/api/services/MessageBuilder.java | 107 ++- .../maven/api/services/MessageBuilderFactory.java | 12 +- .../maven/internal/impl/DefaultMessageBuilder.java | 100 +-- .../impl/DefaultMessageBuilderFactory.java | 6 +- .../main/resources/META-INF/maven/extension.xml | 4 + maven-embedder/pom.xml | 19 +- .../main/java/org/apache/maven/cli/CLIManager.java | 2 +- .../org/apache/maven/cli/CLIReportingUtils.java | 2 +- .../main/java/org/apache/maven/cli/MavenCli.java | 8 +- .../maven/cli/jansi/JansiMessageBuilder.java | 171 ---- .../cli/jansi/JansiMessageBuilderFactory.java | 55 -- .../org/apache/maven/cli/jansi/MessageUtils.java | 201 ----- .../java/org/apache/maven/cli/jansi/Style.java | 148 ---- .../cli/jline/JLineMessageBuilderFactory.java | 294 +++++++ .../org/apache/maven/cli/jline/MessageUtils.java | 101 +++ .../transfer/AbstractMavenTransferListener.java | 45 +- .../cli/transfer/ConsoleMavenTransferListener.java | 6 +- .../apache/maven/cli/transfer/FileSizeFormat.java | 34 + .../src/main/java/org/fusesource/jansi/Ansi.java | 952 +++++++++++++++++++++ .../java/org/apache/maven/cli/MavenCliTest.java | 8 +- .../maven/cli/event/ExecutionEventLoggerTest.java | 6 +- .../transfer/ConsoleMavenTransferListenerTest.java | 2 + .../java/org/slf4j/impl/MavenSimpleLogger.java | 2 +- .../java/org/slf4j/impl/MavenSimpleLoggerTest.java | 16 + pom.xml | 17 +- 27 files changed, 1601 insertions(+), 756 deletions(-) diff --git a/apache-maven/pom.xml b/apache-maven/pom.xml index 5f47f9dc57..eb198048e4 100644 --- a/apache-maven/pom.xml +++ b/apache-maven/pom.xml @@ -98,8 +98,8 @@ under the License. <artifactId>maven-slf4j-provider</artifactId> </dependency> <dependency> - <groupId>org.fusesource.jansi</groupId> - <artifactId>jansi</artifactId> + <groupId>org.jline</groupId> + <artifactId>jline</artifactId> </dependency> <!-- DI Runtime --> diff --git a/apache-maven/src/main/appended-resources/licenses/unrecognized-jline-3.25.0.txt b/apache-maven/src/main/appended-resources/licenses/unrecognized-jline-3.25.0.txt new file mode 100644 index 0000000000..b62fe45716 --- /dev/null +++ b/apache-maven/src/main/appended-resources/licenses/unrecognized-jline-3.25.0.txt @@ -0,0 +1,35 @@ +Copyright (c) 2002-2023, the original author or authors. +All rights reserved. + +https://opensource.org/licenses/BSD-3-Clause + +Redistribution and use in source and binary forms, with or +without modification, are permitted provided that the following +conditions are met: + +Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + +Redistributions in binary form must reproduce the above copyright +notice, this list of conditions and the following disclaimer +in the documentation and/or other materials provided with +the distribution. + +Neither the name of JLine nor the names of its contributors +may be used to endorse or promote products derived from this +software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, +BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY +AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO +EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, +OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED +AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING +IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED +OF THE POSSIBILITY OF SUCH DAMAGE. + diff --git a/api/maven-api-core/src/main/java/org/apache/maven/api/services/MessageBuilder.java b/api/maven-api-core/src/main/java/org/apache/maven/api/services/MessageBuilder.java index d00ad8d954..8a996032e2 100644 --- a/api/maven-api-core/src/main/java/org/apache/maven/api/services/MessageBuilder.java +++ b/api/maven-api-core/src/main/java/org/apache/maven/api/services/MessageBuilder.java @@ -26,7 +26,7 @@ import org.apache.maven.api.annotations.Nonnull; * @since 4.0.0 * @see MessageBuilderFactory */ -public interface MessageBuilder { +public interface MessageBuilder extends Appendable { /** * Append message content in trace style. @@ -36,7 +36,9 @@ public interface MessageBuilder { * @return the current builder */ @Nonnull - MessageBuilder trace(Object message); + default MessageBuilder trace(Object message) { + return style(".trace:-bold,f:magenta", message); + } /** * Append message content in debug style. @@ -46,7 +48,9 @@ public interface MessageBuilder { * @return the current builder */ @Nonnull - MessageBuilder debug(Object message); + default MessageBuilder debug(Object message) { + return style(".debug:-bold,f:cyan", message); + } /** * Append message content in info style. @@ -56,7 +60,9 @@ public interface MessageBuilder { * @return the current builder */ @Nonnull - MessageBuilder info(Object message); + default MessageBuilder info(Object message) { + return style(".info:-bold,f:blue", message); + } /** * Append message content in warning style. @@ -66,7 +72,9 @@ public interface MessageBuilder { * @return the current builder */ @Nonnull - MessageBuilder warning(Object message); + default MessageBuilder warning(Object message) { + return style(".warning:-bold,f:yellow", message); + } /** * Append message content in error style. @@ -76,7 +84,9 @@ public interface MessageBuilder { * @return the current builder */ @Nonnull - MessageBuilder error(Object message); + default MessageBuilder error(Object message) { + return style(".error:-bold,f:red", message); + } /** * Append message content in success style. @@ -86,7 +96,9 @@ public interface MessageBuilder { * @return the current builder */ @Nonnull - MessageBuilder success(Object message); + default MessageBuilder success(Object message) { + return style(".success:-bold,f:green", message); + } /** * Append message content in failure style. @@ -96,7 +108,9 @@ public interface MessageBuilder { * @return the current builder */ @Nonnull - MessageBuilder failure(Object message); + default MessageBuilder failure(Object message) { + return style(".failure:-bold,f:red", message); + } /** * Append message content in strong style. @@ -106,7 +120,9 @@ public interface MessageBuilder { * @return the current builder */ @Nonnull - MessageBuilder strong(Object message); + default MessageBuilder strong(Object message) { + return style(".strong:-bold", message); + } /** * Append message content in mojo style. @@ -116,7 +132,9 @@ public interface MessageBuilder { * @return the current builder */ @Nonnull - MessageBuilder mojo(Object message); + default MessageBuilder mojo(Object message) { + return style(".mojo:-f:green", message); + } /** * Append message content in project style. @@ -126,11 +144,35 @@ public interface MessageBuilder { * @return the current builder */ @Nonnull - MessageBuilder project(Object message); + default MessageBuilder project(Object message) { + return style(".project:-f:cyan", message); + } + + @Nonnull + default MessageBuilder style(String style, Object message) { + return style(style).a(message).resetStyle(); + } + + MessageBuilder style(String style); + + MessageBuilder resetStyle(); // // message building methods modelled after Ansi methods // + + @Nonnull + @Override + MessageBuilder append(CharSequence cs); + + @Nonnull + @Override + MessageBuilder append(CharSequence cs, int start, int end); + + @Nonnull + @Override + MessageBuilder append(char c); + /** * Append content to the message buffer. * @@ -140,7 +182,9 @@ public interface MessageBuilder { * @return the current builder */ @Nonnull - MessageBuilder a(char[] value, int offset, int len); + default MessageBuilder a(char[] value, int offset, int len) { + return append(String.valueOf(value, offset, len)); + } /** * Append content to the message buffer. @@ -149,7 +193,9 @@ public interface MessageBuilder { * @return the current builder */ @Nonnull - MessageBuilder a(char[] value); + default MessageBuilder a(char[] value) { + return append(String.valueOf(value)); + } /** * Append content to the message buffer. @@ -160,7 +206,9 @@ public interface MessageBuilder { * @return the current builder */ @Nonnull - MessageBuilder a(CharSequence value, int start, int end); + default MessageBuilder a(CharSequence value, int start, int end) { + return append(value, start, end); + } /** * Append content to the message buffer. @@ -169,7 +217,9 @@ public interface MessageBuilder { * @return the current builder */ @Nonnull - MessageBuilder a(CharSequence value); + default MessageBuilder a(CharSequence value) { + return append(value); + } /** * Append content to the message buffer. @@ -178,7 +228,9 @@ public interface MessageBuilder { * @return the current builder */ @Nonnull - MessageBuilder a(Object value); + default MessageBuilder a(Object value) { + return append(String.valueOf(value)); + } /** * Append newline to the message buffer. @@ -186,7 +238,9 @@ public interface MessageBuilder { * @return the current builder */ @Nonnull - MessageBuilder newline(); + default MessageBuilder newline() { + return append(System.lineSeparator()); + } /** * Append formatted content to the buffer. @@ -197,20 +251,23 @@ public interface MessageBuilder { * @return the current builder */ @Nonnull - MessageBuilder format(String pattern, Object... args); + default MessageBuilder format(String pattern, Object... args) { + return append(String.format(pattern, args)); + } /** - * Return the built message. + * Set the buffer length. * - * @return the message + * @param length the new length + * @return the current builder */ - @Nonnull - String build(); + MessageBuilder setLength(int length); /** - * Set the buffer length. + * Return the built message. * - * @param length the new length + * @return the message */ - void setLength(int length); + @Nonnull + String build(); } diff --git a/api/maven-api-core/src/main/java/org/apache/maven/api/services/MessageBuilderFactory.java b/api/maven-api-core/src/main/java/org/apache/maven/api/services/MessageBuilderFactory.java index ac4691ecfb..9a595ae110 100644 --- a/api/maven-api-core/src/main/java/org/apache/maven/api/services/MessageBuilderFactory.java +++ b/api/maven-api-core/src/main/java/org/apache/maven/api/services/MessageBuilderFactory.java @@ -48,21 +48,11 @@ public interface MessageBuilderFactory extends Service { @Nonnull MessageBuilder builder(); - /** - * Creates a new message builder backed by the given string builder. - * @param stringBuilder a string builder - * @return a new message builder - */ - @Nonnull - MessageBuilder builder(@Nonnull StringBuilder stringBuilder); - /** * Creates a new message builder of the specified size. * @param size the initial size of the message builder buffer * @return a new message builder */ @Nonnull - default MessageBuilder builder(int size) { - return builder(new StringBuilder(size)); - } + MessageBuilder builder(int size); } diff --git a/maven-core/src/main/java/org/apache/maven/internal/impl/DefaultMessageBuilder.java b/maven-core/src/main/java/org/apache/maven/internal/impl/DefaultMessageBuilder.java index 08f28d9553..692b1e37e5 100644 --- a/maven-core/src/main/java/org/apache/maven/internal/impl/DefaultMessageBuilder.java +++ b/maven-core/src/main/java/org/apache/maven/internal/impl/DefaultMessageBuilder.java @@ -36,111 +36,36 @@ public class DefaultMessageBuilder implements MessageBuilder { } @Override - @Nonnull - public MessageBuilder trace(Object o) { - return a(o); - } - - @Override - @Nonnull - public MessageBuilder debug(Object o) { - return a(o); - } - - @Override - @Nonnull - public MessageBuilder info(Object o) { - return a(o); - } - - @Override - @Nonnull - public MessageBuilder warning(Object o) { - return a(o); - } - - @Override - @Nonnull - public MessageBuilder error(Object o) { - return a(o); - } - - @Override - @Nonnull - public MessageBuilder success(Object o) { - return a(o); - } - - @Override - @Nonnull - public MessageBuilder failure(Object o) { - return a(o); - } - - @Override - @Nonnull - public MessageBuilder strong(Object o) { - return a(o); - } - - @Override - @Nonnull - public MessageBuilder mojo(Object o) { - return a(o); - } - - @Override - @Nonnull - public MessageBuilder project(Object o) { - return a(o); - } - - @Override - @Nonnull - public MessageBuilder a(char[] chars, int i, int i1) { - buffer.append(chars, i, i1); - return this; - } - - @Override - @Nonnull - public MessageBuilder a(char[] chars) { - buffer.append(chars); + public MessageBuilder style(String style) { return this; } @Override - @Nonnull - public MessageBuilder a(CharSequence charSequence, int i, int i1) { - buffer.append(charSequence, i, i1); + public MessageBuilder resetStyle() { return this; } @Override - @Nonnull - public MessageBuilder a(CharSequence charSequence) { - buffer.append(charSequence); + public MessageBuilder append(CharSequence cs) { + buffer.append(cs); return this; } @Override - @Nonnull - public MessageBuilder a(Object o) { - buffer.append(o); + public MessageBuilder append(CharSequence cs, int start, int end) { + buffer.append(cs, start, end); return this; } @Override - @Nonnull - public MessageBuilder newline() { - buffer.append(System.getProperty("line.separator")); + public MessageBuilder append(char c) { + buffer.append(c); return this; } @Override - @Nonnull - public MessageBuilder format(String s, Object... objects) { - buffer.append(String.format(s, objects)); + public MessageBuilder setLength(int length) { + buffer.setLength(length); return this; } @@ -154,9 +79,4 @@ public class DefaultMessageBuilder implements MessageBuilder { public String toString() { return build(); } - - @Override - public void setLength(int length) { - buffer.setLength(length); - } } diff --git a/maven-core/src/main/java/org/apache/maven/internal/impl/DefaultMessageBuilderFactory.java b/maven-core/src/main/java/org/apache/maven/internal/impl/DefaultMessageBuilderFactory.java index ea4c94f492..bb2f6c982e 100644 --- a/maven-core/src/main/java/org/apache/maven/internal/impl/DefaultMessageBuilderFactory.java +++ b/maven-core/src/main/java/org/apache/maven/internal/impl/DefaultMessageBuilderFactory.java @@ -22,8 +22,6 @@ import javax.inject.Inject; import javax.inject.Named; import javax.inject.Singleton; -import java.util.Objects; - import org.apache.maven.api.annotations.Experimental; import org.apache.maven.api.annotations.Nonnull; import org.apache.maven.api.services.MessageBuilder; @@ -57,7 +55,7 @@ public class DefaultMessageBuilderFactory implements MessageBuilderFactory { @Override @Nonnull - public MessageBuilder builder(@Nonnull StringBuilder stringBuilder) { - return new DefaultMessageBuilder(Objects.requireNonNull(stringBuilder)); + public MessageBuilder builder(int size) { + return new DefaultMessageBuilder(new StringBuilder(size)); } } diff --git a/maven-core/src/main/resources/META-INF/maven/extension.xml b/maven-core/src/main/resources/META-INF/maven/extension.xml index 1823336d83..8a87ea31f3 100644 --- a/maven-core/src/main/resources/META-INF/maven/extension.xml +++ b/maven-core/src/main/resources/META-INF/maven/extension.xml @@ -99,6 +99,10 @@ under the License. <exportedPackage>org.codehaus.plexus.logging</exportedPackage> <exportedPackage>org.codehaus.plexus.personality</exportedPackage> + <!-- plexus-interactivity-api --> + <exportedPackage>org.codehaus.plexus.components.interactivity</exportedPackage> + <exportedPackage>org.fusesource.jansi.Ansi</exportedPackage> + <!-- javax.inject (JSR-330) --> <exportedPackage>javax.inject.*</exportedPackage> <!-- javax.enterprise.inject (JSR-299): Must never be exported if needed at plugin level, plugin adds it diff --git a/maven-embedder/pom.xml b/maven-embedder/pom.xml index be4ee0bee3..fcda8edbd4 100644 --- a/maven-embedder/pom.xml +++ b/maven-embedder/pom.xml @@ -88,6 +88,16 @@ under the License. <groupId>org.codehaus.plexus</groupId> <artifactId>plexus-interpolation</artifactId> </dependency> + <dependency> + <groupId>org.codehaus.plexus</groupId> + <artifactId>plexus-interactivity-api</artifactId> + <exclusions> + <exclusion> + <groupId>*</groupId> + <artifactId>*</artifactId> + </exclusion> + </exclusions> + </dependency> <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-api</artifactId> @@ -97,11 +107,6 @@ under the License. <artifactId>commons-cli</artifactId> </dependency> - <dependency> - <groupId>org.fusesource.jansi</groupId> - <artifactId>jansi</artifactId> - <optional>true</optional> - </dependency> <dependency> <groupId>ch.qos.logback</groupId> <artifactId>logback-classic</artifactId> @@ -112,6 +117,10 @@ under the License. <artifactId>slf4j-simple</artifactId> <optional>true</optional> </dependency> + <dependency> + <groupId>org.jline</groupId> + <artifactId>jline</artifactId> + </dependency> <dependency> <groupId>javax.inject</groupId> diff --git a/maven-embedder/src/main/java/org/apache/maven/cli/CLIManager.java b/maven-embedder/src/main/java/org/apache/maven/cli/CLIManager.java index baf1c2127c..cc356cca3c 100644 --- a/maven-embedder/src/main/java/org/apache/maven/cli/CLIManager.java +++ b/maven-embedder/src/main/java/org/apache/maven/cli/CLIManager.java @@ -28,7 +28,7 @@ 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.cli.jansi.MessageUtils; +import org.apache.maven.cli.jline.MessageUtils; /** */ diff --git a/maven-embedder/src/main/java/org/apache/maven/cli/CLIReportingUtils.java b/maven-embedder/src/main/java/org/apache/maven/cli/CLIReportingUtils.java index 5fd319871d..a5538ca93c 100644 --- a/maven-embedder/src/main/java/org/apache/maven/cli/CLIReportingUtils.java +++ b/maven-embedder/src/main/java/org/apache/maven/cli/CLIReportingUtils.java @@ -25,7 +25,7 @@ import java.util.Date; import java.util.Locale; import java.util.Properties; -import org.apache.maven.cli.jansi.MessageUtils; +import org.apache.maven.cli.jline.MessageUtils; import org.apache.maven.utils.Os; import org.slf4j.Logger; diff --git a/maven-embedder/src/main/java/org/apache/maven/cli/MavenCli.java b/maven-embedder/src/main/java/org/apache/maven/cli/MavenCli.java index 67e458d829..a2b51a9c47 100644 --- a/maven-embedder/src/main/java/org/apache/maven/cli/MavenCli.java +++ b/maven-embedder/src/main/java/org/apache/maven/cli/MavenCli.java @@ -58,8 +58,8 @@ import org.apache.maven.cli.event.ExecutionEventLogger; import org.apache.maven.cli.internal.BootstrapCoreExtensionManager; import org.apache.maven.cli.internal.extension.io.CoreExtensionsStaxReader; import org.apache.maven.cli.internal.extension.model.CoreExtension; -import org.apache.maven.cli.jansi.JansiMessageBuilderFactory; -import org.apache.maven.cli.jansi.MessageUtils; +import org.apache.maven.cli.jline.JLineMessageBuilderFactory; +import org.apache.maven.cli.jline.MessageUtils; import org.apache.maven.cli.logging.Slf4jConfiguration; import org.apache.maven.cli.logging.Slf4jConfigurationFactory; import org.apache.maven.cli.logging.Slf4jLoggerManager; @@ -188,7 +188,7 @@ public class MavenCli { // This supports painless invocation by the Verifier during embedded execution of the core ITs public MavenCli(ClassWorld classWorld) { this.classWorld = classWorld; - this.messageBuilderFactory = new JansiMessageBuilderFactory(); + this.messageBuilderFactory = new JLineMessageBuilderFactory(); } public static void main(String[] args) { @@ -1676,7 +1676,7 @@ public class MavenCli { // protected TransferListener getConsoleTransferListener(boolean printResourceNames) { - return new ConsoleMavenTransferListener(System.out, printResourceNames); + return new ConsoleMavenTransferListener(messageBuilderFactory, System.out, printResourceNames); } protected TransferListener getBatchTransferListener() { diff --git a/maven-embedder/src/main/java/org/apache/maven/cli/jansi/JansiMessageBuilder.java b/maven-embedder/src/main/java/org/apache/maven/cli/jansi/JansiMessageBuilder.java deleted file mode 100644 index be51f309b2..0000000000 --- a/maven-embedder/src/main/java/org/apache/maven/cli/jansi/JansiMessageBuilder.java +++ /dev/null @@ -1,171 +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.jansi; - -import org.apache.maven.api.annotations.Experimental; -import org.apache.maven.api.annotations.Nonnull; -import org.apache.maven.api.services.MessageBuilder; -import org.fusesource.jansi.Ansi; - -@Experimental -public class JansiMessageBuilder implements MessageBuilder { - private final Ansi ansi; - private StringBuilder sb; - - @SuppressWarnings("magicnumber") - public JansiMessageBuilder() { - this.sb = new StringBuilder(80); - this.ansi = Ansi.ansi(); - } - - public JansiMessageBuilder(StringBuilder sb) { - this.sb = sb; - this.ansi = Ansi.ansi(sb); - } - - @Override - @Nonnull - public MessageBuilder trace(Object o) { - return style(Style.TRACE, o); - } - - @Override - @Nonnull - public MessageBuilder debug(Object o) { - return style(Style.DEBUG, o); - } - - @Override - @Nonnull - public MessageBuilder info(Object o) { - return style(Style.INFO, o); - } - - @Override - @Nonnull - public MessageBuilder warning(Object o) { - return style(Style.WARNING, o); - } - - @Override - @Nonnull - public MessageBuilder error(Object o) { - return style(Style.ERROR, o); - } - - @Override - @Nonnull - public MessageBuilder success(Object o) { - return style(Style.SUCCESS, o); - } - - @Override - @Nonnull - public MessageBuilder failure(Object o) { - return style(Style.FAILURE, o); - } - - @Override - @Nonnull - public MessageBuilder strong(Object o) { - return style(Style.STRONG, o); - } - - @Override - @Nonnull - public MessageBuilder mojo(Object o) { - return style(Style.MOJO, o); - } - - @Override - @Nonnull - public MessageBuilder project(Object o) { - return style(Style.PROJECT, o); - } - - private MessageBuilder style(Style style, Object o) { - style.apply(ansi).a(o).reset(); - return this; - } - - @Override - @Nonnull - public MessageBuilder a(char[] chars, int i, int i1) { - ansi.a(chars, i, i1); - return this; - } - - @Override - @Nonnull - public MessageBuilder a(char[] chars) { - ansi.a(chars); - return this; - } - - @Override - @Nonnull - public MessageBuilder a(CharSequence charSequence, int i, int i1) { - ansi.a(charSequence, i, i1); - return this; - } - - @Override - @Nonnull - public MessageBuilder a(CharSequence charSequence) { - ansi.a(charSequence); - return this; - } - - @Override - @Nonnull - public MessageBuilder a(Object o) { - ansi.a(o); - return this; - } - - @Override - @Nonnull - public MessageBuilder newline() { - ansi.newline(); - return this; - } - - @Override - @Nonnull - public MessageBuilder format(String s, Object... objects) { - ansi.format(s, objects); - return this; - } - - @Override - @Nonnull - public String build() { - return ansi.toString(); - } - - @Override - public String toString() { - return build(); - } - - @Override - public void setLength(int length) { - sb.setLength(length); - } -} diff --git a/maven-embedder/src/main/java/org/apache/maven/cli/jansi/JansiMessageBuilderFactory.java b/maven-embedder/src/main/java/org/apache/maven/cli/jansi/JansiMessageBuilderFactory.java deleted file mode 100644 index c7d3225183..0000000000 --- a/maven-embedder/src/main/java/org/apache/maven/cli/jansi/JansiMessageBuilderFactory.java +++ /dev/null @@ -1,55 +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.jansi; - -import javax.inject.Named; -import javax.inject.Singleton; - -import org.apache.maven.api.annotations.Experimental; -import org.apache.maven.api.annotations.Nonnull; -import org.apache.maven.api.services.MessageBuilder; -import org.apache.maven.api.services.MessageBuilderFactory; - -@Experimental -@Named -@Singleton -public class JansiMessageBuilderFactory implements MessageBuilderFactory { - - @Override - public boolean isColorEnabled() { - return MessageUtils.isColorEnabled(); - } - - @Override - public int getTerminalWidth() { - return MessageUtils.getTerminalWidth(); - } - - @Override - @Nonnull - public MessageBuilder builder() { - return builder(new StringBuilder()); - } - - @Override - @Nonnull - public MessageBuilder builder(@Nonnull StringBuilder stringBuilder) { - return MessageUtils.builder(stringBuilder); - } -} diff --git a/maven-embedder/src/main/java/org/apache/maven/cli/jansi/MessageUtils.java b/maven-embedder/src/main/java/org/apache/maven/cli/jansi/MessageUtils.java deleted file mode 100644 index 3d0a5d41f7..0000000000 --- a/maven-embedder/src/main/java/org/apache/maven/cli/jansi/MessageUtils.java +++ /dev/null @@ -1,201 +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.jansi; - -import org.apache.maven.api.services.MessageBuilder; -import org.apache.maven.internal.impl.DefaultMessageBuilder; -import org.fusesource.jansi.Ansi; -import org.fusesource.jansi.AnsiConsole; -import org.fusesource.jansi.AnsiMode; - -/** - * Colored message utils, to manage colors. This is the core implementation of the - * {@link JansiMessageBuilderFactory} and {@link JansiMessageBuilder} classes. - * This class should not be used outside of maven-embedder and the public - * {@link org.apache.maven.api.services.MessageBuilderFactory} should be used instead. - * <p> - * Internally, <a href="http://fusesource.github.io/jansi/">Jansi</a> is used to render - * <a href="https://en.wikipedia.org/wiki/ANSI_escape_code#Colors">ANSI colors</a> on any platform. - * <p> - * - * @see MessageBuilder - * @see org.apache.maven.api.services.MessageBuilderFactory - * @see JansiMessageBuilderFactory - * @see JansiMessageBuilder - * @since 4.0.0 - */ -public class MessageUtils { - private static final boolean JANSI; - - /** Reference to the JVM shutdown hook, if registered */ - private static Thread shutdownHook; - - /** Synchronization monitor for the "uninstall" */ - private static final Object STARTUP_SHUTDOWN_MONITOR = new Object(); - - static { - boolean jansi = true; - try { - // Jansi is provided by Maven core since 3.5.0 - Class.forName("org.fusesource.jansi.Ansi"); - } catch (ClassNotFoundException cnfe) { - jansi = false; - } - JANSI = jansi; - } - - /** - * Install color support. - * This method is called by Maven core, and calling it is not necessary in plugins. - */ - public static void systemInstall() { - if (JANSI) { - AnsiConsole.systemInstall(); - } - } - - /** - * Undo a previous {@link #systemInstall()}. If {@link #systemInstall()} was called - * multiple times, {@link #systemUninstall()} must be called call the same number of times before - * it is actually uninstalled. - */ - public static void systemUninstall() { - synchronized (STARTUP_SHUTDOWN_MONITOR) { - doSystemUninstall(); - - // hook can only set when Jansi is true - if (shutdownHook != null) { - try { - Runtime.getRuntime().removeShutdownHook(shutdownHook); - } catch (IllegalStateException ex) { - // ignore - VM is already shutting down - } - } - } - } - - private static void doSystemUninstall() { - if (JANSI) { - AnsiConsole.systemUninstall(); - } - } - - /** - * Enables message color (if Jansi is available). - * @param flag to enable Jansi - */ - public static void setColorEnabled(boolean flag) { - if (JANSI) { - AnsiConsole.out().setMode(flag ? AnsiMode.Force : AnsiMode.Strip); - Ansi.setEnabled(flag); - System.setProperty( - AnsiConsole.JANSI_MODE, flag ? AnsiConsole.JANSI_MODE_FORCE : AnsiConsole.JANSI_MODE_STRIP); - boolean installed = AnsiConsole.isInstalled(); - while (AnsiConsole.isInstalled()) { - AnsiConsole.systemUninstall(); - } - if (installed) { - AnsiConsole.systemInstall(); - } - } - } - - /** - * Is message color enabled: requires Jansi available (through Maven) and the color has not been disabled. - * @return whether colored messages are enabled - */ - public static boolean isColorEnabled() { - return JANSI ? Ansi.isEnabled() : false; - } - - /** - * Create a default message buffer. - * @return a new buffer - */ - public static MessageBuilder builder() { - return builder(new StringBuilder()); - } - - /** - * Create a message buffer with an internal buffer of defined size. - * @param size size of the buffer - * @return a new buffer - */ - public static MessageBuilder builder(int size) { - return builder(new StringBuilder(size)); - } - - /** - * Create a message buffer with defined String builder. - * @param builder initial content of the message buffer - * @return a new buffer - */ - public static MessageBuilder builder(StringBuilder builder) { - return JANSI && isColorEnabled() ? new JansiMessageBuilder(builder) : new DefaultMessageBuilder(builder); - } - - /** - * Remove any ANSI code from a message (colors or other escape sequences). - * @param msg message eventually containing ANSI codes - * @return the message with ANSI codes removed - */ - public static String stripAnsiCodes(String msg) { - return msg.replaceAll("\u001B\\[[;\\d]*[ -/]*[@-~]", ""); - } - - /** - * Register a shutdown hook with the JVM runtime, uninstalling Ansi support on - * JVM shutdown unless is has already been uninstalled at that time. - * <p>Delegates to {@link #doSystemUninstall()} for the actual uninstall procedure - * - * @see Runtime#addShutdownHook(Thread) - * @see MessageUtils#systemUninstall() - * @see #doSystemUninstall() - */ - public static void registerShutdownHook() { - if (JANSI && shutdownHook == null) { - // No shutdown hook registered yet. - shutdownHook = new Thread() { - @Override - public void run() { - synchronized (STARTUP_SHUTDOWN_MONITOR) { - while (AnsiConsole.isInstalled()) { - doSystemUninstall(); - } - } - } - }; - Runtime.getRuntime().addShutdownHook(shutdownHook); - } - } - - /** - * Get the terminal width or -1 if the width cannot be determined. - * - * @return the terminal width - */ - public static int getTerminalWidth() { - if (JANSI) { - int width = AnsiConsole.getTerminalWidth(); - return width > 0 ? width : -1; - } else { - return -1; - } - } -} diff --git a/maven-embedder/src/main/java/org/apache/maven/cli/jansi/Style.java b/maven-embedder/src/main/java/org/apache/maven/cli/jansi/Style.java deleted file mode 100644 index 16ae2ad428..0000000000 --- a/maven-embedder/src/main/java/org/apache/maven/cli/jansi/Style.java +++ /dev/null @@ -1,148 +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.jansi; - -import java.util.Locale; - -import org.fusesource.jansi.Ansi; -import org.fusesource.jansi.Ansi.Color; - -/** - * Configurable message styles. - * @since 4.0.0 - */ -enum Style { - TRACE("bold,magenta"), - DEBUG("bold,cyan"), - INFO("bold,blue"), - WARNING("bold,yellow"), - ERROR("bold,red"), - SUCCESS("bold,green"), - FAILURE("bold,red"), - STRONG("bold"), - MOJO("green"), - PROJECT("cyan"); - - private final boolean bold; - - private final boolean bright; - - private final Color color; - - private final boolean bgBright; - - private final Color bgColor; - - Style(String defaultValue) { - boolean currentBold = false; - boolean currentBright = false; - Color currentColor = null; - boolean currentBgBright = false; - Color currentBgColor = null; - - String value = System.getProperty("style." + name().toLowerCase(Locale.ENGLISH), defaultValue) - .toLowerCase(Locale.ENGLISH); - - for (String token : value.split(",")) { - if ("bold".equals(token)) { - currentBold = true; - } else if (token.startsWith("bg")) { - token = token.substring(2); - if (token.startsWith("bright")) { - currentBgBright = true; - token = token.substring(6); - } - currentBgColor = toColor(token); - } else { - if (token.startsWith("bright")) { - currentBright = true; - token = token.substring(6); - } - currentColor = toColor(token); - } - } - - this.bold = currentBold; - this.bright = currentBright; - this.color = currentColor; - this.bgBright = currentBgBright; - this.bgColor = currentBgColor; - } - - private static Color toColor(String token) { - for (Color color : Color.values()) { - if (color.toString().equalsIgnoreCase(token)) { - return color; - } - } - return null; - } - - Ansi apply(Ansi ansi) { - if (bold) { - ansi.bold(); - } - if (color != null) { - if (bright) { - ansi.fgBright(color); - } else { - ansi.fg(color); - } - } - if (bgColor != null) { - if (bgBright) { - ansi.bgBright(bgColor); - } else { - ansi.bg(bgColor); - } - } - return ansi; - } - - @Override - public String toString() { - if (!bold && color == null && bgColor == null) { - return name(); - } - StringBuilder sb = new StringBuilder(name() + '='); - if (bold) { - sb.append("bold"); - } - if (color != null) { - if (sb.length() > 0) { - sb.append(','); - } - if (bright) { - sb.append("bright"); - } - sb.append(color.name()); - } - if (bgColor != null) { - if (sb.length() > 0) { - sb.append(','); - } - sb.append("bg"); - if (bgBright) { - sb.append("bright"); - } - sb.append(bgColor.name()); - } - return sb.toString(); - } -} diff --git a/maven-embedder/src/main/java/org/apache/maven/cli/jline/JLineMessageBuilderFactory.java b/maven-embedder/src/main/java/org/apache/maven/cli/jline/JLineMessageBuilderFactory.java new file mode 100644 index 0000000000..139b9bf4a4 --- /dev/null +++ b/maven-embedder/src/main/java/org/apache/maven/cli/jline/JLineMessageBuilderFactory.java @@ -0,0 +1,294 @@ +/* + * 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.jline; + +import javax.annotation.Priority; +import javax.inject.Named; +import javax.inject.Singleton; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +import org.apache.maven.api.annotations.Experimental; +import org.apache.maven.api.services.MessageBuilder; +import org.apache.maven.api.services.MessageBuilderFactory; +import org.codehaus.plexus.components.interactivity.InputHandler; +import org.codehaus.plexus.components.interactivity.OutputHandler; +import org.codehaus.plexus.components.interactivity.Prompter; +import org.codehaus.plexus.components.interactivity.PrompterException; +import org.jline.utils.AttributedStringBuilder; +import org.jline.utils.AttributedStyle; +import org.jline.utils.StyleResolver; + +import static org.jline.utils.AttributedStyle.DEFAULT; + +@Experimental +@Named +@Singleton +@Priority(10) +public class JLineMessageBuilderFactory implements MessageBuilderFactory, Prompter, InputHandler, OutputHandler { + + private final StyleResolver resolver; + + public JLineMessageBuilderFactory() { + this.resolver = new MavenStyleResolver(); + } + + @Override + public boolean isColorEnabled() { + return false; + } + + @Override + public int getTerminalWidth() { + return MessageUtils.getTerminalWidth(); + } + + @Override + public MessageBuilder builder() { + return new JlineMessageBuilder(); + } + + @Override + public MessageBuilder builder(int size) { + return new JlineMessageBuilder(size); + } + + @Override + public String readLine() throws IOException { + return doPrompt(null, true); + } + + @Override + public String readPassword() throws IOException { + return doPrompt(null, true); + } + + @Override + public List<String> readMultipleLines() throws IOException { + List<String> lines = new ArrayList<>(); + for (String line = this.readLine(); line != null && !line.isEmpty(); line = readLine()) { + lines.add(line); + } + return lines; + } + + @Override + public void write(String line) throws IOException { + doDisplay(line); + } + + @Override + public void writeLine(String line) throws IOException { + doDisplay(line + System.lineSeparator()); + } + + @Override + public String prompt(String message) throws PrompterException { + return prompt(message, null, null); + } + + @Override + public String prompt(String message, String defaultReply) throws PrompterException { + return prompt(message, null, defaultReply); + } + + @Override + public String prompt(String message, List possibleValues) throws PrompterException { + return prompt(message, possibleValues, null); + } + + @Override + public String prompt(String message, List possibleValues, String defaultReply) throws PrompterException { + return doPrompt(message, possibleValues, defaultReply, false); + } + + @Override + public String promptForPassword(String message) throws PrompterException { + return doPrompt(message, null, null, true); + } + + @Override + public void showMessage(String message) throws PrompterException { + try { + doDisplay(message); + } catch (IOException e) { + throw new PrompterException("Failed to present prompt", e); + } + } + + String doPrompt(String message, List<Object> possibleValues, String defaultReply, boolean password) + throws PrompterException { + String formattedMessage = formatMessage(message, possibleValues, defaultReply); + String line; + do { + try { + line = doPrompt(formattedMessage, password); + if (line == null && defaultReply == null) { + throw new IOException("EOF"); + } + } catch (IOException e) { + throw new PrompterException("Failed to prompt user", e); + } + if (line == null || line.isEmpty()) { + line = defaultReply; + } + if (line != null && (possibleValues != null && !possibleValues.contains(line))) { + try { + doDisplay("Invalid selection.\n"); + } catch (IOException e) { + throw new PrompterException("Failed to present feedback", e); + } + } + } while (line == null || (possibleValues != null && !possibleValues.contains(line))); + return line; + } + + private String formatMessage(String message, List<Object> possibleValues, String defaultReply) { + StringBuilder formatted = new StringBuilder(message.length() * 2); + formatted.append(message); + if (possibleValues != null && !possibleValues.isEmpty()) { + formatted.append(" ("); + for (Iterator<?> it = possibleValues.iterator(); it.hasNext(); ) { + String possibleValue = String.valueOf(it.next()); + formatted.append(possibleValue); + if (it.hasNext()) { + formatted.append('/'); + } + } + formatted.append(')'); + } + if (defaultReply != null) { + formatted.append(' ').append(defaultReply).append(": "); + } + return formatted.toString(); + } + + private void doDisplay(String message) throws IOException { + try { + MessageUtils.terminal.writer().print(message); + MessageUtils.terminal.flush(); + } catch (Exception e) { + throw new IOException("Unable to display message", e); + } + } + + private String doPrompt(String message, boolean password) throws IOException { + try { + return MessageUtils.reader.readLine(message != null ? message + ": " : null, password ? '*' : null); + } catch (Exception e) { + throw new IOException("Unable to prompt user", e); + } + } + + class JlineMessageBuilder implements MessageBuilder { + + final AttributedStringBuilder builder; + + JlineMessageBuilder() { + builder = new AttributedStringBuilder(); + } + + JlineMessageBuilder(int size) { + builder = new AttributedStringBuilder(size); + } + + @Override + public MessageBuilder style(String style) { + if (MessageUtils.isColorEnabled()) { + builder.style(resolver.resolve(style)); + } + return this; + } + + @Override + public MessageBuilder resetStyle() { + builder.style(DEFAULT); + return this; + } + + @Override + public MessageBuilder append(CharSequence cs) { + builder.append(cs); + return this; + } + + @Override + public MessageBuilder append(CharSequence cs, int start, int end) { + builder.append(cs, start, end); + return this; + } + + @Override + public MessageBuilder append(char c) { + builder.append(c); + return this; + } + + @Override + public MessageBuilder setLength(int length) { + builder.setLength(length); + return this; + } + + @Override + public String build() { + return builder.toAnsi(MessageUtils.terminal); + } + + @Override + public String toString() { + return build(); + } + } + + static class MavenStyleResolver extends StyleResolver { + + private final Map<String, AttributedStyle> styles = new ConcurrentHashMap<>(); + + MavenStyleResolver() { + super(s -> System.getProperty("style." + s)); + } + + @Override + public AttributedStyle resolve(String spec) { + return styles.computeIfAbsent(spec, this::doResolve); + } + + @Override + public AttributedStyle resolve(String spec, String defaultSpec) { + return resolve(defaultSpec != null ? spec + ":-" + defaultSpec : spec); + } + + private AttributedStyle doResolve(String spec) { + String def = null; + int i = spec.indexOf(":-"); + if (i != -1) { + String[] parts = spec.split(":-"); + spec = parts[0].trim(); + def = parts[1].trim(); + } + return super.resolve(spec, def); + } + } +} diff --git a/maven-embedder/src/main/java/org/apache/maven/cli/jline/MessageUtils.java b/maven-embedder/src/main/java/org/apache/maven/cli/jline/MessageUtils.java new file mode 100644 index 0000000000..d1fb0f94e3 --- /dev/null +++ b/maven-embedder/src/main/java/org/apache/maven/cli/jline/MessageUtils.java @@ -0,0 +1,101 @@ +/* + * 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.jline; + +import java.io.IOError; +import java.io.IOException; +import java.io.PrintStream; + +import org.apache.maven.api.services.MessageBuilder; +import org.apache.maven.api.services.MessageBuilderFactory; +import org.jline.jansi.AnsiConsole; +import org.jline.reader.LineReader; +import org.jline.reader.LineReaderBuilder; +import org.jline.terminal.Terminal; +import org.jline.terminal.TerminalBuilder; + +public class MessageUtils { + + static Terminal terminal; + static LineReader reader; + static MessageBuilderFactory messageBuilderFactory = new JLineMessageBuilderFactory(); + static boolean colorEnabled = true; + static Thread shutdownHook; + static final Object STARTUP_SHUTDOWN_MONITOR = new Object(); + + static PrintStream prevOut; + static PrintStream prevErr; + + public static void systemInstall() { + try { + terminal = TerminalBuilder.builder().name("Maven").dumb(true).build(); + reader = LineReaderBuilder.builder().terminal(terminal).build(); + AnsiConsole.setTerminal(terminal); + AnsiConsole.systemInstall(); + } catch (IOException e) { + throw new IOError(e); + } + } + + public static void registerShutdownHook() { + if (shutdownHook == null) { + shutdownHook = new Thread(() -> { + synchronized (MessageUtils.STARTUP_SHUTDOWN_MONITOR) { + MessageUtils.doSystemUninstall(); + } + }); + Runtime.getRuntime().addShutdownHook(shutdownHook); + } + } + + public static void systemUninstall() { + doSystemUninstall(); + if (shutdownHook != null) { + try { + Runtime.getRuntime().removeShutdownHook(shutdownHook); + } catch (IllegalStateException var3) { + // ignore + } + } + } + + private static void doSystemUninstall() { + try { + AnsiConsole.systemUninstall(); + } finally { + terminal = null; + } + } + + public static void setColorEnabled(boolean enabled) { + colorEnabled = enabled; + } + + public static boolean isColorEnabled() { + return colorEnabled && terminal != null; + } + + public static int getTerminalWidth() { + return terminal != null ? terminal.getWidth() : -1; + } + + public static MessageBuilder builder() { + return messageBuilderFactory.builder(); + } +} diff --git a/maven-embedder/src/main/java/org/apache/maven/cli/transfer/AbstractMavenTransferListener.java b/maven-embedder/src/main/java/org/apache/maven/cli/transfer/AbstractMavenTransferListener.java index bc6526806c..c38e411e0d 100644 --- a/maven-embedder/src/main/java/org/apache/maven/cli/transfer/AbstractMavenTransferListener.java +++ b/maven-embedder/src/main/java/org/apache/maven/cli/transfer/AbstractMavenTransferListener.java @@ -21,7 +21,8 @@ package org.apache.maven.cli.transfer; import java.io.PrintStream; import java.util.Locale; -import org.apache.maven.cli.jansi.MessageUtils; +import org.apache.maven.api.services.MessageBuilder; +import org.apache.maven.api.services.MessageBuilderFactory; import org.eclipse.aether.transfer.AbstractTransferListener; import org.eclipse.aether.transfer.TransferCancelledException; import org.eclipse.aether.transfer.TransferEvent; @@ -31,31 +32,27 @@ import org.eclipse.aether.transfer.TransferResource; * AbstractMavenTransferListener */ public abstract class AbstractMavenTransferListener extends AbstractTransferListener { + public static final String STYLE = ".transfer:-faint"; - private static final String ESC = "\u001B"; - private static final String ANSI_DARK_SET = ESC + "[90m"; - private static final String ANSI_DARK_RESET = ESC + "[0m"; + protected final MessageBuilderFactory messageBuilderFactory; + protected final PrintStream out; - protected PrintStream out; - - protected AbstractMavenTransferListener(PrintStream out) { + protected AbstractMavenTransferListener(MessageBuilderFactory messageBuilderFactory, PrintStream out) { + this.messageBuilderFactory = messageBuilderFactory; this.out = out; } @Override public void transferInitiated(TransferEvent event) { - String darkOn = MessageUtils.isColorEnabled() ? ANSI_DARK_SET : ""; - String darkOff = MessageUtils.isColorEnabled() ? ANSI_DARK_RESET : ""; - String action = event.getRequestType() == TransferEvent.RequestType.PUT ? "Uploading" : "Downloading"; String direction = event.getRequestType() == TransferEvent.RequestType.PUT ? "to" : "from"; TransferResource resource = event.getResource(); - StringBuilder message = new StringBuilder(); - message.append(darkOn).append(action).append(' ').append(direction).append(' '); - message.append(darkOff).append(resource.getRepositoryId()); - message.append(darkOn).append(": ").append(resource.getRepositoryUrl()); - message.append(darkOff).append(resource.getResourceName()); + MessageBuilder message = messageBuilderFactory.builder(); + message.style(STYLE).append(action).append(' ').append(direction).append(' '); + message.resetStyle().append(resource.getRepositoryId()); + message.style(STYLE).append(": ").append(resource.getRepositoryUrl()); + message.resetStyle().append(resource.getResourceName()); out.println(message.toString()); } @@ -70,9 +67,6 @@ public abstract class AbstractMavenTransferListener extends AbstractTransferList @Override public void transferSucceeded(TransferEvent event) { - String darkOn = MessageUtils.isColorEnabled() ? ANSI_DARK_SET : ""; - String darkOff = MessageUtils.isColorEnabled() ? ANSI_DARK_RESET : ""; - String action = (event.getRequestType() == TransferEvent.RequestType.PUT ? "Uploaded" : "Downloaded"); String direction = event.getRequestType() == TransferEvent.RequestType.PUT ? "to" : "from"; @@ -80,13 +74,12 @@ public abstract class AbstractMavenTransferListener extends AbstractTransferList long contentLength = event.getTransferredBytes(); FileSizeFormat format = new FileSizeFormat(Locale.ENGLISH); - StringBuilder message = new StringBuilder(); - message.append(action).append(darkOn).append(' ').append(direction).append(' '); - message.append(darkOff).append(resource.getRepositoryId()); - message.append(darkOn).append(": ").append(resource.getRepositoryUrl()); - message.append(darkOff).append(resource.getResourceName()); - message.append(darkOn).append(" ("); - format.format(message, contentLength); + MessageBuilder message = messageBuilderFactory.builder(); + message.append(action).style(STYLE).append(' ').append(direction).append(' '); + message.resetStyle().append(resource.getRepositoryId()); + message.style(STYLE).append(": ").append(resource.getRepositoryUrl()); + message.resetStyle().append(resource.getResourceName()); + message.style(STYLE).append(" (").append(format.format(contentLength)); long duration = System.currentTimeMillis() - resource.getTransferStartTime(); if (duration > 0L) { @@ -96,7 +89,7 @@ public abstract class AbstractMavenTransferListener extends AbstractTransferList message.append("/s"); } - message.append(')').append(darkOff); + message.append(')').resetStyle(); out.println(message.toString()); } } diff --git a/maven-embedder/src/main/java/org/apache/maven/cli/transfer/ConsoleMavenTransferListener.java b/maven-embedder/src/main/java/org/apache/maven/cli/transfer/ConsoleMavenTransferListener.java index 78addd1349..2d54f663f5 100644 --- a/maven-embedder/src/main/java/org/apache/maven/cli/transfer/ConsoleMavenTransferListener.java +++ b/maven-embedder/src/main/java/org/apache/maven/cli/transfer/ConsoleMavenTransferListener.java @@ -24,6 +24,7 @@ import java.util.LinkedHashMap; import java.util.Locale; import java.util.Map; +import org.apache.maven.api.services.MessageBuilderFactory; import org.eclipse.aether.transfer.TransferCancelledException; import org.eclipse.aether.transfer.TransferEvent; import org.eclipse.aether.transfer.TransferResource; @@ -41,8 +42,9 @@ public class ConsoleMavenTransferListener extends AbstractMavenTransferListener private boolean printResourceNames; private int lastLength; - public ConsoleMavenTransferListener(PrintStream out, boolean printResourceNames) { - super(out); + public ConsoleMavenTransferListener( + MessageBuilderFactory messageBuilderFactory, PrintStream out, boolean printResourceNames) { + super(messageBuilderFactory, out); this.printResourceNames = printResourceNames; } diff --git a/maven-embedder/src/main/java/org/apache/maven/cli/transfer/FileSizeFormat.java b/maven-embedder/src/main/java/org/apache/maven/cli/transfer/FileSizeFormat.java index 6ae2054e65..35078f9c0c 100644 --- a/maven-embedder/src/main/java/org/apache/maven/cli/transfer/FileSizeFormat.java +++ b/maven-embedder/src/main/java/org/apache/maven/cli/transfer/FileSizeFormat.java @@ -20,6 +20,8 @@ package org.apache.maven.cli.transfer; import java.util.Locale; +import org.apache.maven.api.services.MessageBuilder; + /** * Formats file size with the associated <a href="https://en.wikipedia.org/wiki/Metric_prefix">SI</a> prefix * (GB, MB, kB) and using the patterns <code>#0.0</code> for numbers between 1 and 10 @@ -147,6 +149,38 @@ public class FileSizeFormat { } } + public void format(MessageBuilder builder, long size) { + format(builder, size, null, false); + } + + public void format(MessageBuilder builder, long size, ScaleUnit unit) { + format(builder, size, unit, false); + } + + @SuppressWarnings("checkstyle:magicnumber") + private void format(MessageBuilder builder, long size, ScaleUnit unit, boolean omitSymbol) { + if (size < 0L) { + throw new IllegalArgumentException("file size cannot be negative: " + size); + } + if (unit == null) { + unit = ScaleUnit.getScaleUnit(size); + } + + double scaledSize = (double) size / unit.bytes(); + + if (unit == ScaleUnit.BYTE) { + builder.append(Long.toString(size)); + } else if (scaledSize < 0.05d || scaledSize >= 10.0d) { + builder.append(Long.toString(Math.round(scaledSize))); + } else { + builder.append(Double.toString(Math.round(scaledSize * 10d) / 10d)); + } + + if (!omitSymbol) { + builder.append(" ").append(unit.symbol()); + } + } + public String formatProgress(long progressedSize, long size) { StringBuilder sb = new StringBuilder(); formatProgress(sb, progressedSize, size); diff --git a/maven-embedder/src/main/java/org/fusesource/jansi/Ansi.java b/maven-embedder/src/main/java/org/fusesource/jansi/Ansi.java new file mode 100644 index 0000000000..8cf2051511 --- /dev/null +++ b/maven-embedder/src/main/java/org/fusesource/jansi/Ansi.java @@ -0,0 +1,952 @@ +/* + * 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.fusesource.jansi; + +import java.util.ArrayList; + +/** + * Provides a fluent API for generating + * <a href="https://en.wikipedia.org/wiki/ANSI_escape_code#CSI_sequences">ANSI escape sequences</a>. + * + * This class comes from Jansi and is provided for backward compatibility + * with maven-shared-utils, while Maven has migrated to JLine (into which Jansi has been merged + * since JLine 3.25.0). + */ +@SuppressWarnings({"checkstyle:MagicNumber", "unused"}) +public class Ansi implements Appendable { + + private static final char FIRST_ESC_CHAR = 27; + private static final char SECOND_ESC_CHAR = '['; + + /** + * <a href="https://en.wikipedia.org/wiki/ANSI_escape_code#Colors">ANSI 8 colors</a> for fluent API + */ + public enum Color { + BLACK(0, "BLACK"), + RED(1, "RED"), + GREEN(2, "GREEN"), + YELLOW(3, "YELLOW"), + BLUE(4, "BLUE"), + MAGENTA(5, "MAGENTA"), + CYAN(6, "CYAN"), + WHITE(7, "WHITE"), + DEFAULT(9, "DEFAULT"); + + private final int value; + private final String name; + + Color(int index, String name) { + this.value = index; + this.name = name; + } + + @Override + public String toString() { + return name; + } + + public int value() { + return value; + } + + public int fg() { + return value + 30; + } + + public int bg() { + return value + 40; + } + + public int fgBright() { + return value + 90; + } + + public int bgBright() { + return value + 100; + } + } + + /** + * Display attributes, also know as + * <a href="https://en.wikipedia.org/wiki/ANSI_escape_code#SGR_(Select_Graphic_Rendition)_parameters">SGR + * (Select Graphic Rendition) parameters</a>. + */ + public enum Attribute { + RESET(0, "RESET"), + INTENSITY_BOLD(1, "INTENSITY_BOLD"), + INTENSITY_FAINT(2, "INTENSITY_FAINT"), + ITALIC(3, "ITALIC_ON"), + UNDERLINE(4, "UNDERLINE_ON"), + BLINK_SLOW(5, "BLINK_SLOW"), + BLINK_FAST(6, "BLINK_FAST"), + NEGATIVE_ON(7, "NEGATIVE_ON"), + CONCEAL_ON(8, "CONCEAL_ON"), + STRIKETHROUGH_ON(9, "STRIKETHROUGH_ON"), + UNDERLINE_DOUBLE(21, "UNDERLINE_DOUBLE"), + INTENSITY_BOLD_OFF(22, "INTENSITY_BOLD_OFF"), + ITALIC_OFF(23, "ITALIC_OFF"), + UNDERLINE_OFF(24, "UNDERLINE_OFF"), + BLINK_OFF(25, "BLINK_OFF"), + NEGATIVE_OFF(27, "NEGATIVE_OFF"), + CONCEAL_OFF(28, "CONCEAL_OFF"), + STRIKETHROUGH_OFF(29, "STRIKETHROUGH_OFF"); + + private final int value; + private final String name; + + Attribute(int index, String name) { + this.value = index; + this.name = name; + } + + @Override + public String toString() { + return name; + } + + public int value() { + return value; + } + } + + /** + * ED (Erase in Display) / EL (Erase in Line) parameter (see + * <a href="https://en.wikipedia.org/wiki/ANSI_escape_code#CSI_sequences">CSI sequence J and K</a>) + * @see Ansi#eraseScreen(Erase) + * @see Ansi#eraseLine(Erase) + */ + public enum Erase { + FORWARD(0, "FORWARD"), + BACKWARD(1, "BACKWARD"), + ALL(2, "ALL"); + + private final int value; + private final String name; + + Erase(int index, String name) { + this.value = index; + this.name = name; + } + + @Override + public String toString() { + return name; + } + + public int value() { + return value; + } + } + + @FunctionalInterface + public interface Consumer { + void apply(Ansi ansi); + } + + public static boolean isEnabled() { + return org.apache.maven.cli.jline.MessageUtils.isColorEnabled() && org.jline.jansi.Ansi.isEnabled(); + } + + public static Ansi ansi() { + if (isEnabled()) { + return new Ansi(); + } else { + return new NoAnsi(); + } + } + + public static Ansi ansi(StringBuilder builder) { + if (isEnabled()) { + return new Ansi(builder); + } else { + return new NoAnsi(builder); + } + } + + public static Ansi ansi(int size) { + if (isEnabled()) { + return new Ansi(size); + } else { + return new NoAnsi(size); + } + } + + private static class NoAnsi extends Ansi { + NoAnsi() { + super(); + } + + NoAnsi(int size) { + super(size); + } + + NoAnsi(StringBuilder builder) { + super(builder); + } + + @Override + public Ansi fg(Color color) { + return this; + } + + @Override + public Ansi bg(Color color) { + return this; + } + + @Override + public Ansi fgBright(Color color) { + return this; + } + + @Override + public Ansi bgBright(Color color) { + return this; + } + + @Override + public Ansi fg(int color) { + return this; + } + + @Override + public Ansi fgRgb(int r, int g, int b) { + return this; + } + + @Override + public Ansi bg(int color) { + return this; + } + + @Override + public Ansi bgRgb(int r, int g, int b) { + return this; + } + + @Override + public Ansi a(Attribute attribute) { + return this; + } + + @Override + public Ansi cursor(int row, int column) { + return this; + } + + @Override + public Ansi cursorToColumn(int x) { + return this; + } + + @Override + public Ansi cursorUp(int y) { + return this; + } + + @Override + public Ansi cursorRight(int x) { + return this; + } + + @Override + public Ansi cursorDown(int y) { + return this; + } + + @Override + public Ansi cursorLeft(int x) { + return this; + } + + @Override + public Ansi cursorDownLine() { + return this; + } + + @Override + public Ansi cursorDownLine(final int n) { + return this; + } + + @Override + public Ansi cursorUpLine() { + return this; + } + + @Override + public Ansi cursorUpLine(final int n) { + return this; + } + + @Override + public Ansi eraseScreen() { + return this; + } + + @Override + public Ansi eraseScreen(Erase kind) { + return this; + } + + @Override + public Ansi eraseLine() { + return this; + } + + @Override + public Ansi eraseLine(Erase kind) { + return this; + } + + @Override + public Ansi scrollUp(int rows) { + return this; + } + + @Override + public Ansi scrollDown(int rows) { + return this; + } + + @Override + public Ansi saveCursorPosition() { + return this; + } + + @Override + @Deprecated + public Ansi restorCursorPosition() { + return this; + } + + @Override + public Ansi restoreCursorPosition() { + return this; + } + + @Override + public Ansi reset() { + return this; + } + } + + private final StringBuilder builder; + private final ArrayList<Integer> attributeOptions = new ArrayList<>(5); + + public Ansi() { + this(new StringBuilder(80)); + } + + public Ansi(Ansi parent) { + this(new StringBuilder(parent.builder)); + attributeOptions.addAll(parent.attributeOptions); + } + + public Ansi(int size) { + this(new StringBuilder(size)); + } + + public Ansi(StringBuilder builder) { + this.builder = builder; + } + + public Ansi fg(Color color) { + attributeOptions.add(color.fg()); + return this; + } + + public Ansi fg(int color) { + attributeOptions.add(38); + attributeOptions.add(5); + attributeOptions.add(color & 0xff); + return this; + } + + public Ansi fgRgb(int color) { + return fgRgb(color >> 16, color >> 8, color); + } + + public Ansi fgRgb(int r, int g, int b) { + attributeOptions.add(38); + attributeOptions.add(2); + attributeOptions.add(r & 0xff); + attributeOptions.add(g & 0xff); + attributeOptions.add(b & 0xff); + return this; + } + + public Ansi fgBlack() { + return this.fg(Color.BLACK); + } + + public Ansi fgBlue() { + return this.fg(Color.BLUE); + } + + public Ansi fgCyan() { + return this.fg(Color.CYAN); + } + + public Ansi fgDefault() { + return this.fg(Color.DEFAULT); + } + + public Ansi fgGreen() { + return this.fg(Color.GREEN); + } + + public Ansi fgMagenta() { + return this.fg(Color.MAGENTA); + } + + public Ansi fgRed() { + return this.fg(Color.RED); + } + + public Ansi fgYellow() { + return this.fg(Color.YELLOW); + } + + public Ansi bg(Color color) { + attributeOptions.add(color.bg()); + return this; + } + + public Ansi bg(int color) { + attributeOptions.add(48); + attributeOptions.add(5); + attributeOptions.add(color & 0xff); + return this; + } + + public Ansi bgRgb(int color) { + return bgRgb(color >> 16, color >> 8, color); + } + + public Ansi bgRgb(int r, int g, int b) { + attributeOptions.add(48); + attributeOptions.add(2); + attributeOptions.add(r & 0xff); + attributeOptions.add(g & 0xff); + attributeOptions.add(b & 0xff); + return this; + } + + public Ansi bgCyan() { + return this.bg(Color.CYAN); + } + + public Ansi bgDefault() { + return this.bg(Color.DEFAULT); + } + + public Ansi bgGreen() { + return this.bg(Color.GREEN); + } + + public Ansi bgMagenta() { + return this.bg(Color.MAGENTA); + } + + public Ansi bgRed() { + return this.bg(Color.RED); + } + + public Ansi bgYellow() { + return this.bg(Color.YELLOW); + } + + public Ansi fgBright(Color color) { + attributeOptions.add(color.fgBright()); + return this; + } + + public Ansi fgBrightBlack() { + return this.fgBright(Color.BLACK); + } + + public Ansi fgBrightBlue() { + return this.fgBright(Color.BLUE); + } + + public Ansi fgBrightCyan() { + return this.fgBright(Color.CYAN); + } + + public Ansi fgBrightDefault() { + return this.fgBright(Color.DEFAULT); + } + + public Ansi fgBrightGreen() { + return this.fgBright(Color.GREEN); + } + + public Ansi fgBrightMagenta() { + return this.fgBright(Color.MAGENTA); + } + + public Ansi fgBrightRed() { + return this.fgBright(Color.RED); + } + + public Ansi fgBrightYellow() { + return this.fgBright(Color.YELLOW); + } + + public Ansi bgBright(Color color) { + attributeOptions.add(color.bgBright()); + return this; + } + + public Ansi bgBrightCyan() { + return this.bgBright(Color.CYAN); + } + + public Ansi bgBrightDefault() { + return this.bgBright(Color.DEFAULT); + } + + public Ansi bgBrightGreen() { + return this.bgBright(Color.GREEN); + } + + public Ansi bgBrightMagenta() { + return this.bgBright(Color.MAGENTA); + } + + public Ansi bgBrightRed() { + return this.bgBright(Color.RED); + } + + public Ansi bgBrightYellow() { + return this.bgBright(Color.YELLOW); + } + + public Ansi a(Attribute attribute) { + attributeOptions.add(attribute.value()); + return this; + } + + /** + * Moves the cursor to row n, column m. The values are 1-based. + * Any values less than 1 are mapped to 1. + * + * @param row row (1-based) from top + * @param column column (1 based) from left + * @return this Ansi instance + */ + public Ansi cursor(final int row, final int column) { + return appendEscapeSequence('H', Math.max(1, row), Math.max(1, column)); + } + + /** + * Moves the cursor to column n. The parameter n is 1-based. + * If n is less than 1 it is moved to the first column. + * + * @param x the index (1-based) of the column to move to + * @return this Ansi instance + */ + public Ansi cursorToColumn(final int x) { + return appendEscapeSequence('G', Math.max(1, x)); + } + + /** + * Moves the cursor up. If the parameter y is negative it moves the cursor down. + * + * @param y the number of lines to move up + * @return this Ansi instance + */ + public Ansi cursorUp(final int y) { + return y > 0 ? appendEscapeSequence('A', y) : y < 0 ? cursorDown(-y) : this; + } + + /** + * Moves the cursor down. If the parameter y is negative it moves the cursor up. + * + * @param y the number of lines to move down + * @return this Ansi instance + */ + public Ansi cursorDown(final int y) { + return y > 0 ? appendEscapeSequence('B', y) : y < 0 ? cursorUp(-y) : this; + } + + /** + * Moves the cursor right. If the parameter x is negative it moves the cursor left. + * + * @param x the number of characters to move right + * @return this Ansi instance + */ + public Ansi cursorRight(final int x) { + return x > 0 ? appendEscapeSequence('C', x) : x < 0 ? cursorLeft(-x) : this; + } + + /** + * Moves the cursor left. If the parameter x is negative it moves the cursor right. + * + * @param x the number of characters to move left + * @return this Ansi instance + */ + public Ansi cursorLeft(final int x) { + return x > 0 ? appendEscapeSequence('D', x) : x < 0 ? cursorRight(-x) : this; + } + + /** + * Moves the cursor relative to the current position. The cursor is moved right if x is + * positive, left if negative and down if y is positive and up if negative. + * + * @param x the number of characters to move horizontally + * @param y the number of lines to move vertically + * @return this Ansi instance + * @since 2.2 + */ + public Ansi cursorMove(final int x, final int y) { + return cursorRight(x).cursorDown(y); + } + + /** + * Moves the cursor to the beginning of the line below. + * + * @return this Ansi instance + */ + public Ansi cursorDownLine() { + return appendEscapeSequence('E'); + } + + /** + * Moves the cursor to the beginning of the n-th line below. If the parameter n is negative it + * moves the cursor to the beginning of the n-th line above. + * + * @param n the number of lines to move the cursor + * @return this Ansi instance + */ + public Ansi cursorDownLine(final int n) { + return n < 0 ? cursorUpLine(-n) : appendEscapeSequence('E', n); + } + + /** + * Moves the cursor to the beginning of the line above. + * + * @return this Ansi instance + */ + public Ansi cursorUpLine() { + return appendEscapeSequence('F'); + } + + /** + * Moves the cursor to the beginning of the n-th line above. If the parameter n is negative it + * moves the cursor to the beginning of the n-th line below. + * + * @param n the number of lines to move the cursor + * @return this Ansi instance + */ + public Ansi cursorUpLine(final int n) { + return n < 0 ? cursorDownLine(-n) : appendEscapeSequence('F', n); + } + + public Ansi eraseScreen() { + return appendEscapeSequence('J', Erase.ALL.value()); + } + + public Ansi eraseScreen(final Erase kind) { + return appendEscapeSequence('J', kind.value()); + } + + public Ansi eraseLine() { + return appendEscapeSequence('K'); + } + + public Ansi eraseLine(final Erase kind) { + return appendEscapeSequence('K', kind.value()); + } + + public Ansi scrollUp(final int rows) { + if (rows == Integer.MIN_VALUE) { + return scrollDown(Integer.MAX_VALUE); + } + return rows > 0 ? appendEscapeSequence('S', rows) : rows < 0 ? scrollDown(-rows) : this; + } + + public Ansi scrollDown(final int rows) { + if (rows == Integer.MIN_VALUE) { + return scrollUp(Integer.MAX_VALUE); + } + return rows > 0 ? appendEscapeSequence('T', rows) : rows < 0 ? scrollUp(-rows) : this; + } + + @Deprecated + public Ansi restorCursorPosition() { + return restoreCursorPosition(); + } + + public Ansi saveCursorPosition() { + saveCursorPositionSCO(); + return saveCursorPositionDEC(); + } + + // SCO command + public Ansi saveCursorPositionSCO() { + return appendEscapeSequence('s'); + } + + // DEC command + public Ansi saveCursorPositionDEC() { + builder.append(FIRST_ESC_CHAR); + builder.append('7'); + return this; + } + + public Ansi restoreCursorPosition() { + restoreCursorPositionSCO(); + return restoreCursorPositionDEC(); + } + + // SCO command + public Ansi restoreCursorPositionSCO() { + return appendEscapeSequence('u'); + } + + // DEC command + public Ansi restoreCursorPositionDEC() { + builder.append(FIRST_ESC_CHAR); + builder.append('8'); + return this; + } + + public Ansi reset() { + return a(Attribute.RESET); + } + + public Ansi bold() { + return a(Attribute.INTENSITY_BOLD); + } + + public Ansi boldOff() { + return a(Attribute.INTENSITY_BOLD_OFF); + } + + public Ansi a(String value) { + flushAttributes(); + builder.append(value); + return this; + } + + public Ansi a(boolean value) { + flushAttributes(); + builder.append(value); + return this; + } + + public Ansi a(char value) { + flushAttributes(); + builder.append(value); + return this; + } + + public Ansi a(char[] value, int offset, int len) { + flushAttributes(); + builder.append(value, offset, len); + return this; + } + + public Ansi a(char[] value) { + flushAttributes(); + builder.append(value); + return this; + } + + public Ansi a(CharSequence value, int start, int end) { + flushAttributes(); + builder.append(value, start, end); + return this; + } + + public Ansi a(CharSequence value) { + flushAttributes(); + builder.append(value); + return this; + } + + public Ansi a(double value) { + flushAttributes(); + builder.append(value); + return this; + } + + public Ansi a(float value) { + flushAttributes(); + builder.append(value); + return this; + } + + public Ansi a(int value) { + flushAttributes(); + builder.append(value); + return this; + } + + public Ansi a(long value) { + flushAttributes(); + builder.append(value); + return this; + } + + public Ansi a(Object value) { + flushAttributes(); + builder.append(value); + return this; + } + + public Ansi a(StringBuffer value) { + flushAttributes(); + builder.append(value); + return this; + } + + public Ansi newline() { + flushAttributes(); + builder.append(System.getProperty("line.separator")); + return this; + } + + public Ansi format(String pattern, Object... args) { + flushAttributes(); + builder.append(String.format(pattern, args)); + return this; + } + + /** + * Applies another function to this Ansi instance. + * + * @param fun the function to apply + * @return this Ansi instance + * @since 2.2 + */ + public Ansi apply(Consumer fun) { + fun.apply(this); + return this; + } + + /** + * Uses the {@link org.jline.jansi.AnsiRenderer} + * to generate the ANSI escape sequences for the supplied text. + * + * @param text text + * @return this + * @since 2.2 + */ + public Ansi render(final String text) { + a(new org.jline.jansi.Ansi().render(text).toString()); + return this; + } + + /** + * String formats and renders the supplied arguments. Uses the {@link org.jline.jansi.AnsiRenderer} + * to generate the ANSI escape sequences. + * + * @param text format + * @param args arguments + * @return this + * @since 2.2 + */ + public Ansi render(final String text, Object... args) { + a(String.format(new org.jline.jansi.Ansi().render(text).toString(), args)); + return this; + } + + @Override + public String toString() { + flushAttributes(); + return builder.toString(); + } + + /////////////////////////////////////////////////////////////////// + // Private Helper Methods + /////////////////////////////////////////////////////////////////// + + private Ansi appendEscapeSequence(char command) { + flushAttributes(); + builder.append(FIRST_ESC_CHAR); + builder.append(SECOND_ESC_CHAR); + builder.append(command); + return this; + } + + private Ansi appendEscapeSequence(char command, int option) { + flushAttributes(); + builder.append(FIRST_ESC_CHAR); + builder.append(SECOND_ESC_CHAR); + builder.append(option); + builder.append(command); + return this; + } + + private Ansi appendEscapeSequence(char command, Object... options) { + flushAttributes(); + return doAppendEscapeSequence(command, options); + } + + private void flushAttributes() { + if (attributeOptions.isEmpty()) { + return; + } + if (attributeOptions.size() == 1 && attributeOptions.get(0) == 0) { + builder.append(FIRST_ESC_CHAR); + builder.append(SECOND_ESC_CHAR); + builder.append('m'); + } else { + doAppendEscapeSequence('m', attributeOptions.toArray()); + } + attributeOptions.clear(); + } + + private Ansi doAppendEscapeSequence(char command, Object... options) { + builder.append(FIRST_ESC_CHAR); + builder.append(SECOND_ESC_CHAR); + int size = options.length; + for (int i = 0; i < size; i++) { + if (i != 0) { + builder.append(';'); + } + if (options[i] != null) { + builder.append(options[i]); + } + } + builder.append(command); + return this; + } + + @Override + public Ansi append(CharSequence csq) { + builder.append(csq); + return this; + } + + @Override + public Ansi append(CharSequence csq, int start, int end) { + builder.append(csq, start, end); + return this; + } + + @Override + public Ansi append(char c) { + builder.append(c); + return this; + } +} diff --git a/maven-embedder/src/test/java/org/apache/maven/cli/MavenCliTest.java b/maven-embedder/src/test/java/org/apache/maven/cli/MavenCliTest.java index a68976bb37..318ca6a4f9 100644 --- a/maven-embedder/src/test/java/org/apache/maven/cli/MavenCliTest.java +++ b/maven-embedder/src/test/java/org/apache/maven/cli/MavenCliTest.java @@ -35,7 +35,7 @@ import org.apache.commons.cli.Option; import org.apache.commons.cli.Options; import org.apache.commons.cli.ParseException; import org.apache.maven.Maven; -import org.apache.maven.cli.jansi.MessageUtils; +import org.apache.maven.cli.jline.MessageUtils; import org.apache.maven.cli.transfer.ConsoleMavenTransferListener; import org.apache.maven.cli.transfer.QuietMavenTransferListener; import org.apache.maven.cli.transfer.Slf4jMavenTransferListener; @@ -500,7 +500,7 @@ class MavenCliTest { String versionOut = new String(systemOut.toByteArray(), StandardCharsets.UTF_8); // then - assertEquals(MessageUtils.stripAnsiCodes(versionOut), versionOut); + assertEquals(stripAnsiCodes(versionOut), versionOut); } @Test @@ -675,4 +675,8 @@ class MavenCliTest { project.setArtifactId(artifactId); return project; } + + static String stripAnsiCodes(String msg) { + return msg.replaceAll("\u001b\\[[;\\d]*[ -/]*[@-~]", ""); + } } diff --git a/maven-embedder/src/test/java/org/apache/maven/cli/event/ExecutionEventLoggerTest.java b/maven-embedder/src/test/java/org/apache/maven/cli/event/ExecutionEventLoggerTest.java index 3e260124b5..4ae5326100 100644 --- a/maven-embedder/src/test/java/org/apache/maven/cli/event/ExecutionEventLoggerTest.java +++ b/maven-embedder/src/test/java/org/apache/maven/cli/event/ExecutionEventLoggerTest.java @@ -22,8 +22,8 @@ import java.io.File; import com.google.common.collect.ImmutableList; import org.apache.commons.io.FilenameUtils; -import org.apache.maven.cli.jansi.JansiMessageBuilderFactory; -import org.apache.maven.cli.jansi.MessageUtils; +import org.apache.maven.cli.jline.JLineMessageBuilderFactory; +import org.apache.maven.cli.jline.MessageUtils; import org.apache.maven.execution.ExecutionEvent; import org.apache.maven.execution.MavenSession; import org.apache.maven.project.MavenProject; @@ -45,7 +45,7 @@ class ExecutionEventLoggerTest { private Logger logger; private ExecutionEventLogger executionEventLogger; - private JansiMessageBuilderFactory messageBuilderFactory = new JansiMessageBuilderFactory(); + private JLineMessageBuilderFactory messageBuilderFactory = new JLineMessageBuilderFactory(); @BeforeAll static void setUp() { diff --git a/maven-embedder/src/test/java/org/apache/maven/cli/transfer/ConsoleMavenTransferListenerTest.java b/maven-embedder/src/test/java/org/apache/maven/cli/transfer/ConsoleMavenTransferListenerTest.java index 095124052c..cc726cb71f 100644 --- a/maven-embedder/src/test/java/org/apache/maven/cli/transfer/ConsoleMavenTransferListenerTest.java +++ b/maven-embedder/src/test/java/org/apache/maven/cli/transfer/ConsoleMavenTransferListenerTest.java @@ -26,6 +26,7 @@ import java.util.concurrent.CountDownLatch; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; +import org.apache.maven.cli.jline.JLineMessageBuilderFactory; import org.eclipse.aether.DefaultRepositorySystemSession; import org.eclipse.aether.transfer.TransferCancelledException; import org.eclipse.aether.transfer.TransferEvent; @@ -48,6 +49,7 @@ class ConsoleMavenTransferListenerTest { Map<String, String> output = new ConcurrentHashMap<String, String>(); ConsoleMavenTransferListener listener = new ConsoleMavenTransferListener( + new JLineMessageBuilderFactory(), new PrintStream(System.out) { @Override diff --git a/maven-slf4j-provider/src/main/java/org/slf4j/impl/MavenSimpleLogger.java b/maven-slf4j-provider/src/main/java/org/slf4j/impl/MavenSimpleLogger.java index 988b2239bc..06a689146d 100644 --- a/maven-slf4j-provider/src/main/java/org/slf4j/impl/MavenSimpleLogger.java +++ b/maven-slf4j-provider/src/main/java/org/slf4j/impl/MavenSimpleLogger.java @@ -22,7 +22,7 @@ import java.io.PrintStream; import org.apache.maven.api.services.MessageBuilder; -import static org.apache.maven.cli.jansi.MessageUtils.builder; +import static org.apache.maven.cli.jline.MessageUtils.builder; /** * Logger for Maven, that support colorization of levels and stacktraces. This class implements 2 methods introduced in diff --git a/maven-slf4j-provider/src/test/java/org/slf4j/impl/MavenSimpleLoggerTest.java b/maven-slf4j-provider/src/test/java/org/slf4j/impl/MavenSimpleLoggerTest.java index b51decb6b9..4fbb3d1498 100644 --- a/maven-slf4j-provider/src/test/java/org/slf4j/impl/MavenSimpleLoggerTest.java +++ b/maven-slf4j-provider/src/test/java/org/slf4j/impl/MavenSimpleLoggerTest.java @@ -24,6 +24,9 @@ import java.util.Arrays; import java.util.List; import java.util.NoSuchElementException; +import org.apache.maven.cli.jline.MessageUtils; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInfo; @@ -32,6 +35,19 @@ import static org.junit.jupiter.api.Assertions.assertLinesMatch; class MavenSimpleLoggerTest { + boolean colorEnabled; + + @BeforeEach + void setup() { + colorEnabled = MessageUtils.isColorEnabled(); + MessageUtils.setColorEnabled(false); + } + + @AfterEach + void taerdown() { + MessageUtils.setColorEnabled(colorEnabled); + } + @Test void includesCauseAndSuppressedExceptionsWhenWritingThrowables(TestInfo testInfo) throws Exception { Exception causeOfSuppressed = new NoSuchElementException("cause of suppressed"); diff --git a/pom.xml b/pom.xml index eb32ccebcc..13d6a5844a 100644 --- a/pom.xml +++ b/pom.xml @@ -167,12 +167,13 @@ under the License. <guavafailureaccessVersion>1.0.1</guavafailureaccessVersion> <hamcrestVersion>2.2</hamcrestVersion> <jakartaInjectApiVersion>2.0.1</jakartaInjectApiVersion> - <jansiVersion>2.4.1</jansiVersion> <javaxAnnotationApiVersion>1.3.2</javaxAnnotationApiVersion> + <jlineVersion>3.25.0</jlineVersion> <junitVersion>5.10.1</junitVersion> <jxpathVersion>1.3</jxpathVersion> <logbackClassicVersion>1.2.13</logbackClassicVersion> <mockitoVersion>5.7.0</mockitoVersion> + <plexusInteractivityVersion>1.1</plexusInteractivityVersion> <plexusInterpolationVersion>1.26</plexusInterpolationVersion> <plexusTestingVersion>1.0.0</plexusTestingVersion> <plexusXmlVersion>4.0.1</plexusXmlVersion> @@ -309,9 +310,14 @@ under the License. <version>${plexusInterpolationVersion}</version> </dependency> <dependency> - <groupId>org.fusesource.jansi</groupId> - <artifactId>jansi</artifactId> - <version>${jansiVersion}</version> + <groupId>org.codehaus.plexus</groupId> + <artifactId>plexus-interactivity-api</artifactId> + <version>${plexusInteractivityVersion}</version> + </dependency> + <dependency> + <groupId>org.jline</groupId> + <artifactId>jline</artifactId> + <version>${jlineVersion}</version> </dependency> <dependency> <groupId>org.slf4j</groupId> @@ -690,6 +696,9 @@ under the License. <ignoredScopes> <ignoredScope>test</ignoredScope> </ignoredScopes> + <excludes> + <exclude>org.jline:jline</exclude> + </excludes> </enforceBytecodeVersion> </rules> <fail>true</fail>