diff --git a/src/main/java/com/serliunx/ddns/ManagerLite.java b/src/main/java/com/serliunx/ddns/ManagerLite.java index f952ec0..58e019d 100644 --- a/src/main/java/com/serliunx/ddns/ManagerLite.java +++ b/src/main/java/com/serliunx/ddns/ManagerLite.java @@ -14,21 +14,19 @@ import com.serliunx.ddns.support.command.CommandDispatcher; import com.serliunx.ddns.support.command.target.ConfigCommand; import com.serliunx.ddns.support.command.target.HelpCommand; import com.serliunx.ddns.support.command.target.ReloadCommand; +import com.serliunx.ddns.support.command.CommandCompleter; +import com.serliunx.ddns.support.command.target.StopCommand; import com.serliunx.ddns.support.log.JLineAdaptAppender; -import com.serliunx.ddns.support.thread.ThreadFactoryBuilder; import org.jline.reader.LineReader; import org.jline.reader.LineReaderBuilder; import org.jline.reader.impl.history.DefaultHistory; import org.jline.terminal.Terminal; import org.jline.terminal.TerminalBuilder; -import org.jline.utils.Log; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.slf4j.bridge.SLF4JBridgeHandler; -import javax.smartcardio.TerminalFactory; import java.io.IOException; -import java.util.concurrent.ThreadFactory; /** * 启动类 @@ -100,6 +98,7 @@ public final class ManagerLite { } LineReader lineReader = LineReaderBuilder.builder() .terminal(terminal) + .completer(new CommandCompleter(commandDispatcher)) // 如果想记录历史命令,可以配置一个 History .history(new DefaultHistory()) .build(); @@ -109,21 +108,16 @@ public final class ManagerLite { final String prompt = "client> "; InstanceContextHolder.setAdditional("command-process"); + while (true) { try { String cmd = lineReader.readLine(prompt); - - if ("stop".equalsIgnoreCase(cmd)) { - break; - } commandDispatcher.onCommand(cmd); terminal.flush(); } catch (Exception e) { break; } } - - System.exit(0); } /** @@ -147,6 +141,8 @@ public final class ManagerLite { commandDispatcher.register(new ReloadCommand(configuration, systemInitializer)); // config commandDispatcher.register(new ConfigCommand(configuration)); + // stop + commandDispatcher.register(new StopCommand()); } /** diff --git a/src/main/java/com/serliunx/ddns/support/command/Command.java b/src/main/java/com/serliunx/ddns/support/command/Command.java index ff23d00..639cde5 100644 --- a/src/main/java/com/serliunx/ddns/support/command/Command.java +++ b/src/main/java/com/serliunx/ddns/support/command/Command.java @@ -1,5 +1,10 @@ package com.serliunx.ddns.support.command; +import org.jline.reader.Candidate; +import org.jline.reader.LineReader; +import org.jline.reader.ParsedLine; + +import java.util.ArrayList; import java.util.List; /** @@ -41,4 +46,24 @@ public interface Command { * 获取该指令的用法 */ String getUsage(); + + /** + * 获取参数列表 + * + * @return 参数 + */ + default List getArgs() { + return new ArrayList<>(); + } + + /** + * 命令参数补全 + * + * @param reader Jline的LineReader{@link LineReader} + * @param line 当前命令行的内容 + * @param candidates 候选参数列表 + */ + default void onComplete(LineReader reader, ParsedLine line, int index, List candidates) { + // do nothing by default. + } } diff --git a/src/main/java/com/serliunx/ddns/support/command/CommandCompleter.java b/src/main/java/com/serliunx/ddns/support/command/CommandCompleter.java new file mode 100644 index 0000000..cd59d26 --- /dev/null +++ b/src/main/java/com/serliunx/ddns/support/command/CommandCompleter.java @@ -0,0 +1,50 @@ +package com.serliunx.ddns.support.command; + +import org.jline.reader.Candidate; +import org.jline.reader.Completer; +import org.jline.reader.LineReader; +import org.jline.reader.ParsedLine; + +import java.util.List; +import java.util.Map; + +/** + * Jline 命令补全器 + * + * @author SerLiunx + * @version 1.0.4 + * @since 2025/2/4 + */ +public class CommandCompleter implements Completer { + + private final CommandDispatcher commandDispatcher; + + public CommandCompleter(CommandDispatcher commandDispatcher) { + this.commandDispatcher = commandDispatcher; + } + + @Override + public void complete(LineReader reader, ParsedLine line, List candidates) { + final String currentWord = line.word(); + Map commands = commandDispatcher.getCommands(); + if (commands == null || + commands.isEmpty()) { + return; + } + + // 第一个参数补全所有指令 + if (line.wordIndex() == 0) { + commands.keySet().forEach(k -> { + if (k.startsWith(currentWord)) { + candidates.add(new Candidate(k)); + } + }); + } else { // 第二个及以后交由具体的指令进行补全逻辑 + final Command command = commands.get(line.words().get(0)); + if (command == null) { + return; + } + command.onComplete(reader, line, line.wordIndex(), candidates); + } + } +} diff --git a/src/main/java/com/serliunx/ddns/support/command/target/ConfigCommand.java b/src/main/java/com/serliunx/ddns/support/command/target/ConfigCommand.java index dd746fe..2a55d46 100644 --- a/src/main/java/com/serliunx/ddns/support/command/target/ConfigCommand.java +++ b/src/main/java/com/serliunx/ddns/support/command/target/ConfigCommand.java @@ -2,6 +2,13 @@ package com.serliunx.ddns.support.command.target; import com.serliunx.ddns.config.Configuration; import com.serliunx.ddns.support.command.AbstractCommand; +import org.jline.reader.Candidate; +import org.jline.reader.LineReader; +import org.jline.reader.ParsedLine; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; /** * 指令: config @@ -33,4 +40,37 @@ public class ConfigCommand extends AbstractCommand { final String value = args[1]; return configuration.modify(target, value); } + + @Override + public List getArgs() { + final Map allKeyAndValue; + if (configuration == null || + (allKeyAndValue = configuration.getAllKeyAndValue()) == null) { + return new ArrayList<>(); + } + return new ArrayList<>(allKeyAndValue.keySet()); + } + + @Override + public void onComplete(LineReader reader, ParsedLine line, int index, List candidates) { + if (index < 1) { + return; + } + + final String currentWord = line.word(); + + // 补全配置键 + if (index == 1) { + final Map allKeyAndValue; + if (configuration == null || + (allKeyAndValue = configuration.getAllKeyAndValue()) == null) { + return; + } + allKeyAndValue.keySet().forEach(k -> { + if (k.startsWith(currentWord)) { + candidates.add(new Candidate(k)); + } + }); + } + } } diff --git a/src/main/java/com/serliunx/ddns/support/command/target/HelpCommand.java b/src/main/java/com/serliunx/ddns/support/command/target/HelpCommand.java index aa25f9c..389bdbe 100644 --- a/src/main/java/com/serliunx/ddns/support/command/target/HelpCommand.java +++ b/src/main/java/com/serliunx/ddns/support/command/target/HelpCommand.java @@ -3,7 +3,12 @@ package com.serliunx.ddns.support.command.target; import com.serliunx.ddns.support.command.AbstractCommand; import com.serliunx.ddns.support.command.Command; import com.serliunx.ddns.support.command.CommandDispatcher; +import org.jline.reader.Candidate; +import org.jline.reader.LineReader; +import org.jline.reader.ParsedLine; +import java.util.ArrayList; +import java.util.List; import java.util.Map; /** @@ -21,7 +26,7 @@ public class HelpCommand extends AbstractCommand { @Override public boolean onCommand(String[] args) { - final Map commands = CommandDispatcher.getInstance().getCommands(); + final Map commands = getAllCommands(); log.info("=========================================="); if (hasArgs(args)) { @@ -30,7 +35,7 @@ public class HelpCommand extends AbstractCommand { if (command == null) { log.warn("无法找到指令 {} 的相关信息, 请使用 help 查看可用的指令及帮助!", cmd); } else { - log.info("指令:\t{}\t-\t{}\t-\t{}", cmd, command.getDescription(), command.getUsage()); + log.info("指令:{} - {} - {}", cmd, command.getDescription(), command.getUsage()); } } else { commands.forEach((k, v) -> { @@ -38,13 +43,51 @@ public class HelpCommand extends AbstractCommand { if (k.equals(getName())) { return; } - log.info("{}\t-\t{}\t-\t{}", k, v.getDescription(), v.getUsage()); + log.info("{} - {} - {}", k, v.getDescription(), v.getUsage()); }); - log.info("exit\t-\t退出程序\t-\tstop"); log.info(""); log.info("使用 help <指令> 来查看更详细的帮助信息."); } log.info("=========================================="); return true; } + + @Override + public List getArgs() { + final Map commands = getAllCommands(); + if (commands == null || + commands.isEmpty()) { + return new ArrayList<>(); + } + return new ArrayList<>(commands.keySet()); + } + + @Override + public void onComplete(LineReader reader, ParsedLine line, int index, List candidates) { + final Map commands = getAllCommands(); + if (commands == null || + commands.isEmpty() || index < 1) { + return; + } + + final String currentWord = line.word(); + + if (index == 1) { + commands.keySet().forEach(k -> { + if (k.equals("help")) { + return; + } + if (k.startsWith(currentWord)) { + candidates.add(new Candidate(k)); + } + }); + } + } + + /** + * 获取所有指令 + */ + private Map getAllCommands() { + return CommandDispatcher.getInstance().getCommands(); + } } \ No newline at end of file diff --git a/src/main/java/com/serliunx/ddns/support/command/target/StopCommand.java b/src/main/java/com/serliunx/ddns/support/command/target/StopCommand.java new file mode 100644 index 0000000..f86bdcd --- /dev/null +++ b/src/main/java/com/serliunx/ddns/support/command/target/StopCommand.java @@ -0,0 +1,23 @@ +package com.serliunx.ddns.support.command.target; + +import com.serliunx.ddns.support.command.AbstractCommand; + +/** + * 指令: stop + * + * @author SerLiunx + * @version 1.0.4 + * @since 2025/2/4 + */ +public final class StopCommand extends AbstractCommand { + + public StopCommand() { + super("stop", null, "退出程序", "stop"); + } + + @Override + public boolean onCommand(String[] args) { + System.exit(0); + return true; + } +}