jio.console.Console Maven / Gradle / Ivy
package jio.console;
import fun.tuple.Pair;
import jio.IO;
import jio.time.Clock;
import jsonvalues.JsObj;
import java.time.Duration;
import java.time.Instant;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
/**
* Creates a REPL (read eval print loop) program from a list of user commands. It's executed with the method
* {@link #eval(JsObj)}. The commands returned by the method {@link #getPredefinedCommands()} are always loaded to the
* console.
*
* Predefined Commands:
*
* - {@link ListCommand}: Lists available commands.
* - {@link ReadVarCommand}: Reads a variable value.
* - {@link SetVarCommand}: Sets a variable value.
* - {@link LastCommand}: Executes the last command.
* - {@link HistoryCommand}: Shows the command history.
* - {@link HelpCommand}: Displays help for available commands.
* - {@link DumpCommand}: Dumps the current state.
* - {@link Base64EncodeCommand}: Encodes a string to Base64.
* - {@link Base64DecodeCommand}: Decodes a Base64 string.
* - {@link AddToListCommand}: Adds a value into a list variable.
* - {@link ClearVarCommand}: Removes a variable from the state.
* - {@link EncodeURLCommand}: Encodes a URL.
* - {@link ExitCommand}: Exits the console program.
* - {@link JsPairsCommand}: Lists key-value pairs of a JSON object.
* - {@link JsPrettyCommand}: Pretty-prints a JSON object.
* - {@link JsGetValueCommand}: Gets a value from a JSON object.
* - {@link ClearCommand}: Clears the console screen.
* - {@link EchoCommand}: Displays a message.
* - {@link ScriptCommand}: Executes a script.
* - {@link ReadFileCommand}: Reads a file.
*
*
* To add new commands, create classes that implement the {@link Command} interface
* or extend existing command classes. Then, add these command instances to the list
* of user commands passed to the constructor.
*/
public final class Console {
final State state;
final List commands;
/**
* Constructor to create a Console from a list of user commands.
*
* @param userCommands the list of commands
*/
public Console(List userCommands) {
Objects.requireNonNull(userCommands);
this.state = new State();
this.commands = getPredefinedCommands();
this.commands.addAll(userCommands);
}
private List getPredefinedCommands() {
List commands = new ArrayList<>();
commands.add(new ListCommand(commands).setSaveOutput(false));
commands.add(new ReadVarCommand().setSaveOutput(false));
commands.add(new SetVarCommand().setSaveOutput(false));
commands.add(new LastCommand());
commands.add(new HistoryCommand().setSaveOutput(false));
commands.add(new HelpCommand(commands).setSaveOutput(false));
commands.add(new DumpCommand().setSaveOutput(false));
commands.add(new Base64EncodeCommand());
commands.add(new AddToListCommand());
commands.add(new ClearVarCommand());
commands.add(new Base64DecodeCommand());
commands.add(new EncodeURLCommand());
commands.add(new ExitCommand());
commands.add(new JsPairsCommand().setSaveOutput(false));
commands.add(new JsPrettyCommand());
commands.add(new JsGetValueCommand());
commands.add(new ClearCommand().setSaveOutput(false));
commands.add(new EchoCommand());
commands.add(new ScriptCommand(this));
commands.add(new ReadFileCommand());
return commands;
}
/**
* Executes the console program and the REP (read, eval, print) loop starts executing. A JSON can be specified, and it
* will be passed into every command in case some configuration is needed.
*
* @param conf the configuration JSON
*/
public void eval(JsObj conf) {
System.out.println("""
___ ___ _______ _______ _______ __ _ _______ _______ ___ _______\s
| | | | | | | | | | | | | | |
| | | _ |____| | _ | |_| | _____| _ | | | ___|
| | | | | |____| | | | | | |_____| | | | | | |___\s
___| | | |_| | | _| |_| | _ |_____ | |_| | |___| ___|
| | | | | |_| | | | |_____| | | | |___\s
|_______|___|_______| |_______|_______|_| |__|_______|_______|_______|_______|""");
for (; ; ) {
Programs.READ_LINE
.then(line -> {
if (line.isBlank()) {
return IO.NULL();
}
String trimmedLine = line.trim();
Optional>> opt = parse(conf,
trimmedLine);
if (opt.isPresent()) {
IO command = opt.get()
.second()
.peekSuccess(output -> {
if (output != null && opt.get()
.first().isSaveOutput) {
state.variables.put("output",
output
);
}
}
);
state.historyCommands.add(command);
return IO.lazy(Clock.realTime)
.then(tic -> command.map(result -> Pair.of(tic,
result)))
.peek(pair ->
state.historyResults
.add(String.format("%s, OK, %s ms, %s ",
trimmedLine,
Duration.ofMillis(System.currentTimeMillis() - pair.first())
.toMillis(),
Instant.ofEpochMilli(pair.first())
)
),
error ->
state.historyResults.add(String.format("%s, KO, %s",
trimmedLine,
Instant.now()
)
)
)
.map(Pair::second);
}
return IO.fail(new CommandNotFoundException(line));
})
.then(it -> it != null ? Programs.PRINT_NEW_LINE(it + "\n") : IO.NULL(),
e -> Programs.PRINT_NEW_LINE(e.getMessage() + "\n")
)
.join();
}
}
private String[] replaceVars(final State state,
final String[] tokens
) {
for (int i = 1; i < tokens.length; i++) {
var token = tokens[i];
if (token.startsWith("$")) {
String varName = token.substring(1);
if (!varName.isEmpty()) {
tokens[i] = state.variables.get(varName);
}
}
}
return tokens;
}
Optional>> parse(JsObj conf,
String line
) {
for (Command command : commands) {
try {
Optional> opt = command.executeIfMatch(conf,
state)
.apply(replaceVars(state,
line.split(" ")));
if (opt.isPresent()) {
return Optional.of(Pair.of(command,
opt.get()));
}
} catch (Exception e) {
return Optional.of(Pair.of(command,
IO.fail(e)));
}
}
return Optional.empty();
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy