 
                        
        
                        
        org.jline.console.impl.ConsoleEngineImpl Maven / Gradle / Ivy
/*
 * Copyright (c) 2002-2021, the original author or authors.
 *
 * This software is distributable under the BSD license. See the terms of the
 * BSD license in the documentation provided with this software.
 *
 * https://opensource.org/licenses/BSD-3-Clause
 */
package org.jline.console.impl;
import java.awt.Desktop;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.net.HttpURLConnection;
import java.net.URI;
import java.net.URL;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.nio.file.*;
import java.util.*;
import java.util.function.Supplier;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import org.jline.builtins.Completers.FilesCompleter;
import org.jline.builtins.Completers.OptDesc;
import org.jline.builtins.Completers.OptionCompleter;
import org.jline.builtins.ConfigurationPath;
import org.jline.builtins.Options;
import org.jline.builtins.Options.HelpException;
import org.jline.builtins.Styles;
import org.jline.console.*;
import org.jline.reader.*;
import org.jline.reader.Parser.ParseContext;
import org.jline.reader.impl.completer.AggregateCompleter;
import org.jline.reader.impl.completer.ArgumentCompleter;
import org.jline.reader.impl.completer.NullCompleter;
import org.jline.reader.impl.completer.StringsCompleter;
import org.jline.terminal.Terminal;
import org.jline.utils.AttributedString;
import org.jline.utils.AttributedStringBuilder;
/**
 * Manage console variables, commands and script execution.
 *
 * @author Matti Rinta-Nikkola
 */
