diff --git a/pom.xml b/pom.xml index 8f026e4..546cdbe 100644 --- a/pom.xml +++ b/pom.xml @@ -20,6 +20,11 @@ commons-configuration2 2.9.0 + + org.jline + jline + 3.21.0 + commons-beanutils commons-beanutils diff --git a/src/main/java/com/lingoutil/workcopilot/WorkCopilotApplication.java b/src/main/java/com/lingoutil/workcopilot/WorkCopilotApplication.java index 16a2d9f..412e9a3 100644 --- a/src/main/java/com/lingoutil/workcopilot/WorkCopilotApplication.java +++ b/src/main/java/com/lingoutil/workcopilot/WorkCopilotApplication.java @@ -1,7 +1,15 @@ package com.lingoutil.workcopilot; import com.lingoutil.workcopilot.handler.CommandHandler; +import com.lingoutil.workcopilot.runner.CommandRunner; import com.lingoutil.workcopilot.util.LogUtil; +import org.jline.reader.EndOfFileException; +import org.jline.reader.LineReader; +import org.jline.reader.LineReaderBuilder; +import org.jline.reader.UserInterruptException; +import org.jline.reader.impl.history.DefaultHistory; +import org.jline.terminal.Terminal; +import org.jline.terminal.TerminalBuilder; import java.io.PrintStream; import java.nio.charset.StandardCharsets; @@ -9,19 +17,22 @@ import static com.lingoutil.workcopilot.constant.Constant.LOG_MODE; import static com.lingoutil.workcopilot.constant.Constant.MODE_VERBOSE; +import static com.lingoutil.workcopilot.util.LogUtil.RESET; import static com.lingoutil.workcopilot.util.LogUtil.YELLOW; public class WorkCopilotApplication { public static void main(String[] args) { - Scanner sc = new Scanner(System.in); - // 设置控制台输出为UTF-8编码 System.setOut(new PrintStream(System.out, true, StandardCharsets.UTF_8)); if (args.length == 1) { - // just j command - runWithMultiMode(sc); + if (CommandRunner.getOsType().equals(CommandRunner.MAC)) { + runWithMultiModeOnUnix(); + } + else { + runWithMultiModeOnWin(); + } } else { // j another argument @@ -52,7 +63,58 @@ private static void runWithSingleMode(String[] args) { } } - private static void runWithMultiMode(Scanner sc) { + private static void runWithMultiModeOnUnix() { + try { + // 创建 JLine 终端和读取器 + Terminal terminal = TerminalBuilder.builder().system(true).build(); + LineReader reader = LineReaderBuilder.builder() + .terminal(terminal) + .history(new DefaultHistory()) + .build(); + + LogUtil.info("Welcome to use work copilot \uD83D\uDE80 ~"); + String prompt = YELLOW + "copilot > " + RESET; // 设置为亮黄色 + while (true) { + try { + // 显示提示符并读取输入 + String input = reader.readLine(prompt); + String[] args = ("j " + input).split("\\s+(?=(?:[^\"]*\"[^\"]*\")*[^\"]*$)"); + + boolean verboseMode = LOG_MODE.equals(MODE_VERBOSE); + if (verboseMode) { + LogUtil.log("verbose mode is start: %s", verboseMode); + long startTime = System.currentTimeMillis(); + long endTime = 0; + if (!isValidArgsNum(args)) { + continue; + } + String command = args[1]; + CommandHandler.execute(command, args); + endTime = System.currentTimeMillis(); + LogUtil.log("duration: %s ms", endTime - startTime); + } + else { + if (!isValidArgsNum(args)) { + continue; + } + String command = args[1]; + CommandHandler.execute(command, args); + } + System.out.println(); + } catch (UserInterruptException e) { + LogUtil.info("\nProgram interrupted. Use 'exit' to quit."); + } catch (EndOfFileException e) { + LogUtil.info("\nGoodbye!"); + break; + } + } + } catch (Exception e) { + e.printStackTrace(); + } + } + + private static void runWithMultiModeOnWin() { + Scanner sc = new Scanner(System.in); String[] args; LogUtil.info("Welcome to use work copilot \uD83D\uDE80 ~"); while (true) { diff --git a/src/main/java/com/lingoutil/workcopilot/config/YamlConfig.java b/src/main/java/com/lingoutil/workcopilot/config/YamlConfig.java index 4fc05b1..2d85a09 100644 --- a/src/main/java/com/lingoutil/workcopilot/config/YamlConfig.java +++ b/src/main/java/com/lingoutil/workcopilot/config/YamlConfig.java @@ -121,6 +121,7 @@ public static void addNestedProperty(String parentKey, String childKey, String v String fullKey = parentKey + "." + childKey; LogUtil.log("添加嵌套键值对: " + fullKey + " = " + value); config.setProperty(fullKey, value); + // todo propertyCache.put(fullKey, value); propertiesMapCache.clear(); saveConfig(); diff --git a/src/main/java/com/lingoutil/workcopilot/constant/Constant.java b/src/main/java/com/lingoutil/workcopilot/constant/Constant.java index f3fd36a..4485246 100644 --- a/src/main/java/com/lingoutil/workcopilot/constant/Constant.java +++ b/src/main/java/com/lingoutil/workcopilot/constant/Constant.java @@ -94,7 +94,7 @@ public class Constant { public static final String CATEGORY_BROWSER = "browser"; public static final String CATEGORY_EDITOR = "editor"; public static final String CATEGORY_VPN = "vpn"; - public static final String CATEGORY_OUTER_URL = "outer-url"; + public static final String CATEGORY_OUTER_URL = "outer_url"; public static String LOG_MODE = YamlConfig.initializeProperty(LOG, MODE); diff --git a/src/main/java/com/lingoutil/workcopilot/handler/CommandHandler.java b/src/main/java/com/lingoutil/workcopilot/handler/CommandHandler.java index 48be83b..aebd061 100644 --- a/src/main/java/com/lingoutil/workcopilot/handler/CommandHandler.java +++ b/src/main/java/com/lingoutil/workcopilot/handler/CommandHandler.java @@ -4,6 +4,7 @@ import com.lingoutil.workcopilot.scanner.CommandHandlerScanner; import com.lingoutil.workcopilot.util.LogUtil; +import java.util.Arrays; import java.util.List; import java.util.concurrent.ConcurrentHashMap; import java.util.function.Consumer; @@ -63,6 +64,25 @@ protected final boolean checkArgs(String[] argv, int expectedNum, Consumer errorHandler, int... expectedNums) { + int length = argv.length; + if (!containInArray(expectedNums,length)) { + LogUtil.error("expected argument num is %s, but got %d", Arrays.toString(expectedNums), length); + errorHandler.accept(argv); + return false; + } + return true; + } + + private boolean containInArray(int[] expected, int target) { + for (int i : expected) { + if (i == target) { + return true; + } + } + return false; + } + protected final boolean checkArgs(String[] argv, int expectedNum) { return checkArgs(argv, expectedNum, this::hint); } diff --git a/src/main/java/com/lingoutil/workcopilot/handler/NoteCommandHandler.java b/src/main/java/com/lingoutil/workcopilot/handler/NoteCommandHandler.java index eccf95c..55e170f 100644 --- a/src/main/java/com/lingoutil/workcopilot/handler/NoteCommandHandler.java +++ b/src/main/java/com/lingoutil/workcopilot/handler/NoteCommandHandler.java @@ -16,8 +16,6 @@ protected List loadCommandList() { @Override protected void process(String[] argv) { String alias = argv[2]; - - String path = YamlConfig.getProperty(PATH, alias); if (!YamlConfig.containProperty(PATH, alias) && !YamlConfig.containProperty(INNER_URL, alias) && !YamlConfig.containProperty(OUTER_URL, alias)) { @@ -25,6 +23,8 @@ protected void process(String[] argv) { return; } + String path = YamlConfig.getProperty(PATH, alias); + String category = argv[3]; switch (category) { case CATEGORY_BROWSER -> { @@ -40,7 +40,9 @@ protected void process(String[] argv) { LogUtil.info("Add alias %s to VPN successfully", alias); } case CATEGORY_OUTER_URL -> { + path = YamlConfig.getProperty(INNER_URL, alias); YamlConfig.addNestedProperty(category, alias, path); + YamlConfig.removeNestedProperty(INNER_URL, alias); LogUtil.info("Add alias %s to OUTER_URL successfully", alias); } case SCRIPT -> { diff --git a/src/main/java/com/lingoutil/workcopilot/handler/ReportCommandHandler.java b/src/main/java/com/lingoutil/workcopilot/handler/ReportCommandHandler.java index 1ff8ef0..a8e490c 100644 --- a/src/main/java/com/lingoutil/workcopilot/handler/ReportCommandHandler.java +++ b/src/main/java/com/lingoutil/workcopilot/handler/ReportCommandHandler.java @@ -22,111 +22,134 @@ import static com.lingoutil.workcopilot.constant.Constant.*; public class ReportCommandHandler extends CommandHandler { - @Override - protected List loadCommandList() { - return reportCommands; - } - - private static final DateTimeFormatter DATE_FORMATTER = DateTimeFormatter.ofPattern("yyyy.MM.dd"); - - private Instant parseDate(String dateStr) throws ParseException { - return LocalDate.parse(dateStr, DATE_FORMATTER).atStartOfDay(ZoneId.systemDefault()).toInstant(); - } - - private void updateWeekConfig(String reportPath, int weekNum, Date nextLastDayOfWeek) { - try { - String nextLastDayOfWeekStr = new SimpleDateFormat("yyyy.MM.dd").format(nextLastDayOfWeek); - YamlConfig.addNestedProperty(REPORT, WEEK_NUM, String.valueOf(weekNum)); - YamlConfig.addNestedProperty(REPORT, LAST_DAY_OF_WEEK, nextLastDayOfWeekStr); - LogUtil.info("✅ 更新配置文件成功:周数 = %d, 周结束日期 = %s", weekNum, nextLastDayOfWeekStr); - } - catch (Exception e) { - LogUtil.error("❌ 更新配置文件时出错: %s", e.getMessage()); - } - } - - @Override - protected void process(String[] argv) { - if (argv.length < 3) { - LogUtil.error("❌ 缺少必要参数,请提供脚本名、命令和内容。"); - return; - } - - String content = argv[2].trim(); - - // 如果 content 被引号包围 "",去除引号 - if (content.startsWith("\"") && content.endsWith("\"")) { - content = content.substring(1, content.length() - 1); - } - - if (content.isEmpty()) { - LogUtil.error("⚠️ 内容为空,无法写入。"); - return; - } - - String reportPath = YamlConfig.getProperty(REPORT, WEEK_REPORT); - LogUtil.info("📂 从配置文件中读取到路径:%s", reportPath); - - int weekNum = Integer.parseInt(YamlConfig.getProperty(REPORT, WEEK_NUM)); - String lastDayOfWeekStr = YamlConfig.getProperty(REPORT, LAST_DAY_OF_WEEK); - - File file = new File(reportPath); - if (!file.exists()) { - LogUtil.error("❌ 路径不存在:%s", reportPath); - return; - } - - Date now = new Date(); - try { - Date lastDayOfWeek = new SimpleDateFormat("yyyy.MM.dd").parse(lastDayOfWeekStr); - - if (now.after(addOneDay(lastDayOfWeek))) { - Calendar calendar = Calendar.getInstance(); - calendar.setTime(now); - calendar.add(Calendar.DAY_OF_MONTH, 6); - Date nextLastDayOfWeek = calendar.getTime(); - String newWeekTitle = String.format("# Week%d[%s-%s]\n", weekNum, - DATE_FORMATTER.format(now.toInstant().atZone(ZoneId.systemDefault()).toLocalDate()), - DATE_FORMATTER.format(nextLastDayOfWeek.toInstant().atZone(ZoneId.systemDefault()).toLocalDate()) - ); - weekNum++; - updateWeekConfig(reportPath, weekNum, nextLastDayOfWeek); - appendToFile(reportPath, newWeekTitle); - } - - String todayStr = new SimpleDateFormat("yyyy/MM/dd").format(now); - String logEntry = String.format("- 【%s】 %s\n", todayStr, content); - appendToFile(reportPath, logEntry); - LogUtil.info("✅ 成功将内容写入:%s", reportPath); - } - catch (Exception e) { - LogUtil.error("❌ 操作时发生错误: %s", e.getMessage(), e); - } - } - - // 使用 UTF-8 编码的文件追加方法 - private void appendToFile(String filePath, String content) throws IOException { - try (FileChannel channel = new FileOutputStream(filePath, true).getChannel()) { - byte[] bytes = content.getBytes(StandardCharsets.UTF_8); - ByteBuffer buffer = ByteBuffer.wrap(bytes); - channel.write(buffer); - } - } - - private Date addOneDay(Date date) { - Calendar calendar = Calendar.getInstance(); - calendar.setTime(date); - calendar.add(Calendar.DAY_OF_MONTH, 1); - return calendar.getTime(); - } - - @Override - protected boolean checkArgs(String[] argv) { - return checkArgs(argv, 3, this::hint); - } - - @Override - protected void hint(String[] argv) { - LogUtil.usage("%s %s ", argv[0], argv[1]); - } + @Override + protected List loadCommandList() { + return reportCommands; + } + + private final static String NEW_WEEK_CONFIG_UPDATE = "new"; + + private static final DateTimeFormatter DATE_FORMATTER = DateTimeFormatter.ofPattern("yyyy.MM.dd"); + + private Instant parseDate(String dateStr) throws ParseException { + return LocalDate.parse(dateStr, DATE_FORMATTER).atStartOfDay(ZoneId.systemDefault()).toInstant(); + } + + private void updateWeekConfig(int weekNum, Date nextLastDayOfWeek) { + try { + String nextLastDayOfWeekStr = new SimpleDateFormat("yyyy.MM.dd").format(nextLastDayOfWeek); + YamlConfig.addNestedProperty(REPORT, WEEK_NUM, String.valueOf(weekNum)); + YamlConfig.addNestedProperty(REPORT, LAST_DAY_OF_WEEK, nextLastDayOfWeekStr); + LogUtil.info("✅ 更新配置文件成功:周数 = %d, 周结束日期 = %s", weekNum, nextLastDayOfWeekStr); + } + catch (Exception e) { + LogUtil.error("❌ 更新配置文件时出错: %s", e.getMessage()); + } + } + + @Override + protected void process(String[] argv) { + if (argv.length < 3) { + LogUtil.error("❌ 缺少必要参数,请提供脚本名、命令和内容。"); + return; + } + + String content = argv[2].trim(); + + // 如果 content 被引号包围 "",去除引号 + if (content.startsWith("\"") && content.endsWith("\"")) { + content = content.substring(1, content.length() - 1); + } + + if (content.isEmpty()) { + LogUtil.error("⚠️ 内容为空,无法写入。"); + return; + } + + int weekNum = Integer.parseInt(YamlConfig.getProperty(REPORT, WEEK_NUM)); + String lastDayOfWeekStr = YamlConfig.getProperty(REPORT, LAST_DAY_OF_WEEK); + + if (content.equals(NEW_WEEK_CONFIG_UPDATE)) { + String dataStr = argv.length == 4 ? argv[3] : lastDayOfWeekStr; + Date lastDayOfWeek = null; + Date nextLastDayOfWeek = null; + + try { + lastDayOfWeek = new SimpleDateFormat("yyyy.MM.dd").parse(dataStr); + Calendar calendar = Calendar.getInstance(); + calendar.setTime(lastDayOfWeek); + calendar.add(Calendar.DAY_OF_MONTH, 7); + nextLastDayOfWeek = calendar.getTime(); + } + catch (ParseException e) { + LogUtil.error("更新周数失败,请检查日期字符串是否有误"); + return; + } + updateWeekConfig(weekNum + 1, nextLastDayOfWeek); + return; + } + + String reportPath = YamlConfig.getProperty(REPORT, WEEK_REPORT); + LogUtil.info("📂 从配置文件中读取到路径:%s", reportPath); + + File file = new File(reportPath); + if (!file.exists()) { + LogUtil.error("❌ 路径不存在:%s", reportPath); + return; + } + + Date now = new Date(); + try { + Date lastDayOfWeek = new SimpleDateFormat("yyyy.MM.dd").parse(lastDayOfWeekStr); + + if (now.after(addOneDay(lastDayOfWeek))) { + Calendar calendar = Calendar.getInstance(); + calendar.setTime(now); + calendar.add(Calendar.DAY_OF_MONTH, 6); + Date nextLastDayOfWeek = calendar.getTime(); + String newWeekTitle = String.format("# Week%d[%s-%s]\n", weekNum, + DATE_FORMATTER.format(now.toInstant().atZone(ZoneId.systemDefault()).toLocalDate()), + DATE_FORMATTER.format(nextLastDayOfWeek.toInstant().atZone(ZoneId.systemDefault()).toLocalDate()) + ); + weekNum++; + updateWeekConfig(weekNum, nextLastDayOfWeek); + appendToFile(reportPath, newWeekTitle); + } + + String todayStr = new SimpleDateFormat("yyyy/MM/dd").format(now); + String logEntry = String.format("- 【%s】 %s\n", todayStr, content); + appendToFile(reportPath, logEntry); + LogUtil.info("✅ 成功将内容写入:%s", reportPath); + } + catch (Exception e) { + LogUtil.error("❌ 操作时发生错误: %s", e.getMessage(), e); + } + } + + // 使用 UTF-8 编码的文件追加方法 + private void appendToFile(String filePath, String content) throws IOException { + try (FileChannel channel = new FileOutputStream(filePath, true).getChannel()) { + byte[] bytes = content.getBytes(StandardCharsets.UTF_8); + ByteBuffer buffer = ByteBuffer.wrap(bytes); + channel.write(buffer); + } + } + + private Date addOneDay(Date date) { + Calendar calendar = Calendar.getInstance(); + calendar.setTime(date); + calendar.add(Calendar.DAY_OF_MONTH, 1); + return calendar.getTime(); + } + + @Override + protected boolean checkArgs(String[] argv) { + return checkArgs(argv, this::hint, 3, 4); + } + + @Override + protected void hint(String[] argv) { + LogUtil.usage("%s %s ", argv[0], argv[1]); + LogUtil.usage("%s new [ in pattern yyyy.MM.dd]", argv[0], argv[1]); + } }