This is an automated email from the ASF dual-hosted git repository. joewitt pushed a commit to branch main in repository https://gitbox.apache.org/repos/asf/nifi.git
The following commit(s) were added to refs/heads/main by this push: new a37887305c NIFI-11048 This closes #6858. Added FileNameCompleter to avoid style parsing failures a37887305c is described below commit a37887305ca603105fea8192a36a88a7aa3bc57d Author: exceptionfactory <exceptionfact...@apache.org> AuthorDate: Tue Jan 17 21:32:32 2023 -0600 NIFI-11048 This closes #6858. Added FileNameCompleter to avoid style parsing failures - Added StandardFileNameCompleter with static LS_COLORS to avoid parsing environment variables in Styles.lsStyle() Signed-off-by: Joe Witt <joew...@apache.org> --- nifi-toolkit/nifi-toolkit-cli/pom.xml | 2 +- .../org/apache/nifi/toolkit/cli/CLICompleter.java | 6 +- .../cli/impl/util/StandardFileNameCompleter.java | 115 +++++++++++++++++++++ .../apache/nifi/toolkit/cli/TestCLICompleter.java | 43 ++++---- 4 files changed, 141 insertions(+), 25 deletions(-) diff --git a/nifi-toolkit/nifi-toolkit-cli/pom.xml b/nifi-toolkit/nifi-toolkit-cli/pom.xml index c493cff096..3b8fef145e 100644 --- a/nifi-toolkit/nifi-toolkit-cli/pom.xml +++ b/nifi-toolkit/nifi-toolkit-cli/pom.xml @@ -24,7 +24,7 @@ <description>Tooling to make tls configuration easier</description> <properties> - <jline.version>3.21.0</jline.version> + <jline.version>3.22.0</jline.version> </properties> <build> diff --git a/nifi-toolkit/nifi-toolkit-cli/src/main/java/org/apache/nifi/toolkit/cli/CLICompleter.java b/nifi-toolkit/nifi-toolkit-cli/src/main/java/org/apache/nifi/toolkit/cli/CLICompleter.java index 1a66882882..1c0dd90461 100644 --- a/nifi-toolkit/nifi-toolkit-cli/src/main/java/org/apache/nifi/toolkit/cli/CLICompleter.java +++ b/nifi-toolkit/nifi-toolkit-cli/src/main/java/org/apache/nifi/toolkit/cli/CLICompleter.java @@ -21,7 +21,7 @@ import org.apache.nifi.toolkit.cli.api.CommandGroup; import org.apache.nifi.toolkit.cli.impl.command.CommandOption; import org.apache.nifi.toolkit.cli.impl.command.session.SessionCommandGroup; import org.apache.nifi.toolkit.cli.impl.session.SessionVariable; -import org.jline.builtins.Completers; +import org.apache.nifi.toolkit.cli.impl.util.StandardFileNameCompleter; import org.jline.reader.Candidate; import org.jline.reader.Completer; import org.jline.reader.LineReader; @@ -77,6 +77,8 @@ public class CLICompleter implements Completer { */ private final Map<String, List<String>> commandOptionsMap; + private final Completer fileNameCompleter = new StandardFileNameCompleter(); + /** * Initializes the completer based on the top-level commands and command groups. * @@ -185,7 +187,6 @@ public class CLICompleter implements Completer { final String currWord = line.word(); final String prevWord = line.words().get(line.wordIndex() - 1); if (FILE_COMPLETION_VARS.contains(prevWord)) { - final Completers.FileNameCompleter fileNameCompleter = new Completers.FileNameCompleter(); fileNameCompleter.complete(reader, new ArgumentCompleter.ArgumentLine(currWord, currWord.length()), candidates); } } @@ -196,7 +197,6 @@ public class CLICompleter implements Completer { // determine if the word before the current is an arg that needs file completion, otherwise return all args if (FILE_COMPLETION_ARGS.contains(prevWord)) { - final Completers.FileNameCompleter fileNameCompleter = new Completers.FileNameCompleter(); fileNameCompleter.complete(reader, new ArgumentCompleter.ArgumentLine(currWord, currWord.length()), candidates); } else { final List<String> options = commandOptionsMap.get(secondLevel); diff --git a/nifi-toolkit/nifi-toolkit-cli/src/main/java/org/apache/nifi/toolkit/cli/impl/util/StandardFileNameCompleter.java b/nifi-toolkit/nifi-toolkit-cli/src/main/java/org/apache/nifi/toolkit/cli/impl/util/StandardFileNameCompleter.java new file mode 100644 index 0000000000..3b39dbd5b3 --- /dev/null +++ b/nifi-toolkit/nifi-toolkit-cli/src/main/java/org/apache/nifi/toolkit/cli/impl/util/StandardFileNameCompleter.java @@ -0,0 +1,115 @@ +/* + * 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.nifi.toolkit.cli.impl.util; + +import org.jline.builtins.Completers; +import org.jline.builtins.Styles; +import org.jline.reader.Candidate; +import org.jline.reader.LineReader; +import org.jline.reader.ParsedLine; +import org.jline.utils.StyleResolver; + +import java.io.IOException; +import java.nio.file.DirectoryStream; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.List; + +/** + * Standard File Name Completer overriding references to Styles.lsStyle() to avoid parsing issues with LS_COLORS + */ +public class StandardFileNameCompleter extends Completers.FileNameCompleter { + private static final String STANDARD_LS_COLORS = "di=1;91:ex=1;92:ln=1;96:fi="; + + private static final String HOME_DIRECTORY_ALIAS = "~"; + + private static final String EMPTY = ""; + + private static final StyleResolver STYLE_RESOLVER = Styles.style(STANDARD_LS_COLORS); + + /** + * Complete file names based on JLine 3.22.0 without calling Styles.lsStyle() + * + * @param reader Line Reader + * @param commandLine Parsed Command + * @param candidates Candidates to be populated + */ + @Override + public void complete(final LineReader reader, final ParsedLine commandLine, final List<Candidate> candidates) { + assert commandLine != null; + assert candidates != null; + + final String buffer = commandLine.word().substring(0, commandLine.wordCursor()); + + final Path current; + final String curBuf; + final String sep = getSeparator(reader.isSet(LineReader.Option.USE_FORWARD_SLASH)); + final int lastSep = buffer.lastIndexOf(sep); + try { + if (lastSep >= 0) { + curBuf = buffer.substring(0, lastSep + 1); + if (curBuf.startsWith(HOME_DIRECTORY_ALIAS)) { + if (curBuf.startsWith(HOME_DIRECTORY_ALIAS + sep)) { + current = getUserHome().resolve(curBuf.substring(2)); + } else { + current = getUserHome().getParent().resolve(curBuf.substring(1)); + } + } else { + current = getUserDir().resolve(curBuf); + } + } else { + curBuf = EMPTY; + current = getUserDir(); + } + + try (final DirectoryStream<Path> directory = Files.newDirectoryStream(current, this::accept)) { + directory.forEach(path -> { + final String value = curBuf + path.getFileName().toString(); + if (Files.isDirectory(path)) { + candidates.add( + new Candidate( + value + (reader.isSet(LineReader.Option.AUTO_PARAM_SLASH) ? sep : EMPTY), + getDisplay(reader.getTerminal(), path, STYLE_RESOLVER, sep), + null, + null, + reader.isSet(LineReader.Option.AUTO_REMOVE_SLASH) ? sep : null, + null, + false + ) + ); + } else { + candidates.add( + new Candidate( + value, + getDisplay(reader.getTerminal(), path, STYLE_RESOLVER, sep), + null, + null, + null, + null, + true + ) + ); + } + }); + } catch (final IOException e) { + // Ignore + } + } catch (final Exception e) { + // Ignore + } + } +} diff --git a/nifi-toolkit/nifi-toolkit-cli/src/test/java/org/apache/nifi/toolkit/cli/TestCLICompleter.java b/nifi-toolkit/nifi-toolkit-cli/src/test/java/org/apache/nifi/toolkit/cli/TestCLICompleter.java index 044ed792de..c18e1620af 100644 --- a/nifi-toolkit/nifi-toolkit-cli/src/test/java/org/apache/nifi/toolkit/cli/TestCLICompleter.java +++ b/nifi-toolkit/nifi-toolkit-cli/src/test/java/org/apache/nifi/toolkit/cli/TestCLICompleter.java @@ -40,11 +40,14 @@ import org.junit.jupiter.api.condition.DisabledOnOs; import org.junit.jupiter.api.condition.OS; import org.mockito.Mockito; +import java.io.File; +import java.nio.file.Paths; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.List; import java.util.Map; +import java.util.Optional; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertTrue; @@ -52,6 +55,9 @@ import static org.junit.jupiter.api.Assertions.assertTrue; @DisabledOnOs(OS.WINDOWS) public class TestCLICompleter { + private static final String TEST_RESOURCES_DIRECTORY = "src/test/resources"; + private static final String TEST_PROPERTIES = "test.properties"; + private static CLICompleter completer; private static LineReader lineReader; @@ -158,21 +164,13 @@ public class TestCLICompleter { final String topCommand = NiFiRegistryCommandGroup.REGISTRY_COMMAND_GROUP; final String subCommand = "list-buckets"; - final ParsedLine parsedLine = new TestParsedLine(Arrays.asList(topCommand, subCommand, "-p", "src/test/resources/"), 3); + final String testResourcesDirectory = getTestResourcesDirectory(); + final ParsedLine parsedLine = new TestParsedLine(Arrays.asList(topCommand, subCommand, "-p", testResourcesDirectory), 3); final List<Candidate> candidates = new ArrayList<>(); completer.complete(lineReader, parsedLine, candidates); - assertTrue(candidates.size() > 0); - - boolean found = false; - for (Candidate candidate : candidates) { - if (candidate.value().equals("src/test/resources/test.properties")) { - found = true; - break; - } - } - assertTrue(found); + assertTestPropertiesFound(candidates); } @Test @@ -193,27 +191,30 @@ public class TestCLICompleter { final String topCommand = "session"; final String subCommand = "set"; + final String testResourcesDirectory = getTestResourcesDirectory(); final ParsedLine parsedLine = new TestParsedLine( Arrays.asList( topCommand, subCommand, SessionVariable.NIFI_CLIENT_PROPS.getVariableName(), - "src/test/resources/"), + testResourcesDirectory), 3); final List<Candidate> candidates = new ArrayList<>(); completer.complete(lineReader, parsedLine, candidates); - assertTrue(candidates.size() > 0); - boolean found = false; - for (Candidate candidate : candidates) { - if (candidate.value().equals("src/test/resources/test.properties")) { - found = true; - break; - } - } + assertTestPropertiesFound(candidates); + } + + private String getTestResourcesDirectory() { + return Paths.get(TEST_RESOURCES_DIRECTORY).toAbsolutePath() + File.separator; + } - assertTrue(found); + private void assertTestPropertiesFound(final List<Candidate> candidates) { + final Optional<Candidate> candidateFound = candidates.stream() + .filter(candidate -> candidate.value().endsWith(TEST_PROPERTIES)) + .findFirst(); + assertTrue(candidateFound.isPresent()); } private static class TestParsedLine implements ParsedLine {