From 06c3f00ea178d04a3e98112fa0f1a942bd0eca7b Mon Sep 17 00:00:00 2001 From: Niclas Kleinert Date: Fri, 25 Apr 2025 15:59:48 +0200 Subject: [PATCH 01/30] updated get method to correctly display an error --- src/main/java/de/featjar/base/env/Process.java | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/src/main/java/de/featjar/base/env/Process.java b/src/main/java/de/featjar/base/env/Process.java index a0af640..472d458 100644 --- a/src/main/java/de/featjar/base/env/Process.java +++ b/src/main/java/de/featjar/base/env/Process.java @@ -22,6 +22,7 @@ import de.featjar.base.FeatJAR; import de.featjar.base.data.Problem; +import de.featjar.base.data.Problem.Severity; import de.featjar.base.data.Result; import de.featjar.base.data.Void; import java.io.BufferedReader; @@ -39,6 +40,7 @@ import java.util.concurrent.TimeUnit; import java.util.function.Consumer; import java.util.function.Supplier; +import java.util.stream.Collectors; /** * Executes an external executable in a process. @@ -134,8 +136,19 @@ public java.lang.Process start() throws IOException { @Override public Result> get() { List output = new ArrayList<>(); - Result result = run(output::add, output::add); - return result.map(r -> output); + List error = new ArrayList<>(); + Result result = run(output::add, error::add); + + if (result.isPresent()) { + output.addAll(error); + return result.map(r -> output); + } else { + if (error.isEmpty()) { + return result.map(r -> null); + } else { + return Result.empty(new Problem(error.stream().collect(Collectors.joining("\n")), Severity.ERROR)); + } + } } /** From dfa9856d0d5e1acd9299d4389c63109de7b052f8 Mon Sep 17 00:00:00 2001 From: Niclas Date: Thu, 9 Oct 2025 13:33:33 +0200 Subject: [PATCH 02/30] init shell in base --- src/main/java/de/featjar/base/FeatJAR.java | 10 ++ .../java/de/featjar/base/cli/Commands.java | 5 + .../java/de/featjar/base/cli/ICommand.java | 10 +- .../featjar/base/shell/ClearShellCommand.java | 34 +++++ .../base/shell/DeleteShellCommand.java | 39 ++++++ .../featjar/base/shell/ExitShellCommand.java | 22 +++ .../featjar/base/shell/HelpShellCommand.java | 33 +++++ .../de/featjar/base/shell/IShellCommand.java | 25 ++++ .../featjar/base/shell/PrintShellCommand.java | 51 +++++++ .../featjar/base/shell/RunShellCommand.java | 34 +++++ .../java/de/featjar/base/shell/Shell.java | 128 ++++++++++++++++++ .../de/featjar/base/shell/ShellCommands.java | 5 + .../de/featjar/base/shell/ShellSession.java | 89 ++++++++++++ .../java/de/featjar/base/shell/Variables.java | 32 +++++ src/main/resources/extensions.xml | 9 ++ 15 files changed, 525 insertions(+), 1 deletion(-) create mode 100644 src/main/java/de/featjar/base/shell/ClearShellCommand.java create mode 100644 src/main/java/de/featjar/base/shell/DeleteShellCommand.java create mode 100644 src/main/java/de/featjar/base/shell/ExitShellCommand.java create mode 100644 src/main/java/de/featjar/base/shell/HelpShellCommand.java create mode 100644 src/main/java/de/featjar/base/shell/IShellCommand.java create mode 100644 src/main/java/de/featjar/base/shell/PrintShellCommand.java create mode 100644 src/main/java/de/featjar/base/shell/RunShellCommand.java create mode 100644 src/main/java/de/featjar/base/shell/Shell.java create mode 100644 src/main/java/de/featjar/base/shell/ShellCommands.java create mode 100644 src/main/java/de/featjar/base/shell/ShellSession.java create mode 100644 src/main/java/de/featjar/base/shell/Variables.java diff --git a/src/main/java/de/featjar/base/FeatJAR.java b/src/main/java/de/featjar/base/FeatJAR.java index deb218a..397be66 100644 --- a/src/main/java/de/featjar/base/FeatJAR.java +++ b/src/main/java/de/featjar/base/FeatJAR.java @@ -203,6 +203,16 @@ public static Configuration testConfiguration() { configuration.cacheConfig.setCachePolicy(Cache.CachePolicy.CACHE_NONE); return configuration; } + + public static Configuration shellConfiguration() { + final Configuration configuration = new Configuration(); + configuration + .logConfig + .logToSystemOut(Log.Verbosity.MESSAGE, Log.Verbosity.INFO, Log.Verbosity.PROGRESS) + .logToSystemErr(Log.Verbosity.ERROR); + configuration.cacheConfig.setCachePolicy(Cache.CachePolicy.CACHE_NONE); + return configuration; + } /** * {@return the current FeatJAR instance} diff --git a/src/main/java/de/featjar/base/cli/Commands.java b/src/main/java/de/featjar/base/cli/Commands.java index f926aa6..39993f4 100644 --- a/src/main/java/de/featjar/base/cli/Commands.java +++ b/src/main/java/de/featjar/base/cli/Commands.java @@ -26,6 +26,7 @@ import de.featjar.base.io.IO; import de.featjar.base.io.format.IFormat; import de.featjar.base.io.format.IFormatSupplier; + import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; @@ -72,6 +73,10 @@ public class Commands extends AExtensionPoint { */ public static final Pattern STANDARD_INPUT_PATTERN = Pattern.compile(STANDARD_INPUT + "(\\.(.+))?"); + public static Commands getInstance() { + return FeatJAR.extensionPoint(Commands.class); + } + /** * Runs a given function in a new thread, aborting it when it is not done after a timeout expires. * If the entire process should be stopped afterwards, {@link System#exit(int)} must be called explicitly. diff --git a/src/main/java/de/featjar/base/cli/ICommand.java b/src/main/java/de/featjar/base/cli/ICommand.java index b1c52df..b3d87b1 100644 --- a/src/main/java/de/featjar/base/cli/ICommand.java +++ b/src/main/java/de/featjar/base/cli/ICommand.java @@ -20,11 +20,13 @@ */ package de.featjar.base.cli; -import de.featjar.base.extension.IExtension; import java.util.ArrayList; import java.util.List; import java.util.Optional; +import de.featjar.base.extension.IExtension; +import de.featjar.base.shell.ShellSession; + /** * A command run within a {@link Commands}. * @@ -62,4 +64,10 @@ default Optional getShortName() { * @return exit code */ int run(OptionList optionParser); + + default OptionList getShellOptions(ShellSession session, List cmdParams) { + OptionList optionList = new OptionList(); + optionList.parseArguments(); + return optionList; + } } diff --git a/src/main/java/de/featjar/base/shell/ClearShellCommand.java b/src/main/java/de/featjar/base/shell/ClearShellCommand.java new file mode 100644 index 0000000..889303b --- /dev/null +++ b/src/main/java/de/featjar/base/shell/ClearShellCommand.java @@ -0,0 +1,34 @@ +package de.featjar.base.shell; + +import java.util.List; +import java.util.Objects; +import java.util.Optional; + +import de.featjar.base.FeatJAR; + +public class ClearShellCommand implements IShellCommand { + + @Override + public void execute(ShellSession session, List cmdParams) { + String choice = Shell.readCommand("Clearing the entire session. Proceed ? (y)es (n)o") + .orElse("").toLowerCase().trim(); + + if(Objects.equals("y", choice)) { + session.clear(); + FeatJAR.log().info("Clearing successful"); + } else if(Objects.equals("n", choice)) { + FeatJAR.log().info("Clearing aborted"); + } + } + + @Override + public Optional getShortName() { + return Optional.of("clear"); + } + + @Override + public Optional getDescription(){ + return Optional.of("delete the entire session"); + } + +} diff --git a/src/main/java/de/featjar/base/shell/DeleteShellCommand.java b/src/main/java/de/featjar/base/shell/DeleteShellCommand.java new file mode 100644 index 0000000..cd0875c --- /dev/null +++ b/src/main/java/de/featjar/base/shell/DeleteShellCommand.java @@ -0,0 +1,39 @@ +package de.featjar.base.shell; + +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.Optional; +import java.util.stream.Collectors; + +import de.featjar.base.FeatJAR; + +public class DeleteShellCommand implements IShellCommand { + + @Override + public void execute(ShellSession session, List cmdParams) { + + if (cmdParams.isEmpty()) { + session.printVariables(); + cmdParams = Shell.readCommand("Enter the variable names you want to delete or leave blank to abort:") + .map(c -> Arrays.stream(c.split("\\s+")).collect(Collectors.toList())) + .orElse(Collections.emptyList()); + } + + cmdParams.forEach(e -> { + session.remove(e).ifPresentOrElse(a -> FeatJAR.log().info("Removing of " + e + " successful"), + () -> FeatJAR.log().error("Could not find a variable named " + e)); + }); + } + + @Override + public Optional getShortName() { + return Optional.of("delete"); + } + + @Override + public Optional getDescription() { + return Optional.of("delete session variables - ..."); + } + +} diff --git a/src/main/java/de/featjar/base/shell/ExitShellCommand.java b/src/main/java/de/featjar/base/shell/ExitShellCommand.java new file mode 100644 index 0000000..019bad8 --- /dev/null +++ b/src/main/java/de/featjar/base/shell/ExitShellCommand.java @@ -0,0 +1,22 @@ +package de.featjar.base.shell; + +import java.util.List; +import java.util.Optional; + +public class ExitShellCommand implements IShellCommand { + + @Override + public void execute(ShellSession session, List cmdParams) { + System.exit(0); + } + + @Override + public Optional getShortName() { + return Optional.of("exit"); + } + + @Override + public Optional getDescription(){ + return Optional.of("leave shell"); + } +} diff --git a/src/main/java/de/featjar/base/shell/HelpShellCommand.java b/src/main/java/de/featjar/base/shell/HelpShellCommand.java new file mode 100644 index 0000000..32bd34c --- /dev/null +++ b/src/main/java/de/featjar/base/shell/HelpShellCommand.java @@ -0,0 +1,33 @@ +package de.featjar.base.shell; + +import java.util.List; +import java.util.Optional; + +import de.featjar.base.FeatJAR; + +public class HelpShellCommand implements IShellCommand { + + @Override + public void execute(ShellSession session, List cmdParams) { + printCommands(); + } + + public void printCommands() { + FeatJAR.log().info("Interactive shell - supported commands are (capitalization is not taken into account):\n"); + FeatJAR.extensionPoint(ShellCommands.class).getExtensions() + .stream().map(c -> c.getShortName().orElse("") + .concat(" - " + c.getDescription().orElse(""))) + .forEach(FeatJAR.log()::info); + FeatJAR.log().info("\n"); + } + + @Override + public Optional getShortName() { + return Optional.of("help"); + } + + @Override + public Optional getDescription(){ + return Optional.of("print all commads"); + } +} diff --git a/src/main/java/de/featjar/base/shell/IShellCommand.java b/src/main/java/de/featjar/base/shell/IShellCommand.java new file mode 100644 index 0000000..d371367 --- /dev/null +++ b/src/main/java/de/featjar/base/shell/IShellCommand.java @@ -0,0 +1,25 @@ +package de.featjar.base.shell; + +import java.util.List; +import java.util.Optional; + +import de.featjar.base.extension.IExtension; + +public interface IShellCommand extends IExtension{ + + void execute(ShellSession session, List cmdParams); + + /** + * {@return this command's short name, if any} The short name can be used to call this command from the CLI. + */ + default Optional getShortName() { + return Optional.empty(); + } + + /** + * {@return this command's description name, if any} + */ + default Optional getDescription(){ + return Optional.empty(); + } +} diff --git a/src/main/java/de/featjar/base/shell/PrintShellCommand.java b/src/main/java/de/featjar/base/shell/PrintShellCommand.java new file mode 100644 index 0000000..76cc368 --- /dev/null +++ b/src/main/java/de/featjar/base/shell/PrintShellCommand.java @@ -0,0 +1,51 @@ +package de.featjar.base.shell; + +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.Optional; +import java.util.stream.Collectors; + +import de.featjar.base.FeatJAR; +import de.featjar.base.tree.structure.ITree; + + +public class PrintShellCommand implements IShellCommand { + + @Override + public void execute(ShellSession session, List cmdParams) { + // TODO layer for electing the type and then + + if(cmdParams.isEmpty()) { + session.printVariables(); + cmdParams = Shell.readCommand("Enter the variable names you want to print or leave blank to abort:") + .map(c -> Arrays.stream(c.toLowerCase().split("\\s+")).collect(Collectors.toList())) + .orElse(Collections.emptyList()); + } + + cmdParams.forEach(e -> { + session.getElement(e) + .ifPresentOrElse(m -> { + FeatJAR.log().info(e + ":"); + printMap(m); + }, () -> FeatJAR.log().error("Could not find a variable named " + e)); + }); + } + + private void printMap(Object v) { + if (v instanceof ITree) { + FeatJAR.log().info(((ITree) v).print()); + } else { + FeatJAR.log().info(v); + } + FeatJAR.log().info(""); + } + + public Optional getShortName() { + return Optional.of("print"); + } + + public Optional getDescription(){ + return Optional.of("print the content of variables - ..."); + } +} diff --git a/src/main/java/de/featjar/base/shell/RunShellCommand.java b/src/main/java/de/featjar/base/shell/RunShellCommand.java new file mode 100644 index 0000000..5f2a1c6 --- /dev/null +++ b/src/main/java/de/featjar/base/shell/RunShellCommand.java @@ -0,0 +1,34 @@ +package de.featjar.base.shell; + +import java.util.List; +import java.util.Optional; + +import de.featjar.base.FeatJAR; +import de.featjar.base.cli.Commands; +import de.featjar.base.cli.ICommand; +import de.featjar.base.cli.OptionList; + +public class RunShellCommand implements IShellCommand { + + @Override + public void execute(ShellSession session, List cmdParams) { + ICommand cliCommand = Commands.getInstance().getExtension(cmdParams.get(0)).get(); + OptionList shellOptions = cliCommand.getShellOptions(session, cmdParams.subList(1, cmdParams.size())); + cliCommand.getOptions().forEach(o -> { + FeatJAR.log().message(o+"="+shellOptions.getResult(o).map(String::valueOf).orElse("")); + } + ); + cliCommand.run(shellOptions); + } + + @Override + public Optional getShortName() { + return Optional.of("run"); + } + + @Override + public Optional getDescription() { + return Optional.of("run..."); + } + +} diff --git a/src/main/java/de/featjar/base/shell/Shell.java b/src/main/java/de/featjar/base/shell/Shell.java new file mode 100644 index 0000000..3a05ee6 --- /dev/null +++ b/src/main/java/de/featjar/base/shell/Shell.java @@ -0,0 +1,128 @@ +package de.featjar.base.shell; + +import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Optional; +import java.util.Scanner; +import java.util.stream.Collectors; + +import de.featjar.base.FeatJAR; +import de.featjar.base.data.Problem; +import de.featjar.base.data.Problem.Severity; +import de.featjar.base.data.Result; + +public class Shell { + + public static Shell shell = null; + ShellSession session = new ShellSession(); + private static final Scanner shellScanner = new Scanner(System.in); + + private Shell() { + FeatJAR.initialize(FeatJAR.shellConfiguration()); + printArt(); + new HelpShellCommand().printCommands(); + run(); + } + + public static Shell getInstance() { + return (shell == null) ? (shell = new Shell()) : shell; + } + + public static void main(String[] args) { + Shell.getInstance(); + } + + private void run() { + while (true) { +// List cmdArg = Arrays.stream(readCommand("$").split("\\s+")).collect(Collectors.toList()); + List cmdArg = readCommand("$") + .map(c -> Arrays.stream(c.split("\\s+")).collect(Collectors.toList())) + .orElse(Collections.emptyList()); + + if(!cmdArg.isEmpty()) { + Result command = parseCommand(cmdArg.get(0)); + cmdArg.remove(0); + command.ifPresent(cmd -> cmd.execute(session, cmdArg)); + } + } + } + + private Result parseCommand(String commandString) { + ShellCommands shellCommandsExentionsPoint = FeatJAR.extensionPoint(ShellCommands.class); + List commands = shellCommandsExentionsPoint + .getExtensions().stream().filter(command -> command.getShortName() + .map(name -> name.toLowerCase().startsWith(commandString)).orElse(Boolean.FALSE)) + .collect(Collectors.toList()); + + if (commands.size() > 1) { + Map temp = new HashMap(); + int i = 1; + + FeatJAR.log().info + ("Command name %s is ambiguous! choose one of the following %d commands: \n", commandString, commands.size()); + + for(IShellCommand c : commands) { + FeatJAR.log().info(i + "." + c.getShortName().get() + " - " + c.getDescription().get()); + temp.put(i, c); + i++; + } + + String choice = readCommand("").orElse(""); + + for (Map.Entry entry : temp.entrySet()) { + if (Objects.equals(entry.getKey(), Integer.parseInt(choice))) { + return Result.of(entry.getValue()); + } + } + return Result.empty(addProblem(Severity.ERROR + , "Command name '%s' is ambiguous! It matches the following commands: \n%s and wrong number !" + , commandString, commands.stream().map(IShellCommand::getIdentifier).collect(Collectors.joining("\n")))); + } + + IShellCommand command = null; + if (commands.isEmpty()) { + Result matchingExtension = shellCommandsExentionsPoint.getMatchingExtension(commandString); + if (matchingExtension.isEmpty()) { + FeatJAR.log().info("No such command '%s'. \n shows all viable commands", commandString); + + return Result.empty(addProblem(Severity.ERROR, "No command matched the name '%s'!", commandString)); + } + command = matchingExtension.get(); + } else { + if(commands.get(0).getShortName().get().matches(commandString)) { + command = commands.get(0); + return Result.of(command); + } + String choice = readCommand("Do you mean: " + commands.get(0).getShortName().get() + "? (ENTER) or (a)bort").orElse(""); + if(choice.isEmpty()) { + command = commands.get(0); + } else { + return Result.empty(); + } + } + return Result.of(command); + } + + private Problem addProblem(Severity severity, String message, Object... arguments) { + return new Problem(String.format(message, arguments), severity); + } + + public static Optional readCommand(String prompt) { + FeatJAR.log().info(prompt); + String input = shellScanner.nextLine().trim(); + return input.isEmpty() ? Optional.empty() : Optional.of(input); + } + + public static void printArt() { + FeatJAR.log().info(" _____ _ _ _ ____ ____ _ _ _ "); + FeatJAR.log().info("| ___|___ __ _ | |_ | | / \\ | _ \\ / ___| | |__ ___ | || |"); + FeatJAR.log().info("| |_ / _ \\ / _` || __|_ | | / _ \\ | |_) |\\___ \\ | '_ \\ / _ \\| || |"); + FeatJAR.log().info("| _|| __/| (_| || |_| |_| |/ ___ \\ | _ < ___) || | | || __/| || |"); + FeatJAR.log().info("|_| \\___| \\__,_| \\__|\\___//_/ \\_\\|_| \\_\\|____/ |_| |_| \\___||_||_|"); + FeatJAR.log().info("\n"); + } +} diff --git a/src/main/java/de/featjar/base/shell/ShellCommands.java b/src/main/java/de/featjar/base/shell/ShellCommands.java new file mode 100644 index 0000000..e759cbf --- /dev/null +++ b/src/main/java/de/featjar/base/shell/ShellCommands.java @@ -0,0 +1,5 @@ +package de.featjar.base.shell; + +import de.featjar.base.extension.AExtensionPoint; + +public class ShellCommands extends AExtensionPoint{} diff --git a/src/main/java/de/featjar/base/shell/ShellSession.java b/src/main/java/de/featjar/base/shell/ShellSession.java new file mode 100644 index 0000000..4a36211 --- /dev/null +++ b/src/main/java/de/featjar/base/shell/ShellSession.java @@ -0,0 +1,89 @@ +package de.featjar.base.shell; + +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Optional; + +import de.featjar.base.FeatJAR; + +public class ShellSession { + + private static class StoredElement { + private Class type; + private T element; + + public StoredElement(Class type, T element) { + this.type = type; + this.element = element; + } + } + + private final Map> elements; + + @SuppressWarnings("unchecked") + public Optional get(String key, Class type) { + StoredElement storedElement = elements.get(key); + + if(storedElement == null) { + return Optional.empty(); + } + if (storedElement.type == type) { + return Optional.of((T) storedElement.type.cast(storedElement.element)); + } else { + throw new RuntimeException("Wrong Type"); // TODO Result von Problem addProblem + } + } + + public Optional getType(String key) { + return Optional.ofNullable(elements.get(key)).map(e -> e.type.getSimpleName()); + } + + public Optional getElement(String key) { + return Optional.ofNullable(elements.get(key)).map(e -> e.element); + } + + public ShellSession() { + elements = new LinkedHashMap<>(); + } + + public void put(String key, T element, Class type) { + elements.put(key, new StoredElement(type, element)); + } + + public Optional remove(String key) { + return Optional.ofNullable(elements.remove(key)); + } + + public void clear() { + elements.clear(); + } + + public int getSize() { + return elements.size(); + } + + public boolean containsKey(String key) { + return elements.containsKey(key); + } + + public void printVariable(String key) { + for (Entry> entry : elements.entrySet()) { + if(entry.getKey().equals(key)) { + FeatJAR.log().info("Variable: " + key + " Type: " + entry.getValue().type.getSimpleName() + "\n"); + break; + } + } + } + + public void printVariables() { + elements.entrySet().forEach(m -> FeatJAR.log().info(m.getKey() + " (" + + m.getValue().type.getSimpleName() + ")" )); + } + + public void printSortedByVarNames() { + // TODO sort, group (type or name) + + elements.entrySet().stream().sorted(Map.Entry.comparingByKey()).forEach(m -> FeatJAR.log().info(m.getKey() + " " + m.getValue().type.getSimpleName())); + } +} diff --git a/src/main/java/de/featjar/base/shell/Variables.java b/src/main/java/de/featjar/base/shell/Variables.java new file mode 100644 index 0000000..e97a9c7 --- /dev/null +++ b/src/main/java/de/featjar/base/shell/Variables.java @@ -0,0 +1,32 @@ +package de.featjar.base.shell; + +import java.util.List; +import java.util.Optional; + +public class Variables implements IShellCommand{ + + @Override + public void execute(ShellSession session, List cmdParams) { + +// if(cmdParams.size() == 1) { +// cmdParams.get(0).e +// } + + + + + session.printVariables(); + + } + + @Override + public Optional getShortName() { + return Optional.of("variables"); + } + + @Override + public Optional getDescription(){ + return Optional.of("print the name and type of all session variables"); + } + +} diff --git a/src/main/resources/extensions.xml b/src/main/resources/extensions.xml index 5a44fcc..aa26490 100644 --- a/src/main/resources/extensions.xml +++ b/src/main/resources/extensions.xml @@ -5,4 +5,13 @@ + + + + + + + + + From 5a7495d06668a342b85a850cb3d0f1a9a8381102 Mon Sep 17 00:00:00 2001 From: Niclas Date: Mon, 13 Oct 2025 13:01:20 +0200 Subject: [PATCH 03/30] connected Shellcommand and ACommand --- src/main/java/de/featjar/base/cli/ACommand.java | 13 +++++++++++++ src/main/java/de/featjar/base/cli/ICommand.java | 5 +++++ src/main/java/de/featjar/base/cli/OptionList.java | 6 ++++++ 3 files changed, 24 insertions(+) diff --git a/src/main/java/de/featjar/base/cli/ACommand.java b/src/main/java/de/featjar/base/cli/ACommand.java index 5485593..16c7764 100644 --- a/src/main/java/de/featjar/base/cli/ACommand.java +++ b/src/main/java/de/featjar/base/cli/ACommand.java @@ -22,6 +22,9 @@ import java.nio.file.Path; import java.util.List; +import java.util.Optional; + +import de.featjar.base.shell.ShellSession; /** * The abstract class for any command. @@ -49,4 +52,14 @@ public abstract class ACommand implements ICommand { public final List> getOptions() { return Option.getAllOptions(getClass()); } + + public OptionList getShellOptions(ShellSession session, List cmdParams) { + OptionList optionList = new OptionList(); + Optional path = session.get(cmdParams.get(0), Path.class); + + optionList.parseArguments(); + optionList.parseProperties(INPUT_OPTION, String.valueOf(path.get())); + + return optionList; + } //TODO OUTPUT, look } diff --git a/src/main/java/de/featjar/base/cli/ICommand.java b/src/main/java/de/featjar/base/cli/ICommand.java index b3d87b1..1bef9d9 100644 --- a/src/main/java/de/featjar/base/cli/ICommand.java +++ b/src/main/java/de/featjar/base/cli/ICommand.java @@ -20,6 +20,8 @@ */ package de.featjar.base.cli; +import java.nio.file.Path; +import java.nio.file.Paths; import java.util.ArrayList; import java.util.List; import java.util.Optional; @@ -67,7 +69,10 @@ default Optional getShortName() { default OptionList getShellOptions(ShellSession session, List cmdParams) { OptionList optionList = new OptionList(); + optionList.parseArguments(); + + return optionList; } } diff --git a/src/main/java/de/featjar/base/cli/OptionList.java b/src/main/java/de/featjar/base/cli/OptionList.java index f424d33..e8bc615 100644 --- a/src/main/java/de/featjar/base/cli/OptionList.java +++ b/src/main/java/de/featjar/base/cli/OptionList.java @@ -35,6 +35,7 @@ import java.io.InputStream; import java.nio.file.Files; import java.nio.file.Path; +import java.nio.file.Paths; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; @@ -357,6 +358,11 @@ private void parseConfigurationFiles(List problemList) { } } } + + public void parseProperties(Option option, String command) { + Result parse = option.parse(command); + properties.put(option.getName(), parse.get()); + } private void parseRemainingArguments(List problemList) { ListIterator listIterator = arguments.listIterator(); From 31a3032b75b9a255cea2fca282d5028294a0574a Mon Sep 17 00:00:00 2001 From: Niclas Date: Mon, 13 Oct 2025 13:03:10 +0200 Subject: [PATCH 04/30] moved classes to base --- .../featjar/base/shell/PrintShellCommand.java | 2 +- .../featjar/base/shell/RunShellCommand.java | 24 ++++++++++++++----- .../java/de/featjar/base/shell/Shell.java | 2 +- .../de/featjar/base/shell/ShellCommands.java | 8 ++++++- .../de/featjar/base/shell/ShellSession.java | 6 ++++- .../java/de/featjar/base/shell/Variables.java | 7 ------ 6 files changed, 32 insertions(+), 17 deletions(-) diff --git a/src/main/java/de/featjar/base/shell/PrintShellCommand.java b/src/main/java/de/featjar/base/shell/PrintShellCommand.java index 76cc368..f035e9f 100644 --- a/src/main/java/de/featjar/base/shell/PrintShellCommand.java +++ b/src/main/java/de/featjar/base/shell/PrintShellCommand.java @@ -14,7 +14,7 @@ public class PrintShellCommand implements IShellCommand { @Override public void execute(ShellSession session, List cmdParams) { - // TODO layer for electing the type and then + // TODO layer for electing the type if(cmdParams.isEmpty()) { session.printVariables(); diff --git a/src/main/java/de/featjar/base/shell/RunShellCommand.java b/src/main/java/de/featjar/base/shell/RunShellCommand.java index 5f2a1c6..f5f6e02 100644 --- a/src/main/java/de/featjar/base/shell/RunShellCommand.java +++ b/src/main/java/de/featjar/base/shell/RunShellCommand.java @@ -1,6 +1,7 @@ package de.featjar.base.shell; import java.util.List; +import java.util.NoSuchElementException; import java.util.Optional; import de.featjar.base.FeatJAR; @@ -12,13 +13,24 @@ public class RunShellCommand implements IShellCommand { @Override public void execute(ShellSession session, List cmdParams) { - ICommand cliCommand = Commands.getInstance().getExtension(cmdParams.get(0)).get(); - OptionList shellOptions = cliCommand.getShellOptions(session, cmdParams.subList(1, cmdParams.size())); - cliCommand.getOptions().forEach(o -> { - FeatJAR.log().message(o+"="+shellOptions.getResult(o).map(String::valueOf).orElse("")); + + if(cmdParams.isEmpty()) { + return; } - ); - cliCommand.run(shellOptions); + + try { + ICommand cliCommand = Commands.getInstance().getExtension(cmdParams.get(0)).get(); + OptionList shellOptions = cliCommand.getShellOptions(session, cmdParams.subList(1, cmdParams.size())); + cliCommand.getOptions().forEach(o -> { + FeatJAR.log().message(o+"="+shellOptions.getResult(o).map(String::valueOf).orElse("")); + }); + cliCommand.run(shellOptions); + + } catch (NoSuchElementException e) { + FeatJAR.log().message("Nothing there"); + } + + } @Override diff --git a/src/main/java/de/featjar/base/shell/Shell.java b/src/main/java/de/featjar/base/shell/Shell.java index 3a05ee6..d556c6d 100644 --- a/src/main/java/de/featjar/base/shell/Shell.java +++ b/src/main/java/de/featjar/base/shell/Shell.java @@ -52,7 +52,7 @@ private void run() { } private Result parseCommand(String commandString) { - ShellCommands shellCommandsExentionsPoint = FeatJAR.extensionPoint(ShellCommands.class); + ShellCommands shellCommandsExentionsPoint = ShellCommands.getInstance(); List commands = shellCommandsExentionsPoint .getExtensions().stream().filter(command -> command.getShortName() .map(name -> name.toLowerCase().startsWith(commandString)).orElse(Boolean.FALSE)) diff --git a/src/main/java/de/featjar/base/shell/ShellCommands.java b/src/main/java/de/featjar/base/shell/ShellCommands.java index e759cbf..538ca7a 100644 --- a/src/main/java/de/featjar/base/shell/ShellCommands.java +++ b/src/main/java/de/featjar/base/shell/ShellCommands.java @@ -1,5 +1,11 @@ package de.featjar.base.shell; +import de.featjar.base.FeatJAR; import de.featjar.base.extension.AExtensionPoint; -public class ShellCommands extends AExtensionPoint{} +public class ShellCommands extends AExtensionPoint{ + + public static ShellCommands getInstance() { + return FeatJAR.extensionPoint(ShellCommands.class); + } +} diff --git a/src/main/java/de/featjar/base/shell/ShellSession.java b/src/main/java/de/featjar/base/shell/ShellSession.java index 4a36211..c470e42 100644 --- a/src/main/java/de/featjar/base/shell/ShellSession.java +++ b/src/main/java/de/featjar/base/shell/ShellSession.java @@ -1,5 +1,6 @@ package de.featjar.base.shell; +import java.util.Comparator; import java.util.LinkedHashMap; import java.util.Map; import java.util.Map.Entry; @@ -82,8 +83,11 @@ public void printVariables() { } public void printSortedByVarNames() { - // TODO sort, group (type or name) + // TODO implement both methods in parser elements.entrySet().stream().sorted(Map.Entry.comparingByKey()).forEach(m -> FeatJAR.log().info(m.getKey() + " " + m.getValue().type.getSimpleName())); + } + public void printSortedByType() { + elements.entrySet().stream().sorted(Comparator.comparing(e -> String.valueOf(e.getValue().type))).forEach(m -> FeatJAR.log().info(m.getKey() + " " + m.getValue().type.getSimpleName())); } } diff --git a/src/main/java/de/featjar/base/shell/Variables.java b/src/main/java/de/featjar/base/shell/Variables.java index e97a9c7..f7df65a 100644 --- a/src/main/java/de/featjar/base/shell/Variables.java +++ b/src/main/java/de/featjar/base/shell/Variables.java @@ -8,13 +8,6 @@ public class Variables implements IShellCommand{ @Override public void execute(ShellSession session, List cmdParams) { -// if(cmdParams.size() == 1) { -// cmdParams.get(0).e -// } - - - - session.printVariables(); } From c28cb0b28b9724a5e7a8ad895c019f74def3e22e Mon Sep 17 00:00:00 2001 From: Niclas Date: Tue, 14 Oct 2025 14:06:40 +0200 Subject: [PATCH 05/30] fix: deleting not necessary result return --- src/main/java/de/featjar/base/cli/ICommand.java | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/main/java/de/featjar/base/cli/ICommand.java b/src/main/java/de/featjar/base/cli/ICommand.java index 1bef9d9..a3fb0ef 100644 --- a/src/main/java/de/featjar/base/cli/ICommand.java +++ b/src/main/java/de/featjar/base/cli/ICommand.java @@ -20,8 +20,6 @@ */ package de.featjar.base.cli; -import java.nio.file.Path; -import java.nio.file.Paths; import java.util.ArrayList; import java.util.List; import java.util.Optional; @@ -71,8 +69,7 @@ default OptionList getShellOptions(ShellSession session, List cmdParams) OptionList optionList = new OptionList(); optionList.parseArguments(); - - + return optionList; } } From 583ae37ed0fd0ee89a424dc7781591dcae2ffcbe Mon Sep 17 00:00:00 2001 From: Niclas Date: Tue, 14 Oct 2025 14:07:08 +0200 Subject: [PATCH 06/30] feat: added error handling --- .../java/de/featjar/base/cli/ACommand.java | 23 +++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/src/main/java/de/featjar/base/cli/ACommand.java b/src/main/java/de/featjar/base/cli/ACommand.java index 16c7764..d8846ee 100644 --- a/src/main/java/de/featjar/base/cli/ACommand.java +++ b/src/main/java/de/featjar/base/cli/ACommand.java @@ -55,11 +55,34 @@ public final List> getOptions() { public OptionList getShellOptions(ShellSession session, List cmdParams) { OptionList optionList = new OptionList(); + + if(cmdParams.isEmpty()) { + throw new IllegalArgumentException("No path object specified"); + } + Optional path = session.get(cmdParams.get(0), Path.class); + if(path.isEmpty()) { + throw new IllegalArgumentException(String.format("'%s' is not a session object", cmdParams.get(0))); + } + optionList.parseArguments(); optionList.parseProperties(INPUT_OPTION, String.valueOf(path.get())); return optionList; } //TODO OUTPUT, look + + public OptionList getShellOptionsWIP(ShellSession session, List cmdParams) { + OptionList optionList = new OptionList(); + Optional path = session.get(cmdParams.get(0), Path.class); + + optionList.parseArguments(); + + optionList.getOptions(); + + + optionList.parseProperties(INPUT_OPTION, String.valueOf(path.get())); + + return optionList; + } //TODO OUTPUT, look } From 5642f76000b519f44a4c21a204e57fcc385ddb18 Mon Sep 17 00:00:00 2001 From: Niclas Date: Tue, 14 Oct 2025 14:08:06 +0200 Subject: [PATCH 07/30] fix: use message instead of info --- .../featjar/base/shell/ClearShellCommand.java | 4 +-- .../base/shell/DeleteShellCommand.java | 2 +- .../featjar/base/shell/HelpShellCommand.java | 6 ++-- .../featjar/base/shell/PrintShellCommand.java | 8 ++--- .../featjar/base/shell/RunShellCommand.java | 27 ++++++++++------- .../java/de/featjar/base/shell/Shell.java | 30 +++++++++++-------- .../de/featjar/base/shell/ShellSession.java | 12 +++++--- 7 files changed, 51 insertions(+), 38 deletions(-) diff --git a/src/main/java/de/featjar/base/shell/ClearShellCommand.java b/src/main/java/de/featjar/base/shell/ClearShellCommand.java index 889303b..5bb2efd 100644 --- a/src/main/java/de/featjar/base/shell/ClearShellCommand.java +++ b/src/main/java/de/featjar/base/shell/ClearShellCommand.java @@ -15,9 +15,9 @@ public void execute(ShellSession session, List cmdParams) { if(Objects.equals("y", choice)) { session.clear(); - FeatJAR.log().info("Clearing successful"); + FeatJAR.log().message("Clearing successful"); } else if(Objects.equals("n", choice)) { - FeatJAR.log().info("Clearing aborted"); + FeatJAR.log().message("Clearing aborted"); } } diff --git a/src/main/java/de/featjar/base/shell/DeleteShellCommand.java b/src/main/java/de/featjar/base/shell/DeleteShellCommand.java index cd0875c..4cd41ce 100644 --- a/src/main/java/de/featjar/base/shell/DeleteShellCommand.java +++ b/src/main/java/de/featjar/base/shell/DeleteShellCommand.java @@ -21,7 +21,7 @@ public void execute(ShellSession session, List cmdParams) { } cmdParams.forEach(e -> { - session.remove(e).ifPresentOrElse(a -> FeatJAR.log().info("Removing of " + e + " successful"), + session.remove(e).ifPresentOrElse(a -> FeatJAR.log().message("Removing of " + e + " successful"), () -> FeatJAR.log().error("Could not find a variable named " + e)); }); } diff --git a/src/main/java/de/featjar/base/shell/HelpShellCommand.java b/src/main/java/de/featjar/base/shell/HelpShellCommand.java index 32bd34c..9f46239 100644 --- a/src/main/java/de/featjar/base/shell/HelpShellCommand.java +++ b/src/main/java/de/featjar/base/shell/HelpShellCommand.java @@ -13,12 +13,12 @@ public void execute(ShellSession session, List cmdParams) { } public void printCommands() { - FeatJAR.log().info("Interactive shell - supported commands are (capitalization is not taken into account):\n"); + FeatJAR.log().message("Interactive shell - supported commands are (capitalization is not taken into account):\n"); FeatJAR.extensionPoint(ShellCommands.class).getExtensions() .stream().map(c -> c.getShortName().orElse("") .concat(" - " + c.getDescription().orElse(""))) - .forEach(FeatJAR.log()::info); - FeatJAR.log().info("\n"); + .forEach(FeatJAR.log()::message); + FeatJAR.log().message("\n"); } @Override diff --git a/src/main/java/de/featjar/base/shell/PrintShellCommand.java b/src/main/java/de/featjar/base/shell/PrintShellCommand.java index f035e9f..4d7e742 100644 --- a/src/main/java/de/featjar/base/shell/PrintShellCommand.java +++ b/src/main/java/de/featjar/base/shell/PrintShellCommand.java @@ -26,7 +26,7 @@ public void execute(ShellSession session, List cmdParams) { cmdParams.forEach(e -> { session.getElement(e) .ifPresentOrElse(m -> { - FeatJAR.log().info(e + ":"); + FeatJAR.log().message(e + ":"); printMap(m); }, () -> FeatJAR.log().error("Could not find a variable named " + e)); }); @@ -34,11 +34,11 @@ public void execute(ShellSession session, List cmdParams) { private void printMap(Object v) { if (v instanceof ITree) { - FeatJAR.log().info(((ITree) v).print()); + FeatJAR.log().message(((ITree) v).print()); } else { - FeatJAR.log().info(v); + FeatJAR.log().message(v); } - FeatJAR.log().info(""); + FeatJAR.log().message(""); } public Optional getShortName() { diff --git a/src/main/java/de/featjar/base/shell/RunShellCommand.java b/src/main/java/de/featjar/base/shell/RunShellCommand.java index f5f6e02..77d8f26 100644 --- a/src/main/java/de/featjar/base/shell/RunShellCommand.java +++ b/src/main/java/de/featjar/base/shell/RunShellCommand.java @@ -8,6 +8,7 @@ import de.featjar.base.cli.Commands; import de.featjar.base.cli.ICommand; import de.featjar.base.cli.OptionList; +import de.featjar.base.data.Result; public class RunShellCommand implements IShellCommand { @@ -15,22 +16,26 @@ public class RunShellCommand implements IShellCommand { public void execute(ShellSession session, List cmdParams) { if(cmdParams.isEmpty()) { + FeatJAR.log().info(String.format("Usage: %s", getDescription().orElse(""))); return; - } - + } try { - ICommand cliCommand = Commands.getInstance().getExtension(cmdParams.get(0)).get(); - OptionList shellOptions = cliCommand.getShellOptions(session, cmdParams.subList(1, cmdParams.size())); - cliCommand.getOptions().forEach(o -> { + Result cliCommand = Commands.getInstance().getExtension(cmdParams.get(0)); + + if(cliCommand.isEmpty()) { + FeatJAR.log().error(String.format("Command '%s' not found", cmdParams.get(0))); + return; + } + OptionList shellOptions = cliCommand.get().getShellOptions(session, cmdParams.subList(1, cmdParams.size())); + //TODO Alter options + cliCommand.get().getOptions().forEach(o -> { FeatJAR.log().message(o+"="+shellOptions.getResult(o).map(String::valueOf).orElse("")); }); - cliCommand.run(shellOptions); + cliCommand.get().run(shellOptions); - } catch (NoSuchElementException e) { - FeatJAR.log().message("Nothing there"); + } catch (IllegalArgumentException iae) { + FeatJAR.log().error(iae.getMessage()); } - - } @Override @@ -40,7 +45,7 @@ public Optional getShortName() { @Override public Optional getDescription() { - return Optional.of("run..."); + return Optional.of("run... WIP"); } } diff --git a/src/main/java/de/featjar/base/shell/Shell.java b/src/main/java/de/featjar/base/shell/Shell.java index d556c6d..9914d6a 100644 --- a/src/main/java/de/featjar/base/shell/Shell.java +++ b/src/main/java/de/featjar/base/shell/Shell.java @@ -59,21 +59,25 @@ private Result parseCommand(String commandString) { .collect(Collectors.toList()); if (commands.size() > 1) { - Map temp = new HashMap(); + Map ambiguousCommands = new HashMap(); int i = 1; FeatJAR.log().info - ("Command name %s is ambiguous! choose one of the following %d commands: \n", commandString, commands.size()); + ("Command name %s is ambiguous! choose one of the following %d commands (leave balnk to abort): \n", commandString, commands.size()); for(IShellCommand c : commands) { - FeatJAR.log().info(i + "." + c.getShortName().get() + " - " + c.getDescription().get()); - temp.put(i, c); + FeatJAR.log().message(i + "." + c.getShortName().get() + " - " + c.getDescription().get()); + ambiguousCommands.put(i, c); i++; } String choice = readCommand("").orElse(""); - for (Map.Entry entry : temp.entrySet()) { + if(choice.isBlank()) { + return Result.empty(); + } + + for (Map.Entry entry : ambiguousCommands.entrySet()) { if (Objects.equals(entry.getKey(), Integer.parseInt(choice))) { return Result.of(entry.getValue()); } @@ -87,7 +91,7 @@ private Result parseCommand(String commandString) { if (commands.isEmpty()) { Result matchingExtension = shellCommandsExentionsPoint.getMatchingExtension(commandString); if (matchingExtension.isEmpty()) { - FeatJAR.log().info("No such command '%s'. \n shows all viable commands", commandString); + FeatJAR.log().message("No such command '%s'. \n shows all viable commands", commandString); return Result.empty(addProblem(Severity.ERROR, "No command matched the name '%s'!", commandString)); } @@ -112,17 +116,17 @@ private Problem addProblem(Severity severity, String message, Object... argument } public static Optional readCommand(String prompt) { - FeatJAR.log().info(prompt); + FeatJAR.log().message(prompt); String input = shellScanner.nextLine().trim(); return input.isEmpty() ? Optional.empty() : Optional.of(input); } public static void printArt() { - FeatJAR.log().info(" _____ _ _ _ ____ ____ _ _ _ "); - FeatJAR.log().info("| ___|___ __ _ | |_ | | / \\ | _ \\ / ___| | |__ ___ | || |"); - FeatJAR.log().info("| |_ / _ \\ / _` || __|_ | | / _ \\ | |_) |\\___ \\ | '_ \\ / _ \\| || |"); - FeatJAR.log().info("| _|| __/| (_| || |_| |_| |/ ___ \\ | _ < ___) || | | || __/| || |"); - FeatJAR.log().info("|_| \\___| \\__,_| \\__|\\___//_/ \\_\\|_| \\_\\|____/ |_| |_| \\___||_||_|"); - FeatJAR.log().info("\n"); + FeatJAR.log().message(" _____ _ _ _ ____ ____ _ _ _ "); + FeatJAR.log().message("| ___|___ __ _ | |_ | | / \\ | _ \\ / ___| | |__ ___ | || |"); + FeatJAR.log().message("| |_ / _ \\ / _` || __|_ | | / _ \\ | |_) |\\___ \\ | '_ \\ / _ \\| || |"); + FeatJAR.log().message("| _|| __/| (_| || |_| |_| |/ ___ \\ | _ < ___) || | | || __/| || |"); + FeatJAR.log().message("|_| \\___| \\__,_| \\__|\\___//_/ \\_\\|_| \\_\\|____/ |_| |_| \\___||_||_|"); + FeatJAR.log().message("\n"); } } diff --git a/src/main/java/de/featjar/base/shell/ShellSession.java b/src/main/java/de/featjar/base/shell/ShellSession.java index c470e42..a025fb4 100644 --- a/src/main/java/de/featjar/base/shell/ShellSession.java +++ b/src/main/java/de/featjar/base/shell/ShellSession.java @@ -68,26 +68,30 @@ public boolean containsKey(String key) { return elements.containsKey(key); } + public boolean isEmpty() { + return elements.isEmpty(); + } + public void printVariable(String key) { for (Entry> entry : elements.entrySet()) { if(entry.getKey().equals(key)) { - FeatJAR.log().info("Variable: " + key + " Type: " + entry.getValue().type.getSimpleName() + "\n"); + FeatJAR.log().message("Variable: " + key + " Type: " + entry.getValue().type.getSimpleName() + "\n"); break; } } } public void printVariables() { - elements.entrySet().forEach(m -> FeatJAR.log().info(m.getKey() + " (" + elements.entrySet().forEach(m -> FeatJAR.log().message(m.getKey() + " (" + m.getValue().type.getSimpleName() + ")" )); } public void printSortedByVarNames() { // TODO implement both methods in parser - elements.entrySet().stream().sorted(Map.Entry.comparingByKey()).forEach(m -> FeatJAR.log().info(m.getKey() + " " + m.getValue().type.getSimpleName())); + elements.entrySet().stream().sorted(Map.Entry.comparingByKey()).forEach(m -> FeatJAR.log().message(m.getKey() + " " + m.getValue().type.getSimpleName())); } public void printSortedByType() { - elements.entrySet().stream().sorted(Comparator.comparing(e -> String.valueOf(e.getValue().type))).forEach(m -> FeatJAR.log().info(m.getKey() + " " + m.getValue().type.getSimpleName())); + elements.entrySet().stream().sorted(Comparator.comparing(e -> String.valueOf(e.getValue().type))).forEach(m -> FeatJAR.log().message(m.getKey() + " " + m.getValue().type.getSimpleName())); } } From e776cd567edbbe4693b482889f75e0f35c33a90e Mon Sep 17 00:00:00 2001 From: Niclas Date: Thu, 23 Oct 2025 21:20:26 +0200 Subject: [PATCH 08/30] feat: advanced key handling for raw terminal --- .../java/de/featjar/base/shell/Shell.java | 357 ++++++++++++++++-- 1 file changed, 331 insertions(+), 26 deletions(-) diff --git a/src/main/java/de/featjar/base/shell/Shell.java b/src/main/java/de/featjar/base/shell/Shell.java index 9914d6a..502d76f 100644 --- a/src/main/java/de/featjar/base/shell/Shell.java +++ b/src/main/java/de/featjar/base/shell/Shell.java @@ -1,13 +1,21 @@ package de.featjar.base.shell; +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStreamReader; +import java.nio.file.Path; +import java.nio.file.Paths; import java.util.Arrays; import java.util.Collections; import java.util.HashMap; +import java.util.LinkedList; import java.util.List; +import java.util.ListIterator; import java.util.Map; import java.util.Objects; import java.util.Optional; import java.util.Scanner; +import java.util.concurrent.CancellationException; import java.util.stream.Collectors; import de.featjar.base.FeatJAR; @@ -15,17 +23,51 @@ import de.featjar.base.data.Problem.Severity; import de.featjar.base.data.Result; -public class Shell { +public class Shell { public static Shell shell = null; - ShellSession session = new ShellSession(); - private static final Scanner shellScanner = new Scanner(System.in); + private ShellSession session; + private final String osName; + private final Scanner shellScanner; + private List history; + private ListIterator historyIterator; + private final BufferedReader reader; + private StringBuilder input; + private int cursorX = 0, cursorY = 0; + private boolean lastArrowKeyUp = false; + + private static final String TERMINAL_COLOR_RED = "\033[0;31m"; + private static final String TERMINAL_COLOR_RESET = "\033[0m"; + + private static final int KEY_ARROW_UP = 1000; + private static final int KEY_ARROW_DOWN = 1001; + private static final int KEY_ARROW_LEFT = 1002; + private static final int KEY_ARROW_RIGHT = 1003; + private static final int KEY_BACKSPACE = 1004; + private static final int KEY_DELETE = 1005; + private static final int KEY_ENTER = 1006; + private static final int KEY_ESCAPE = 1007; + private static final int KEY_PAGE_UP = 1010; + private static final int KEY_PAGE_DOWN = 1011; + private static final int KEY_ALT = 1012; + + private static enum LoopControl {NORMAL, BREAK, CONTINUE}; private Shell() { - FeatJAR.initialize(FeatJAR.shellConfiguration()); - printArt(); - new HelpShellCommand().printCommands(); - run(); + this.session = new ShellSession(); + this.osName = System.getProperty("os.name"); + this.history = new LinkedList(); + this.historyIterator = history.listIterator(); + + if(isWindows()) { + this.shellScanner = new Scanner(System.in); + this.reader = null; + } else { + this.shellScanner = null; + this.reader = new BufferedReader(new InputStreamReader(System.in)); + } + //TODO remove the next line ! + session.put("p", Paths.get("../feature-model/testFeatureModels/basic.xml"), Path.class); } public static Shell getInstance() { @@ -33,25 +75,40 @@ public static Shell getInstance() { } public static void main(String[] args) { - Shell.getInstance(); + Shell.getInstance().run(); } private void run() { + FeatJAR.initialize(FeatJAR.shellConfiguration()); + printArt(); + new HelpShellCommand().printCommands(); while (true) { -// List cmdArg = Arrays.stream(readCommand("$").split("\\s+")).collect(Collectors.toList()); - List cmdArg = readCommand("$") + List cmdArg = null; + try { + cmdArg = readCommand("$") .map(c -> Arrays.stream(c.split("\\s+")).collect(Collectors.toList())) .orElse(Collections.emptyList()); - + } catch (CancellationException e) { + exitInputMode(); + System.exit(0); + } if(!cmdArg.isEmpty()) { + try { Result command = parseCommand(cmdArg.get(0)); cmdArg.remove(0); - command.ifPresent(cmd -> cmd.execute(session, cmdArg)); + if(command.isPresent()) { + history.add(command.get().getShortName().get() + " " + cmdArg.stream().map(String::valueOf).collect(Collectors.joining(" "))); + command.get().execute(session, cmdArg); + } + } catch (CancellationException e) { + FeatJAR.log().message(e.getMessage()); + } + } } } - private Result parseCommand(String commandString) { + private Result parseCommand(String commandString) { ShellCommands shellCommandsExentionsPoint = ShellCommands.getInstance(); List commands = shellCommandsExentionsPoint .getExtensions().stream().filter(command -> command.getShortName() @@ -61,24 +118,32 @@ private Result parseCommand(String commandString) { if (commands.size() > 1) { Map ambiguousCommands = new HashMap(); int i = 1; - + FeatJAR.log().info - ("Command name %s is ambiguous! choose one of the following %d commands (leave balnk to abort): \n", commandString, commands.size()); + (wrapErorrColor("Command name '%s' is ambiguous! choose one of the following %d commands (leave blank to abort): \n") + , commandString, commands.size()); for(IShellCommand c : commands) { FeatJAR.log().message(i + "." + c.getShortName().get() + " - " + c.getDescription().get()); ambiguousCommands.put(i, c); i++; } - + String choice = readCommand("").orElse(""); - + if(choice.isBlank()) { return Result.empty(); } - + int parsedChoice; + try { + parsedChoice = Integer.parseInt(choice); + }catch (NumberFormatException e) { + return Result.empty(addProblem(Severity.ERROR, String.format("'%s' is no vaild number", choice), e)); + } + + for (Map.Entry entry : ambiguousCommands.entrySet()) { - if (Objects.equals(entry.getKey(), Integer.parseInt(choice))) { + if (Objects.equals(entry.getKey(), parsedChoice)) { return Result.of(entry.getValue()); } } @@ -91,8 +156,7 @@ private Result parseCommand(String commandString) { if (commands.isEmpty()) { Result matchingExtension = shellCommandsExentionsPoint.getMatchingExtension(commandString); if (matchingExtension.isEmpty()) { - FeatJAR.log().message("No such command '%s'. \n shows all viable commands", commandString); - + FeatJAR.log().message(Shell.wrapErorrColor("No such command '"+commandString+"'. \n shows all viable commands")); return Result.empty(addProblem(Severity.ERROR, "No command matched the name '%s'!", commandString)); } command = matchingExtension.get(); @@ -114,14 +178,255 @@ private Result parseCommand(String commandString) { private Problem addProblem(Severity severity, String message, Object... arguments) { return new Problem(String.format(message, arguments), severity); } - + + private boolean isWindows() { + return osName.startsWith("Windows"); + } + + + /** + * Displays the typed characters in the console. + * '\r' moves the cursor to the beginning of the line + * '\u001B[2K' or '\033[2K' erases the entire line + * '\u001B' (unicode) or '\033' (octal) for ESC work fine here + * '\u001B[#G' moves cursor to column # + * see for more documentation: https://gist.github.com/fnky/458719343aabd01cfb17a3a4f7296797 + * @param cursor the cursor position + * @param typedText the typed characters + */ + + private static void displayCharacters(int cursor, String typedText) { + FeatJAR.log().noLineBreakMessage("\r"); + FeatJAR.log().noLineBreakMessage("\033[2K"); + FeatJAR.log().noLineBreakMessage("$"+typedText); + FeatJAR.log().noLineBreakMessage("\033[" + (cursor + 2) + "G"); + } + + public static String wrapErorrColor(String message) { + return TERMINAL_COLOR_RED + message + TERMINAL_COLOR_RESET; + } + public static Optional readCommand(String prompt) { - FeatJAR.log().message(prompt); - String input = shellScanner.nextLine().trim(); - return input.isEmpty() ? Optional.empty() : Optional.of(input); + return Shell.getInstance().WIP(prompt); + } + + private Optional WIP(String prompt) { + FeatJAR.log().noLineBreakMessage(prompt); + + if(isWindows()) { + String inputWindows = shellScanner.nextLine().trim(); + return inputWindows.isEmpty() ? Optional.empty() : Optional.of(inputWindows); + } + + input = new StringBuilder(); + int inputCharacter; + historyIterator = history.listIterator(history.size()); + String cmd = (history.size() > 0) ? history.get(history.size() -1) : ""; + + try { + enterInputMode(); + while (true){ + + inputCharacter = reader.read(); + + if(inputCharacter == '\r' || inputCharacter == '\n') { + FeatJAR.log().noLineBreakMessage("\r\n"); + break; + } + + if (inputCharacter == 127 || inputCharacter == 8) { + handleBackspaceKey(); + continue; + } + + if(inputCharacter == 27) { + if(reader.ready()) { + inputCharacter = reader.read(); + } + if(inputCharacter == '[') { + inputCharacter = reader.read(); + switch (inputCharacter) { + case 'A': + if(historyIterator == null) { + continue; + } + + if(historyIterator.hasPrevious()) { + cmd = historyIterator.previous(); + moveUpHistory(input, cmd); + continue; + } + + if(!historyIterator.hasPrevious()) { + moveUpHistory(input, cmd); + continue; + } + case 'B': + if(historyIterator == null) { + continue; + } + + if(lastArrowKeyUp && historyIterator.hasNext()) { + cmd = historyIterator.next(); + } + + if(!historyIterator.hasNext()) { + moveOutOfHistory(input); + continue; + } + + cmd = historyIterator.next(); + moveDownHistory(input, cmd); + continue; + + case 'C': + moveCursorRight(input); + break; + case 'D': + moveCursorLeft(); + break; + } + if (inputCharacter == 51) { + handleDeleteKey(inputCharacter); + } + } else if (input.length() != 0){ + historyIterator = history.listIterator(history.size()); + resetInputLine(input); + } else { + exitInputMode(); + throw new CancellationException("\nCommand canceled\n"); + } + } else { + handleNormalKey(inputCharacter); + } + lastArrowKeyUp = false; + } + + } catch (IOException e) { + + e.printStackTrace(); + } finally { + exitInputMode(); + } + cursorX = 0; + + return input.length() == 0 ? Optional.empty() : Optional.of(String.valueOf(input)); + } + + private void handleBackspaceKey() { + if (input.length() != 0) { + + if(cursorX >= 0) { + if(cursorX <= input.length() && cursorX != 0) { + handleBackspaceKey(input); + } + if(cursorX != 0) { + cursorX--; + } + } +// else { +// FeatJAR.log().noLineBreakMessage(("\b \b")); +// } + } + } + + private void handleDeleteKey(int inputCharacter) throws IOException { + if(reader.ready()) { + inputCharacter = reader.read(); + } + if(inputCharacter == '~') { + handleDeleteKey(input); + } + } + + private void handleNormalKey(int inputCharacter) { + cursorX++; + + if(input.length() == 0) { + input.append((char) inputCharacter); + } else { + input.insert(cursorX-1,(char) inputCharacter); + } + displayCharacters(cursorX, input.toString()); + } + + private void resetInputLine(StringBuilder input) { + input.setLength(0); + input.append(""); + displayCharacters(cursorX, ""); + } + + private void moveDownHistory(StringBuilder input, String cmd) { + input.setLength(0); + input.append(cmd); + displayCharacters(cursorX, cmd); + lastArrowKeyUp = false; + } + + private void moveOutOfHistory(StringBuilder input) { + resetInputLine(input); + lastArrowKeyUp = false; + } + + private void moveUpHistory(StringBuilder input, String cmd) { + input.setLength(0); + input.append(cmd); + displayCharacters(cursorX, cmd); + lastArrowKeyUp = true; + } + + private void moveCursorLeft() { + FeatJAR.log().noLineBreakMessage("\033[D"+""); + if(cursorX > 0) { + cursorX--; + } + } + + private void moveCursorRight(StringBuilder input) { + if (cursorX < input.length()) { + FeatJAR.log().noLineBreakMessage("\033[C"); + cursorX++; + } + } + + private void handleDeleteKey(StringBuilder input) { + if(input.length() != 0 && cursorX != input.length()) { + input.deleteCharAt(cursorX); + displayCharacters(cursorX, input.toString()); + } + } + + private void handleBackspaceKey(StringBuilder input) { + input.deleteCharAt(cursorX-1); + displayCharacters(cursorX, input.toString()); + FeatJAR.log().noLineBreakMessage("\b"); + } + + /** + * TODO + */ + + private void enterInputMode () { + try { + Runtime.getRuntime().exec(new String[]{"sh","-c","stty -icanon -echo min 1 time 0 -isig -ixon opost onlcr Date: Thu, 23 Oct 2025 21:40:38 +0200 Subject: [PATCH 09/30] refactor: extracted methods from readShellCommand --- .../java/de/featjar/base/shell/Shell.java | 153 ++++++++---------- 1 file changed, 70 insertions(+), 83 deletions(-) diff --git a/src/main/java/de/featjar/base/shell/Shell.java b/src/main/java/de/featjar/base/shell/Shell.java index 502d76f..3740a68 100644 --- a/src/main/java/de/featjar/base/shell/Shell.java +++ b/src/main/java/de/featjar/base/shell/Shell.java @@ -33,25 +33,13 @@ public class Shell { private ListIterator historyIterator; private final BufferedReader reader; private StringBuilder input; + String historyCommandLine; private int cursorX = 0, cursorY = 0; private boolean lastArrowKeyUp = false; private static final String TERMINAL_COLOR_RED = "\033[0;31m"; private static final String TERMINAL_COLOR_RESET = "\033[0m"; - private static final int KEY_ARROW_UP = 1000; - private static final int KEY_ARROW_DOWN = 1001; - private static final int KEY_ARROW_LEFT = 1002; - private static final int KEY_ARROW_RIGHT = 1003; - private static final int KEY_BACKSPACE = 1004; - private static final int KEY_DELETE = 1005; - private static final int KEY_ENTER = 1006; - private static final int KEY_ESCAPE = 1007; - private static final int KEY_PAGE_UP = 1010; - private static final int KEY_PAGE_DOWN = 1011; - private static final int KEY_ALT = 1012; - - private static enum LoopControl {NORMAL, BREAK, CONTINUE}; private Shell() { this.session = new ShellSession(); @@ -165,7 +153,7 @@ private Result parseCommand(String commandString) { command = commands.get(0); return Result.of(command); } - String choice = readCommand("Do you mean: " + commands.get(0).getShortName().get() + "? (ENTER) or (a)bort").orElse(""); + String choice = readCommand("Do you mean: " + commands.get(0).getShortName().get() + "? (ENTER) or (a)bort\n").orElse(""); if(choice.isEmpty()) { command = commands.get(0); } else { @@ -207,10 +195,10 @@ public static String wrapErorrColor(String message) { } public static Optional readCommand(String prompt) { - return Shell.getInstance().WIP(prompt); + return Shell.getInstance().readShellCommand(prompt); } - private Optional WIP(String prompt) { + private Optional readShellCommand(String prompt) { FeatJAR.log().noLineBreakMessage(prompt); if(isWindows()) { @@ -221,7 +209,7 @@ private Optional WIP(String prompt) { input = new StringBuilder(); int inputCharacter; historyIterator = history.listIterator(history.size()); - String cmd = (history.size() > 0) ? history.get(history.size() -1) : ""; + historyCommandLine = (history.size() > 0) ? history.get(history.size() -1) : ""; try { enterInputMode(); @@ -245,61 +233,24 @@ private Optional WIP(String prompt) { } if(inputCharacter == '[') { inputCharacter = reader.read(); - switch (inputCharacter) { - case 'A': - if(historyIterator == null) { - continue; - } - - if(historyIterator.hasPrevious()) { - cmd = historyIterator.previous(); - moveUpHistory(input, cmd); - continue; - } - - if(!historyIterator.hasPrevious()) { - moveUpHistory(input, cmd); - continue; - } - case 'B': - if(historyIterator == null) { - continue; - } - - if(lastArrowKeyUp && historyIterator.hasNext()) { - cmd = historyIterator.next(); - } - - if(!historyIterator.hasNext()) { - moveOutOfHistory(input); - continue; - } - - cmd = historyIterator.next(); - moveDownHistory(input, cmd); - continue; - - case 'C': - moveCursorRight(input); - break; - case 'D': - moveCursorLeft(); - break; - } + handleArrowKeys(inputCharacter); if (inputCharacter == 51) { handleDeleteKey(inputCharacter); + lastArrowKeyUp = false; } } else if (input.length() != 0){ historyIterator = history.listIterator(history.size()); - resetInputLine(input); + resetInputLine(); + lastArrowKeyUp = false; } else { exitInputMode(); throw new CancellationException("\nCommand canceled\n"); } } else { handleNormalKey(inputCharacter); + lastArrowKeyUp = false; } - lastArrowKeyUp = false; +// lastArrowKeyUp = false; } } catch (IOException e) { @@ -313,20 +264,62 @@ private Optional WIP(String prompt) { return input.length() == 0 ? Optional.empty() : Optional.of(String.valueOf(input)); } + private void handleArrowKeys(int inputCharacter) { + switch (inputCharacter) { + case 'A': + if(historyIterator == null) { + return; + } + + if(historyIterator.hasPrevious()) { + historyCommandLine = historyIterator.previous(); + moveUpHistory(); + return; + } + + if(!historyIterator.hasPrevious()) { + moveUpHistory(); + return; + } + case 'B': + if(historyIterator == null) { + return; + } + + if(lastArrowKeyUp && historyIterator.hasNext()) { + historyCommandLine = historyIterator.next(); + } + + if(!historyIterator.hasNext()) { + moveOutOfHistory(); + return; + } + + historyCommandLine = historyIterator.next(); + moveDownHistory(); + return; + + case 'C': + moveCursorRight(); + break; + case 'D': + moveCursorLeft(); + break; + } + } + private void handleBackspaceKey() { if (input.length() != 0) { - if(cursorX >= 0) { if(cursorX <= input.length() && cursorX != 0) { - handleBackspaceKey(input); + input.deleteCharAt(cursorX-1); + displayCharacters(cursorX, input.toString()); + FeatJAR.log().noLineBreakMessage("\b"); } if(cursorX != 0) { cursorX--; } } -// else { -// FeatJAR.log().noLineBreakMessage(("\b \b")); -// } } } @@ -335,7 +328,7 @@ private void handleDeleteKey(int inputCharacter) throws IOException { inputCharacter = reader.read(); } if(inputCharacter == '~') { - handleDeleteKey(input); + handleDeleteKey(); } } @@ -350,28 +343,28 @@ private void handleNormalKey(int inputCharacter) { displayCharacters(cursorX, input.toString()); } - private void resetInputLine(StringBuilder input) { + private void resetInputLine() { input.setLength(0); input.append(""); displayCharacters(cursorX, ""); } - private void moveDownHistory(StringBuilder input, String cmd) { + private void moveDownHistory() { input.setLength(0); - input.append(cmd); - displayCharacters(cursorX, cmd); + input.append(historyCommandLine); + displayCharacters(cursorX, historyCommandLine); lastArrowKeyUp = false; } - private void moveOutOfHistory(StringBuilder input) { - resetInputLine(input); + private void moveOutOfHistory() { + resetInputLine(); lastArrowKeyUp = false; } - private void moveUpHistory(StringBuilder input, String cmd) { + private void moveUpHistory() { input.setLength(0); - input.append(cmd); - displayCharacters(cursorX, cmd); + input.append(historyCommandLine); + displayCharacters(cursorX, historyCommandLine); lastArrowKeyUp = true; } @@ -382,26 +375,20 @@ private void moveCursorLeft() { } } - private void moveCursorRight(StringBuilder input) { + private void moveCursorRight() { if (cursorX < input.length()) { FeatJAR.log().noLineBreakMessage("\033[C"); cursorX++; } } - private void handleDeleteKey(StringBuilder input) { + private void handleDeleteKey() { if(input.length() != 0 && cursorX != input.length()) { input.deleteCharAt(cursorX); displayCharacters(cursorX, input.toString()); } } - private void handleBackspaceKey(StringBuilder input) { - input.deleteCharAt(cursorX-1); - displayCharacters(cursorX, input.toString()); - FeatJAR.log().noLineBreakMessage("\b"); - } - /** * TODO */ From f0a6c6a67b14a4ff83654d78d32471d82e9b5854 Mon Sep 17 00:00:00 2001 From: Niclas Date: Fri, 24 Oct 2025 22:11:55 +0200 Subject: [PATCH 10/30] style: separated messages --- src/main/java/de/featjar/base/shell/HelpShellCommand.java | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/main/java/de/featjar/base/shell/HelpShellCommand.java b/src/main/java/de/featjar/base/shell/HelpShellCommand.java index 9f46239..e30060d 100644 --- a/src/main/java/de/featjar/base/shell/HelpShellCommand.java +++ b/src/main/java/de/featjar/base/shell/HelpShellCommand.java @@ -13,7 +13,11 @@ public void execute(ShellSession session, List cmdParams) { } public void printCommands() { - FeatJAR.log().message("Interactive shell - supported commands are (capitalization is not taken into account):\n"); + FeatJAR.log().message("Interactive shell"); + FeatJAR.log().message("Capitalization is NOT taken into account"); + FeatJAR.log().message("You can cancel ANY command by pressing the (ESC) key"); + FeatJAR.log().message("Supported commands are: \n"); + FeatJAR.extensionPoint(ShellCommands.class).getExtensions() .stream().map(c -> c.getShortName().orElse("") .concat(" - " + c.getDescription().orElse(""))) From 3904f26ac6391e405da25c969796f6c78d48401d Mon Sep 17 00:00:00 2001 From: Niclas Date: Fri, 24 Oct 2025 22:13:44 +0200 Subject: [PATCH 11/30] style: removed empty lines --- src/main/java/de/featjar/base/shell/Variables.java | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/main/java/de/featjar/base/shell/Variables.java b/src/main/java/de/featjar/base/shell/Variables.java index f7df65a..ec22a67 100644 --- a/src/main/java/de/featjar/base/shell/Variables.java +++ b/src/main/java/de/featjar/base/shell/Variables.java @@ -7,9 +7,7 @@ public class Variables implements IShellCommand{ @Override public void execute(ShellSession session, List cmdParams) { - session.printVariables(); - } @Override From 0626eaea193814ed9bbc6c41235bdcbbc8c9b437 Mon Sep 17 00:00:00 2001 From: Niclas Date: Fri, 24 Oct 2025 22:17:15 +0200 Subject: [PATCH 12/30] feat: possibility to alter options --- .../featjar/base/shell/RunShellCommand.java | 67 ++++++++++++++++--- 1 file changed, 57 insertions(+), 10 deletions(-) diff --git a/src/main/java/de/featjar/base/shell/RunShellCommand.java b/src/main/java/de/featjar/base/shell/RunShellCommand.java index 77d8f26..9429f67 100644 --- a/src/main/java/de/featjar/base/shell/RunShellCommand.java +++ b/src/main/java/de/featjar/base/shell/RunShellCommand.java @@ -1,20 +1,20 @@ package de.featjar.base.shell; import java.util.List; -import java.util.NoSuchElementException; import java.util.Optional; +import java.util.concurrent.atomic.AtomicInteger; import de.featjar.base.FeatJAR; import de.featjar.base.cli.Commands; import de.featjar.base.cli.ICommand; +import de.featjar.base.cli.Option; import de.featjar.base.cli.OptionList; import de.featjar.base.data.Result; public class RunShellCommand implements IShellCommand { @Override - public void execute(ShellSession session, List cmdParams) { - + public void execute(ShellSession session, List cmdParams) { if(cmdParams.isEmpty()) { FeatJAR.log().info(String.format("Usage: %s", getDescription().orElse(""))); return; @@ -27,15 +27,62 @@ public void execute(ShellSession session, List cmdParams) { return; } OptionList shellOptions = cliCommand.get().getShellOptions(session, cmdParams.subList(1, cmdParams.size())); - //TODO Alter options - cliCommand.get().getOptions().forEach(o -> { - FeatJAR.log().message(o+"="+shellOptions.getResult(o).map(String::valueOf).orElse("")); - }); - cliCommand.get().run(shellOptions); + + shellOptions = alterOptions(cliCommand, shellOptions); + + int runResult = cliCommand.get().run(shellOptions); + + if(runResult == 0) { + FeatJAR.log().message("Successfull"); + } else { + FeatJAR.log().error(Shell.wrapErorrColor("Errorcode '%d' occured in command '%s'"), runResult, cliCommand.get().getIdentifier()); + } } catch (IllegalArgumentException iae) { - FeatJAR.log().error(iae.getMessage()); + iae.printStackTrace(); + FeatJAR.log().error(Shell.wrapErorrColor(iae.getMessage())); + FeatJAR.log().info(String.format("Usage %s", getDescription().get())); + + } + } + + private OptionList alterOptions(Result cliCommand, OptionList shellOptions) { + String choice; + + while(true) { + AtomicInteger i = new AtomicInteger(1); + List> options = cliCommand.get().getOptions(); + int numberChoice; + + options.forEach(o -> { + FeatJAR.log() + .message(i.getAndIncrement()+". "+o+"="+shellOptions.getResult(o) + .map(String::valueOf).orElse("")); + }); + choice = String.valueOf(Shell + .readCommand("Alter options ?\nSelect a number or leave blank to proceed:\n") + .orElse("")) + .toLowerCase(); + + if(choice.isBlank()) { + break; + } + try { + numberChoice = Integer.parseInt(choice)-1; + } catch (NumberFormatException e) { + FeatJAR.log().error("Only decimal numbers are a valid choice"); + continue; + } + + if(options.size() - 1 < numberChoice || numberChoice < 1) { + FeatJAR.log().error("Number does not exist"); + continue; + } + + choice = String.valueOf(Shell.readCommand("Enter the new value:\n").orElse("")); + shellOptions.parseProperties(options.get(numberChoice), choice); } + return shellOptions; } @Override @@ -45,7 +92,7 @@ public Optional getShortName() { @Override public Optional getDescription() { - return Optional.of("run... WIP"); + return Optional.of("run WIP"); } } From 86249bfba12586dbe2d6775f389a77afc6a77d0e Mon Sep 17 00:00:00 2001 From: Niclas Date: Fri, 24 Oct 2025 22:18:43 +0200 Subject: [PATCH 13/30] feat: proper implemented a method to get shell options --- .../java/de/featjar/base/cli/ACommand.java | 82 ++++++++----------- 1 file changed, 34 insertions(+), 48 deletions(-) diff --git a/src/main/java/de/featjar/base/cli/ACommand.java b/src/main/java/de/featjar/base/cli/ACommand.java index d8846ee..6ed5330 100644 --- a/src/main/java/de/featjar/base/cli/ACommand.java +++ b/src/main/java/de/featjar/base/cli/ACommand.java @@ -33,56 +33,42 @@ */ public abstract class ACommand implements ICommand { - /** - * Input option for loading files. - */ - public static final Option INPUT_OPTION = Option.newOption("input", Option.PathParser) - .setDescription("Path to input file(s)") - .setValidator(Option.PathValidator); + /** + * Input option for loading files. + */ + public static final Option INPUT_OPTION = Option.newOption("input", Option.PathParser) + .setDescription("Path to input file(s)").setValidator(Option.PathValidator); - /** - * Output option for saving files. - */ - public static final Option OUTPUT_OPTION = - Option.newOption("output", Option.PathParser).setDescription("Path to output file(s)"); + /** + * Output option for saving files. + */ + public static final Option OUTPUT_OPTION = Option.newOption("output", Option.PathParser) + .setDescription("Path to output file(s)"); - /** - * {@return all options registered for the calling class} - */ - public final List> getOptions() { - return Option.getAllOptions(getClass()); - } - - public OptionList getShellOptions(ShellSession session, List cmdParams) { - OptionList optionList = new OptionList(); - - if(cmdParams.isEmpty()) { - throw new IllegalArgumentException("No path object specified"); - } - - Optional path = session.get(cmdParams.get(0), Path.class); - - if(path.isEmpty()) { - throw new IllegalArgumentException(String.format("'%s' is not a session object", cmdParams.get(0))); - } - - optionList.parseArguments(); - optionList.parseProperties(INPUT_OPTION, String.valueOf(path.get())); - - return optionList; - } //TODO OUTPUT, look - - public OptionList getShellOptionsWIP(ShellSession session, List cmdParams) { - OptionList optionList = new OptionList(); - Optional path = session.get(cmdParams.get(0), Path.class); - - optionList.parseArguments(); - - optionList.getOptions(); - - + /** + * {@return all options registered for the calling class} + */ + public final List> getOptions() { + return Option.getAllOptions(getClass()); + } + + public OptionList getShellOptions(ShellSession session, List cmdParams) { + OptionList optionList = new OptionList(); + + if (cmdParams.isEmpty()) { + throw new IllegalArgumentException("No path object specified"); + } + + Optional path = session.get(cmdParams.get(0), Path.class); + + if (path.isEmpty()) { + throw new IllegalArgumentException(String.format("'%s' is not a session object", cmdParams.get(0))); + } + + optionList.parseArguments(); optionList.parseProperties(INPUT_OPTION, String.valueOf(path.get())); - + return optionList; - } //TODO OUTPUT, look + } + } From e8d488a75f7d4a932290b437fec5208aca339796 Mon Sep 17 00:00:00 2001 From: Niclas Date: Fri, 24 Oct 2025 22:22:33 +0200 Subject: [PATCH 14/30] reafactor: improved readability, feat: added auto tab completion --- .../java/de/featjar/base/shell/Shell.java | 259 ++++++++++++------ 1 file changed, 170 insertions(+), 89 deletions(-) diff --git a/src/main/java/de/featjar/base/shell/Shell.java b/src/main/java/de/featjar/base/shell/Shell.java index 3740a68..b6ce2b6 100644 --- a/src/main/java/de/featjar/base/shell/Shell.java +++ b/src/main/java/de/featjar/base/shell/Shell.java @@ -36,6 +36,7 @@ public class Shell { String historyCommandLine; private int cursorX = 0, cursorY = 0; private boolean lastArrowKeyUp = false; +// List commandList; private static final String TERMINAL_COLOR_RED = "\033[0;31m"; private static final String TERMINAL_COLOR_RESET = "\033[0m"; @@ -95,7 +96,7 @@ private void run() { } } } - + private Result parseCommand(String commandString) { ShellCommands shellCommandsExentionsPoint = ShellCommands.getInstance(); List commands = shellCommandsExentionsPoint @@ -116,7 +117,7 @@ private Result parseCommand(String commandString) { ambiguousCommands.put(i, c); i++; } - + String choice = readCommand("").orElse(""); if(choice.isBlank()) { @@ -149,7 +150,7 @@ private Result parseCommand(String commandString) { } command = matchingExtension.get(); } else { - if(commands.get(0).getShortName().get().matches(commandString)) { + if(commands.get(0).getShortName().get().toLowerCase().matches(commandString)) { command = commands.get(0); return Result.of(command); } @@ -166,6 +167,40 @@ private Result parseCommand(String commandString) { private Problem addProblem(Severity severity, String message, Object... arguments) { return new Problem(String.format(message, arguments), severity); } + + private void handleTabulatorAutoComplete() { + if (input.length() == 0) { + return; + } + List commands = ShellCommands.getInstance().getExtensions().stream() + .filter(command -> command.getShortName().map(name -> name.toLowerCase().startsWith(String.valueOf(input))).orElse(Boolean.FALSE)) + .map(cmd -> cmd.getShortName().get().toLowerCase()) + .collect(Collectors.toList()); + + if(commands.isEmpty()) { + return; + } + + String prefix = commands.get(0); + + for (int i = 1; i < commands.size(); i++) { + prefix = calculateSimilarPrefix(prefix, commands.get(i)); + } + input.setLength(0); + input = input.append(prefix); + cursorX = input.length(); + + displayCharacters(cursorX, input.toString()); + } + + private String calculateSimilarPrefix(String first, String second) { + int prefixLength = Math.min(first.length(), second.length()); + int i = 0; + while (i < prefixLength && first.charAt(i) == second.charAt(i)) { + i++; + } + return first.substring(0, i); + } private boolean isWindows() { return osName.startsWith("Windows"); @@ -207,7 +242,7 @@ private Optional readShellCommand(String prompt) { } input = new StringBuilder(); - int inputCharacter; + int key; historyIterator = history.listIterator(history.size()); historyCommandLine = (history.size() > 0) ? history.get(history.size() -1) : ""; @@ -215,97 +250,147 @@ private Optional readShellCommand(String prompt) { enterInputMode(); while (true){ - inputCharacter = reader.read(); - - if(inputCharacter == '\r' || inputCharacter == '\n') { + key = reader.read(); + + if(isInterrupt(key)) { + System.exit(2); + } + if (isEOF(key)){ + break; + } + if(isEnter(key)) { FeatJAR.log().noLineBreakMessage("\r\n"); break; } - - if (inputCharacter == 127 || inputCharacter == 8) { + + if(isTabulator(key)) { + handleTabulatorAutoComplete(); + continue; + } + + if (isBackspace(key)) { handleBackspaceKey(); continue; } - - if(inputCharacter == 27) { - if(reader.ready()) { - inputCharacter = reader.read(); - } - if(inputCharacter == '[') { - inputCharacter = reader.read(); - handleArrowKeys(inputCharacter); - if (inputCharacter == 51) { - handleDeleteKey(inputCharacter); - lastArrowKeyUp = false; - } - } else if (input.length() != 0){ - historyIterator = history.listIterator(history.size()); - resetInputLine(); - lastArrowKeyUp = false; - } else { - exitInputMode(); - throw new CancellationException("\nCommand canceled\n"); - } - } else { - handleNormalKey(inputCharacter); - lastArrowKeyUp = false; - } -// lastArrowKeyUp = false; + if(isEscape(key)) { + handleEscapeKey(key); + } +// else { + handleNormalKey(key); +// } } - } catch (IOException e) { - e.printStackTrace(); } finally { exitInputMode(); } cursorX = 0; - return input.length() == 0 ? Optional.empty() : Optional.of(String.valueOf(input)); + return input.length() == 0 ? Optional.empty() : Optional.of(String.valueOf(input).toLowerCase()); } - private void handleArrowKeys(int inputCharacter) { - switch (inputCharacter) { - case 'A': - if(historyIterator == null) { - return; - } - - if(historyIterator.hasPrevious()) { - historyCommandLine = historyIterator.previous(); - moveUpHistory(); - return; + private void handleEscapeKey(int key) throws IOException { + key = getNextKey(key); + if(key == '[') { + key = getNextKey(key); + if(isPageKey(key)) { + //TODO implement handling + lastArrowKeyUp = false; + } else if (isDelete(key)) { + handleDeleteKey(key); + lastArrowKeyUp = false; + } else { + handleArrowKeys(key); } + + } else if (input.length() != 0) { + historyIterator = history.listIterator(history.size()); + resetInputLine(); + lastArrowKeyUp = false; + } else { + exitInputMode(); + throw new CancellationException("\nCommand canceled\n"); + } + } - if(!historyIterator.hasPrevious()) { - moveUpHistory(); - return; - } - case 'B': - if(historyIterator == null) { - return; - } + private int getNextKey(int key) throws IOException { + return reader.ready() ? reader.read() : key; + } - if(lastArrowKeyUp && historyIterator.hasNext()) { - historyCommandLine = historyIterator.next(); - } + private boolean isPageKey(int key) throws IOException { + return (key == 53 || key == 54) && getNextKey(key) == 126; + } - if(!historyIterator.hasNext()) { - moveOutOfHistory(); - return; - } + private boolean isTabulator(int key) { + return key == 9; + } + + private boolean isEOF(int key) { + return key == 4; + } + + private boolean isInterrupt(int key) { + return key == 3; + } + + private boolean isDelete(int key) { + return key == 51; + } + + private boolean isEscape(int key) { + return key == 27; + } + + private boolean isBackspace(int key) { + return key == 127; // || isTabulator(key) + } - historyCommandLine = historyIterator.next(); - moveDownHistory(); - return; + private boolean isEnter(int key) { + return key == '\r' || key == '\n'; + } - case 'C': - moveCursorRight(); - break; - case 'D': - moveCursorLeft(); - break; - } + private void handleArrowKeys(int key) { + switch (key) { + case 'A': + if(historyIterator == null) { + return; + } + + if(historyIterator.hasPrevious()) { + historyCommandLine = historyIterator.previous(); + moveUpHistory(); + return; + } + + if(!historyIterator.hasPrevious()) { + moveUpHistory(); + return; + } + case 'B': + if(historyIterator == null) { + return; + } + + if(lastArrowKeyUp && historyIterator.hasNext()) { + historyCommandLine = historyIterator.next(); + } + + if(!historyIterator.hasNext()) { + moveOutOfHistory(); + return; + } + + historyCommandLine = historyIterator.next(); + moveDownHistory(); + return; + + case 'C': + moveCursorRight(); + break; + case 'D': + moveCursorLeft(); + break; + } } private void handleBackspaceKey() { @@ -323,29 +408,32 @@ private void handleBackspaceKey() { } } - private void handleDeleteKey(int inputCharacter) throws IOException { - if(reader.ready()) { - inputCharacter = reader.read(); - } - if(inputCharacter == '~') { - handleDeleteKey(); + private void handleDeleteKey(int key) throws IOException { + key = getNextKey(key); + if(key == '~') { + if(input.length() != 0 && cursorX != input.length()) { + input.deleteCharAt(cursorX); + displayCharacters(cursorX, input.toString()); + } } } - private void handleNormalKey(int inputCharacter) { + private void handleNormalKey(int key) { cursorX++; if(input.length() == 0) { - input.append((char) inputCharacter); + input.append((char) key); } else { - input.insert(cursorX-1,(char) inputCharacter); + input.insert(cursorX-1,(char) key); } displayCharacters(cursorX, input.toString()); + lastArrowKeyUp = false; } private void resetInputLine() { input.setLength(0); input.append(""); + cursorX = 0; displayCharacters(cursorX, ""); } @@ -382,13 +470,6 @@ private void moveCursorRight() { } } - private void handleDeleteKey() { - if(input.length() != 0 && cursorX != input.length()) { - input.deleteCharAt(cursorX); - displayCharacters(cursorX, input.toString()); - } - } - /** * TODO */ From edc3d0940de527cba9bec5a1585feeb01ffa54cc Mon Sep 17 00:00:00 2001 From: Niclas Date: Thu, 30 Oct 2025 14:46:07 +0100 Subject: [PATCH 15/30] fix: serval auto tabulator and cursor fixes --- .../featjar/base/shell/ClearShellCommand.java | 59 +- .../base/shell/DeleteShellCommand.java | 72 +- .../featjar/base/shell/ExitShellCommand.java | 38 +- .../featjar/base/shell/HelpShellCommand.java | 65 +- .../de/featjar/base/shell/IShellCommand.java | 34 +- .../featjar/base/shell/PrintShellCommand.java | 83 +- .../featjar/base/shell/RunShellCommand.java | 185 ++-- .../java/de/featjar/base/shell/Shell.java | 920 +++++++++--------- .../de/featjar/base/shell/ShellCommands.java | 30 +- .../de/featjar/base/shell/ShellSession.java | 197 ++-- .../java/de/featjar/base/shell/Variables.java | 39 +- 11 files changed, 983 insertions(+), 739 deletions(-) diff --git a/src/main/java/de/featjar/base/shell/ClearShellCommand.java b/src/main/java/de/featjar/base/shell/ClearShellCommand.java index 5bb2efd..cabe215 100644 --- a/src/main/java/de/featjar/base/shell/ClearShellCommand.java +++ b/src/main/java/de/featjar/base/shell/ClearShellCommand.java @@ -1,3 +1,23 @@ +/* + * Copyright (C) 2025 FeatJAR-Development-Team + * + * This file is part of FeatJAR-base. + * + * base is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3.0 of the License, + * or (at your option) any later version. + * + * base is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with base. If not, see . + * + * See for further information. + */ package de.featjar.base.shell; import java.util.List; @@ -8,27 +28,28 @@ public class ClearShellCommand implements IShellCommand { - @Override - public void execute(ShellSession session, List cmdParams) { - String choice = Shell.readCommand("Clearing the entire session. Proceed ? (y)es (n)o") - .orElse("").toLowerCase().trim(); - - if(Objects.equals("y", choice)) { - session.clear(); - FeatJAR.log().message("Clearing successful"); - } else if(Objects.equals("n", choice)) { - FeatJAR.log().message("Clearing aborted"); - } - } - - @Override + @Override + public void execute(ShellSession session, List cmdParams) { + String choice = Shell.readCommand("Clearing the entire session. Proceed ? (y)es (n)o") + .orElse("") + .toLowerCase() + .trim(); + + if (Objects.equals("y", choice)) { + session.clear(); + FeatJAR.log().message("Clearing successful"); + } else if (Objects.equals("n", choice)) { + FeatJAR.log().message("Clearing aborted"); + } + } + + @Override public Optional getShortName() { return Optional.of("clear"); } - - @Override - public Optional getDescription(){ - return Optional.of("delete the entire session"); + + @Override + public Optional getDescription() { + return Optional.of("delete the entire session"); } - } diff --git a/src/main/java/de/featjar/base/shell/DeleteShellCommand.java b/src/main/java/de/featjar/base/shell/DeleteShellCommand.java index 4cd41ce..fdfc944 100644 --- a/src/main/java/de/featjar/base/shell/DeleteShellCommand.java +++ b/src/main/java/de/featjar/base/shell/DeleteShellCommand.java @@ -1,3 +1,23 @@ +/* + * Copyright (C) 2025 FeatJAR-Development-Team + * + * This file is part of FeatJAR-base. + * + * base is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3.0 of the License, + * or (at your option) any later version. + * + * base is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with base. If not, see . + * + * See for further information. + */ package de.featjar.base.shell; import java.util.Arrays; @@ -10,30 +30,30 @@ public class DeleteShellCommand implements IShellCommand { - @Override - public void execute(ShellSession session, List cmdParams) { - - if (cmdParams.isEmpty()) { - session.printVariables(); - cmdParams = Shell.readCommand("Enter the variable names you want to delete or leave blank to abort:") - .map(c -> Arrays.stream(c.split("\\s+")).collect(Collectors.toList())) - .orElse(Collections.emptyList()); - } - - cmdParams.forEach(e -> { - session.remove(e).ifPresentOrElse(a -> FeatJAR.log().message("Removing of " + e + " successful"), - () -> FeatJAR.log().error("Could not find a variable named " + e)); - }); - } - - @Override - public Optional getShortName() { - return Optional.of("delete"); - } - - @Override - public Optional getDescription() { - return Optional.of("delete session variables - ..."); - } - + @Override + public void execute(ShellSession session, List cmdParams) { + + if (cmdParams.isEmpty()) { + session.printVariables(); + cmdParams = Shell.readCommand("Enter the variable names you want to delete or leave blank to abort:") + .map(c -> Arrays.stream(c.split("\\s+")).collect(Collectors.toList())) + .orElse(Collections.emptyList()); + } + + cmdParams.forEach(e -> { + session.remove(e) + .ifPresentOrElse(a -> FeatJAR.log().message("Removing of " + e + " successful"), () -> FeatJAR.log() + .error("Could not find a variable named " + e)); + }); + } + + @Override + public Optional getShortName() { + return Optional.of("delete"); + } + + @Override + public Optional getDescription() { + return Optional.of("delete session variables - ..."); + } } diff --git a/src/main/java/de/featjar/base/shell/ExitShellCommand.java b/src/main/java/de/featjar/base/shell/ExitShellCommand.java index 019bad8..d59cdb6 100644 --- a/src/main/java/de/featjar/base/shell/ExitShellCommand.java +++ b/src/main/java/de/featjar/base/shell/ExitShellCommand.java @@ -1,22 +1,42 @@ +/* + * Copyright (C) 2025 FeatJAR-Development-Team + * + * This file is part of FeatJAR-base. + * + * base is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3.0 of the License, + * or (at your option) any later version. + * + * base is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with base. If not, see . + * + * See for further information. + */ package de.featjar.base.shell; import java.util.List; import java.util.Optional; public class ExitShellCommand implements IShellCommand { - - @Override - public void execute(ShellSession session, List cmdParams) { - System.exit(0); - } - + + @Override + public void execute(ShellSession session, List cmdParams) { + System.exit(0); + } + @Override public Optional getShortName() { return Optional.of("exit"); } - + @Override - public Optional getDescription(){ - return Optional.of("leave shell"); + public Optional getDescription() { + return Optional.of("leave shell"); } } diff --git a/src/main/java/de/featjar/base/shell/HelpShellCommand.java b/src/main/java/de/featjar/base/shell/HelpShellCommand.java index e30060d..92b46dd 100644 --- a/src/main/java/de/featjar/base/shell/HelpShellCommand.java +++ b/src/main/java/de/featjar/base/shell/HelpShellCommand.java @@ -1,3 +1,23 @@ +/* + * Copyright (C) 2025 FeatJAR-Development-Team + * + * This file is part of FeatJAR-base. + * + * base is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3.0 of the License, + * or (at your option) any later version. + * + * base is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with base. If not, see . + * + * See for further information. + */ package de.featjar.base.shell; import java.util.List; @@ -6,32 +26,33 @@ import de.featjar.base.FeatJAR; public class HelpShellCommand implements IShellCommand { - - @Override - public void execute(ShellSession session, List cmdParams) { - printCommands(); - } - - public void printCommands() { - FeatJAR.log().message("Interactive shell"); - FeatJAR.log().message("Capitalization is NOT taken into account"); - FeatJAR.log().message("You can cancel ANY command by pressing the (ESC) key"); - FeatJAR.log().message("Supported commands are: \n"); - - FeatJAR.extensionPoint(ShellCommands.class).getExtensions() - .stream().map(c -> c.getShortName().orElse("") - .concat(" - " + c.getDescription().orElse(""))) - .forEach(FeatJAR.log()::message); - FeatJAR.log().message("\n"); - } - + + @Override + public void execute(ShellSession session, List cmdParams) { + printCommands(); + } + + public void printCommands() { + FeatJAR.log().message("Interactive shell"); + FeatJAR.log().message("Capitalization of COMMANDS is NOT taken into account"); + FeatJAR.log().message("You can cancel ANY command by pressing the (ESC) key"); + FeatJAR.log().message("Supported commands are: \n"); + + FeatJAR.extensionPoint(ShellCommands.class).getExtensions().stream() + .map(c -> c.getShortName() + .orElse("") + .concat(" - " + c.getDescription().orElse(""))) + .forEach(FeatJAR.log()::message); + FeatJAR.log().message("\n"); + } + @Override public Optional getShortName() { return Optional.of("help"); } - + @Override - public Optional getDescription(){ - return Optional.of("print all commads"); + public Optional getDescription() { + return Optional.of("print all commads"); } } diff --git a/src/main/java/de/featjar/base/shell/IShellCommand.java b/src/main/java/de/featjar/base/shell/IShellCommand.java index d371367..bcd4ebc 100644 --- a/src/main/java/de/featjar/base/shell/IShellCommand.java +++ b/src/main/java/de/featjar/base/shell/IShellCommand.java @@ -1,3 +1,23 @@ +/* + * Copyright (C) 2025 FeatJAR-Development-Team + * + * This file is part of FeatJAR-base. + * + * base is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3.0 of the License, + * or (at your option) any later version. + * + * base is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with base. If not, see . + * + * See for further information. + */ package de.featjar.base.shell; import java.util.List; @@ -5,21 +25,21 @@ import de.featjar.base.extension.IExtension; -public interface IShellCommand extends IExtension{ - - void execute(ShellSession session, List cmdParams); - +public interface IShellCommand extends IExtension { + + void execute(ShellSession session, List cmdParams); + /** * {@return this command's short name, if any} The short name can be used to call this command from the CLI. */ default Optional getShortName() { return Optional.empty(); } - + /** * {@return this command's description name, if any} */ - default Optional getDescription(){ - return Optional.empty(); + default Optional getDescription() { + return Optional.empty(); } } diff --git a/src/main/java/de/featjar/base/shell/PrintShellCommand.java b/src/main/java/de/featjar/base/shell/PrintShellCommand.java index 4d7e742..20d90e8 100644 --- a/src/main/java/de/featjar/base/shell/PrintShellCommand.java +++ b/src/main/java/de/featjar/base/shell/PrintShellCommand.java @@ -1,3 +1,23 @@ +/* + * Copyright (C) 2025 FeatJAR-Development-Team + * + * This file is part of FeatJAR-base. + * + * base is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3.0 of the License, + * or (at your option) any later version. + * + * base is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with base. If not, see . + * + * See for further information. + */ package de.featjar.base.shell; import java.util.Arrays; @@ -9,43 +29,42 @@ import de.featjar.base.FeatJAR; import de.featjar.base.tree.structure.ITree; - public class PrintShellCommand implements IShellCommand { - @Override - public void execute(ShellSession session, List cmdParams) { - // TODO layer for electing the type - - if(cmdParams.isEmpty()) { - session.printVariables(); - cmdParams = Shell.readCommand("Enter the variable names you want to print or leave blank to abort:") - .map(c -> Arrays.stream(c.toLowerCase().split("\\s+")).collect(Collectors.toList())) - .orElse(Collections.emptyList()); - } - - cmdParams.forEach(e -> { - session.getElement(e) - .ifPresentOrElse(m -> { - FeatJAR.log().message(e + ":"); - printMap(m); - }, () -> FeatJAR.log().error("Could not find a variable named " + e)); - }); - } - - private void printMap(Object v) { - if (v instanceof ITree) { - FeatJAR.log().message(((ITree) v).print()); - } else { - FeatJAR.log().message(v); - } - FeatJAR.log().message(""); - } - + @Override + public void execute(ShellSession session, List cmdParams) { + if (cmdParams.isEmpty()) { + session.printVariables(); + cmdParams = Shell.readCommand("Enter the variable names you want to print or leave blank to abort:") + .map(c -> Arrays.stream(c.toLowerCase().split("\\s+")).collect(Collectors.toList())) + .orElse(Collections.emptyList()); + } + + cmdParams.forEach(e -> { + session.getElement(e) + .ifPresentOrElse( + m -> { + FeatJAR.log().message(e + ":"); + printMap(m); + }, + () -> FeatJAR.log().error("Could not find a variable named " + e)); + }); + } + + private void printMap(Object v) { + if (v instanceof ITree) { + FeatJAR.log().message(((ITree) v).print()); + } else { + FeatJAR.log().message(v); + } + FeatJAR.log().message(""); + } + public Optional getShortName() { return Optional.of("print"); } - public Optional getDescription(){ - return Optional.of("print the content of variables - ..."); + public Optional getDescription() { + return Optional.of("print the content of variables - ..."); } } diff --git a/src/main/java/de/featjar/base/shell/RunShellCommand.java b/src/main/java/de/featjar/base/shell/RunShellCommand.java index 9429f67..2978885 100644 --- a/src/main/java/de/featjar/base/shell/RunShellCommand.java +++ b/src/main/java/de/featjar/base/shell/RunShellCommand.java @@ -1,3 +1,23 @@ +/* + * Copyright (C) 2025 FeatJAR-Development-Team + * + * This file is part of FeatJAR-base. + * + * base is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3.0 of the License, + * or (at your option) any later version. + * + * base is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with base. If not, see . + * + * See for further information. + */ package de.featjar.base.shell; import java.util.List; @@ -12,87 +32,88 @@ import de.featjar.base.data.Result; public class RunShellCommand implements IShellCommand { - - @Override - public void execute(ShellSession session, List cmdParams) { - if(cmdParams.isEmpty()) { - FeatJAR.log().info(String.format("Usage: %s", getDescription().orElse(""))); - return; - } - try { - Result cliCommand = Commands.getInstance().getExtension(cmdParams.get(0)); - - if(cliCommand.isEmpty()) { - FeatJAR.log().error(String.format("Command '%s' not found", cmdParams.get(0))); - return; - } - OptionList shellOptions = cliCommand.get().getShellOptions(session, cmdParams.subList(1, cmdParams.size())); - - shellOptions = alterOptions(cliCommand, shellOptions); - - int runResult = cliCommand.get().run(shellOptions); - - if(runResult == 0) { - FeatJAR.log().message("Successfull"); - } else { - FeatJAR.log().error(Shell.wrapErorrColor("Errorcode '%d' occured in command '%s'"), runResult, cliCommand.get().getIdentifier()); - } - - } catch (IllegalArgumentException iae) { - iae.printStackTrace(); - FeatJAR.log().error(Shell.wrapErorrColor(iae.getMessage())); - FeatJAR.log().info(String.format("Usage %s", getDescription().get())); - - } - } - - private OptionList alterOptions(Result cliCommand, OptionList shellOptions) { - String choice; - - while(true) { - AtomicInteger i = new AtomicInteger(1); - List> options = cliCommand.get().getOptions(); - int numberChoice; - - options.forEach(o -> { - FeatJAR.log() - .message(i.getAndIncrement()+". "+o+"="+shellOptions.getResult(o) - .map(String::valueOf).orElse("")); - }); - choice = String.valueOf(Shell - .readCommand("Alter options ?\nSelect a number or leave blank to proceed:\n") - .orElse("")) - .toLowerCase(); - - if(choice.isBlank()) { - break; - } - try { - numberChoice = Integer.parseInt(choice)-1; - } catch (NumberFormatException e) { - FeatJAR.log().error("Only decimal numbers are a valid choice"); - continue; - } - - if(options.size() - 1 < numberChoice || numberChoice < 1) { - FeatJAR.log().error("Number does not exist"); - continue; - } - - choice = String.valueOf(Shell.readCommand("Enter the new value:\n").orElse("")); - shellOptions.parseProperties(options.get(numberChoice), choice); - } - return shellOptions; - } - - @Override - public Optional getShortName() { - return Optional.of("run"); - } - - @Override - public Optional getDescription() { - return Optional.of("run WIP"); - } + @Override + public void execute(ShellSession session, List cmdParams) { + if (cmdParams.isEmpty()) { + FeatJAR.log().info(String.format("Usage: %s", getDescription().orElse(""))); + return; + } + try { + Result cliCommand = Commands.getInstance().getExtension(cmdParams.get(0)); + + if (cliCommand.isEmpty()) { + FeatJAR.log().error(String.format("Command '%s' not found", cmdParams.get(0))); + return; + } + OptionList shellOptions = cliCommand.get().getShellOptions(session, cmdParams.subList(1, cmdParams.size())); + + shellOptions = alterOptions(cliCommand, shellOptions); + + int runResult = cliCommand.get().run(shellOptions); + + if (runResult == 0) { + FeatJAR.log().message("Successfull"); + } else { + FeatJAR.log() + .error( + "Errorcode '%d' occured in command '%s'", + runResult, + cliCommand.get().getIdentifier()); + } + + } catch (IllegalArgumentException iae) { + iae.printStackTrace(); + FeatJAR.log().error(iae.getMessage()); + FeatJAR.log().info(String.format("Usage %s", getDescription().get())); + } + } + + private OptionList alterOptions(Result cliCommand, OptionList shellOptions) { + String choice; + + while (true) { + AtomicInteger i = new AtomicInteger(1); + List> options = cliCommand.get().getOptions(); + int numberChoice; + + options.forEach(o -> { + FeatJAR.log() + .message(i.getAndIncrement() + ". " + o + "=" + + shellOptions.getResult(o).map(String::valueOf).orElse("")); + }); + choice = String.valueOf(Shell.readCommand("Alter options ?\nSelect a number or leave blank to proceed:\n") + .orElse("")) + .toLowerCase(); + + if (choice.isBlank()) { + break; + } + try { + numberChoice = Integer.parseInt(choice) - 1; + } catch (NumberFormatException e) { + FeatJAR.log().error("Only decimal numbers are a valid choice"); + continue; + } + + if (options.size() - 1 < numberChoice || numberChoice < 1) { + FeatJAR.log().error("Number does not exist"); + continue; + } + + choice = String.valueOf(Shell.readCommand("Enter the new value:\n").orElse("")); + shellOptions.parseProperties(options.get(numberChoice), choice); + } + return shellOptions; + } + + @Override + public Optional getShortName() { + return Optional.of("run"); + } + + @Override + public Optional getDescription() { + return Optional.of("run WIP"); + } } diff --git a/src/main/java/de/featjar/base/shell/Shell.java b/src/main/java/de/featjar/base/shell/Shell.java index b6ce2b6..6115128 100644 --- a/src/main/java/de/featjar/base/shell/Shell.java +++ b/src/main/java/de/featjar/base/shell/Shell.java @@ -1,3 +1,23 @@ +/* + * Copyright (C) 2025 FeatJAR-Development-Team + * + * This file is part of FeatJAR-base. + * + * base is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3.0 of the License, + * or (at your option) any later version. + * + * base is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with base. If not, see . + * + * See for further information. + */ package de.featjar.base.shell; import java.io.BufferedReader; @@ -23,189 +43,195 @@ import de.featjar.base.data.Problem.Severity; import de.featjar.base.data.Result; - public class Shell { - public static Shell shell = null; - private ShellSession session; - private final String osName; - private final Scanner shellScanner; - private List history; - private ListIterator historyIterator; - private final BufferedReader reader; - private StringBuilder input; - String historyCommandLine; - private int cursorX = 0, cursorY = 0; - private boolean lastArrowKeyUp = false; -// List commandList; - - private static final String TERMINAL_COLOR_RED = "\033[0;31m"; - private static final String TERMINAL_COLOR_RESET = "\033[0m"; - - - private Shell() { - this.session = new ShellSession(); - this.osName = System.getProperty("os.name"); - this.history = new LinkedList(); - this.historyIterator = history.listIterator(); - - if(isWindows()) { - this.shellScanner = new Scanner(System.in); - this.reader = null; - } else { - this.shellScanner = null; - this.reader = new BufferedReader(new InputStreamReader(System.in)); - } - //TODO remove the next line ! - session.put("p", Paths.get("../feature-model/testFeatureModels/basic.xml"), Path.class); - } - - public static Shell getInstance() { - return (shell == null) ? (shell = new Shell()) : shell; - } - - public static void main(String[] args) { - Shell.getInstance().run(); - } - - private void run() { - FeatJAR.initialize(FeatJAR.shellConfiguration()); - printArt(); - new HelpShellCommand().printCommands(); - while (true) { - List cmdArg = null; - try { - cmdArg = readCommand("$") - .map(c -> Arrays.stream(c.split("\\s+")).collect(Collectors.toList())) - .orElse(Collections.emptyList()); - } catch (CancellationException e) { - exitInputMode(); - System.exit(0); - } - if(!cmdArg.isEmpty()) { - try { - Result command = parseCommand(cmdArg.get(0)); - cmdArg.remove(0); - if(command.isPresent()) { - history.add(command.get().getShortName().get() + " " + cmdArg.stream().map(String::valueOf).collect(Collectors.joining(" "))); - command.get().execute(session, cmdArg); - } - } catch (CancellationException e) { - FeatJAR.log().message(e.getMessage()); - } - - } - } - } - - private Result parseCommand(String commandString) { - ShellCommands shellCommandsExentionsPoint = ShellCommands.getInstance(); - List commands = shellCommandsExentionsPoint - .getExtensions().stream().filter(command -> command.getShortName() - .map(name -> name.toLowerCase().startsWith(commandString)).orElse(Boolean.FALSE)) - .collect(Collectors.toList()); - - if (commands.size() > 1) { - Map ambiguousCommands = new HashMap(); - int i = 1; - - FeatJAR.log().info - (wrapErorrColor("Command name '%s' is ambiguous! choose one of the following %d commands (leave blank to abort): \n") - , commandString, commands.size()); - - for(IShellCommand c : commands) { - FeatJAR.log().message(i + "." + c.getShortName().get() + " - " + c.getDescription().get()); - ambiguousCommands.put(i, c); - i++; - } - - String choice = readCommand("").orElse(""); - - if(choice.isBlank()) { - return Result.empty(); - } - int parsedChoice; - try { - parsedChoice = Integer.parseInt(choice); - }catch (NumberFormatException e) { - return Result.empty(addProblem(Severity.ERROR, String.format("'%s' is no vaild number", choice), e)); - } - - - for (Map.Entry entry : ambiguousCommands.entrySet()) { - if (Objects.equals(entry.getKey(), parsedChoice)) { - return Result.of(entry.getValue()); - } - } - return Result.empty(addProblem(Severity.ERROR - , "Command name '%s' is ambiguous! It matches the following commands: \n%s and wrong number !" - , commandString, commands.stream().map(IShellCommand::getIdentifier).collect(Collectors.joining("\n")))); - } - - IShellCommand command = null; - if (commands.isEmpty()) { - Result matchingExtension = shellCommandsExentionsPoint.getMatchingExtension(commandString); - if (matchingExtension.isEmpty()) { - FeatJAR.log().message(Shell.wrapErorrColor("No such command '"+commandString+"'. \n shows all viable commands")); - return Result.empty(addProblem(Severity.ERROR, "No command matched the name '%s'!", commandString)); - } - command = matchingExtension.get(); - } else { - if(commands.get(0).getShortName().get().toLowerCase().matches(commandString)) { - command = commands.get(0); - return Result.of(command); - } - String choice = readCommand("Do you mean: " + commands.get(0).getShortName().get() + "? (ENTER) or (a)bort\n").orElse(""); - if(choice.isEmpty()) { - command = commands.get(0); - } else { - return Result.empty(); - } - } - return Result.of(command); - } - - private Problem addProblem(Severity severity, String message, Object... arguments) { - return new Problem(String.format(message, arguments), severity); - } - + private static Shell instance; + private ShellSession session; + private final Scanner shellScanner; + private List history; + private ListIterator historyIterator; + private final BufferedReader reader; + private StringBuilder input; + String historyCommandLine; + private int cursorX, cursorY; + private boolean lastArrowKeyUp; + private final static String START_OF_TERMINAL_LINE = "$ "; + private final static int CURSOR_START_POSITION_LENGTH = START_OF_TERMINAL_LINE.length() + 1; + + private Shell() { + this.session = new ShellSession(); + this.history = new LinkedList<>(); + this.historyIterator = history.listIterator(); + + if (isWindows()) { + this.shellScanner = new Scanner(System.in); + this.reader = null; + } else { + this.shellScanner = null; + this.reader = new BufferedReader(new InputStreamReader(System.in)); + } + // TODO remove the next line ! + session.put("p", Paths.get("../feature-model/testFeatureModels/basic.xml"), Path.class); + } + + public static Shell getInstance() { + return (instance == null) ? (instance = new Shell()) : instance; + } + + public static void main(String[] args) { + Shell.getInstance().run(); + } + + private void run() { + FeatJAR.initialize(FeatJAR.shellConfiguration()); + printArt(); + new HelpShellCommand().printCommands(); + while (true) { + List cmdArg = null; + try { + cmdArg = readCommand(START_OF_TERMINAL_LINE) + .map(c -> Arrays.stream(c.split("\\s+")).collect(Collectors.toList())) + .orElse(Collections.emptyList()); + } catch (CancellationException e) { + FeatJAR.log().message(e.getMessage()); + exitInputMode(); + System.exit(0); + } + if (!cmdArg.isEmpty()) { + try { + Result command = parseCommand(cmdArg.get(0)); + cmdArg.remove(0); + if (command.isPresent()) { + history.add(command.get().getShortName().get() + " " + + cmdArg.stream().map(String::valueOf).collect(Collectors.joining(" "))); + command.get().execute(session, cmdArg); + } + } catch (CancellationException e) { + FeatJAR.log().message(e.getMessage()); + } + } + } + } + + private Result parseCommand(String commandString) { + ShellCommands shellCommandsExentionsPoint = ShellCommands.getInstance(); + List commands = shellCommandsExentionsPoint.getExtensions().stream() + .filter(command -> command.getShortName() + .map(name -> name.toLowerCase().startsWith(commandString)) + .orElse(Boolean.FALSE)) + .collect(Collectors.toList()); + + if (commands.size() > 1) { + Map ambiguousCommands = new HashMap(); + int i = 1; + + FeatJAR.log() + .info( + ("Command name '%s' is ambiguous! choose one of the following %d commands (leave blank to abort): \n"), + commandString, + commands.size()); + + for (IShellCommand c : commands) { + FeatJAR.log() + .message(i + "." + c.getShortName().get() + " - " + + c.getDescription().get()); + ambiguousCommands.put(i, c); + i++; + } + + String choice = readCommand("").orElse(""); + + if (choice.isBlank()) { + return Result.empty(); + } + int parsedChoice; + try { + parsedChoice = Integer.parseInt(choice); + } catch (NumberFormatException e) { + return Result.empty(addProblem(Severity.ERROR, String.format("'%s' is no vaild number", choice), e)); + } + + for (Map.Entry entry : ambiguousCommands.entrySet()) { + if (Objects.equals(entry.getKey(), parsedChoice)) { + return Result.of(entry.getValue()); + } + } + return Result.empty(addProblem( + Severity.ERROR, + "Command name '%s' is ambiguous! It matches the following commands: \n%s and wrong number !", + commandString, + commands.stream().map(IShellCommand::getIdentifier).collect(Collectors.joining("\n")))); + } + + IShellCommand command = null; + if (commands.isEmpty()) { + Result matchingExtension = shellCommandsExentionsPoint.getMatchingExtension(commandString); + if (matchingExtension.isEmpty()) { + FeatJAR.log() + .message( + "No such command '" + commandString + "'. \n shows all viable commands"); + return Result.empty(addProblem(Severity.ERROR, "No command matched the name '%s'!", commandString)); + } + command = matchingExtension.get(); + } else { + if (commands.get(0).getShortName().get().toLowerCase().matches(commandString)) { + command = commands.get(0); + return Result.of(command); + } + String choice = readCommand( + "Do you mean: " + commands.get(0).getShortName().get() + "? (ENTER) or (a)bort\n") + .orElse(""); + if (choice.isEmpty()) { + command = commands.get(0); + } else { + return Result.empty(); + } + } + return Result.of(command); + } + + private Problem addProblem(Severity severity, String message, Object... arguments) { + return new Problem(String.format(message, arguments), severity); + } + private void handleTabulatorAutoComplete() { if (input.length() == 0) { - return; + return; } - List commands = ShellCommands.getInstance().getExtensions().stream() - .filter(command -> command.getShortName().map(name -> name.toLowerCase().startsWith(String.valueOf(input))).orElse(Boolean.FALSE)) - .map(cmd -> cmd.getShortName().get().toLowerCase()) - .collect(Collectors.toList()); - - if(commands.isEmpty()) { - return; - } - + List commands = ShellCommands.getInstance().getExtensions().stream() + .filter(command -> command.getShortName() + .map(name -> name.toLowerCase().startsWith(String.valueOf(input))) + .orElse(Boolean.FALSE)) + .map(cmd -> cmd.getShortName().get().toLowerCase()) + .collect(Collectors.toList()); + + if (commands.isEmpty()) { + return; + } + String prefix = commands.get(0); - + for (int i = 1; i < commands.size(); i++) { prefix = calculateSimilarPrefix(prefix, commands.get(i)); } - input.setLength(0); + input.setLength(0); input = input.append(prefix); cursorX = input.length(); - - displayCharacters(cursorX, input.toString()); + + displayCharacters(input.toString()); } - private String calculateSimilarPrefix(String first, String second) { - int prefixLength = Math.min(first.length(), second.length()); + private String calculateSimilarPrefix(String oldPrefix, String nextString) { + int minPrefixLength = Math.min(oldPrefix.length(), nextString.length()); int i = 0; - while (i < prefixLength && first.charAt(i) == second.charAt(i)) { - i++; + while (i < minPrefixLength && oldPrefix.charAt(i) == nextString.charAt(i)) { + i++; } - return first.substring(0, i); - } - - private boolean isWindows() { - return osName.startsWith("Windows"); - } + return oldPrefix.substring(0, i); + } + private boolean isWindows() { + return System.getProperty("os.name").startsWith("Windows"); + } /** * Displays the typed characters in the console. @@ -214,292 +240,302 @@ private boolean isWindows() { * '\u001B' (unicode) or '\033' (octal) for ESC work fine here * '\u001B[#G' moves cursor to column # * see for more documentation: https://gist.github.com/fnky/458719343aabd01cfb17a3a4f7296797 - * @param cursor the cursor position * @param typedText the typed characters */ + private void displayCharacters(String typedText) { + FeatJAR.log().noLineBreakMessage("\r"); + FeatJAR.log().noLineBreakMessage("\033[2K"); + FeatJAR.log().noLineBreakMessage("$ " + typedText); + FeatJAR.log().noLineBreakMessage("\033[" + (cursorX + CURSOR_START_POSITION_LENGTH) + "G"); + // TODO cursor dynamic + " " after $ + } + + + private void resetMousePointer() { + FeatJAR.log().noLineBreakMessage("\033[" + CURSOR_START_POSITION_LENGTH + "G"); + } - private static void displayCharacters(int cursor, String typedText) { - FeatJAR.log().noLineBreakMessage("\r"); - FeatJAR.log().noLineBreakMessage("\033[2K"); - FeatJAR.log().noLineBreakMessage("$"+typedText); - FeatJAR.log().noLineBreakMessage("\033[" + (cursor + 2) + "G"); - } - - public static String wrapErorrColor(String message) { - return TERMINAL_COLOR_RED + message + TERMINAL_COLOR_RESET; - } - - public static Optional readCommand(String prompt) { - return Shell.getInstance().readShellCommand(prompt); - } - - private Optional readShellCommand(String prompt) { - FeatJAR.log().noLineBreakMessage(prompt); - - if(isWindows()) { - String inputWindows = shellScanner.nextLine().trim(); - return inputWindows.isEmpty() ? Optional.empty() : Optional.of(inputWindows); - } - - input = new StringBuilder(); - int key; - historyIterator = history.listIterator(history.size()); - historyCommandLine = (history.size() > 0) ? history.get(history.size() -1) : ""; - - try { - enterInputMode(); - while (true){ - - key = reader.read(); - - if(isInterrupt(key)) { - System.exit(2); - } - if (isEOF(key)){ - break; - } - if(isEnter(key)) { - FeatJAR.log().noLineBreakMessage("\r\n"); - break; - } - - if(isTabulator(key)) { - handleTabulatorAutoComplete(); - continue; - } - - if (isBackspace(key)) { - handleBackspaceKey(); - continue; - } - if(isEscape(key)) { - handleEscapeKey(key); - } -// else { - handleNormalKey(key); -// } - } - } catch (IOException e) { - e.printStackTrace(); - } finally { - exitInputMode(); - } - cursorX = 0; - - return input.length() == 0 ? Optional.empty() : Optional.of(String.valueOf(input).toLowerCase()); - } - - private void handleEscapeKey(int key) throws IOException { - key = getNextKey(key); - if(key == '[') { - key = getNextKey(key); - if(isPageKey(key)) { - //TODO implement handling - lastArrowKeyUp = false; - } else if (isDelete(key)) { - handleDeleteKey(key); - lastArrowKeyUp = false; - } else { - handleArrowKeys(key); - } - - } else if (input.length() != 0) { - historyIterator = history.listIterator(history.size()); - resetInputLine(); - lastArrowKeyUp = false; - } else { - exitInputMode(); - throw new CancellationException("\nCommand canceled\n"); - } - } - - private int getNextKey(int key) throws IOException { - return reader.ready() ? reader.read() : key; - } - - private boolean isPageKey(int key) throws IOException { - return (key == 53 || key == 54) && getNextKey(key) == 126; - } - - private boolean isTabulator(int key) { - return key == 9; - } - - private boolean isEOF(int key) { - return key == 4; - } - - private boolean isInterrupt(int key) { - return key == 3; - } - - private boolean isDelete(int key) { - return key == 51; - } - - private boolean isEscape(int key) { - return key == 27; - } - - private boolean isBackspace(int key) { - return key == 127; // || isTabulator(key) - } - - private boolean isEnter(int key) { - return key == '\r' || key == '\n'; - } - - private void handleArrowKeys(int key) { - switch (key) { - case 'A': - if(historyIterator == null) { - return; - } - - if(historyIterator.hasPrevious()) { - historyCommandLine = historyIterator.previous(); - moveUpHistory(); - return; - } - - if(!historyIterator.hasPrevious()) { - moveUpHistory(); - return; - } - case 'B': - if(historyIterator == null) { - return; - } - - if(lastArrowKeyUp && historyIterator.hasNext()) { - historyCommandLine = historyIterator.next(); - } - - if(!historyIterator.hasNext()) { - moveOutOfHistory(); - return; - } - - historyCommandLine = historyIterator.next(); - moveDownHistory(); - return; - - case 'C': - moveCursorRight(); - break; - case 'D': - moveCursorLeft(); - break; - } - } - - private void handleBackspaceKey() { - if (input.length() != 0) { - if(cursorX >= 0) { - if(cursorX <= input.length() && cursorX != 0) { - input.deleteCharAt(cursorX-1); - displayCharacters(cursorX, input.toString()); - FeatJAR.log().noLineBreakMessage("\b"); - } - if(cursorX != 0) { - cursorX--; - } - } - } - } - - private void handleDeleteKey(int key) throws IOException { - key = getNextKey(key); - if(key == '~') { - if(input.length() != 0 && cursorX != input.length()) { - input.deleteCharAt(cursorX); - displayCharacters(cursorX, input.toString()); - } - } - } - - private void handleNormalKey(int key) { - cursorX++; - - if(input.length() == 0) { - input.append((char) key); - } else { - input.insert(cursorX-1,(char) key); - } - displayCharacters(cursorX, input.toString()); - lastArrowKeyUp = false; - } - - private void resetInputLine() { - input.setLength(0); - input.append(""); - cursorX = 0; - displayCharacters(cursorX, ""); - } - - private void moveDownHistory() { - input.setLength(0); - input.append(historyCommandLine); - displayCharacters(cursorX, historyCommandLine); - lastArrowKeyUp = false; - } - - private void moveOutOfHistory() { - resetInputLine(); - lastArrowKeyUp = false; - } - - private void moveUpHistory() { - input.setLength(0); - input.append(historyCommandLine); - displayCharacters(cursorX, historyCommandLine); - lastArrowKeyUp = true; - } - - private void moveCursorLeft() { - FeatJAR.log().noLineBreakMessage("\033[D"+""); - if(cursorX > 0) { - cursorX--; - } - } - - private void moveCursorRight() { - if (cursorX < input.length()) { - FeatJAR.log().noLineBreakMessage("\033[C"); - cursorX++; - } - } - - /** + /* * TODO */ - private void enterInputMode () { + public static Optional readCommand(String prompt) { + return Shell.getInstance().readShellCommand(prompt); + } + + private Optional readShellCommand(String prompt) { + FeatJAR.log().noLineBreakMessage(prompt); + + if (isWindows()) { + String inputWindows = shellScanner.nextLine().trim(); + return inputWindows.isEmpty() ? Optional.empty() : Optional.of(inputWindows); + } + + input = new StringBuilder(); + int key; + historyIterator = history.listIterator(history.size()); + historyCommandLine = (history.size() > 0) ? history.get(history.size() - 1) : ""; + try { - Runtime.getRuntime().exec(new String[]{"sh","-c","stty -icanon -echo min 1 time 0 -isig -ixon opost onlcr = 0) { + if (cursorX <= input.length() && cursorX != 0) { + input.deleteCharAt(cursorX - 1); + displayCharacters(input.toString()); + FeatJAR.log().noLineBreakMessage("\b"); + } + if (cursorX != 0) { + cursorX--; + } // TODO 238, CursorY + } + } + } + + private void handleDeleteKey(int key) throws IOException { + key = getNextKey(key); + if (key == '~') { + if (input.length() != 0 && cursorX != input.length()) { + input.deleteCharAt(cursorX); + displayCharacters(input.toString()); + } + } + } + + private void handleNormalKey(int key) { + cursorX++; + + if (input.length() == 0) { + input.append((char) key); + } else { + input.insert(cursorX - 1, (char) key); + } + displayCharacters(input.toString()); + lastArrowKeyUp = false; + } + + private void resetInputLine() { + input.setLength(0); + input.append(""); + cursorX = 0; + displayCharacters(""); + } + + private void moveDownHistory() { + input.setLength(0); + input.append(historyCommandLine); + displayCharacters(historyCommandLine); + lastArrowKeyUp = false; + } + + private void moveOutOfHistory() { + resetInputLine(); + lastArrowKeyUp = false; + } + + private void moveUpHistory() { + input.setLength(0); + input.append(historyCommandLine); + cursorX = input.length() - 1; + displayCharacters(historyCommandLine); + lastArrowKeyUp = true; + } + + private void moveCursorLeft() { + if (cursorX > 0) { + cursorX--; + FeatJAR.log().noLineBreakMessage("\033[D"); // +"" ?? + } + } + + private void moveCursorRight() { + if (cursorX < input.length()) { + cursorX++; + FeatJAR.log().noLineBreakMessage("\033[C"); + } + } /** - * TODO + *Sets the terminal into a 'raw' like mode that has no line buffer such that the shell can read a single key press, signals like CTRL+C do still work */ + private void enterInputMode() { + try { + /* + * sh executes the command in a new console. + * -c tells the shell to read commands from the following string. + * stty change and print terminal line settings + * -icanon disables the classical line buffered input mode, instead every key press gets directly send to the terminal + * -echo has to be disabled in combination with icanon to allow ANSI escape sequences to actually to what they are supposed to do + * (e.g. "\033[D" to move the cursor one space to the left). Otherwise the control code gets directly printed to the console + * without executing the the ANSI escape sequence. + */ + Runtime.getRuntime().exec(new String[]{"sh","-c","stty -icanon -echo . + * + * See for further information. + */ package de.featjar.base.shell; import de.featjar.base.FeatJAR; import de.featjar.base.extension.AExtensionPoint; -public class ShellCommands extends AExtensionPoint{ - - public static ShellCommands getInstance() { - return FeatJAR.extensionPoint(ShellCommands.class); - } +public class ShellCommands extends AExtensionPoint { + + public static ShellCommands getInstance() { + return FeatJAR.extensionPoint(ShellCommands.class); + } } diff --git a/src/main/java/de/featjar/base/shell/ShellSession.java b/src/main/java/de/featjar/base/shell/ShellSession.java index a025fb4..b2a7644 100644 --- a/src/main/java/de/featjar/base/shell/ShellSession.java +++ b/src/main/java/de/featjar/base/shell/ShellSession.java @@ -1,3 +1,23 @@ +/* + * Copyright (C) 2025 FeatJAR-Development-Team + * + * This file is part of FeatJAR-base. + * + * base is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3.0 of the License, + * or (at your option) any later version. + * + * base is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with base. If not, see . + * + * See for further information. + */ package de.featjar.base.shell; import java.util.Comparator; @@ -9,89 +29,96 @@ import de.featjar.base.FeatJAR; public class ShellSession { - - private static class StoredElement { - private Class type; - private T element; - - public StoredElement(Class type, T element) { - this.type = type; - this.element = element; - } - } - - private final Map> elements; - - @SuppressWarnings("unchecked") - public Optional get(String key, Class type) { - StoredElement storedElement = elements.get(key); - - if(storedElement == null) { - return Optional.empty(); - } - if (storedElement.type == type) { - return Optional.of((T) storedElement.type.cast(storedElement.element)); - } else { - throw new RuntimeException("Wrong Type"); // TODO Result von Problem addProblem - } - } - - public Optional getType(String key) { - return Optional.ofNullable(elements.get(key)).map(e -> e.type.getSimpleName()); - } - - public Optional getElement(String key) { - return Optional.ofNullable(elements.get(key)).map(e -> e.element); - } - - public ShellSession() { - elements = new LinkedHashMap<>(); - } - - public void put(String key, T element, Class type) { - elements.put(key, new StoredElement(type, element)); - } - - public Optional remove(String key) { - return Optional.ofNullable(elements.remove(key)); - } - - public void clear() { - elements.clear(); - } - - public int getSize() { - return elements.size(); - } - - public boolean containsKey(String key) { - return elements.containsKey(key); - } - - public boolean isEmpty() { - return elements.isEmpty(); - } - - public void printVariable(String key) { - for (Entry> entry : elements.entrySet()) { - if(entry.getKey().equals(key)) { - FeatJAR.log().message("Variable: " + key + " Type: " + entry.getValue().type.getSimpleName() + "\n"); - break; - } - } - } - - public void printVariables() { - elements.entrySet().forEach(m -> FeatJAR.log().message(m.getKey() + " (" - + m.getValue().type.getSimpleName() + ")" )); - } - - public void printSortedByVarNames() { - // TODO implement both methods in parser - - elements.entrySet().stream().sorted(Map.Entry.comparingByKey()).forEach(m -> FeatJAR.log().message(m.getKey() + " " + m.getValue().type.getSimpleName())); - } - public void printSortedByType() { - elements.entrySet().stream().sorted(Comparator.comparing(e -> String.valueOf(e.getValue().type))).forEach(m -> FeatJAR.log().message(m.getKey() + " " + m.getValue().type.getSimpleName())); - } + + private static class StoredElement { + private Class type; + private T element; + + public StoredElement(Class type, T element) { + this.type = type; + this.element = element; + } + } + + private final Map> elements; + + @SuppressWarnings("unchecked") + public Optional get(String key, Class type) { + StoredElement storedElement = elements.get(key); + + if (storedElement == null) { + return Optional.empty(); + } + if (storedElement.type == type) { + return Optional.of((T) storedElement.type.cast(storedElement.element)); + } else { + throw new RuntimeException("Wrong Type"); // TODO Result von Problem addProblem + } + } + + public Optional getType(String key) { + return Optional.ofNullable(elements.get(key)).map(e -> e.type); + } + + public Optional getElement(String key) { + return Optional.ofNullable(elements.get(key)).map(e -> e.element); + } + + public ShellSession() { + elements = new LinkedHashMap<>(); + } + + public void put(String key, T element, Class type) { + elements.put(key, new StoredElement(type, element)); + } + + public Optional remove(String key) { + return Optional.ofNullable(elements.remove(key)); + } + + public void clear() { + elements.clear(); + } + + public int getSize() { + return elements.size(); + } + + public boolean containsKey(String key) { + return elements.containsKey(key); + } + + public boolean isEmpty() { + return elements.isEmpty(); + } + + public void printVariable(String key) { + for (Entry> entry : elements.entrySet()) { + if (entry.getKey().equals(key)) { + FeatJAR.log() + .message("Variable: " + key + " Type: " + + entry.getValue().type.getSimpleName() + "\n"); + break; + } + } + } + + public void printVariables() { + elements.entrySet().forEach(m -> FeatJAR.log() + .message(m.getKey() + " (" + m.getValue().type.getSimpleName() + ")")); + } + + public void printSortedByVarNames() { + // TODO implement both methods in parser + + elements.entrySet().stream().sorted(Map.Entry.comparingByKey()).forEach(m -> FeatJAR.log() + .message(m.getKey() + " " + m.getValue().type.getSimpleName())); + } + + public void printSortedByType() { + elements.entrySet().stream() + .sorted(Comparator.comparing(e -> String.valueOf(e.getValue().type))) + .forEach(m -> FeatJAR.log() + .message(m.getKey() + " " + m.getValue().type.getSimpleName())); + } } diff --git a/src/main/java/de/featjar/base/shell/Variables.java b/src/main/java/de/featjar/base/shell/Variables.java index ec22a67..8667801 100644 --- a/src/main/java/de/featjar/base/shell/Variables.java +++ b/src/main/java/de/featjar/base/shell/Variables.java @@ -1,23 +1,42 @@ +/* + * Copyright (C) 2025 FeatJAR-Development-Team + * + * This file is part of FeatJAR-base. + * + * base is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3.0 of the License, + * or (at your option) any later version. + * + * base is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with base. If not, see . + * + * See for further information. + */ package de.featjar.base.shell; import java.util.List; import java.util.Optional; -public class Variables implements IShellCommand{ +public class Variables implements IShellCommand { + + @Override + public void execute(ShellSession session, List cmdParams) { + session.printVariables(); + } - @Override - public void execute(ShellSession session, List cmdParams) { - session.printVariables(); - } - @Override public Optional getShortName() { return Optional.of("variables"); } - + @Override - public Optional getDescription(){ - return Optional.of("print the name and type of all session variables"); + public Optional getDescription() { + return Optional.of("print the name and type of all session variables"); } - } From a8aa8ddd4987acf915a06979ffe8bbb572818f62 Mon Sep 17 00:00:00 2001 From: Niclas Date: Thu, 30 Oct 2025 14:48:41 +0100 Subject: [PATCH 16/30] feat: ability to manipulate option lists and run normal commands in the shell --- .../java/de/featjar/base/cli/ACommand.java | 63 +++++++++---------- .../java/de/featjar/base/cli/Commands.java | 3 +- .../java/de/featjar/base/cli/ICommand.java | 17 +++-- .../java/de/featjar/base/cli/OptionList.java | 5 +- 4 files changed, 42 insertions(+), 46 deletions(-) diff --git a/src/main/java/de/featjar/base/cli/ACommand.java b/src/main/java/de/featjar/base/cli/ACommand.java index 6ed5330..48e8635 100644 --- a/src/main/java/de/featjar/base/cli/ACommand.java +++ b/src/main/java/de/featjar/base/cli/ACommand.java @@ -20,12 +20,11 @@ */ package de.featjar.base.cli; +import de.featjar.base.shell.ShellSession; import java.nio.file.Path; import java.util.List; import java.util.Optional; -import de.featjar.base.shell.ShellSession; - /** * The abstract class for any command. * @@ -33,42 +32,42 @@ */ public abstract class ACommand implements ICommand { - /** - * Input option for loading files. - */ - public static final Option INPUT_OPTION = Option.newOption("input", Option.PathParser) - .setDescription("Path to input file(s)").setValidator(Option.PathValidator); - - /** - * Output option for saving files. - */ - public static final Option OUTPUT_OPTION = Option.newOption("output", Option.PathParser) - .setDescription("Path to output file(s)"); + /** + * Input option for loading files. + */ + public static final Option INPUT_OPTION = Option.newOption("input", Option.PathParser) + .setDescription("Path to input file(s)") + .setValidator(Option.PathValidator); - /** - * {@return all options registered for the calling class} - */ - public final List> getOptions() { - return Option.getAllOptions(getClass()); - } + /** + * Output option for saving files. + */ + public static final Option OUTPUT_OPTION = + Option.newOption("output", Option.PathParser).setDescription("Path to output file(s)"); - public OptionList getShellOptions(ShellSession session, List cmdParams) { - OptionList optionList = new OptionList(); + /** + * {@return all options registered for the calling class} + */ + public final List> getOptions() { + return Option.getAllOptions(getClass()); + } - if (cmdParams.isEmpty()) { - throw new IllegalArgumentException("No path object specified"); - } + public OptionList getShellOptions(ShellSession session, List cmdParams) { + OptionList optionList = new OptionList(); - Optional path = session.get(cmdParams.get(0), Path.class); + if (cmdParams.isEmpty()) { + throw new IllegalArgumentException("No path object specified"); + } - if (path.isEmpty()) { - throw new IllegalArgumentException(String.format("'%s' is not a session object", cmdParams.get(0))); - } + Optional path = session.get(cmdParams.get(0), Path.class); - optionList.parseArguments(); - optionList.parseProperties(INPUT_OPTION, String.valueOf(path.get())); + if (path.isEmpty()) { + throw new IllegalArgumentException(String.format("'%s' is not a session object", cmdParams.get(0))); + } - return optionList; - } + optionList.parseArguments(); + optionList.parseProperties(INPUT_OPTION, String.valueOf(path.get())); + return optionList; + } } diff --git a/src/main/java/de/featjar/base/cli/Commands.java b/src/main/java/de/featjar/base/cli/Commands.java index 39993f4..c3844d2 100644 --- a/src/main/java/de/featjar/base/cli/Commands.java +++ b/src/main/java/de/featjar/base/cli/Commands.java @@ -26,7 +26,6 @@ import de.featjar.base.io.IO; import de.featjar.base.io.format.IFormat; import de.featjar.base.io.format.IFormatSupplier; - import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; @@ -76,7 +75,7 @@ public class Commands extends AExtensionPoint { public static Commands getInstance() { return FeatJAR.extensionPoint(Commands.class); } - + /** * Runs a given function in a new thread, aborting it when it is not done after a timeout expires. * If the entire process should be stopped afterwards, {@link System#exit(int)} must be called explicitly. diff --git a/src/main/java/de/featjar/base/cli/ICommand.java b/src/main/java/de/featjar/base/cli/ICommand.java index a3fb0ef..683555b 100644 --- a/src/main/java/de/featjar/base/cli/ICommand.java +++ b/src/main/java/de/featjar/base/cli/ICommand.java @@ -20,13 +20,12 @@ */ package de.featjar.base.cli; +import de.featjar.base.extension.IExtension; +import de.featjar.base.shell.ShellSession; import java.util.ArrayList; import java.util.List; import java.util.Optional; -import de.featjar.base.extension.IExtension; -import de.featjar.base.shell.ShellSession; - /** * A command run within a {@link Commands}. * @@ -64,12 +63,12 @@ default Optional getShortName() { * @return exit code */ int run(OptionList optionParser); - + default OptionList getShellOptions(ShellSession session, List cmdParams) { - OptionList optionList = new OptionList(); - - optionList.parseArguments(); - - return optionList; + OptionList optionList = new OptionList(); + + optionList.parseArguments(); + + return optionList; } } diff --git a/src/main/java/de/featjar/base/cli/OptionList.java b/src/main/java/de/featjar/base/cli/OptionList.java index e8bc615..7e436af 100644 --- a/src/main/java/de/featjar/base/cli/OptionList.java +++ b/src/main/java/de/featjar/base/cli/OptionList.java @@ -35,7 +35,6 @@ import java.io.InputStream; import java.nio.file.Files; import java.nio.file.Path; -import java.nio.file.Paths; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; @@ -358,10 +357,10 @@ private void parseConfigurationFiles(List problemList) { } } } - + public void parseProperties(Option option, String command) { Result parse = option.parse(command); - properties.put(option.getName(), parse.get()); + properties.put(option.getName(), parse.get()); } private void parseRemainingArguments(List problemList) { From f5c86338886d4f149234ed26a9a24f0677a96e07 Mon Sep 17 00:00:00 2001 From: Niclas Date: Thu, 30 Oct 2025 14:50:25 +0100 Subject: [PATCH 17/30] feat: added shell colors for three log error levels --- .../de/featjar/base/log/ColorFormatter.java | 55 +++++++++++++++++++ src/main/java/de/featjar/base/log/Log.java | 12 ++++ 2 files changed, 67 insertions(+) create mode 100644 src/main/java/de/featjar/base/log/ColorFormatter.java diff --git a/src/main/java/de/featjar/base/log/ColorFormatter.java b/src/main/java/de/featjar/base/log/ColorFormatter.java new file mode 100644 index 0000000..49a8373 --- /dev/null +++ b/src/main/java/de/featjar/base/log/ColorFormatter.java @@ -0,0 +1,55 @@ +/* + * Copyright (C) 2025 FeatJAR-Development-Team + * + * This file is part of FeatJAR-base. + * + * base is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3.0 of the License, + * or (at your option) any later version. + * + * base is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with base. If not, see . + * + * See for further information. + */ +package de.featjar.base.log; + +import de.featjar.base.log.Log.Verbosity; + +public class ColorFormatter implements IFormatter { + + private static final String TERMINAL_COLOR_LIGHT_BLUE = "\033[38;2;173;236;255m"; + private static final String TERMINAL_COLOR_YELLOW = "\033[38;2;255;255;0m"; + private static final String TERMINAL_COLOR_RED = "\033[38;2;255;0;0m"; + private static final String TERMINAL_COLOR_RESET = "\033[0m"; + + @Override + public String getPrefix(String message, Verbosity verbosity) { + switch (verbosity) { + case INFO: + return TERMINAL_COLOR_LIGHT_BLUE; + case WARNING: + return TERMINAL_COLOR_YELLOW; + case ERROR: + return TERMINAL_COLOR_RED; + case MESSAGE: + case PROGRESS: + case DEBUG: + break; + default: + throw new IllegalStateException(String.valueOf(verbosity)); + } + return ""; + } + + @Override + public String getSuffix(String message, Verbosity verbosity) { + return TERMINAL_COLOR_RESET; + } +} diff --git a/src/main/java/de/featjar/base/log/Log.java b/src/main/java/de/featjar/base/log/Log.java index ea0485a..cfa3297 100644 --- a/src/main/java/de/featjar/base/log/Log.java +++ b/src/main/java/de/featjar/base/log/Log.java @@ -314,6 +314,18 @@ default void plainMessage(Object messageObject) { plainMessage(() -> String.valueOf(messageObject)); } + default void noLineBreakMessage(Supplier message) { + print(message, Verbosity.MESSAGE, false); + } + + default void noLineBreakMessage(String formatMessage, Object... elements) { + noLineBreakMessage(() -> String.format(formatMessage, elements)); + } + + default void noLineBreakMessage(Object messageObject) { + noLineBreakMessage(() -> String.valueOf(messageObject)); + } + /** * Logs a debug message. * From 759f5ab16d43eafec65d1d6f806d8d00fd8c3bb9 Mon Sep 17 00:00:00 2001 From: Niclas Date: Thu, 30 Oct 2025 14:52:04 +0100 Subject: [PATCH 18/30] fix: added the three new error colors to the configuration --- src/main/java/de/featjar/base/FeatJAR.java | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/main/java/de/featjar/base/FeatJAR.java b/src/main/java/de/featjar/base/FeatJAR.java index 397be66..c7c8c6c 100644 --- a/src/main/java/de/featjar/base/FeatJAR.java +++ b/src/main/java/de/featjar/base/FeatJAR.java @@ -32,6 +32,7 @@ import de.featjar.base.io.IO; import de.featjar.base.log.BufferedLog; import de.featjar.base.log.CallerFormatter; +import de.featjar.base.log.ColorFormatter; import de.featjar.base.log.ConfigurableLog; import de.featjar.base.log.EmptyProgressBar; import de.featjar.base.log.IProgressBar; @@ -203,13 +204,14 @@ public static Configuration testConfiguration() { configuration.cacheConfig.setCachePolicy(Cache.CachePolicy.CACHE_NONE); return configuration; } - + public static Configuration shellConfiguration() { final Configuration configuration = new Configuration(); configuration .logConfig .logToSystemOut(Log.Verbosity.MESSAGE, Log.Verbosity.INFO, Log.Verbosity.PROGRESS) - .logToSystemErr(Log.Verbosity.ERROR); + .logToSystemErr(Log.Verbosity.ERROR, Log.Verbosity.WARNING) + .addFormatter(new ColorFormatter()); configuration.cacheConfig.setCachePolicy(Cache.CachePolicy.CACHE_NONE); return configuration; } From f96d1eb3c5ae346623cba772cbc5962ba541b04e Mon Sep 17 00:00:00 2001 From: Niclas Date: Thu, 30 Oct 2025 15:44:45 +0100 Subject: [PATCH 19/30] feat: small help info improvements --- src/main/java/de/featjar/base/shell/ClearShellCommand.java | 2 +- src/main/java/de/featjar/base/shell/DeleteShellCommand.java | 2 +- src/main/java/de/featjar/base/shell/ExitShellCommand.java | 2 +- src/main/java/de/featjar/base/shell/HelpShellCommand.java | 4 ++-- src/main/java/de/featjar/base/shell/PrintShellCommand.java | 2 +- src/main/java/de/featjar/base/shell/RunShellCommand.java | 2 +- src/main/java/de/featjar/base/shell/Shell.java | 3 --- src/main/java/de/featjar/base/shell/Variables.java | 2 +- 8 files changed, 8 insertions(+), 11 deletions(-) diff --git a/src/main/java/de/featjar/base/shell/ClearShellCommand.java b/src/main/java/de/featjar/base/shell/ClearShellCommand.java index cabe215..f1e0cea 100644 --- a/src/main/java/de/featjar/base/shell/ClearShellCommand.java +++ b/src/main/java/de/featjar/base/shell/ClearShellCommand.java @@ -50,6 +50,6 @@ public Optional getShortName() { @Override public Optional getDescription() { - return Optional.of("delete the entire session"); + return Optional.of("- delete the entire session"); } } diff --git a/src/main/java/de/featjar/base/shell/DeleteShellCommand.java b/src/main/java/de/featjar/base/shell/DeleteShellCommand.java index fdfc944..a0e89b5 100644 --- a/src/main/java/de/featjar/base/shell/DeleteShellCommand.java +++ b/src/main/java/de/featjar/base/shell/DeleteShellCommand.java @@ -54,6 +54,6 @@ public Optional getShortName() { @Override public Optional getDescription() { - return Optional.of("delete session variables - ..."); + return Optional.of("- ... - delete session variables"); } } diff --git a/src/main/java/de/featjar/base/shell/ExitShellCommand.java b/src/main/java/de/featjar/base/shell/ExitShellCommand.java index d59cdb6..d429638 100644 --- a/src/main/java/de/featjar/base/shell/ExitShellCommand.java +++ b/src/main/java/de/featjar/base/shell/ExitShellCommand.java @@ -37,6 +37,6 @@ public Optional getShortName() { @Override public Optional getDescription() { - return Optional.of("leave shell"); + return Optional.of("- leave shell"); } } diff --git a/src/main/java/de/featjar/base/shell/HelpShellCommand.java b/src/main/java/de/featjar/base/shell/HelpShellCommand.java index 92b46dd..702ecb1 100644 --- a/src/main/java/de/featjar/base/shell/HelpShellCommand.java +++ b/src/main/java/de/featjar/base/shell/HelpShellCommand.java @@ -41,7 +41,7 @@ public void printCommands() { FeatJAR.extensionPoint(ShellCommands.class).getExtensions().stream() .map(c -> c.getShortName() .orElse("") - .concat(" - " + c.getDescription().orElse(""))) + .concat(" " + c.getDescription().orElse(""))) .forEach(FeatJAR.log()::message); FeatJAR.log().message("\n"); } @@ -53,6 +53,6 @@ public Optional getShortName() { @Override public Optional getDescription() { - return Optional.of("print all commads"); + return Optional.of("- print all commads"); } } diff --git a/src/main/java/de/featjar/base/shell/PrintShellCommand.java b/src/main/java/de/featjar/base/shell/PrintShellCommand.java index 20d90e8..839847e 100644 --- a/src/main/java/de/featjar/base/shell/PrintShellCommand.java +++ b/src/main/java/de/featjar/base/shell/PrintShellCommand.java @@ -65,6 +65,6 @@ public Optional getShortName() { } public Optional getDescription() { - return Optional.of("print the content of variables - ..."); + return Optional.of(" ... - print the content of variables"); } } diff --git a/src/main/java/de/featjar/base/shell/RunShellCommand.java b/src/main/java/de/featjar/base/shell/RunShellCommand.java index 2978885..102f698 100644 --- a/src/main/java/de/featjar/base/shell/RunShellCommand.java +++ b/src/main/java/de/featjar/base/shell/RunShellCommand.java @@ -114,6 +114,6 @@ public Optional getShortName() { @Override public Optional getDescription() { - return Optional.of("run WIP"); + return Optional.of(" - launch non shellcommands"); } } diff --git a/src/main/java/de/featjar/base/shell/Shell.java b/src/main/java/de/featjar/base/shell/Shell.java index 6115128..e3c4a5b 100644 --- a/src/main/java/de/featjar/base/shell/Shell.java +++ b/src/main/java/de/featjar/base/shell/Shell.java @@ -69,8 +69,6 @@ private Shell() { this.shellScanner = null; this.reader = new BufferedReader(new InputStreamReader(System.in)); } - // TODO remove the next line ! - session.put("p", Paths.get("../feature-model/testFeatureModels/basic.xml"), Path.class); } public static Shell getInstance() { @@ -247,7 +245,6 @@ private void displayCharacters(String typedText) { FeatJAR.log().noLineBreakMessage("\033[2K"); FeatJAR.log().noLineBreakMessage("$ " + typedText); FeatJAR.log().noLineBreakMessage("\033[" + (cursorX + CURSOR_START_POSITION_LENGTH) + "G"); - // TODO cursor dynamic + " " after $ } diff --git a/src/main/java/de/featjar/base/shell/Variables.java b/src/main/java/de/featjar/base/shell/Variables.java index 8667801..32c443f 100644 --- a/src/main/java/de/featjar/base/shell/Variables.java +++ b/src/main/java/de/featjar/base/shell/Variables.java @@ -37,6 +37,6 @@ public Optional getShortName() { @Override public Optional getDescription() { - return Optional.of("print the name and type of all session variables"); + return Optional.of("- print the name and type of all session variables"); } } From ca59a9fa6782d67a0fbd5ca360d577d0bd63cc8c Mon Sep 17 00:00:00 2001 From: Niclas Date: Fri, 31 Oct 2025 09:03:31 +0100 Subject: [PATCH 20/30] fix: align descriptions of shellcommands --- src/main/java/de/featjar/base/shell/DeleteShellCommand.java | 2 +- src/main/java/de/featjar/base/shell/PrintShellCommand.java | 2 +- src/main/java/de/featjar/base/shell/RunShellCommand.java | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/main/java/de/featjar/base/shell/DeleteShellCommand.java b/src/main/java/de/featjar/base/shell/DeleteShellCommand.java index a0e89b5..9f84be8 100644 --- a/src/main/java/de/featjar/base/shell/DeleteShellCommand.java +++ b/src/main/java/de/featjar/base/shell/DeleteShellCommand.java @@ -54,6 +54,6 @@ public Optional getShortName() { @Override public Optional getDescription() { - return Optional.of("- ... - delete session variables"); + return Optional.of("- ... - delete session variables"); } } diff --git a/src/main/java/de/featjar/base/shell/PrintShellCommand.java b/src/main/java/de/featjar/base/shell/PrintShellCommand.java index 839847e..f9d38cb 100644 --- a/src/main/java/de/featjar/base/shell/PrintShellCommand.java +++ b/src/main/java/de/featjar/base/shell/PrintShellCommand.java @@ -65,6 +65,6 @@ public Optional getShortName() { } public Optional getDescription() { - return Optional.of(" ... - print the content of variables"); + return Optional.of("... - print the content of variables"); } } diff --git a/src/main/java/de/featjar/base/shell/RunShellCommand.java b/src/main/java/de/featjar/base/shell/RunShellCommand.java index 102f698..d7a630e 100644 --- a/src/main/java/de/featjar/base/shell/RunShellCommand.java +++ b/src/main/java/de/featjar/base/shell/RunShellCommand.java @@ -114,6 +114,6 @@ public Optional getShortName() { @Override public Optional getDescription() { - return Optional.of(" - launch non shellcommands"); + return Optional.of(" - launch non shellcommands"); } } From 642ef584adb346bfe54ebec69dcf745a7a85391a Mon Sep 17 00:00:00 2001 From: Niclas Date: Sat, 1 Nov 2025 13:06:10 +0100 Subject: [PATCH 21/30] fix: replaced iterator of command history with normal index --- .../java/de/featjar/base/shell/Shell.java | 105 ++++++++++-------- 1 file changed, 59 insertions(+), 46 deletions(-) diff --git a/src/main/java/de/featjar/base/shell/Shell.java b/src/main/java/de/featjar/base/shell/Shell.java index e3c4a5b..12c1f64 100644 --- a/src/main/java/de/featjar/base/shell/Shell.java +++ b/src/main/java/de/featjar/base/shell/Shell.java @@ -48,19 +48,19 @@ public class Shell { private ShellSession session; private final Scanner shellScanner; private List history; - private ListIterator historyIterator; + private int historyIterator; private final BufferedReader reader; private StringBuilder input; String historyCommandLine; private int cursorX, cursorY; private boolean lastArrowKeyUp; + private boolean lastArrowKeyDown; private final static String START_OF_TERMINAL_LINE = "$ "; - private final static int CURSOR_START_POSITION_LENGTH = START_OF_TERMINAL_LINE.length() + 1; + private final static int CURSOR_START_POSITION = START_OF_TERMINAL_LINE.length() + 1; private Shell() { this.session = new ShellSession(); this.history = new LinkedList<>(); - this.historyIterator = history.listIterator(); if (isWindows()) { this.shellScanner = new Scanner(System.in); @@ -215,7 +215,7 @@ private void handleTabulatorAutoComplete() { input = input.append(prefix); cursorX = input.length(); - displayCharacters(input.toString()); + displayCharacters(String.valueOf(input)); } private String calculateSimilarPrefix(String oldPrefix, String nextString) { @@ -233,27 +233,33 @@ private boolean isWindows() { /** * Displays the typed characters in the console. - * '\r' moves the cursor to the beginning of the line - * '\u001B[2K' or '\033[2K' erases the entire line - * '\u001B' (unicode) or '\033' (octal) for ESC work fine here - * '\u001B[#G' moves cursor to column # - * see for more documentation: https://gist.github.com/fnky/458719343aabd01cfb17a3a4f7296797 * @param typedText the typed characters */ private void displayCharacters(String typedText) { + /* + * '\r' moves the cursor to the beginning of the line + * '\u001B[2K' or '\033[2K' erases the entire line + * '\u001B' (unicode) or '\033' (octal) for ESC work fine here + * '\u001B[#G' moves cursor to column # + * see for more documentation: https://gist.github.com/fnky/458719343aabd01cfb17a3a4f7296797 + */ FeatJAR.log().noLineBreakMessage("\r"); FeatJAR.log().noLineBreakMessage("\033[2K"); FeatJAR.log().noLineBreakMessage("$ " + typedText); - FeatJAR.log().noLineBreakMessage("\033[" + (cursorX + CURSOR_START_POSITION_LENGTH) + "G"); + FeatJAR.log().noLineBreakMessage("\033[" + (cursorX + CURSOR_START_POSITION) + "G"); } private void resetMousePointer() { - FeatJAR.log().noLineBreakMessage("\033[" + CURSOR_START_POSITION_LENGTH + "G"); + FeatJAR.log().noLineBreakMessage("\033[" + CURSOR_START_POSITION + "G"); } - /* - * TODO + /** + * Static access for {@link #readShellCommand(String)} + * reads characters one by one without line buffering into a String + * handles special keys (e.g. ESC) + * @param prompt the message that is shown in the terminal + * @return */ public static Optional readCommand(String prompt) { @@ -270,7 +276,7 @@ private Optional readShellCommand(String prompt) { input = new StringBuilder(); int key; - historyIterator = history.listIterator(history.size()); + historyIterator = history.size() - 1; historyCommandLine = (history.size() > 0) ? history.get(history.size() - 1) : ""; try { @@ -324,7 +330,7 @@ private void handleEscapeKey(int key) throws IOException { } } else if (input.length() != 0) { - historyIterator = history.listIterator(history.size()); + historyIterator = history.size() - 1; resetInputLine(); lastArrowKeyUp = false; } else { @@ -371,44 +377,46 @@ private boolean isEnter(int key) { } private void handleArrowKeys(int key) { + final char ARROW_UP = 'A', ARROW_DOWN = 'B', ARROW_RIGHT = 'C', ARROW_LEFT = 'D'; switch (key) { - case 'A': - if (historyIterator == null) { - return; - } - - if (historyIterator.hasPrevious()) { - historyCommandLine = historyIterator.previous(); - moveUpHistory(); + case ARROW_UP: + if (historyIterator == 0 || (lastArrowKeyUp && (historyIterator == history.size() - 1))) { + historyCommandLine = history.get(historyIterator); + moveToEndOfHistory(); + return; + } + + if((historyIterator == history.size() - 1) && (historyIterator - 1) >= 0) { + historyCommandLine = history.get(historyIterator); + historyIterator--; + moveToEndOfHistory(); return; - } - - if (!historyIterator.hasPrevious()) { - moveUpHistory(); + } + + if(lastArrowKeyDown && (historyIterator - 1) >= 0) { + historyIterator--; + historyCommandLine = history.get(historyIterator); + moveToEndOfHistory(); return; + } + case ARROW_DOWN: + if (lastArrowKeyUp && historyIterator != 0 && (historyIterator + 1) < history.size()) { + historyIterator++; + historyCommandLine = history.get(historyIterator); } - case 'B': - if (historyIterator == null) { - return; - } - - if (lastArrowKeyUp && historyIterator.hasNext()) { - historyCommandLine = historyIterator.next(); - } - - if (!historyIterator.hasNext()) { + if(!((historyIterator + 1) < history.size())) { moveOutOfHistory(); return; } - - historyCommandLine = historyIterator.next(); - moveDownHistory(); + historyIterator++; + historyCommandLine = history.get(historyIterator); + + moveToStartofHistory(); return; - - case 'C': + case ARROW_RIGHT: moveCursorRight(); break; - case 'D': + case ARROW_LEFT: moveCursorLeft(); break; } @@ -449,6 +457,7 @@ private void handleNormalKey(int key) { } displayCharacters(input.toString()); lastArrowKeyUp = false; + lastArrowKeyDown = false; } private void resetInputLine() { @@ -458,30 +467,34 @@ private void resetInputLine() { displayCharacters(""); } - private void moveDownHistory() { + private void moveToStartofHistory() { input.setLength(0); input.append(historyCommandLine); + cursorX = input.length() - 1; displayCharacters(historyCommandLine); lastArrowKeyUp = false; + lastArrowKeyDown = true; } private void moveOutOfHistory() { resetInputLine(); lastArrowKeyUp = false; + lastArrowKeyDown = true; } - private void moveUpHistory() { + private void moveToEndOfHistory() { input.setLength(0); input.append(historyCommandLine); cursorX = input.length() - 1; displayCharacters(historyCommandLine); lastArrowKeyUp = true; + lastArrowKeyDown = false; } private void moveCursorLeft() { if (cursorX > 0) { cursorX--; - FeatJAR.log().noLineBreakMessage("\033[D"); // +"" ?? + FeatJAR.log().noLineBreakMessage("\033[D"); } } From a38ca198f0a441361fc74c1365cf9fc33dbe7445 Mon Sep 17 00:00:00 2001 From: Niclas Date: Sun, 2 Nov 2025 19:22:15 +0100 Subject: [PATCH 22/30] fix: historyIndex --- src/main/java/de/featjar/base/shell/Shell.java | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/main/java/de/featjar/base/shell/Shell.java b/src/main/java/de/featjar/base/shell/Shell.java index 12c1f64..0db1375 100644 --- a/src/main/java/de/featjar/base/shell/Shell.java +++ b/src/main/java/de/featjar/base/shell/Shell.java @@ -399,6 +399,15 @@ private void handleArrowKeys(int key) { moveToEndOfHistory(); return; } + + if(historyIterator > 0) { + historyCommandLine = history.get(historyIterator); + historyIterator--; + moveToEndOfHistory(); + return; + } + + case ARROW_DOWN: if (lastArrowKeyUp && historyIterator != 0 && (historyIterator + 1) < history.size()) { historyIterator++; From a3e32e66763f26b2e2882dd9226c3b4778ce9bc5 Mon Sep 17 00:00:00 2001 From: Niclas Date: Sun, 2 Nov 2025 19:23:57 +0100 Subject: [PATCH 23/30] refactor: changed index name --- .../java/de/featjar/base/shell/Shell.java | 40 +++++++++---------- 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/src/main/java/de/featjar/base/shell/Shell.java b/src/main/java/de/featjar/base/shell/Shell.java index 0db1375..7f50439 100644 --- a/src/main/java/de/featjar/base/shell/Shell.java +++ b/src/main/java/de/featjar/base/shell/Shell.java @@ -48,7 +48,7 @@ public class Shell { private ShellSession session; private final Scanner shellScanner; private List history; - private int historyIterator; + private int historyIndex; private final BufferedReader reader; private StringBuilder input; String historyCommandLine; @@ -276,7 +276,7 @@ private Optional readShellCommand(String prompt) { input = new StringBuilder(); int key; - historyIterator = history.size() - 1; + historyIndex = history.size() - 1; historyCommandLine = (history.size() > 0) ? history.get(history.size() - 1) : ""; try { @@ -330,7 +330,7 @@ private void handleEscapeKey(int key) throws IOException { } } else if (input.length() != 0) { - historyIterator = history.size() - 1; + historyIndex = history.size() - 1; resetInputLine(); lastArrowKeyUp = false; } else { @@ -380,45 +380,45 @@ private void handleArrowKeys(int key) { final char ARROW_UP = 'A', ARROW_DOWN = 'B', ARROW_RIGHT = 'C', ARROW_LEFT = 'D'; switch (key) { case ARROW_UP: - if (historyIterator == 0 || (lastArrowKeyUp && (historyIterator == history.size() - 1))) { - historyCommandLine = history.get(historyIterator); + if (historyIndex == 0 || (lastArrowKeyUp && (historyIndex == history.size() - 1))) { + historyCommandLine = history.get(historyIndex); moveToEndOfHistory(); return; } - if((historyIterator == history.size() - 1) && (historyIterator - 1) >= 0) { - historyCommandLine = history.get(historyIterator); - historyIterator--; + if((historyIndex == history.size() - 1) && (historyIndex - 1) >= 0) { + historyCommandLine = history.get(historyIndex); + historyIndex--; moveToEndOfHistory(); return; } - if(lastArrowKeyDown && (historyIterator - 1) >= 0) { - historyIterator--; - historyCommandLine = history.get(historyIterator); + if(lastArrowKeyDown && (historyIndex - 1) >= 0) { + historyIndex--; + historyCommandLine = history.get(historyIndex); moveToEndOfHistory(); return; } - if(historyIterator > 0) { - historyCommandLine = history.get(historyIterator); - historyIterator--; + if(historyIndex > 0) { + historyCommandLine = history.get(historyIndex); + historyIndex--; moveToEndOfHistory(); return; } case ARROW_DOWN: - if (lastArrowKeyUp && historyIterator != 0 && (historyIterator + 1) < history.size()) { - historyIterator++; - historyCommandLine = history.get(historyIterator); + if (lastArrowKeyUp && historyIndex != 0 && (historyIndex + 1) < history.size()) { + historyIndex++; + historyCommandLine = history.get(historyIndex); } - if(!((historyIterator + 1) < history.size())) { + if(!((historyIndex + 1) < history.size())) { moveOutOfHistory(); return; } - historyIterator++; - historyCommandLine = history.get(historyIterator); + historyIndex++; + historyCommandLine = history.get(historyIndex); moveToStartofHistory(); return; From 5d798fb84ab9582f51e9965cca09cdaf1a62e125 Mon Sep 17 00:00:00 2001 From: Niclas Date: Sun, 2 Nov 2025 21:17:22 +0100 Subject: [PATCH 24/30] fix: final fix for iterator replacement --- .../java/de/featjar/base/shell/Shell.java | 62 +++++++++++++------ 1 file changed, 42 insertions(+), 20 deletions(-) diff --git a/src/main/java/de/featjar/base/shell/Shell.java b/src/main/java/de/featjar/base/shell/Shell.java index 7f50439..4352152 100644 --- a/src/main/java/de/featjar/base/shell/Shell.java +++ b/src/main/java/de/featjar/base/shell/Shell.java @@ -380,48 +380,70 @@ private void handleArrowKeys(int key) { final char ARROW_UP = 'A', ARROW_DOWN = 'B', ARROW_RIGHT = 'C', ARROW_LEFT = 'D'; switch (key) { case ARROW_UP: - if (historyIndex == 0 || (lastArrowKeyUp && (historyIndex == history.size() - 1))) { +// System.out.println("\nUPINDEX: " + historyIndex + " SIZE:" + history.size() + "\n"); + +// if(lastArrowKeyDown && (historyIndex == history.size() - 1)) { +// historyIndex--; +// historyCommandLine = history.get(historyIndex); +// System.out.println("\n " +historyIndex + "\n"); +// moveToEndOfHistory(); +// return; +// } + + if (historyIndex == 0 || (!lastArrowKeyUp && (historyIndex == history.size() - 1))) { + if(lastArrowKeyDown) { + historyIndex--; + } historyCommandLine = history.get(historyIndex); moveToEndOfHistory(); return; } - if((historyIndex == history.size() - 1) && (historyIndex - 1) >= 0) { - historyCommandLine = history.get(historyIndex); - historyIndex--; - moveToEndOfHistory(); - return; - } + - if(lastArrowKeyDown && (historyIndex - 1) >= 0) { + if(historyIndex > 0) { historyIndex--; historyCommandLine = history.get(historyIndex); moveToEndOfHistory(); return; } - if(historyIndex > 0) { - historyCommandLine = history.get(historyIndex); - historyIndex--; - moveToEndOfHistory(); - return; - } + +// if((historyIndex == history.size() - 1) && historyIndex > 0) { +// historyCommandLine = history.get(historyIndex); +// historyIndex--; +// moveToEndOfHistory(); +// return; +// } +// +// if(lastArrowKeyDown && historyIndex > 0) { +// historyIndex--; +// historyCommandLine = history.get(historyIndex); +// moveToEndOfHistory(); +// return; +// } + case ARROW_DOWN: if (lastArrowKeyUp && historyIndex != 0 && (historyIndex + 1) < history.size()) { historyIndex++; historyCommandLine = history.get(historyIndex); + moveToStartofHistory(); + return; } if(!((historyIndex + 1) < history.size())) { moveOutOfHistory(); return; + } else { + historyIndex++; + historyCommandLine = history.get(historyIndex); + moveToStartofHistory(); + return; } - historyIndex++; - historyCommandLine = history.get(historyIndex); - - moveToStartofHistory(); - return; + +// System.out.println("\nDOWNINDEX: " + historyIndex + " SIZE:" + history.size() + "\n"); + case ARROW_RIGHT: moveCursorRight(); break; @@ -488,7 +510,7 @@ private void moveToStartofHistory() { private void moveOutOfHistory() { resetInputLine(); lastArrowKeyUp = false; - lastArrowKeyDown = true; + lastArrowKeyDown = false; } private void moveToEndOfHistory() { From f50745f214887967b55a54c12a07a35b6a01e2f7 Mon Sep 17 00:00:00 2001 From: Niclas Date: Fri, 7 Nov 2025 13:09:34 +0100 Subject: [PATCH 25/30] docs: added descriptions for all shell classes --- src/main/java/de/featjar/base/FeatJAR.java | 3 + .../java/de/featjar/base/cli/ACommand.java | 3 + .../java/de/featjar/base/cli/ICommand.java | 7 + .../java/de/featjar/base/cli/OptionList.java | 11 +- .../featjar/base/shell/ClearShellCommand.java | 8 +- .../base/shell/DeleteShellCommand.java | 8 +- .../featjar/base/shell/ExitShellCommand.java | 5 + .../featjar/base/shell/HelpShellCommand.java | 31 +++- .../de/featjar/base/shell/IShellCommand.java | 14 +- .../featjar/base/shell/RunShellCommand.java | 17 +- .../java/de/featjar/base/shell/Shell.java | 156 +++++++----------- .../de/featjar/base/shell/ShellCommands.java | 5 + .../de/featjar/base/shell/ShellSession.java | 67 +++++++- .../java/de/featjar/base/shell/Variables.java | 5 + 14 files changed, 209 insertions(+), 131 deletions(-) diff --git a/src/main/java/de/featjar/base/FeatJAR.java b/src/main/java/de/featjar/base/FeatJAR.java index c7c8c6c..02a6800 100644 --- a/src/main/java/de/featjar/base/FeatJAR.java +++ b/src/main/java/de/featjar/base/FeatJAR.java @@ -205,6 +205,9 @@ public static Configuration testConfiguration() { return configuration; } + /** + * {@return a new FeatJAR configuration with values intended for shell settings} + */ public static Configuration shellConfiguration() { final Configuration configuration = new Configuration(); configuration diff --git a/src/main/java/de/featjar/base/cli/ACommand.java b/src/main/java/de/featjar/base/cli/ACommand.java index 48e8635..8f31bac 100644 --- a/src/main/java/de/featjar/base/cli/ACommand.java +++ b/src/main/java/de/featjar/base/cli/ACommand.java @@ -52,6 +52,9 @@ public final List> getOptions() { return Option.getAllOptions(getClass()); } + /** + * {@return an option list with all parsed arguments and properties including a path variable from the session} + */ public OptionList getShellOptions(ShellSession session, List cmdParams) { OptionList optionList = new OptionList(); diff --git a/src/main/java/de/featjar/base/cli/ICommand.java b/src/main/java/de/featjar/base/cli/ICommand.java index 683555b..bcbda5b 100644 --- a/src/main/java/de/featjar/base/cli/ICommand.java +++ b/src/main/java/de/featjar/base/cli/ICommand.java @@ -64,6 +64,13 @@ default Optional getShortName() { */ int run(OptionList optionParser); + /** + * Parses arguments into an option list. + * + * @param session the shell session + * @param cmdParams the given arguments for the command + * @return an option list containing parsed arguments + */ default OptionList getShellOptions(ShellSession session, List cmdParams) { OptionList optionList = new OptionList(); diff --git a/src/main/java/de/featjar/base/cli/OptionList.java b/src/main/java/de/featjar/base/cli/OptionList.java index 7e436af..f3ace96 100644 --- a/src/main/java/de/featjar/base/cli/OptionList.java +++ b/src/main/java/de/featjar/base/cli/OptionList.java @@ -357,9 +357,14 @@ private void parseConfigurationFiles(List problemList) { } } } - - public void parseProperties(Option option, String command) { - Result parse = option.parse(command); + /** + * Adds a option with custom value to properties. + * + * @param option the option + * @param optionValue the command + */ + public void parseProperties(Option option, String optionValue) { + Result parse = option.parse(optionValue); properties.put(option.getName(), parse.get()); } diff --git a/src/main/java/de/featjar/base/shell/ClearShellCommand.java b/src/main/java/de/featjar/base/shell/ClearShellCommand.java index f1e0cea..e1f3881 100644 --- a/src/main/java/de/featjar/base/shell/ClearShellCommand.java +++ b/src/main/java/de/featjar/base/shell/ClearShellCommand.java @@ -20,12 +20,16 @@ */ package de.featjar.base.shell; +import de.featjar.base.FeatJAR; import java.util.List; import java.util.Objects; import java.util.Optional; -import de.featjar.base.FeatJAR; - +/** + * Deletes all variables from the entire shell session. + * + * @author Niclas Kleinert + */ public class ClearShellCommand implements IShellCommand { @Override diff --git a/src/main/java/de/featjar/base/shell/DeleteShellCommand.java b/src/main/java/de/featjar/base/shell/DeleteShellCommand.java index 9f84be8..a00f993 100644 --- a/src/main/java/de/featjar/base/shell/DeleteShellCommand.java +++ b/src/main/java/de/featjar/base/shell/DeleteShellCommand.java @@ -20,14 +20,18 @@ */ package de.featjar.base.shell; +import de.featjar.base.FeatJAR; import java.util.Arrays; import java.util.Collections; import java.util.List; import java.util.Optional; import java.util.stream.Collectors; -import de.featjar.base.FeatJAR; - +/** + * Deletes a given list of variables from the shell session. + * + * @author Niclas Kleinert + */ public class DeleteShellCommand implements IShellCommand { @Override diff --git a/src/main/java/de/featjar/base/shell/ExitShellCommand.java b/src/main/java/de/featjar/base/shell/ExitShellCommand.java index d429638..f0e1a6c 100644 --- a/src/main/java/de/featjar/base/shell/ExitShellCommand.java +++ b/src/main/java/de/featjar/base/shell/ExitShellCommand.java @@ -23,6 +23,11 @@ import java.util.List; import java.util.Optional; +/** + * Exits the shell. + * + * @author Niclas Kleinert + */ public class ExitShellCommand implements IShellCommand { @Override diff --git a/src/main/java/de/featjar/base/shell/HelpShellCommand.java b/src/main/java/de/featjar/base/shell/HelpShellCommand.java index 702ecb1..45b4da3 100644 --- a/src/main/java/de/featjar/base/shell/HelpShellCommand.java +++ b/src/main/java/de/featjar/base/shell/HelpShellCommand.java @@ -20,25 +20,29 @@ */ package de.featjar.base.shell; +import de.featjar.base.FeatJAR; import java.util.List; import java.util.Optional; -import de.featjar.base.FeatJAR; - +/** + * Prints basic usage informations of the shell and all available shell commands. + * + * @author Niclas Kleinert + */ public class HelpShellCommand implements IShellCommand { @Override public void execute(ShellSession session, List cmdParams) { - printCommands(); + printBasicUsage(); + printAllCommands(); } - public void printCommands() { - FeatJAR.log().message("Interactive shell"); - FeatJAR.log().message("Capitalization of COMMANDS is NOT taken into account"); - FeatJAR.log().message("You can cancel ANY command by pressing the (ESC) key"); + /** + * Prints all {@link IShellCommand} that are registered at {@link ShellCommands} + */ + public void printAllCommands() { FeatJAR.log().message("Supported commands are: \n"); - - FeatJAR.extensionPoint(ShellCommands.class).getExtensions().stream() + ShellCommands.getInstance().getExtensions().stream() .map(c -> c.getShortName() .orElse("") .concat(" " + c.getDescription().orElse(""))) @@ -46,6 +50,15 @@ public void printCommands() { FeatJAR.log().message("\n"); } + /** + * Prints basic usage informations of the shell. + */ + private void printBasicUsage() { + FeatJAR.log().message("Interactive shell"); + FeatJAR.log().message("Capitalization of COMMANDS is NOT taken into account"); + FeatJAR.log().message("You can cancel ANY command by pressing the (ESC) key"); + } + @Override public Optional getShortName() { return Optional.of("help"); diff --git a/src/main/java/de/featjar/base/shell/IShellCommand.java b/src/main/java/de/featjar/base/shell/IShellCommand.java index bcd4ebc..9b15277 100644 --- a/src/main/java/de/featjar/base/shell/IShellCommand.java +++ b/src/main/java/de/featjar/base/shell/IShellCommand.java @@ -20,13 +20,23 @@ */ package de.featjar.base.shell; +import de.featjar.base.extension.IExtension; import java.util.List; import java.util.Optional; -import de.featjar.base.extension.IExtension; - +/** + * A shell command run within a {@link ShellCommands} + * + * @author Niclas Kleinert + */ public interface IShellCommand extends IExtension { + /** + * Executes the shell command. + * + * @param session the storage location of all variables + * @param cmdParams all arguments except the shell command + */ void execute(ShellSession session, List cmdParams); /** diff --git a/src/main/java/de/featjar/base/shell/RunShellCommand.java b/src/main/java/de/featjar/base/shell/RunShellCommand.java index d7a630e..4e8f47c 100644 --- a/src/main/java/de/featjar/base/shell/RunShellCommand.java +++ b/src/main/java/de/featjar/base/shell/RunShellCommand.java @@ -20,17 +20,23 @@ */ package de.featjar.base.shell; -import java.util.List; -import java.util.Optional; -import java.util.concurrent.atomic.AtomicInteger; - import de.featjar.base.FeatJAR; +import de.featjar.base.cli.ACommand; import de.featjar.base.cli.Commands; import de.featjar.base.cli.ICommand; import de.featjar.base.cli.Option; import de.featjar.base.cli.OptionList; import de.featjar.base.data.Result; +import java.util.List; +import java.util.Optional; +import java.util.concurrent.atomic.AtomicInteger; +/** + * Allows {@link ACommand} to be run via the shell with the possibility to + * alter certain options before the command is executed. + * + * @author Niclas Kleinert + */ public class RunShellCommand implements IShellCommand { @Override @@ -58,8 +64,7 @@ public void execute(ShellSession session, List cmdParams) { FeatJAR.log() .error( "Errorcode '%d' occured in command '%s'", - runResult, - cliCommand.get().getIdentifier()); + runResult, cliCommand.get().getIdentifier()); } } catch (IllegalArgumentException iae) { diff --git a/src/main/java/de/featjar/base/shell/Shell.java b/src/main/java/de/featjar/base/shell/Shell.java index 4352152..b72ac3e 100644 --- a/src/main/java/de/featjar/base/shell/Shell.java +++ b/src/main/java/de/featjar/base/shell/Shell.java @@ -20,17 +20,18 @@ */ package de.featjar.base.shell; +import de.featjar.base.FeatJAR; +import de.featjar.base.data.Problem; +import de.featjar.base.data.Problem.Severity; +import de.featjar.base.data.Result; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; -import java.nio.file.Path; -import java.nio.file.Paths; import java.util.Arrays; import java.util.Collections; import java.util.HashMap; import java.util.LinkedList; import java.util.List; -import java.util.ListIterator; import java.util.Map; import java.util.Objects; import java.util.Optional; @@ -38,11 +39,11 @@ import java.util.concurrent.CancellationException; import java.util.stream.Collectors; -import de.featjar.base.FeatJAR; -import de.featjar.base.data.Problem; -import de.featjar.base.data.Problem.Severity; -import de.featjar.base.data.Result; - +/** + * The basic shell that provides direct access to all commands of FeatJAR. + * + * @author Niclas Kleinert + */ public class Shell { private static Shell instance; private ShellSession session; @@ -52,11 +53,11 @@ public class Shell { private final BufferedReader reader; private StringBuilder input; String historyCommandLine; - private int cursorX, cursorY; + private int cursorX; private boolean lastArrowKeyUp; - private boolean lastArrowKeyDown; - private final static String START_OF_TERMINAL_LINE = "$ "; - private final static int CURSOR_START_POSITION = START_OF_TERMINAL_LINE.length() + 1; + private boolean lastArrowKeyDown; + private static final String START_OF_TERMINAL_LINE = "$ "; + private static final int CURSOR_START_POSITION = START_OF_TERMINAL_LINE.length() + 1; private Shell() { this.session = new ShellSession(); @@ -82,7 +83,7 @@ public static void main(String[] args) { private void run() { FeatJAR.initialize(FeatJAR.shellConfiguration()); printArt(); - new HelpShellCommand().printCommands(); + new HelpShellCommand().execute(null, null); while (true) { List cmdArg = null; try { @@ -164,9 +165,7 @@ private Result parseCommand(String commandString) { if (commands.isEmpty()) { Result matchingExtension = shellCommandsExentionsPoint.getMatchingExtension(commandString); if (matchingExtension.isEmpty()) { - FeatJAR.log() - .message( - "No such command '" + commandString + "'. \n shows all viable commands"); + FeatJAR.log().message("No such command '" + commandString + "'. \n shows all viable commands"); return Result.empty(addProblem(Severity.ERROR, "No command matched the name '%s'!", commandString)); } command = matchingExtension.get(); @@ -236,32 +235,27 @@ private boolean isWindows() { * @param typedText the typed characters */ private void displayCharacters(String typedText) { - /* - * '\r' moves the cursor to the beginning of the line - * '\u001B[2K' or '\033[2K' erases the entire line - * '\u001B' (unicode) or '\033' (octal) for ESC work fine here - * '\u001B[#G' moves cursor to column # - * see for more documentation: https://gist.github.com/fnky/458719343aabd01cfb17a3a4f7296797 - */ + /* + * '\r' moves the cursor to the beginning of the line + * '\u001B[2K' or '\033[2K' erases the entire line + * '\u001B' (unicode) or '\033' (octal) for ESC work fine here + * '\u001B[#G' moves cursor to column # + * see for more documentation: https://gist.github.com/fnky/458719343aabd01cfb17a3a4f7296797 + */ FeatJAR.log().noLineBreakMessage("\r"); FeatJAR.log().noLineBreakMessage("\033[2K"); FeatJAR.log().noLineBreakMessage("$ " + typedText); FeatJAR.log().noLineBreakMessage("\033[" + (cursorX + CURSOR_START_POSITION) + "G"); } - - - private void resetMousePointer() { - FeatJAR.log().noLineBreakMessage("\033[" + CURSOR_START_POSITION + "G"); - } /** * Static access for {@link #readShellCommand(String)} - * reads characters one by one without line buffering into a String - * handles special keys (e.g. ESC) + * Reads characters one by one without line buffering into a string until ENTER is pressed. + * Handles special keys. ESC cancels every command. Ensures that arrow keys work as in a normal shell (including a terminal history). + * Page keys are ignored. Interrupts do not need special treatment and, therefore, work as usual. * @param prompt the message that is shown in the terminal - * @return + * @return all normal keys combined into a string */ - public static Optional readCommand(String prompt) { return Shell.getInstance().readShellCommand(prompt); } @@ -310,9 +304,7 @@ private Optional readShellCommand(String prompt) { } cursorX = 0; - return input.length() == 0 - ? Optional.empty() - : Optional.of(String.valueOf(input)); + return input.length() == 0 ? Optional.empty() : Optional.of(String.valueOf(input)); } private void handleEscapeKey(int key) throws IOException { @@ -335,8 +327,7 @@ private void handleEscapeKey(int key) throws IOException { lastArrowKeyUp = false; } else { exitInputMode(); - throw new CancellationException( - "\nCommand canceled\n"); + throw new CancellationException("\nCommand canceled\n"); } } @@ -352,14 +343,6 @@ private boolean isTabulator(int key) { return key == 9; } - private boolean isEOF(int key) { - return key == 4; - } - - private boolean isInterrupt(int key) { - return key == 3; - } - private boolean isDelete(int key) { return key == 51; } @@ -369,7 +352,7 @@ private boolean isEscape(int key) { } private boolean isBackspace(int key) { - return key == 127 || key == 8; // || isTabulator(key) + return key == 127 || key == 8; } private boolean isEnter(int key) { @@ -377,62 +360,37 @@ private boolean isEnter(int key) { } private void handleArrowKeys(int key) { - final char ARROW_UP = 'A', ARROW_DOWN = 'B', ARROW_RIGHT = 'C', ARROW_LEFT = 'D'; + final char ARROW_UP = 'A', ARROW_DOWN = 'B', ARROW_RIGHT = 'C', ARROW_LEFT = 'D'; switch (key) { case ARROW_UP: -// System.out.println("\nUPINDEX: " + historyIndex + " SIZE:" + history.size() + "\n"); - -// if(lastArrowKeyDown && (historyIndex == history.size() - 1)) { -// historyIndex--; -// historyCommandLine = history.get(historyIndex); -// System.out.println("\n " +historyIndex + "\n"); -// moveToEndOfHistory(); -// return; -// } - - if (historyIndex == 0 || (!lastArrowKeyUp && (historyIndex == history.size() - 1))) { - if(lastArrowKeyDown) { - historyIndex--; - } + if (history.isEmpty()) { + return; + } + if (historyIndex == 0 || (!lastArrowKeyUp && (historyIndex == history.size() - 1))) { + if (lastArrowKeyDown) { + historyIndex--; + } historyCommandLine = history.get(historyIndex); - moveToEndOfHistory(); - return; - } - - - - if(historyIndex > 0) { - historyIndex--; + moveToEndOfHistory(); + return; + } + if (historyIndex > 0) { + historyIndex--; historyCommandLine = history.get(historyIndex); moveToEndOfHistory(); return; - } - - -// if((historyIndex == history.size() - 1) && historyIndex > 0) { -// historyCommandLine = history.get(historyIndex); -// historyIndex--; -// moveToEndOfHistory(); -// return; -// } -// -// if(lastArrowKeyDown && historyIndex > 0) { -// historyIndex--; -// historyCommandLine = history.get(historyIndex); -// moveToEndOfHistory(); -// return; -// } - - - - case ARROW_DOWN: + } + case ARROW_DOWN: + if (history.isEmpty()) { + return; + } if (lastArrowKeyUp && historyIndex != 0 && (historyIndex + 1) < history.size()) { historyIndex++; historyCommandLine = history.get(historyIndex); moveToStartofHistory(); return; } - if(!((historyIndex + 1) < history.size())) { + if (!((historyIndex + 1) < history.size())) { moveOutOfHistory(); return; } else { @@ -441,9 +399,6 @@ private void handleArrowKeys(int key) { moveToStartofHistory(); return; } - -// System.out.println("\nDOWNINDEX: " + historyIndex + " SIZE:" + history.size() + "\n"); - case ARROW_RIGHT: moveCursorRight(); break; @@ -463,7 +418,7 @@ private void handleBackspaceKey() { } if (cursorX != 0) { cursorX--; - } // TODO 238, CursorY + } } } } @@ -493,7 +448,6 @@ private void handleNormalKey(int key) { private void resetInputLine() { input.setLength(0); - input.append(""); cursorX = 0; displayCharacters(""); } @@ -507,6 +461,10 @@ private void moveToStartofHistory() { lastArrowKeyDown = true; } + /* + * moves out of the command history and resets the two lastArrowKey booleans + */ + private void moveOutOfHistory() { resetInputLine(); lastArrowKeyUp = false; @@ -549,8 +507,10 @@ private void enterInputMode() { * -echo has to be disabled in combination with icanon to allow ANSI escape sequences to actually to what they are supposed to do * (e.g. "\033[D" to move the cursor one space to the left). Otherwise the control code gets directly printed to the console * without executing the the ANSI escape sequence. - */ - Runtime.getRuntime().exec(new String[]{"sh","-c","stty -icanon -echo { public static ShellCommands getInstance() { diff --git a/src/main/java/de/featjar/base/shell/ShellSession.java b/src/main/java/de/featjar/base/shell/ShellSession.java index b2a7644..218e5db 100644 --- a/src/main/java/de/featjar/base/shell/ShellSession.java +++ b/src/main/java/de/featjar/base/shell/ShellSession.java @@ -20,14 +20,18 @@ */ package de.featjar.base.shell; +import de.featjar.base.FeatJAR; import java.util.Comparator; import java.util.LinkedHashMap; import java.util.Map; import java.util.Map.Entry; import java.util.Optional; -import de.featjar.base.FeatJAR; - +/** + * The session in which all loaded formats are stored. + * + * @author Niclas Kleinert + */ public class ShellSession { private static class StoredElement { @@ -42,6 +46,14 @@ public StoredElement(Class type, T element) { private final Map> elements; + /** + * similar functionality as {@link #getElement(String)} + * but avoids unchecked cast warnings when the type of element is known + * @param generic type of element + * @param key the elements' key + * @param type the elements' type + * @return the element of the shell session or an empty optional if the element is not present + */ @SuppressWarnings("unchecked") public Optional get(String key, Class type) { StoredElement storedElement = elements.get(key); @@ -52,14 +64,22 @@ public Optional get(String key, Class type) { if (storedElement.type == type) { return Optional.of((T) storedElement.type.cast(storedElement.element)); } else { - throw new RuntimeException("Wrong Type"); // TODO Result von Problem addProblem + throw new RuntimeException("Wrong Type"); } } + /** + * @param key the elements' key + * @return the type of the element or null if it is not present + */ public Optional getType(String key) { return Optional.ofNullable(elements.get(key)).map(e -> e.type); } + /** + * @param key the elements' key + * @return the element that is mapped to the key or null if it is not present + */ public Optional getElement(String key) { return Optional.ofNullable(elements.get(key)).map(e -> e.element); } @@ -68,30 +88,58 @@ public ShellSession() { elements = new LinkedHashMap<>(); } + /** + * puts an element into the session + * @param generic type of element + * @param key the elements' key + * @param element the element of the shell session + * @param type the elements' type + */ public void put(String key, T element, Class type) { elements.put(key, new StoredElement(type, element)); } + /** + * removes a specific variable of the shell session + * @param key the variables' key + * @return non-null previous value if the removal was successful + */ public Optional remove(String key) { return Optional.ofNullable(elements.remove(key)); } - + /** + * removes all elements of the session + */ public void clear() { elements.clear(); } + /** + * @return the number of elements in the session + */ public int getSize() { return elements.size(); } - + /** + * checks if the shell session contains a variable with a specific key + * @param key the elements' key + * @return true if a variable with given key is present + */ public boolean containsKey(String key) { return elements.containsKey(key); } + /** + * checks if the shell session is empty + * @return true if no element is present + */ public boolean isEmpty() { return elements.isEmpty(); } - + /** + * prints a single if there is a matching key + * @param key the elements' key + */ public void printVariable(String key) { for (Entry> entry : elements.entrySet()) { if (entry.getKey().equals(key)) { @@ -103,18 +151,21 @@ public void printVariable(String key) { } } + /** + * prints all in the session present variables + */ public void printVariables() { elements.entrySet().forEach(m -> FeatJAR.log() .message(m.getKey() + " (" + m.getValue().type.getSimpleName() + ")")); } + @SuppressWarnings({"unused"}) public void printSortedByVarNames() { - // TODO implement both methods in parser - elements.entrySet().stream().sorted(Map.Entry.comparingByKey()).forEach(m -> FeatJAR.log() .message(m.getKey() + " " + m.getValue().type.getSimpleName())); } + @SuppressWarnings({"unused"}) public void printSortedByType() { elements.entrySet().stream() .sorted(Comparator.comparing(e -> String.valueOf(e.getValue().type))) diff --git a/src/main/java/de/featjar/base/shell/Variables.java b/src/main/java/de/featjar/base/shell/Variables.java index 32c443f..136d228 100644 --- a/src/main/java/de/featjar/base/shell/Variables.java +++ b/src/main/java/de/featjar/base/shell/Variables.java @@ -23,6 +23,11 @@ import java.util.List; import java.util.Optional; +/** + * Shows all stored variables within {@link ShellSession} + * + * @author Niclas Kleinert + */ public class Variables implements IShellCommand { @Override From 84de8c712e0887ddaf8ac682ea61c4f4b9090753 Mon Sep 17 00:00:00 2001 From: Niclas Date: Fri, 7 Nov 2025 13:10:56 +0100 Subject: [PATCH 26/30] refactor-docs: splitted methods for better redability, added descriptions --- .../java/de/featjar/base/shell/PrintShellCommand.java | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/main/java/de/featjar/base/shell/PrintShellCommand.java b/src/main/java/de/featjar/base/shell/PrintShellCommand.java index f9d38cb..4372ed6 100644 --- a/src/main/java/de/featjar/base/shell/PrintShellCommand.java +++ b/src/main/java/de/featjar/base/shell/PrintShellCommand.java @@ -20,15 +20,19 @@ */ package de.featjar.base.shell; +import de.featjar.base.FeatJAR; +import de.featjar.base.tree.structure.ITree; import java.util.Arrays; import java.util.Collections; import java.util.List; import java.util.Optional; import java.util.stream.Collectors; -import de.featjar.base.FeatJAR; -import de.featjar.base.tree.structure.ITree; - +/** + * Prints the content of given shell session variables. + * + * @author Niclas Kleinert + */ public class PrintShellCommand implements IShellCommand { @Override From 97c9e6f7bcba36000f44d8aba3ed171d337df6ae Mon Sep 17 00:00:00 2001 From: Niclas Date: Fri, 7 Nov 2025 13:11:34 +0100 Subject: [PATCH 27/30] docs: added descriptions for shell related class --- src/main/java/de/featjar/base/log/ColorFormatter.java | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/main/java/de/featjar/base/log/ColorFormatter.java b/src/main/java/de/featjar/base/log/ColorFormatter.java index 49a8373..531a4a2 100644 --- a/src/main/java/de/featjar/base/log/ColorFormatter.java +++ b/src/main/java/de/featjar/base/log/ColorFormatter.java @@ -22,9 +22,14 @@ import de.featjar.base.log.Log.Verbosity; +/** + * Prepends different colors to logs and appends a reset of the colors as suffix. + * + * @author Niclas Kleinert + */ public class ColorFormatter implements IFormatter { - private static final String TERMINAL_COLOR_LIGHT_BLUE = "\033[38;2;173;236;255m"; + private static final String TERMINAL_COLOR_LIGHT_BLUE = "\033[38;2;173;236;255m"; private static final String TERMINAL_COLOR_YELLOW = "\033[38;2;255;255;0m"; private static final String TERMINAL_COLOR_RED = "\033[38;2;255;0;0m"; private static final String TERMINAL_COLOR_RESET = "\033[0m"; From 83c8eea93d29900859ae5f5fab32d9a78ee8c70e Mon Sep 17 00:00:00 2001 From: Niclas Date: Tue, 11 Nov 2025 12:29:10 +0100 Subject: [PATCH 28/30] refactor-feat-docs: switched from optional to result, added helper methods and updated docs --- .../java/de/featjar/base/cli/ACommand.java | 4 +- .../java/de/featjar/base/cli/OptionList.java | 2 +- .../base/shell/DeleteShellCommand.java | 5 +- .../featjar/base/shell/PrintShellCommand.java | 14 +-- .../de/featjar/base/shell/ShellSession.java | 89 ++++++++++++------- 5 files changed, 70 insertions(+), 44 deletions(-) diff --git a/src/main/java/de/featjar/base/cli/ACommand.java b/src/main/java/de/featjar/base/cli/ACommand.java index 8f31bac..bd1fb00 100644 --- a/src/main/java/de/featjar/base/cli/ACommand.java +++ b/src/main/java/de/featjar/base/cli/ACommand.java @@ -20,10 +20,10 @@ */ package de.featjar.base.cli; +import de.featjar.base.data.Result; import de.featjar.base.shell.ShellSession; import java.nio.file.Path; import java.util.List; -import java.util.Optional; /** * The abstract class for any command. @@ -62,7 +62,7 @@ public OptionList getShellOptions(ShellSession session, List cmdParams) throw new IllegalArgumentException("No path object specified"); } - Optional path = session.get(cmdParams.get(0), Path.class); + Result path = session.get(cmdParams.get(0), Path.class); if (path.isEmpty()) { throw new IllegalArgumentException(String.format("'%s' is not a session object", cmdParams.get(0))); diff --git a/src/main/java/de/featjar/base/cli/OptionList.java b/src/main/java/de/featjar/base/cli/OptionList.java index f3ace96..aa73bd1 100644 --- a/src/main/java/de/featjar/base/cli/OptionList.java +++ b/src/main/java/de/featjar/base/cli/OptionList.java @@ -359,7 +359,7 @@ private void parseConfigurationFiles(List problemList) { } /** * Adds a option with custom value to properties. - * + * * @param option the option * @param optionValue the command */ diff --git a/src/main/java/de/featjar/base/shell/DeleteShellCommand.java b/src/main/java/de/featjar/base/shell/DeleteShellCommand.java index a00f993..0352561 100644 --- a/src/main/java/de/featjar/base/shell/DeleteShellCommand.java +++ b/src/main/java/de/featjar/base/shell/DeleteShellCommand.java @@ -21,6 +21,7 @@ package de.featjar.base.shell; import de.featjar.base.FeatJAR; +import de.featjar.base.log.Log.Verbosity; import java.util.Arrays; import java.util.Collections; import java.util.List; @@ -46,8 +47,8 @@ public void execute(ShellSession session, List cmdParams) { cmdParams.forEach(e -> { session.remove(e) - .ifPresentOrElse(a -> FeatJAR.log().message("Removing of " + e + " successful"), () -> FeatJAR.log() - .error("Could not find a variable named " + e)); + .ifPresent(a -> FeatJAR.log().message("Removing of " + e + " successful")) + .orElseLog(Verbosity.ERROR); }); } diff --git a/src/main/java/de/featjar/base/shell/PrintShellCommand.java b/src/main/java/de/featjar/base/shell/PrintShellCommand.java index 4372ed6..a067f97 100644 --- a/src/main/java/de/featjar/base/shell/PrintShellCommand.java +++ b/src/main/java/de/featjar/base/shell/PrintShellCommand.java @@ -21,6 +21,7 @@ package de.featjar.base.shell; import de.featjar.base.FeatJAR; +import de.featjar.base.log.Log.Verbosity; import de.featjar.base.tree.structure.ITree; import java.util.Arrays; import java.util.Collections; @@ -45,13 +46,12 @@ public void execute(ShellSession session, List cmdParams) { } cmdParams.forEach(e -> { - session.getElement(e) - .ifPresentOrElse( - m -> { - FeatJAR.log().message(e + ":"); - printMap(m); - }, - () -> FeatJAR.log().error("Could not find a variable named " + e)); + session.get(e) + .ifPresent(m -> { + FeatJAR.log().message(e + ":"); + printMap(m); + }) + .orElseLog(Verbosity.ERROR); }); } diff --git a/src/main/java/de/featjar/base/shell/ShellSession.java b/src/main/java/de/featjar/base/shell/ShellSession.java index 218e5db..b89249b 100644 --- a/src/main/java/de/featjar/base/shell/ShellSession.java +++ b/src/main/java/de/featjar/base/shell/ShellSession.java @@ -21,11 +21,13 @@ package de.featjar.base.shell; import de.featjar.base.FeatJAR; +import de.featjar.base.data.Problem; +import de.featjar.base.data.Problem.Severity; +import de.featjar.base.data.Result; import java.util.Comparator; import java.util.LinkedHashMap; import java.util.Map; import java.util.Map.Entry; -import java.util.Optional; /** * The session in which all loaded formats are stored. @@ -46,50 +48,76 @@ public StoredElement(Class type, T element) { private final Map> elements; + public ShellSession() { + elements = new LinkedHashMap<>(); + } + /** - * similar functionality as {@link #getElement(String)} - * but avoids unchecked cast warnings when the type of element is known + * Returns the element if the key is present in the session + * and casts the element to the known type. + * * @param generic type of element * @param key the elements' key - * @param type the elements' type - * @return the element of the shell session or an empty optional if the element is not present + * @param kownType the elements' type + * @return the element of the shell session or an empty result if the element is not present */ @SuppressWarnings("unchecked") - public Optional get(String key, Class type) { + public Result get(String key, Class kownType) { StoredElement storedElement = elements.get(key); if (storedElement == null) { - return Optional.empty(); + return Result.empty(addNotPresentProblem(key)); } - if (storedElement.type == type) { - return Optional.of((T) storedElement.type.cast(storedElement.element)); + if (storedElement.type == kownType) { + return Result.of((T) storedElement.type.cast(storedElement.element)); } else { throw new RuntimeException("Wrong Type"); } } /** + * Returns the element if the key is present in the session or + * ,otherwise, an empty result containing an error message. + * * @param key the elements' key - * @return the type of the element or null if it is not present + * @return the element that is mapped to the key or an empty result if it is not present */ - public Optional getType(String key) { - return Optional.ofNullable(elements.get(key)).map(e -> e.type); + public Result get(String key) { + return elements.get(key) != null + ? Result.of(elements.get(key)).map(e -> e.element) + : Result.empty(addNotPresentProblem(key)); } /** + * Returns the type of an element or + * ,otherwise, an empty result containing an error message. + * * @param key the elements' key - * @return the element that is mapped to the key or null if it is not present + * @return the type of the element or an empty result if the element is not present */ - public Optional getElement(String key) { - return Optional.ofNullable(elements.get(key)).map(e -> e.element); + public Result getType(String key) { + return elements.get(key) != null + ? Result.of(elements.get(key)).map(e -> e.type) + : Result.empty(addNotPresentProblem(key)); } - public ShellSession() { - elements = new LinkedHashMap<>(); + /** + * Removes a single element of the shell session. + * + * @param key the elements' key + * @return non-null previous value if the removal was successful + */ + public Result remove(String key) { + return Result.of(elements.remove(key)).or(Result.empty(addNotPresentProblem(key))); + } + + private Problem addNotPresentProblem(String key) { + return new Problem(String.format("A variable named '%s' is not present in the session!", key), Severity.ERROR); } /** - * puts an element into the session + * Puts an element into the session. + * * @param generic type of element * @param key the elements' key * @param element the element of the shell session @@ -100,28 +128,22 @@ public void put(String key, T element, Class type) { } /** - * removes a specific variable of the shell session - * @param key the variables' key - * @return non-null previous value if the removal was successful - */ - public Optional remove(String key) { - return Optional.ofNullable(elements.remove(key)); - } - /** - * removes all elements of the session + * Removes all elements of the session. */ public void clear() { elements.clear(); } /** - * @return the number of elements in the session + * {@return the number of elements in the session} */ public int getSize() { return elements.size(); } + /** - * checks if the shell session contains a variable with a specific key + * Checks if the shell session contains a element with a specific key. + * * @param key the elements' key * @return true if a variable with given key is present */ @@ -130,14 +152,17 @@ public boolean containsKey(String key) { } /** - * checks if the shell session is empty + * Checks if the shell session is empty. + * * @return true if no element is present */ public boolean isEmpty() { return elements.isEmpty(); } + /** - * prints a single if there is a matching key + * Prints a single if there is a matching key. + * * @param key the elements' key */ public void printVariable(String key) { @@ -152,7 +177,7 @@ public void printVariable(String key) { } /** - * prints all in the session present variables + * Prints everything present in the session. */ public void printVariables() { elements.entrySet().forEach(m -> FeatJAR.log() From c4ea0d97f98390b79994d2db5f4fc92a4b6080dd Mon Sep 17 00:00:00 2001 From: Niclas Date: Tue, 11 Nov 2025 12:41:45 +0100 Subject: [PATCH 29/30] docs: fixed typos --- src/main/java/de/featjar/base/shell/Shell.java | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/main/java/de/featjar/base/shell/Shell.java b/src/main/java/de/featjar/base/shell/Shell.java index b72ac3e..168ed9d 100644 --- a/src/main/java/de/featjar/base/shell/Shell.java +++ b/src/main/java/de/featjar/base/shell/Shell.java @@ -232,6 +232,7 @@ private boolean isWindows() { /** * Displays the typed characters in the console. + * * @param typedText the typed characters */ private void displayCharacters(String typedText) { @@ -253,6 +254,7 @@ private void displayCharacters(String typedText) { * Reads characters one by one without line buffering into a string until ENTER is pressed. * Handles special keys. ESC cancels every command. Ensures that arrow keys work as in a normal shell (including a terminal history). * Page keys are ignored. Interrupts do not need special treatment and, therefore, work as usual. + * * @param prompt the message that is shown in the terminal * @return all normal keys combined into a string */ @@ -462,7 +464,7 @@ private void moveToStartofHistory() { } /* - * moves out of the command history and resets the two lastArrowKey booleans + * Moves out of the command history and resets the two lastArrowKey booleans. */ private void moveOutOfHistory() { @@ -495,7 +497,8 @@ private void moveCursorRight() { } /** - *Sets the terminal into a 'raw' like mode that has no line buffer such that the shell can read a single key press, signals like CTRL+C do still work + *Sets the terminal into a 'raw' like mode that has no line buffer such that the shell can read a single key press, + *signals like CTRL+C do still work. */ private void enterInputMode() { try { @@ -517,7 +520,7 @@ private void enterInputMode() { } /** - * Resets the the changes made in {@link Shell#enterInputMode()} and sets the terminal back into 'cooked' (normal) mode + * Resets the the changes made in {@link Shell#enterInputMode()} and sets the terminal back into 'cooked' (normal) mode. */ private void exitInputMode() { try { From b0197e8bb3b18abadc5ef95daf56e9b51e01a71f Mon Sep 17 00:00:00 2001 From: Niclas Date: Mon, 17 Nov 2025 10:52:55 +0100 Subject: [PATCH 30/30] refactor: clearer naming of some methods --- src/main/java/de/featjar/base/shell/DeleteShellCommand.java | 2 +- src/main/java/de/featjar/base/shell/PrintShellCommand.java | 2 +- src/main/java/de/featjar/base/shell/ShellSession.java | 6 +++--- src/main/java/de/featjar/base/shell/Variables.java | 2 +- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/main/java/de/featjar/base/shell/DeleteShellCommand.java b/src/main/java/de/featjar/base/shell/DeleteShellCommand.java index 0352561..b80a6cb 100644 --- a/src/main/java/de/featjar/base/shell/DeleteShellCommand.java +++ b/src/main/java/de/featjar/base/shell/DeleteShellCommand.java @@ -39,7 +39,7 @@ public class DeleteShellCommand implements IShellCommand { public void execute(ShellSession session, List cmdParams) { if (cmdParams.isEmpty()) { - session.printVariables(); + session.printAll(); cmdParams = Shell.readCommand("Enter the variable names you want to delete or leave blank to abort:") .map(c -> Arrays.stream(c.split("\\s+")).collect(Collectors.toList())) .orElse(Collections.emptyList()); diff --git a/src/main/java/de/featjar/base/shell/PrintShellCommand.java b/src/main/java/de/featjar/base/shell/PrintShellCommand.java index a067f97..08756eb 100644 --- a/src/main/java/de/featjar/base/shell/PrintShellCommand.java +++ b/src/main/java/de/featjar/base/shell/PrintShellCommand.java @@ -39,7 +39,7 @@ public class PrintShellCommand implements IShellCommand { @Override public void execute(ShellSession session, List cmdParams) { if (cmdParams.isEmpty()) { - session.printVariables(); + session.printAll(); cmdParams = Shell.readCommand("Enter the variable names you want to print or leave blank to abort:") .map(c -> Arrays.stream(c.toLowerCase().split("\\s+")).collect(Collectors.toList())) .orElse(Collections.emptyList()); diff --git a/src/main/java/de/featjar/base/shell/ShellSession.java b/src/main/java/de/featjar/base/shell/ShellSession.java index b89249b..79b66ef 100644 --- a/src/main/java/de/featjar/base/shell/ShellSession.java +++ b/src/main/java/de/featjar/base/shell/ShellSession.java @@ -161,11 +161,11 @@ public boolean isEmpty() { } /** - * Prints a single if there is a matching key. + * Prints a single element if there is a matching key. * * @param key the elements' key */ - public void printVariable(String key) { + public void printSingleELement(String key) { for (Entry> entry : elements.entrySet()) { if (entry.getKey().equals(key)) { FeatJAR.log() @@ -179,7 +179,7 @@ public void printVariable(String key) { /** * Prints everything present in the session. */ - public void printVariables() { + public void printAll() { elements.entrySet().forEach(m -> FeatJAR.log() .message(m.getKey() + " (" + m.getValue().type.getSimpleName() + ")")); } diff --git a/src/main/java/de/featjar/base/shell/Variables.java b/src/main/java/de/featjar/base/shell/Variables.java index 136d228..8adc700 100644 --- a/src/main/java/de/featjar/base/shell/Variables.java +++ b/src/main/java/de/featjar/base/shell/Variables.java @@ -32,7 +32,7 @@ public class Variables implements IShellCommand { @Override public void execute(ShellSession session, List cmdParams) { - session.printVariables(); + session.printAll(); } @Override