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 <joewitt@apache.org>
This commit is contained in:
exceptionfactory 2023-01-17 21:32:32 -06:00 committed by Joe Witt
parent 4d78ebc487
commit a37887305c
No known key found for this signature in database
GPG Key ID: 9093BF854F811A1A
4 changed files with 141 additions and 25 deletions

View File

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

View File

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

View File

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

View File

@ -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);
}
assertTrue(found);
private String getTestResourcesDirectory() {
return Paths.get(TEST_RESOURCES_DIRECTORY).toAbsolutePath() + File.separator;
}
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 {