[MNG-7995] Switch to JLine to provide line editing (#1279)

This commit is contained in:
Guillaume Nodet 2024-01-08 11:37:09 +01:00 committed by GitHub
parent 47fc18faf9
commit 782e8679bf
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
27 changed files with 1601 additions and 756 deletions

View File

@ -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 -->

View File

@ -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.

View File

@ -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,7 +251,17 @@ 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));
}
/**
* Set the buffer length.
*
* @param length the new length
* @return the current builder
*/
MessageBuilder setLength(int length);
/**
* Return the built message.
@ -206,11 +270,4 @@ public interface MessageBuilder {
*/
@Nonnull
String build();
/**
* Set the buffer length.
*
* @param length the new length
*/
void setLength(int length);
}

View File

@ -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);
}

View File

@ -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);
public MessageBuilder style(String style) {
return this;
}
@Override
@Nonnull
public MessageBuilder a(char[] chars) {
buffer.append(chars);
public MessageBuilder resetStyle() {
return this;
}
@Override
@Nonnull
public MessageBuilder a(CharSequence charSequence, int i, int i1) {
buffer.append(charSequence, i, i1);
public MessageBuilder append(CharSequence cs) {
buffer.append(cs);
return this;
}
@Override
@Nonnull
public MessageBuilder a(CharSequence charSequence) {
buffer.append(charSequence);
public MessageBuilder append(CharSequence cs, int start, int end) {
buffer.append(cs, start, end);
return this;
}
@Override
@Nonnull
public MessageBuilder a(Object o) {
buffer.append(o);
public MessageBuilder append(char c) {
buffer.append(c);
return this;
}
@Override
@Nonnull
public MessageBuilder newline() {
buffer.append(System.getProperty("line.separator"));
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);
}
}

View File

@ -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));
}
}

View File

@ -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

View File

@ -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>

View File

@ -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;
/**
*/

View File

@ -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;

View File

@ -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() {

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -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;
}
}
}

View File

@ -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();
}
}

View File

@ -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);
}
}
}

View File

@ -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();
}
}

View File

@ -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());
}
}

View File

@ -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;
}

View File

@ -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);

View File

@ -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;
}
}

View File

@ -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]*[ -/]*[@-~]", "");
}
}

View File

@ -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() {

View File

@ -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

View File

@ -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

View File

@ -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");

17
pom.xml
View File

@ -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>