public class ConsoleEngineImpl extends JlineCommandRegistry implements ConsoleEngine {
    public enum Command {SHOW
                       , DEL
                       , PRNT
                       , ALIAS
                       , PIPE
                       , UNALIAS
                       , DOC
                       , SLURP}
    private static final String VAR_CONSOLE_OPTIONS = "CONSOLE_OPTIONS";
    private static final String VAR_PATH = "PATH";
    private static final String[] OPTION_HELP = {"-?", "--help"};
    private static final String OPTION_VERBOSE = "-v";
    private static final String SLURP_FORMAT_TEXT = "TEXT";
    private static final String END_HELP = "END_HELP";
    private static final int HELP_MAX_SIZE = 30;
    private final ScriptEngine engine;
    private Exception exception;
    private SystemRegistry systemRegistry;
    private String scriptExtension = "jline";
    private final Supplier workDir;
    private final Map aliases = new HashMap<>();
    private final Map> pipes = new HashMap<>();
    private Path aliasFile;
    private LineReader reader;
    private boolean executing = false;
    private final Printer printer;
    public ConsoleEngineImpl(ScriptEngine engine, Printer printer
            , Supplier workDir, ConfigurationPath configPath) throws IOException {
        this(null, engine, printer, workDir, configPath);
    }
    @SuppressWarnings("unchecked")
    public ConsoleEngineImpl(Set commands, ScriptEngine engine, Printer printer
                           , Supplier workDir, ConfigurationPath configPath) throws IOException {
        super();
        this.engine = engine;
        this.workDir = workDir;
        this.printer = printer;
        Map commandName = new HashMap<>();
        Map commandExecute = new HashMap<>();
        Set cmds;
        if (commands == null) {
            cmds = new HashSet<>(EnumSet.allOf(Command.class));
        } else {
            cmds = new HashSet<>(commands);
        }
        for (Command c: cmds) {
            commandName.put(c, c.name().toLowerCase());
        }
        commandExecute.put(Command.DEL, new CommandMethods(this::del, this::variableCompleter));
        commandExecute.put(Command.SHOW, new CommandMethods(this::show, this::variableCompleter));
        commandExecute.put(Command.PRNT, new CommandMethods(this::prnt, this::prntCompleter));
        commandExecute.put(Command.SLURP, new CommandMethods(this::slurpcmd, this::slurpCompleter));
        commandExecute.put(Command.ALIAS, new CommandMethods(this::aliascmd, this::aliasCompleter));
        commandExecute.put(Command.UNALIAS, new CommandMethods(this::unalias, this::unaliasCompleter));
        commandExecute.put(Command.DOC, new CommandMethods(this::doc, this::docCompleter));
        commandExecute.put(Command.PIPE, new CommandMethods(this::pipe, this::defaultCompleter));
        aliasFile = configPath.getUserConfig("aliases.json");
        if (aliasFile == null) {
            aliasFile = configPath.getUserConfig("aliases.json", true);
            persist(aliasFile, aliases);
        } else {
            aliases.putAll((Map)slurp(aliasFile));
        }
        registerCommands(commandName, commandExecute);
    }
    @Override
    public void setLineReader(LineReader reader) {
        this.reader = reader;
    }
    private Parser parser() {
        return reader.getParser();
    }
    private Terminal terminal() {
        return systemRegistry.terminal();
    }
    public boolean isExecuting() {
        return executing;
    }
    @Override
    public void setSystemRegistry(SystemRegistry systemRegistry) {
        this.systemRegistry = systemRegistry;
    }
    @Override
    public void setScriptExtension(String extension) {
        this.scriptExtension = extension;
    }
    @Override
    public boolean hasAlias(String name) {
        return aliases.containsKey(name);
    }
    @Override
    public String getAlias(String name) {
        return aliases.getOrDefault(name, null);
    }
    @Override
    public Map> getPipes() {
        return pipes;
    }
    @Override
    public List getNamedPipes() {
        List out = new ArrayList<>();
        List opers = new ArrayList<>();
        for (String p : pipes.keySet()) {
            if (p.matches("[a-zA-Z0-9]+")) {
                out.add(p);
            } else {
                opers.add(p);
            }
        }
        opers.addAll(systemRegistry.getPipeNames());
        for (Map.Entry entry : aliases.entrySet()) {
            if (opers.contains(entry.getValue().split(" ")[0])) {
                out.add(entry.getKey());
            }
        }
        return out;
    }
    @Override
    public List scriptCompleters() {
        List out = new ArrayList<>();
        out.add(new ArgumentCompleter(new StringsCompleter(this::scriptNames)
                                    , new OptionCompleter(NullCompleter.INSTANCE
                                                        , this::commandOptions
                                                        , 1)
                                       ));
        out.add(new ArgumentCompleter(new StringsCompleter(this::commandAliasNames)
                                     , NullCompleter.INSTANCE));
        return out;
    }
    private Set commandAliasNames() {
        Set opers = pipes.keySet().stream().filter(p -> !p.matches("\\w+")).collect(Collectors.toSet());
        opers.addAll(systemRegistry.getPipeNames());
        return aliases.entrySet().stream()
                .filter(e -> !opers.contains(e.getValue().split(" ")[0]))
                .map(Map.Entry::getKey).collect(Collectors.toSet());
    }
    private Set scriptNames() {
        return scripts().keySet();
    }
    @SuppressWarnings("unchecked")
    @Override
    public Map scripts() {
        Map out = new HashMap<>();
        try {
            List scripts = new ArrayList<>();
            if (engine.hasVariable(VAR_PATH)) {
                List dirs = new ArrayList<>();
                for (String file : (List) engine.get(VAR_PATH)) {
                    file = file.startsWith("~") ? file.replace("~", System.getProperty("user.home")) : file;
                    File dir = new File(file);
                    if (dir.exists() && dir.isDirectory()) {
                        dirs.add(file);
                    }
                }
                for (String pp : dirs) {
                    for (String e : scriptExtensions()) {
                        String regex = pp + "/*." + e;
                        PathMatcher pathMatcher = FileSystems.getDefault().getPathMatcher("glob:" + regex);
                        Files.find(Paths.get(new File(regex).getParent()), Integer.MAX_VALUE,
                                    (path, f) -> pathMatcher.matches(path)).forEach(scripts::add);
                    }
                }
            }
            for (Path p : scripts) {
                String name = p.toFile().getName();
                int idx = name.lastIndexOf(".");
                out.put(name.substring(0, idx), name.substring(idx + 1).equals(scriptExtension));
            }
        } catch (NoSuchFileException e) {
            error("Failed reading PATH. No file found: " + e.getMessage());
        } catch (InvalidPathException e) {
            error("Failed reading PATH. Invalid path:");
            error(e.toString());
        } catch (Exception e) {
            error("Failed reading PATH:");
            trace(e);
            engine.put("exception", e);
        }
        return out;
    }
    @Override
    public Object[] expandParameters(String[] args) throws Exception {
        Object[] out = new Object[args.length];
        String regexPath = "(.*)\\$\\{(.*?)}(/.*)";
        for (int i = 0; i < args.length; i++) {
            if (args[i].matches(regexPath)) {
                Matcher matcher = Pattern.compile(regexPath).matcher(args[i]);
                if (matcher.find()) {
                    out[i] = matcher.group(1) + engine.get(matcher.group(2)) + matcher.group(3);
                } else {
                    throw new IllegalArgumentException();
                }
            } else if (args[i].startsWith("${")) {
                out[i] = engine.execute(expandName(args[i]));
            } else if (args[i].startsWith("$")) {
                out[i] = engine.get(expandName(args[i]));
            } else {
                out[i] = engine.deserialize(args[i]);
            }
        }
        return out;
    }
    private String expandToList(String[] args) {
        return expandToList(Arrays.asList(args));
    }
    @Override
    public String expandToList(List params) {
        StringBuilder sb = new StringBuilder();
        sb.append("[");
        boolean first = true;
        for (String param : params) {
            if (!first) {
                sb.append(",");
            }
            if (param.equalsIgnoreCase("true") || param.equalsIgnoreCase("false") || param.equalsIgnoreCase("null")) {
                sb.append(param.toLowerCase());
            } else if (isNumber(param)) {
                sb.append(param);
            } else {
                sb.append(param.startsWith("$") ? param.substring(1) : quote(param));
            }
            first = false;
        }
        sb.append("]");
        return sb.toString();
    }
    private String expandName(String name) {
        String regexVar = "[a-zA-Z_]+[a-zA-Z0-9_-]*";
        String out = name;
        if (name.matches("^\\$" + regexVar)) {
            out = name.substring(1);
        } else if (name.matches("^\\$\\{" + regexVar + "}.*")) {
            Matcher matcher = Pattern.compile("^\\$\\{(" + regexVar + ")}(.*)").matcher(name);
            if (matcher.find()) {
                out = matcher.group(1) + matcher.group(2);
            } else {
                throw new IllegalArgumentException();
            }
        }
        return out;
    }
    private boolean isNumber(String str) {
        return str.matches("-?\\d+(\\.\\d+)?");
    }
    private boolean isCodeBlock(String line) {
        return line.contains("\n") && line.trim().endsWith("}");
    }
    private boolean isCommandLine(String line) {
        String command = parser().getCommand(line);
        boolean out = false;
        if (command != null && command.startsWith(":")) {
            command = command.substring(1);
            if (hasAlias(command)) {
                command = getAlias(command);
            }
            if (systemRegistry.hasCommand(command)) {
                out = true;
            } else {
                ScriptFile sf = new ScriptFile(command, "", new String[0]);
                if (sf.isScript()) {
                    out = true;
                }
            }
        }
        return out;
    }
    private String quote(String var) {
        if ((var.startsWith("\"") && var.endsWith("\""))
                || (var.startsWith("'") && var.endsWith("'"))) {
            return var;
        }
        if (var.contains("\\\"")) {
            return "'" + var + "'";
        }
        return "\"" + var + "\"";
    }
    private List scriptExtensions() {
        List extensions = new ArrayList<>(engine.getExtensions());
        extensions.add(scriptExtension);
        return extensions;
    }
    private class ScriptFile {
        private File script;
        private String extension = "";
        private String cmdLine;
        private String[] args;
        private boolean verbose;
        private Object result;
        @SuppressWarnings("unchecked")
        public ScriptFile(String command, String cmdLine, String[] args) {
            if (!parser().validCommandName(command)) {
                return;
            }
            try {
                this.script = new File(command);
                this.cmdLine = cmdLine;
                if (script.exists()) {
                    scriptExtension(command);
                } else if (engine.hasVariable(VAR_PATH)) {
                    boolean found = false;
                    for (String p : (List) engine.get(VAR_PATH)) {
                        for (String e : scriptExtensions()) {
                            String file = command + "." + e;
                            Path path = Paths.get(p, file);
                            if (path.toFile().exists()) {
                                script = path.toFile();
                                scriptExtension(command);
                                found = true;
                                break;
                            }
                        }
                        if (found) {
                            break;
                        }
                    }
                }
                doArgs(args);
            } catch (Exception e) {
                // ignore
            }
        }
        public ScriptFile(File script, String cmdLine, String[] args) {
            if (!script.exists()) {
                throw new IllegalArgumentException("Script file not found!");
            }
            this.script = script;
            this.cmdLine = cmdLine;
            scriptExtension(script.getName());
            doArgs(args);
        }
        private void scriptExtension(String command) {
            String name = script.getName();
            this.extension = name.contains(".") ? name.substring(name.lastIndexOf(".") + 1) : "";
            if (!isEngineScript() && !isConsoleScript()) {
                throw new IllegalArgumentException("Command not found: " + command);
            }
        }
        private void doArgs(String[] args) {
            List _args = new ArrayList<>();
            if (isConsoleScript()) {
                _args.add(script.getAbsolutePath());
            }
            for (String a : args) {
                if (isConsoleScript()) {
                    if (!a.equals(OPTION_VERBOSE)) {
                        _args.add(a);
                    } else {
                        this.verbose = true;
                    }
                } else {
                    _args.add(a);
                }
            }
            this.args = _args.toArray(new String[0]);
        }
        private boolean isEngineScript() {
            return engine.getExtensions().contains(extension);
        }
        private boolean isConsoleScript() {
            return scriptExtension.equals(extension);
        }
        private boolean isScript() {
            return engine.getExtensions().contains(extension) || scriptExtension.equals(extension);
        }
        public boolean execute() throws Exception {
            if (!isScript()) {
                return false;
            }
            result = null;
            if (Arrays.asList(args).contains(OPTION_HELP[0]) || Arrays.asList(args).contains(OPTION_HELP[1])) {
                try(BufferedReader br = new BufferedReader(new FileReader(script))) {
                    int size = 0;
                    StringBuilder usage = new StringBuilder();
                    boolean helpEnd = false;
                    boolean headComment = false;
                    for(String l; (l = br.readLine()) != null; ) {
                        size++;
                        l = l.replaceAll("\\s+$", "");
                        String line = l;
                        if (size > HELP_MAX_SIZE || line.endsWith(END_HELP)) {
                            helpEnd = line.endsWith(END_HELP);
                            break;
                        }
                        if (headComment || size < 3) {
                            String ltr = l.trim();
                            if (ltr.startsWith("*") || ltr.startsWith("#")) {
                                headComment = true;
                                line = ltr.length() > 1 ? ltr.substring(2) : "";
                            } else if (ltr.startsWith("/*") || ltr.startsWith("//")) {
                                headComment = true;
                                line = ltr.length() > 2 ? ltr.substring(3) : "";
                            }
                        }
                        usage.append(line).append('\n');
                    }
                    if (usage.length() > 0) {
                        usage.append("\n");
                        if (!helpEnd) {
                            usage.insert(0, "\n");
                        }
                        throw new HelpException(usage.toString());
                    } else {
                        internalExecute();
                    }
                }
            } else {
                internalExecute();
            }
            return true;
        }
        private String expandParameterName(String parameter) {
            if (parameter.startsWith("$")) {
                return expandName(parameter);
            } else if (isNumber(parameter)) {
                return parameter;
            }
            return quote(parameter);
        }
        private void internalExecute() throws Exception {
            if (isEngineScript()) {
                result = engine.execute(script, expandParameters(args));
            } else if (isConsoleScript()) {
                executing = true;
                boolean done = true;
                String line = "";
                try (BufferedReader br = new BufferedReader(new FileReader(script))) {
                    for (String l; (l = br.readLine()) != null;) {
                        if (l.trim().isEmpty() || l.trim().startsWith("#")) {
                            done = true;
                            continue;
                        }
                        try {
                            line += l;
                            parser().parse(line, line.length() + 1, ParseContext.ACCEPT_LINE);
                            done = true;
                            for (int i = 1; i < args.length; i++) {
                                line = line.replaceAll("\\s\\$" + i + "\\b",
                                             (" " + expandParameterName(args[i]) + " "));
                                line = line.replaceAll("\\$\\{" + i + "(|:-.*)}",
                                             expandParameterName(args[i]));
                            }
                            line = line.replaceAll("\\$\\{@}", expandToList(args));
                            line = line.replaceAll("\\$@", expandToList(args));
                            line = line.replaceAll("\\s\\$\\d\\b", "");
                            line = line.replaceAll("\\$\\{\\d+}", "");
                            Matcher matcher = Pattern.compile("\\$\\{\\d+:-(.*?)}").matcher(line);
                            if (matcher.find()) {
                                line = matcher.replaceAll(expandParameterName(matcher.group(1)));
                            }
                            if (verbose) {
                                AttributedStringBuilder asb = new AttributedStringBuilder();
                                asb.styled(Styles.prntStyle().resolve(".vs"), line);
                                asb.toAttributedString().println(terminal());
                                terminal().flush();
                            }
                            println(systemRegistry.execute(line));
                            line = "";
                        } catch (EOFError e) {
                            done = false;
                            line += "\n";
                        } catch (SyntaxError e) {
                            throw e;
                        } catch (EndOfFileException e) {
                            done = true;
                            result = engine.get("_return");
                            postProcess(cmdLine, result);
                            break;
                        } catch (Exception e) {
                            executing = false;
                            throw new IllegalArgumentException(line + "\n" + e.getMessage());
                        }
                    }
                    if (!done) {
                        executing = false;
                        throw new IllegalArgumentException("Incompleted command: \n" + line);
                    }
                    executing = false;
                }
            }
        }
        public Object getResult() {
            return result;
        }
        public String toString() {
            StringBuilder sb = new StringBuilder();
            sb.append("[");
            try {
                sb.append("script:").append(script.getCanonicalPath());
            } catch (Exception e) {
                sb.append(e.getMessage());
            }
            sb.append(", ");
            sb.append("extension:").append(extension);
            sb.append(", ");
            sb.append("cmdLine:").append(cmdLine);
            sb.append(", ");
            sb.append("args:").append(Arrays.asList(args));
            sb.append(", ");
            sb.append("verbose:").append(verbose);
            sb.append(", ");
            sb.append("result:").append(result);
            sb.append("]");
            return sb.toString();
        }
    }
    @Override
    public Object execute(File script, String cmdLine, String[] args) throws Exception {
        ScriptFile file = new ScriptFile(script, cmdLine, args);
        file.execute();
        return file.getResult();
    }
    @Override
    public String expandCommandLine(String line) {
        String out;
        if (isCommandLine(line)) {
            StringBuilder sb = new StringBuilder();
            List ws = parser().parse(line, 0, ParseContext.COMPLETE).words();
            int idx = ws.get(0).lastIndexOf(":");
            if (idx > 0) {
                sb.append(ws.get(0).substring(0, idx));
            }
            String[] argv = new String[ws.size()];
            for (int i = 1; i < ws.size(); i++) {
                argv[i] = ws.get(i);
                if (argv[i].startsWith("${")) {
                    Matcher argvMatcher = Pattern.compile("\\$\\{(.*)}").matcher(argv[i]);
                    if (argvMatcher.find()) {
                        argv[i] = argv[i].replace(argv[i], argvMatcher.group(1));
                    }
                } else if (argv[i].startsWith("$")) {
                    argv[i] = argv[i].substring(1);
                } else {
                    argv[i] = quote(argv[i]);
                }
            }
            String cmd = hasAlias(ws.get(0).substring(idx + 1)) ? getAlias(ws.get(0).substring(idx + 1))
                                                                : ws.get(0).substring(idx + 1);
            sb.append(SystemRegistry.class.getCanonicalName()).append(".get().invoke('").append(cmd).append("'");
            for (int i = 1; i < argv.length; i++) {
                sb.append(", ");
                sb.append(argv[i]);
            }
            sb.append(")");
            out = sb.toString();
        } else {
            out = line;
        }
        return out;
    }
    @Override
    public Object execute(String cmd, String line, String[] args) throws Exception {
        if (line.trim().startsWith("#")) {
            return null;
        }
        Object out = null;
        ScriptFile file = null;
        if (parser().validCommandName(cmd)) {
            file = new ScriptFile(cmd, line, args);
        } else {
            File f = new File(line.split("\\s+")[0]);
            if (f.exists()) {
                file = new ScriptFile(f, line, args);
            }
        }
        if (file != null && file.execute()) {
            out = file.getResult();
        } else {
            line = line.trim();
            if (isCodeBlock(line)) {
                StringBuilder sb = new StringBuilder();
                for (String s: line.split("\\r?\\n")) {
                    sb.append(expandCommandLine(s));
                    sb.append("\n");
                }
                line = sb.toString();
            }
            if (engine.hasVariable(line)) {
                out = engine.get(line);
            } else if (parser().getVariable(line) == null) {
                out = engine.execute(line);
                engine.put("_", out);
            } else {
                engine.execute(line);
            }
        }
        return out;
    }
    @Override
    public void purge() {
        engine.del("_*");
    }
    @Override
    public void putVariable(String name, Object value) {
        engine.put(name, value);
    }
    @Override
    public Object getVariable(String name) {
        if (!engine.hasVariable(name)) {
            throw new IllegalArgumentException("Variable " + name + " does not exists!");
        }
        return engine.get(name);
    }
    @Override
    public boolean hasVariable(String name) {
        return engine.hasVariable(name);
    }
    @Override
    public boolean executeWidget(Object function) {
        engine.put("_reader", reader);
        engine.put("_widgetFunction", function);
        try {
            if (engine.getEngineName().equals("GroovyEngine")) {
                engine.execute("def _buffer() {_reader.getBuffer()}");
                engine.execute("def _widget(w) {_reader.callWidget(w)}");
            }
            engine.execute("_widgetFunction()");
        } catch (Exception e) {
            trace(e);
            return false;
        } finally {
            purge();
        }
        return true;
    }
    @SuppressWarnings("unchecked")
    private Map consoleOptions() {
        return engine.hasVariable(VAR_CONSOLE_OPTIONS) ? (Map) engine.get(VAR_CONSOLE_OPTIONS)
                                                       : new HashMap<>();
    }
    @SuppressWarnings("unchecked")
    @Override
    public T consoleOption(String option, T defval) {
        T out = defval;
        try {
            out = (T) consoleOptions().getOrDefault(option, defval);
        } catch (Exception e) {
            trace(new Exception("Bad CONSOLE_OPTION value: " + e.getMessage()));
        }
        return out;
    }
    private boolean consoleOption(String option) {
        boolean out = false;
        try {
            out = consoleOptions().containsKey(option);
        } catch (Exception e) {
            trace(new Exception("Bad CONSOLE_OPTION value: " + e.getMessage()));
        }
        return out;
    }
    @Override
    public ExecutionResult postProcess(String line, Object result, String output) {
        ExecutionResult out;
        Object _output = output != null && !output.trim().isEmpty() && !consoleOption("no-splittedOutput")
                         ? output.split("\\r?\\n") : output;
        String consoleVar = parser().getVariable(line);
        if (consoleVar != null && result != null) {
            engine.put("output", _output);
        }
        if (systemRegistry.hasCommand(parser().getCommand(line))) {
            out = postProcess(line, consoleVar != null && result == null ? _output : result);
        } else {
            Object _result = result == null ? _output : result;
            int status = saveResult(consoleVar, _result);
            out = new ExecutionResult(status, consoleVar != null && !consoleVar.startsWith("_") ? null : _result);
        }
        return out;
    }
    private ExecutionResult postProcess(String line, Object result) {
        int status = 0;
        Object out = result instanceof String && ((String)result).trim().isEmpty() ? null : result;
        String consoleVar = parser().getVariable(line);
        if (consoleVar != null) {
            status = saveResult(consoleVar, result);
            out = null;
        } else if (!parser().getCommand(line).equals("show")) {
            if (result != null) {
                status = saveResult("_", result);
            } else {
                status = 1;
            }
        }
        return new ExecutionResult(status, out);
    }
    @Override
    public ExecutionResult postProcess(Object result) {
        return new ExecutionResult(saveResult(null, result), result);
    }
    private int saveResult(String var, Object result) {
        int out;
        try {
            engine.put("_executionResult", result);
            if (var != null) {
                if (var.contains(".") || var.contains("[")) {
                    engine.execute(var + " = _executionResult");
                } else {
                    engine.put(var, result);
                }
            }
            out = (int) engine.execute("_executionResult ? 0 : 1");
        } catch (Exception e) {
            trace(e);
            out = 1;
        }
        return out;
    }
    @Override
    public Object invoke(CommandRegistry.CommandSession session, String command, Object... args) throws Exception {
        exception = null;
        Object out = null;
        if (hasCommand(command)) {
            out = getCommandMethods(command).execute().apply(new CommandInput(command, args, session));
        } else {
            String[] _args = new String[args.length];
            for (int i = 0; i < args.length; i++) {
                if (!(args[i] instanceof String)) {
                    throw new IllegalArgumentException();
                }
                _args[i] = args[i].toString();
            }
            ScriptFile sf = new ScriptFile(command, "", _args);
            if (sf.execute()) {
                out = sf.getResult();
            }
        }
        if (exception != null) {
            throw exception;
        }
        return out;
    }
    public void trace(final Object object) {
        Object toPrint = object;
        int level = consoleOption("trace", 0);
        Map options = new HashMap<>();
        if (level < 2) {
            options.put("exception", "message");
        }
        if (level == 0) {
            if (!(object instanceof Throwable)) {
                toPrint = null;
            }
        } else if (level == 1) {
            if (object instanceof SystemRegistryImpl.CommandData) {
                toPrint = ((SystemRegistryImpl.CommandData)object).rawLine();
            }
        } else if (level > 1) {
            if (object instanceof SystemRegistryImpl.CommandData) {
                toPrint = object.toString();
            }
        }
        printer.println(options, toPrint);
    }
    private void error(String message) {
        AttributedStringBuilder asb = new AttributedStringBuilder();
        asb.styled(Styles.prntStyle().resolve(".em"), message);
        asb.println(terminal());
    }
    @Override
    public void println(Object object) {
        printer.println(object);
    }
    private Object show(CommandInput input) {
        final String[] usage = {
                "show -  list console variables",
                "Usage: show [VARIABLE]",
                "  -? --help                       Displays command help",
        };
        try {
            parseOptions(usage, input.args());
            Map options = new HashMap<>();
            options.put(Printer.MAX_DEPTH, 0);
            printer.println(options, engine.find(input.args().length > 0 ? input.args()[0] : null));
        } catch (Exception e) {
            exception = e;
        }
        return null;
    }
    private Object del(CommandInput input) {
        final String[] usage = {
                "del -  delete console variables, methods, classes and imports",
                "Usage: del [var1] ...",
                "  -? --help                       Displays command help",
        };
        try {
            parseOptions(usage, input.args());
            engine.del(input.args());
        } catch (Exception e) {
            exception = e;
        }
        return null;
    }
    private Object prnt(CommandInput input) {
        Exception result = printer.prntCommand(input);
        if (result != null) {
            exception = result;
        }
        return null;
    }
    private Object slurpcmd(CommandInput input) {
        final String[] usage = {
                "slurp -  slurp file or string variable context to object",
                "Usage: slurp [OPTIONS] file|variable",
                "  -? --help                       Displays command help",
                "  -e --encoding=ENCODING          Encoding (default UTF-8)",
                "  -f --format=FORMAT              Serialization format"
        };
        Object out = null;
        try {
            Options opt = parseOptions(usage, input.xargs());
            if (!opt.args().isEmpty()){
                Object _arg = opt.argObjects().get(0);
                if (!(_arg instanceof String)) {
                    throw new IllegalArgumentException("Invalid parameter type: " + _arg.getClass().getSimpleName());
                }
                String arg = (String)_arg;
                Charset encoding = opt.isSet("encoding") ? Charset.forName(opt.get("encoding")): StandardCharsets.UTF_8;
                String format = opt.isSet("format") ? opt.get("format") : engine.getSerializationFormats().get(0);
                try {
                    Path path = Paths.get(arg);
                    if (path.toFile().exists()) {
                        if (!format.equals(SLURP_FORMAT_TEXT)) {
                            out = slurp(path, encoding, format);
                        } else {
                            out = Files.readAllLines(Paths.get(arg), encoding);
                        }
                    } else {
                        if (!format.equals(SLURP_FORMAT_TEXT)) {
                            out = engine.deserialize(arg, format);
                        } else {
                            out = arg.split("\n");
                        }
                    }
                } catch (Exception e) {
                    out = engine.deserialize(arg, format);
                }
            }
        } catch (Exception e) {
            exception = e;
        }
        return out;
    }
    @Override
    public void persist(Path file, Object object) {
        engine.persist(file, object);
    }
    @Override
    public Object slurp(Path file) throws IOException {
        return slurp(file, StandardCharsets.UTF_8, engine.getSerializationFormats().get(0));
    }
    private Object slurp(Path file, Charset encoding, String format) throws IOException {
        byte[] encoded = Files.readAllBytes(file);
        return engine.deserialize(new String(encoded, encoding), format);
    }
    private Object aliascmd(CommandInput input) {
        final String[] usage = {
                "alias -  create command alias",
                "Usage: alias [ALIAS] [COMMANDLINE]",
                "  -? --help                       Displays command help"
        };
        Object out = null;
        try {
            Options opt = parseOptions(usage, input.args());
            List args = opt.args();
            if (args.isEmpty()) {
                out = aliases;
            } else if (args.size() == 1) {
                out = aliases.getOrDefault(args.get(0), null);
            } else {
                String alias = String.join(" ", args.subList(1, args.size()));
                for (int j = 0; j < 10; j++) {
                    alias = alias.replaceAll("%" + j , "\\$" + j);
                    alias = alias.replaceAll("%\\{" + j + "}", "\\$\\{" + j + "\\}");
                    alias = alias.replaceAll("%\\{" + j + ":-", "\\$\\{" + j + ":-");
                }
                alias = alias.replaceAll("%@" , "\\$@");
                alias = alias.replaceAll("%\\{@}", "\\$\\{@\\}");
                aliases.put(args.get(0), alias);
                persist(aliasFile, aliases);
            }
        } catch (Exception e) {
            exception = e;
        }
        return out;
    }
    private Object unalias(CommandInput input) {
        final String[] usage = {
                "unalias -  remove command alias",
                "Usage: unalias [ALIAS...]",
                "  -? --help                       Displays command help"
        };
        try {
            Options opt = parseOptions(usage, input.args());
            for (String a : opt.args()) {
                aliases.remove(a);
            }
            persist(aliasFile, aliases);
        } catch (Exception e) {
            exception = e;
        }
        return null;
    }
    private Object pipe(CommandInput input) {
        final String[] usage = {
                "pipe -  create/delete pipe operator",
                "Usage: pipe [OPERATOR] [PREFIX] [POSTFIX]",
                "       pipe --list",
                "       pipe --delete [OPERATOR...]",
                "  -? --help                       Displays command help",
                "  -d --delete                     Delete pipe operators",
                "  -l --list                       List pipe operators",
        };
        try {
            Options opt = parseOptions(usage, input.args());
            Map options = new HashMap<>();
            if (opt.isSet("delete")) {
                if ( opt.args().size() == 1 && opt.args().get(0).equals("*")) {
                    pipes.clear();
                } else {
                    for (String p: opt.args()) {
                        pipes.remove(p.trim());
                    }
                }
            } else if (opt.isSet("list") || opt.args().size() == 0) {
                options.put(Printer.MAX_DEPTH, 0);
                printer.println(options, pipes);
            } else if (opt.args().size() != 3) {
                exception = new IllegalArgumentException("Bad number of arguments!");
            } else if (systemRegistry.getPipeNames().contains(opt.args().get(0))) {
                exception = new IllegalArgumentException("Reserved pipe operator");
            } else {
                List fixes = new ArrayList<>();
                fixes.add(opt.args().get(1));
                fixes.add(opt.args().get(2));
                pipes.put(opt.args().get(0), fixes);
            }
        } catch (Exception e) {
            exception = e;
        }
        return null;
    }
    private Object doc(CommandInput input) {
        final String[] usage = {
                "doc -  open document on browser",
                "Usage: doc [OBJECT]",
                "  -? --help                       Displays command help"
        };
        try {
            parseOptions(usage, input.xargs());
            if (input.xargs().length == 0) {
                return null;
            }
            if (!Desktop.isDesktopSupported()) {
                throw new IllegalStateException("Desktop is not supported!");
            }
            Map docs;
            try {
                docs = consoleOption("docs", null);
            } catch (Exception e) {
                Exception exception = new IllegalStateException("Bad documents configuration!");
                exception.addSuppressed(e);
                throw exception;
            }
            if (docs == null) {
                throw new IllegalStateException("No documents configuration!");
            }
            boolean done = false;
            Object arg = input.xargs()[0];
            if (arg instanceof String) {
                String address = (String)docs.get(input.args()[0]);
                if (address != null) {
                    done = true;
                    if (urlExists(address)) {
                        Desktop.getDesktop().browse(new URI(address));
                    } else {
                        throw new IllegalArgumentException("Document not found: " + address);
                    }
                }
            }
            if (!done) {
                String name;
                if (arg instanceof String && ((String)arg).matches("([a-z]+\\.)+[A-Z][a-zA-Z]+")) {
                    name = (String)arg;
                } else {
                    name = arg.getClass().getCanonicalName();
                }
                name = name.replaceAll("\\.", "/") + ".html";
                Object doc = null;
                for (Map.Entry entry : docs.entrySet()) {
                    if (name.matches(entry.getKey())) {
                        doc = entry.getValue();
                        break;
                    }
                }
                if (doc == null) {
                    throw new IllegalArgumentException("No document configuration for " + name);
                }
                String url = name;
                if (doc instanceof Collection) {
                    for (Object o : (Collection>) doc) {
                        url = o + name;
                        if (urlExists(url)) {
                            Desktop.getDesktop().browse(new URI(url));
                            done = true;
                        }
                    }
                } else {
                    url = doc + name;
                    if (urlExists(url)) {
                        Desktop.getDesktop().browse(new URI(url));
                        done = true;
                    }
                }
                if (!done) {
                    throw new IllegalArgumentException("Document not found: " + url);
                }
            }
        } catch (Exception e) {
            exception = e;
        }
        return null;
    }
    private boolean urlExists(String weburl) {
        try {
            URL url = new URL(weburl);
            HttpURLConnection huc = (HttpURLConnection) url.openConnection();
            huc.setRequestMethod("HEAD");
            return huc.getResponseCode() == HttpURLConnection.HTTP_OK;
        } catch (Exception e) {
            return false;
        }
    }
    private List slurpCompleter(String command) {
        List completers = new ArrayList<>();
        List optDescs = commandOptions("slurp");
        for (OptDesc o : optDescs) {
            if (o.shortOption() != null && o.shortOption().equals("-f")) {
                List formats = new ArrayList<>(engine.getDeserializationFormats());
                formats.add(SLURP_FORMAT_TEXT);
                o.setValueCompleter(new StringsCompleter(formats));
                break;
            }
        }
        AggregateCompleter argCompleter = new AggregateCompleter(new FilesCompleter(workDir)
                                                               , new StringsCompleter(this::variableReferences));
        completers.add(new ArgumentCompleter(NullCompleter.INSTANCE
                               , new OptionCompleter(Arrays.asList(argCompleter
                                                                 , NullCompleter.INSTANCE)
                                                   , optDescs
                                                   , 1)
                                            ));
        return completers;
    }
    private List variableCompleter(String command) {
        List completers = new ArrayList<>();
        completers.add(new StringsCompleter(() -> engine.find().keySet()));
        return completers;
    }
    private List variableReferences() {
        List out = new ArrayList<>();
        for (String v : engine.find().keySet()) {
            out.add("$" + v);
        }
        return out;
    }
    private List prntCompleter(String command) {
        List completers = new ArrayList<>();
        completers.add(new ArgumentCompleter(NullCompleter.INSTANCE
                       , new OptionCompleter(Arrays.asList(new StringsCompleter(this::variableReferences)
                                                         , NullCompleter.INSTANCE)
                                           , this::commandOptions
                                           , 1)
                                    ));
        return completers;
    }
    private static class AliasValueCompleter implements Completer {
        private final Map aliases;
        public AliasValueCompleter(Map aliases) {
            this.aliases = aliases;
        }
        @Override
        public void complete(LineReader reader, ParsedLine commandLine, List candidates) {
            assert commandLine != null;
            assert candidates != null;
            List words = commandLine.words();
            if (words.size() > 1) {
                String h = words.get(words.size()-2);
                if (h != null && h.length() > 0) {
                     if(aliases.containsKey(h)){
                          String v = aliases.get(h);
                          candidates.add(new Candidate(AttributedString.stripAnsi(v)
                                           , v, null, null, null, null, true));
                     }
                }
            }
        }
    }
    private List aliasCompleter(String command) {
        List completers = new ArrayList<>();
        List params = new ArrayList<>();
        params.add(new StringsCompleter(aliases::keySet));
        params.add(new AliasValueCompleter(aliases));
        completers.add(new ArgumentCompleter(NullCompleter.INSTANCE
                , new OptionCompleter(params
                                    , this::commandOptions
                                    , 1)
                             ));
        return completers;
    }
    private List unaliasCompleter(String command) {
        List completers = new ArrayList<>();
        completers.add(new ArgumentCompleter(NullCompleter.INSTANCE
                , new OptionCompleter(new StringsCompleter(aliases::keySet)
                                    , this::commandOptions
                                    , 1)
                             ));
        return completers;
    }
    private List docs() {
        List out = new ArrayList<>();
        Map docs = consoleOption("docs", null);
        if (docs == null) {
            return out;
        }
        for (String v : engine.find().keySet()) {
            out.add("$" + v);
        }
        if (!docs.isEmpty()) {
            for (String d :  docs.keySet()) {
                if (d.matches("\\w+")) {
                    out.add(d);
                }
            }
        }
        return out;
    }
    private List docCompleter(String command) {
        List completers = new ArrayList<>();
        completers.add(new ArgumentCompleter(NullCompleter.INSTANCE
                       , new OptionCompleter(Arrays.asList(new StringsCompleter(this::docs)
                                                         , NullCompleter.INSTANCE)
                                           , this::commandOptions
                                           , 1)
                                    ));
        return completers;
    }
}
                                                                 © 2015 - 2025 Weber Informatics LLC | Privacy Policy