代码片段管理工具:rofi前端+Java守护进程
This commit is contained in:
98
CodeSnippetDaemon/src/main/java/work/slhaf/snippet/App.java
Normal file
98
CodeSnippetDaemon/src/main/java/work/slhaf/snippet/App.java
Normal file
@@ -0,0 +1,98 @@
|
||||
package work.slhaf.snippet;
|
||||
|
||||
import cn.hutool.core.bean.BeanUtil;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import work.slhaf.snippet.common.Constant;
|
||||
import work.slhaf.snippet.entity.db.Index;
|
||||
import work.slhaf.snippet.entity.file.RebuildEntity;
|
||||
import work.slhaf.snippet.gateway.CodeSnippetSocketServer;
|
||||
import work.slhaf.snippet.service.IndexManager;
|
||||
import work.slhaf.snippet.service.SnippetManager;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.sql.SQLException;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@Slf4j
|
||||
public class App{
|
||||
|
||||
void main() throws IOException, SQLException {
|
||||
beforeLaunch();
|
||||
CodeSnippetSocketServer server = new CodeSnippetSocketServer();
|
||||
server.launch();
|
||||
}
|
||||
|
||||
private void beforeLaunch() throws SQLException, IOException {
|
||||
log.info("启动前检查环境");
|
||||
//检查对应目录是否存在并创建
|
||||
checkEnv();
|
||||
checkDB();
|
||||
}
|
||||
|
||||
private void checkDB() throws SQLException, IOException {
|
||||
log.info("检查索引数据库");
|
||||
//获取当前数据路径下的文件目录,查看文件路径、sha值与索引数据库是否一致,如果不一致需要重建
|
||||
IndexManager indexManager = IndexManager.getInstance();
|
||||
SnippetManager snippetManager = new SnippetManager();
|
||||
|
||||
File data = new File(System.getenv(Constant.Property.DIR));
|
||||
File[] files = data.listFiles();
|
||||
if (files == null || files.length == 0) {
|
||||
if (!indexManager.isEmpty()) {
|
||||
log.info("数据库为空,但文件目录不为空,删除数据库");
|
||||
indexManager.resetIndex();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
HashMap<String, String> sha2PathDB = indexManager.getIndexStatus();
|
||||
HashMap<String, String> sha2PathMD = snippetManager.getFileStatus();
|
||||
if (!sha2PathMD.equals(sha2PathDB)) {
|
||||
log.info("数据库与文件目录不一致,重建索引数据库");
|
||||
List<RebuildEntity> snippets = snippetManager.listAll();
|
||||
Set<Index> indexSet = snippets.stream().map(entity -> {
|
||||
Index index = new Index();
|
||||
BeanUtil.copyProperties(entity, index);
|
||||
return index;
|
||||
})
|
||||
.collect(Collectors.toSet());
|
||||
indexManager.rebuildIndex(indexSet);
|
||||
}
|
||||
log.info("索引数据库检查通过");
|
||||
}
|
||||
|
||||
private void checkEnv() {
|
||||
log.info("检查环境变量");
|
||||
getEnvOrThrow(Constant.Property.PORT);
|
||||
getEnvOrThrow(Constant.Property.API_KEY);
|
||||
getEnvOrThrow(Constant.Property.BASE_URL);
|
||||
getEnvOrThrow(Constant.Property.MODEL);
|
||||
checkDir(Constant.Property.DIR);
|
||||
checkDir(Constant.Property.CONF);
|
||||
log.info("环境变量检查通过");
|
||||
}
|
||||
|
||||
private void checkDir(String dir) {
|
||||
File file = new File(getEnvOrThrow(dir));
|
||||
if (file.exists()) return;
|
||||
|
||||
boolean ok = file.mkdirs();
|
||||
if (!ok) {
|
||||
throw new RuntimeException("创建目录失败: " + dir);
|
||||
} else {
|
||||
log.info("创建目录成功: {}", dir);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private String getEnvOrThrow(String key) {
|
||||
String value = System.getenv(key);
|
||||
if (value == null) throw new RuntimeException("未找到环境变量: " + key);
|
||||
return value;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
package work.slhaf.snippet.common;
|
||||
|
||||
public class Constant {
|
||||
|
||||
public static class Property {
|
||||
private static final String BASE = "CODE_SNIPPET_";
|
||||
public static final String DIR = BASE + "DIR";
|
||||
public static final String CONF = BASE + "CONF";
|
||||
public static final String PORT = BASE + "PORT";
|
||||
public static final String API_KEY = BASE + "API_KEY";
|
||||
public static final String BASE_URL = BASE + "BASE_URL";
|
||||
public static final String MODEL = BASE + "MODEL";
|
||||
}
|
||||
|
||||
public enum Action {
|
||||
LIST, EDIT, ADD, DELETE
|
||||
}
|
||||
|
||||
public enum Status {
|
||||
SUCCESS, FAILED
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
package work.slhaf.snippet.common;
|
||||
|
||||
public class SnippetUtil {
|
||||
public static String extractJson(String jsonStr) {
|
||||
jsonStr = jsonStr.replace("“", "\"").replace("”", "\"");
|
||||
int start = jsonStr.indexOf("{");
|
||||
int end = jsonStr.lastIndexOf("}");
|
||||
if (start != -1 && end != -1 && start < end) {
|
||||
return jsonStr.substring(start, end + 1);
|
||||
}
|
||||
return jsonStr;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
package work.slhaf.snippet.common.chat;
|
||||
|
||||
import cn.hutool.http.HttpRequest;
|
||||
import cn.hutool.http.HttpResponse;
|
||||
import cn.hutool.json.JSONUtil;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
import work.slhaf.snippet.common.chat.constant.ChatConstant;
|
||||
import work.slhaf.snippet.common.chat.pojo.ChatBody;
|
||||
import work.slhaf.snippet.common.chat.pojo.ChatResponse;
|
||||
import work.slhaf.snippet.common.chat.pojo.Message;
|
||||
import work.slhaf.snippet.common.chat.pojo.PrimaryChatResponse;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
public class ChatClient {
|
||||
private String clientId;
|
||||
|
||||
private String url;
|
||||
private String apikey;
|
||||
private String model;
|
||||
|
||||
private double top_p;
|
||||
private double temperature;
|
||||
private int max_tokens;
|
||||
|
||||
public ChatClient(String url, String apikey, String model) {
|
||||
this.url = url;
|
||||
this.apikey = apikey;
|
||||
this.model = model;
|
||||
}
|
||||
|
||||
public ChatResponse runChat(List<Message> messages) {
|
||||
HttpRequest request = HttpRequest.post(url);
|
||||
request.header("Content-Type", "application/json");
|
||||
request.header("Authorization", "Bearer " + apikey);
|
||||
|
||||
ChatBody body;
|
||||
if (top_p > 0) {
|
||||
body = ChatBody.builder()
|
||||
.model(model)
|
||||
.messages(messages)
|
||||
.top_p(top_p)
|
||||
.temperature(temperature)
|
||||
.max_tokens(max_tokens)
|
||||
.build();
|
||||
} else {
|
||||
body = ChatBody.builder()
|
||||
.model(model)
|
||||
.messages(messages)
|
||||
.build();
|
||||
}
|
||||
|
||||
HttpResponse response = request.body(JSONUtil.toJsonStr(body)).execute();
|
||||
ChatResponse finalResponse;
|
||||
|
||||
PrimaryChatResponse primaryChatResponse = JSONUtil.toBean(response.body(), PrimaryChatResponse.class);
|
||||
finalResponse = ChatResponse.builder()
|
||||
.type(ChatConstant.Response.SUCCESS)
|
||||
.message(primaryChatResponse.getChoices().get(0).getMessage().getContent())
|
||||
.usageBean(primaryChatResponse.getUsage())
|
||||
.build();
|
||||
|
||||
response.close();
|
||||
return finalResponse;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
package work.slhaf.snippet.common.chat.constant;
|
||||
|
||||
public class ChatConstant {
|
||||
|
||||
public static class Character {
|
||||
public static final String USER = "user";
|
||||
public static final String SYSTEM = "system";
|
||||
public static final String ASSISTANT = "assistant";
|
||||
}
|
||||
|
||||
public static class Response {
|
||||
public static final String SUCCESS = "success";
|
||||
public static final String ERROR = "error";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
package work.slhaf.snippet.common.chat.pojo;
|
||||
|
||||
import lombok.*;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Builder
|
||||
@Data
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
public class ChatBody {
|
||||
@NonNull
|
||||
private String model;
|
||||
@NonNull
|
||||
private List<Message> messages;
|
||||
@Builder.Default
|
||||
private double temperature = 1;
|
||||
@Builder.Default
|
||||
private double top_p = 1;
|
||||
private boolean stream;
|
||||
@Builder.Default
|
||||
private int max_tokens = 1024;
|
||||
private int presence_penalty;
|
||||
private int frequency_penalty;
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
package work.slhaf.snippet.common.chat.pojo;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
@Data
|
||||
@Builder
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
public class ChatResponse {
|
||||
private String type;
|
||||
private String message;
|
||||
private PrimaryChatResponse.UsageBean usageBean;
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
package work.slhaf.snippet.common.chat.pojo;
|
||||
|
||||
import lombok.*;
|
||||
|
||||
@Builder
|
||||
@Data
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
public class Message {
|
||||
|
||||
@NonNull
|
||||
private String role;
|
||||
@NonNull
|
||||
private String content;
|
||||
}
|
||||
@@ -0,0 +1,111 @@
|
||||
package work.slhaf.snippet.common.chat.pojo;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
public class PrimaryChatResponse {
|
||||
|
||||
/**
|
||||
* id
|
||||
*/
|
||||
private String id;
|
||||
/**
|
||||
* object
|
||||
*/
|
||||
private String object;
|
||||
/**
|
||||
* created
|
||||
*/
|
||||
private int created;
|
||||
/**
|
||||
* model
|
||||
*/
|
||||
private String model;
|
||||
/**
|
||||
* choices
|
||||
*/
|
||||
private List<ChoicesBean> choices;
|
||||
/**
|
||||
* usage
|
||||
*/
|
||||
private UsageBean usage;
|
||||
/**
|
||||
* system_fingerprint
|
||||
*/
|
||||
private String system_fingerprint;
|
||||
|
||||
@Setter
|
||||
@Getter
|
||||
public static class UsageBean {
|
||||
/**
|
||||
* prompt_tokens
|
||||
*/
|
||||
private int prompt_tokens;
|
||||
/**
|
||||
* completion_tokens
|
||||
*/
|
||||
private int completion_tokens;
|
||||
/**
|
||||
* total_tokens
|
||||
*/
|
||||
private int total_tokens;
|
||||
/**
|
||||
* prompt_cache_hit_tokens
|
||||
*/
|
||||
private int prompt_cache_hit_tokens;
|
||||
/**
|
||||
* prompt_cache_miss_tokens
|
||||
*/
|
||||
private int prompt_cache_miss_tokens;
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "UsageBean{" +
|
||||
"prompt_tokens=" + prompt_tokens +
|
||||
", completion_tokens=" + completion_tokens +
|
||||
", total_tokens=" + total_tokens +
|
||||
", prompt_cache_hit_tokens=" + prompt_cache_hit_tokens +
|
||||
", prompt_cache_miss_tokens=" + prompt_cache_miss_tokens +
|
||||
'}';
|
||||
}
|
||||
}
|
||||
|
||||
@Setter
|
||||
@Getter
|
||||
public static class ChoicesBean {
|
||||
/**
|
||||
* index
|
||||
*/
|
||||
private int index;
|
||||
/**
|
||||
* message
|
||||
*/
|
||||
private MessageBean message;
|
||||
/**
|
||||
* logprobs
|
||||
*/
|
||||
private Object logprobs;
|
||||
/**
|
||||
* finish_reason
|
||||
*/
|
||||
private String finish_reason;
|
||||
|
||||
@Setter
|
||||
@Getter
|
||||
public static class MessageBean {
|
||||
/**
|
||||
* role
|
||||
*/
|
||||
private String role;
|
||||
/**
|
||||
* content
|
||||
*/
|
||||
private String content;
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
package work.slhaf.snippet.entity;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* 片段完整数据,存储至完整的markdown文件中
|
||||
* 而数据库条目只需要元信息即可
|
||||
*/
|
||||
@Data
|
||||
public class Snippet {
|
||||
|
||||
private String language;
|
||||
private String[] tags;
|
||||
private String description;
|
||||
|
||||
private String content;
|
||||
|
||||
public String toMarkdown() {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
|
||||
// Add Snippet content
|
||||
sb.append("## Snippet\n");
|
||||
sb.append("```").append(language).append("\n");
|
||||
sb.append(content).append("\n");
|
||||
sb.append("```\n\n");
|
||||
|
||||
// Add MetaData section
|
||||
sb.append("## MetaData\n");
|
||||
sb.append("- Language\n");
|
||||
sb.append(" - ").append(language).append("\n");
|
||||
|
||||
sb.append("- Tags\n");
|
||||
for (String tag : tags) {
|
||||
sb.append(" - ").append(tag).append("\n");
|
||||
}
|
||||
|
||||
sb.append("- Description\n");
|
||||
sb.append(" - ").append(description).append("\n");
|
||||
|
||||
return sb.toString();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
package work.slhaf.snippet.entity;
|
||||
|
||||
import lombok.Data;
|
||||
import work.slhaf.snippet.common.Constant;
|
||||
|
||||
@Data
|
||||
public class SocketInputData {
|
||||
private Constant.Action action;
|
||||
private String data;
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
package work.slhaf.snippet.entity;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import work.slhaf.snippet.common.Constant;
|
||||
|
||||
@Data
|
||||
@AllArgsConstructor
|
||||
public class SocketOutputData {
|
||||
private Constant.Status status;
|
||||
private String data;
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
package work.slhaf.snippet.entity.db;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
public class Index {
|
||||
private int id;
|
||||
private String name;
|
||||
private String language;
|
||||
private String[] tags;
|
||||
private String sha;
|
||||
private String description;
|
||||
private String path;
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
package work.slhaf.snippet.entity.file;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
public class AddEntity {
|
||||
private String name;
|
||||
private String language;
|
||||
private String content;
|
||||
|
||||
public boolean checkEmpty(){
|
||||
return name.isEmpty() || language.isEmpty() || content.isEmpty();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
package work.slhaf.snippet.entity.file;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
public class EditEntity {
|
||||
private String id;
|
||||
private String path;
|
||||
|
||||
private String[] tags;
|
||||
private String description;
|
||||
|
||||
private String content;
|
||||
|
||||
public boolean checkEmpty() {
|
||||
return id.isEmpty() ||
|
||||
path.isEmpty() ||
|
||||
content.isEmpty();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
package work.slhaf.snippet.entity.file;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
public class ListEntity implements Comparable<ListEntity> {
|
||||
private String id;
|
||||
private String path;
|
||||
private String name;
|
||||
private int score;
|
||||
|
||||
@Override
|
||||
public int compareTo(ListEntity listEntity) {
|
||||
return Integer.compare(listEntity.getScore(), this.score);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
package work.slhaf.snippet.entity.file;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
public class MetaDataEntity {
|
||||
private String description;
|
||||
private String[] tags;
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
package work.slhaf.snippet.entity.file;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
public class RebuildEntity {
|
||||
private String name;
|
||||
private String path;
|
||||
private String sha;
|
||||
|
||||
private String language;
|
||||
private String[] tags;
|
||||
private String description;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
package work.slhaf.snippet.gateway;
|
||||
|
||||
import cn.hutool.json.JSONUtil;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import work.slhaf.snippet.common.Constant;
|
||||
import work.slhaf.snippet.entity.SocketInputData;
|
||||
import work.slhaf.snippet.entity.SocketOutputData;
|
||||
import work.slhaf.snippet.service.ActionHandler;
|
||||
|
||||
import java.io.*;
|
||||
import java.net.Socket;
|
||||
|
||||
@Slf4j
|
||||
public record ClientSocket(Socket socket, ActionHandler handler) implements Runnable {
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
System.out.println("建立连接: " + socket.getRemoteSocketAddress());
|
||||
try (BufferedReader reader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
|
||||
BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()))) {
|
||||
|
||||
StringBuilder sb = new StringBuilder();
|
||||
String line;
|
||||
while ((line = reader.readLine()) != null) {
|
||||
sb.append(line);
|
||||
}
|
||||
|
||||
SocketInputData inputData = JSONUtil.toBean(sb.toString(), SocketInputData.class);
|
||||
SocketOutputData outputData = handler.handle(inputData);
|
||||
|
||||
writer.write(JSONUtil.toJsonStr(outputData));
|
||||
writer.newLine();
|
||||
writer.flush();
|
||||
|
||||
} catch (Exception e) {
|
||||
SocketOutputData outputData = new SocketOutputData(Constant.Status.FAILED, e.getLocalizedMessage());
|
||||
try {
|
||||
BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));
|
||||
writer.write(JSONUtil.toJsonStr(outputData));
|
||||
writer.newLine();
|
||||
writer.flush();
|
||||
} catch (IOException ex) {
|
||||
log.error(ex.getLocalizedMessage());
|
||||
}
|
||||
} finally {
|
||||
try {
|
||||
socket.close();
|
||||
} catch (IOException e) {
|
||||
log.error(e.getLocalizedMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
package work.slhaf.snippet.gateway;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import work.slhaf.snippet.common.Constant;
|
||||
import work.slhaf.snippet.service.ActionHandler;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.ServerSocket;
|
||||
import java.sql.SQLException;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.Future;
|
||||
|
||||
@Slf4j
|
||||
public class CodeSnippetSocketServer {
|
||||
|
||||
public void launch() throws IOException, SQLException {
|
||||
int port = Integer.parseInt(System.getenv(Constant.Property.PORT));
|
||||
ActionHandler handler = new ActionHandler();
|
||||
try (ServerSocket socket = new ServerSocket(port);
|
||||
ExecutorService executor = Executors.newVirtualThreadPerTaskExecutor()) {
|
||||
socket.setReuseAddress(true);
|
||||
while (true) {
|
||||
Future<?> future = executor.submit(new ClientSocket(socket.accept(), handler));
|
||||
try{
|
||||
future.get();
|
||||
}catch (Exception e){
|
||||
log.error(e.getLocalizedMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,120 @@
|
||||
package work.slhaf.snippet.service;
|
||||
|
||||
import cn.hutool.core.bean.BeanUtil;
|
||||
import cn.hutool.json.JSONUtil;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import work.slhaf.snippet.common.Constant;
|
||||
import work.slhaf.snippet.entity.Snippet;
|
||||
import work.slhaf.snippet.entity.SocketInputData;
|
||||
import work.slhaf.snippet.entity.SocketOutputData;
|
||||
import work.slhaf.snippet.entity.db.Index;
|
||||
import work.slhaf.snippet.entity.file.AddEntity;
|
||||
import work.slhaf.snippet.entity.file.EditEntity;
|
||||
import work.slhaf.snippet.entity.file.ListEntity;
|
||||
import work.slhaf.snippet.entity.file.MetaDataEntity;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.sql.SQLException;
|
||||
import java.util.List;
|
||||
|
||||
@Slf4j
|
||||
public class ActionHandler {
|
||||
|
||||
private final SnippetManager snippetManager = new SnippetManager();
|
||||
private final IndexManager indexManager = IndexManager.getInstance();
|
||||
private final MetaDataExtractor metaDataExtractor = new MetaDataExtractor();
|
||||
|
||||
public ActionHandler() throws SQLException {
|
||||
}
|
||||
|
||||
public SocketOutputData handle(SocketInputData inputData) {
|
||||
log.info("收到请求: {}", inputData.getAction());
|
||||
String data = inputData.getData();
|
||||
try {
|
||||
return switch (inputData.getAction()) {
|
||||
case Constant.Action.ADD -> handleAdd(JSONUtil.toBean(data, AddEntity.class));
|
||||
case Constant.Action.EDIT -> handleEdit(JSONUtil.toBean(data, EditEntity.class));
|
||||
case Constant.Action.DELETE -> handleDelete(data);
|
||||
case Constant.Action.LIST -> handleList(data);
|
||||
};
|
||||
} catch (Exception e) {
|
||||
log.error("处理请求失败: {}", e.getLocalizedMessage());
|
||||
return new SocketOutputData(Constant.Status.FAILED, e.getLocalizedMessage());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private SocketOutputData handleList(String key) throws SQLException {
|
||||
List<ListEntity> result = indexManager.list(key);
|
||||
return new SocketOutputData(Constant.Status.SUCCESS, JSONUtil.toJsonStr(result));
|
||||
}
|
||||
|
||||
private SocketOutputData handleDelete(String path) throws SQLException, IOException {
|
||||
if (path.isEmpty()) {
|
||||
return new SocketOutputData(Constant.Status.FAILED, "Path不能为空!");
|
||||
}
|
||||
indexManager.delete(path);
|
||||
snippetManager.delete(path);
|
||||
return new SocketOutputData(Constant.Status.SUCCESS, "删除成功: " + path);
|
||||
}
|
||||
|
||||
private SocketOutputData handleEdit(EditEntity entity) throws IOException {
|
||||
if (entity.checkEmpty()) {
|
||||
return new SocketOutputData(Constant.Status.FAILED, "Id、Path、代码内容均不能为空!");
|
||||
}
|
||||
try {
|
||||
snippetManager.update(entity, SnippetManager.UpdateAction.EDIT);
|
||||
Index index = new Index();
|
||||
BeanUtil.copyProperties(entity, index);
|
||||
String sha = snippetManager.sha(entity.getPath());
|
||||
index.setSha(sha);
|
||||
indexManager.update(index);
|
||||
snippetManager.update(entity, SnippetManager.UpdateAction.CONFIRM);
|
||||
return new SocketOutputData(Constant.Status.SUCCESS, "文件编辑成功: " + entity.getPath());
|
||||
} catch (Exception e) {
|
||||
snippetManager.update(entity, SnippetManager.UpdateAction.FALLBACK);
|
||||
log.error("文件编辑失败, 已回滚: {}", e.getLocalizedMessage());
|
||||
return new SocketOutputData(Constant.Status.FAILED, e.getLocalizedMessage());
|
||||
}
|
||||
}
|
||||
|
||||
private SocketOutputData handleAdd(AddEntity entity) throws IOException {
|
||||
if (entity.checkEmpty()) {
|
||||
return new SocketOutputData(Constant.Status.FAILED, "Language、Name、代码片段内容均不能为空!");
|
||||
}
|
||||
Path path = Path.of(System.getenv(Constant.Property.DIR), entity.getLanguage().toLowerCase(), entity.getName() + ".md");
|
||||
try {
|
||||
Snippet snippet = fixSnippet(entity);
|
||||
snippetManager.add(snippet, path);
|
||||
Index index = fixIndexEntity(snippet, path);
|
||||
index.setName(entity.getName());
|
||||
indexManager.add(index);
|
||||
return new SocketOutputData(Constant.Status.SUCCESS, "代码片段已添加, 路径: " + path);
|
||||
} catch (Exception e) {
|
||||
Files.deleteIfExists(path);
|
||||
log.error("文件添加失败: {}", e.getLocalizedMessage());
|
||||
return new SocketOutputData(Constant.Status.FAILED, e.getLocalizedMessage());
|
||||
}
|
||||
}
|
||||
|
||||
private Index fixIndexEntity(Snippet snippet, Path path) {
|
||||
Index index = new Index();
|
||||
BeanUtil.copyProperties(snippet, index);
|
||||
index.setPath(path.toString());
|
||||
index.setSha(snippetManager.sha(index.getPath()));
|
||||
return index;
|
||||
}
|
||||
|
||||
private Snippet fixSnippet(AddEntity entity) {
|
||||
MetaDataEntity metaData = metaDataExtractor.extract(entity);
|
||||
Snippet snippet = new Snippet();
|
||||
snippet.setLanguage(entity.getLanguage().toLowerCase());
|
||||
snippet.setContent(entity.getContent());
|
||||
snippet.setDescription(metaData.getDescription());
|
||||
snippet.setTags(metaData.getTags());
|
||||
|
||||
return snippet;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,178 @@
|
||||
package work.slhaf.snippet.service;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import work.slhaf.snippet.common.Constant;
|
||||
import work.slhaf.snippet.entity.db.Index;
|
||||
import work.slhaf.snippet.entity.file.ListEntity;
|
||||
|
||||
import java.sql.*;
|
||||
import java.util.*;
|
||||
|
||||
@Slf4j
|
||||
public class IndexManager {
|
||||
|
||||
private static volatile IndexManager instance;
|
||||
|
||||
private final Connection connection;
|
||||
|
||||
public static IndexManager getInstance() throws SQLException {
|
||||
if (instance == null) {
|
||||
synchronized (IndexManager.class) {
|
||||
if (instance == null) {
|
||||
instance = new IndexManager();
|
||||
}
|
||||
}
|
||||
}
|
||||
return instance;
|
||||
}
|
||||
|
||||
private IndexManager() throws SQLException {
|
||||
connection = DriverManager.getConnection("jdbc:sqlite:" + System.getenv(Constant.Property.CONF) + "/index.db");
|
||||
checkTable(connection);
|
||||
}
|
||||
|
||||
private void checkTable(Connection connection) throws SQLException {
|
||||
Statement statement = connection.createStatement();
|
||||
statement.execute("""
|
||||
create table if not exists code_snippet_index(
|
||||
id integer primary key,
|
||||
name varchar(255) not null,
|
||||
language varchar(60) not null,
|
||||
tags varchar(300) not null,
|
||||
sha varchar(100) not null,
|
||||
description varchar(300) not null,
|
||||
path varchar(300) not null
|
||||
)
|
||||
""");
|
||||
statement.close();
|
||||
}
|
||||
|
||||
public void rebuildIndex(Set<Index> indexSet) throws SQLException {
|
||||
log.info("重建索引数据库");
|
||||
resetIndex();
|
||||
for (Index index : indexSet) {
|
||||
add(index);
|
||||
}
|
||||
}
|
||||
|
||||
public void add(Index index) throws SQLException {
|
||||
PreparedStatement statement = connection.prepareStatement("""
|
||||
insert into code_snippet_index(name,language, tags, sha, description, path)
|
||||
values (?,?,?,?,?,?)
|
||||
""");
|
||||
statement.setString(1, index.getName());
|
||||
statement.setString(2, index.getLanguage());
|
||||
statement.setString(3, Arrays.toString(index.getTags()));
|
||||
statement.setString(4, index.getSha());
|
||||
statement.setString(5, index.getDescription());
|
||||
statement.setString(6, index.getPath());
|
||||
statement.execute();
|
||||
statement.close();
|
||||
}
|
||||
|
||||
public void update(Index index) throws SQLException {
|
||||
PreparedStatement statement = connection.prepareStatement("""
|
||||
update code_snippet_index
|
||||
set tags = ?, description = ?, sha = ?
|
||||
where id = ?
|
||||
""");
|
||||
statement.setString(1, Arrays.toString(index.getTags()));
|
||||
statement.setString(2, index.getDescription());
|
||||
statement.setString(3, index.getSha());
|
||||
statement.executeUpdate();
|
||||
statement.close();
|
||||
}
|
||||
|
||||
public void delete(String path) throws SQLException {
|
||||
PreparedStatement statement = connection.prepareStatement("delete from code_snippet_index where path = ?");
|
||||
statement.setString(1, path);
|
||||
statement.executeUpdate();
|
||||
}
|
||||
|
||||
public List<ListEntity> list(String input) throws SQLException {
|
||||
String[] keywords = input.trim().toLowerCase().split("\\s+");
|
||||
|
||||
// 构建 SQL 占位符字符串
|
||||
StringBuilder sb = new StringBuilder("SELECT * FROM code_snippet_index WHERE ");
|
||||
List<String> params = new ArrayList<>();
|
||||
for (int k = 0; k < keywords.length; k++) {
|
||||
if (k > 0) sb.append(" OR ");
|
||||
sb.append("(name LIKE ? OR description LIKE ? OR tags LIKE ? OR language LIKE ?)");
|
||||
for (int i = 0; i < 4; i++) params.add("%" + keywords[k] + "%");
|
||||
}
|
||||
|
||||
PreparedStatement statement = connection.prepareStatement(sb.toString());
|
||||
for (int i = 0; i < params.size(); i++) {
|
||||
statement.setString(i + 1, params.get(i));
|
||||
}
|
||||
|
||||
ResultSet rs = statement.executeQuery();
|
||||
List<ListEntity> list = new ArrayList<>();
|
||||
|
||||
while (rs.next()) {
|
||||
ListEntity entity = new ListEntity();
|
||||
entity.setId(rs.getString("id"));
|
||||
entity.setPath(rs.getString("path"));
|
||||
entity.setName(rs.getString("name"));
|
||||
entity.setScore(calculateScore(rs, keywords)); // 多关键字打分
|
||||
list.add(entity);
|
||||
}
|
||||
|
||||
// 按分数降序排序
|
||||
list.sort(Comparator.comparingInt(ListEntity::getScore).reversed());
|
||||
return list;
|
||||
}
|
||||
|
||||
// 多关键字加权打分
|
||||
private int calculateScore(ResultSet rs, String[] keywords) throws SQLException {
|
||||
int score = 0;
|
||||
String name = rs.getString("name").toLowerCase();
|
||||
String description = rs.getString("description").toLowerCase();
|
||||
String language = rs.getString("language").toLowerCase();
|
||||
|
||||
String tagsRaw = rs.getString("tags");
|
||||
List<String> tagList = Arrays.stream(tagsRaw.substring(1, tagsRaw.length() - 1)
|
||||
.split(","))
|
||||
.map(String::toLowerCase)
|
||||
.toList();
|
||||
|
||||
for (String key : keywords) {
|
||||
if (name.contains(key)) score += 5;
|
||||
if (tagList.stream().anyMatch(t -> t.contains(key))) score += 4;
|
||||
if (description.contains(key)) score += 3;
|
||||
if (language.contains(key)) score += 2;
|
||||
}
|
||||
return score;
|
||||
}
|
||||
|
||||
|
||||
public boolean isEmpty() throws SQLException {
|
||||
Statement statement = connection.createStatement();
|
||||
//判断表是否为空
|
||||
ResultSet rs = statement.executeQuery("select count(*) from code_snippet_index");
|
||||
rs.next();
|
||||
int count = rs.getInt(1);
|
||||
statement.close();
|
||||
return count == 0;
|
||||
}
|
||||
|
||||
public void resetIndex() throws SQLException {
|
||||
log.info("重置索引");
|
||||
//删除表中全部索引条目
|
||||
Statement statement = connection.createStatement();
|
||||
statement.execute("delete from code_snippet_index");
|
||||
statement.close();
|
||||
}
|
||||
|
||||
public HashMap<String, String> getIndexStatus() throws SQLException {
|
||||
Statement statement = connection.createStatement();
|
||||
ResultSet rs = statement.executeQuery("""
|
||||
select sha,path from code_snippet_index
|
||||
""");
|
||||
HashMap<String, String> map = new HashMap<>();
|
||||
while (rs.next()) {
|
||||
map.put(rs.getString("path"), rs.getString("sha"));
|
||||
}
|
||||
return map;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
package work.slhaf.snippet.service;
|
||||
|
||||
import cn.hutool.json.JSONUtil;
|
||||
import work.slhaf.snippet.common.Constant;
|
||||
import work.slhaf.snippet.common.chat.ChatClient;
|
||||
import work.slhaf.snippet.common.chat.constant.ChatConstant;
|
||||
import work.slhaf.snippet.common.chat.pojo.ChatResponse;
|
||||
import work.slhaf.snippet.common.chat.pojo.Message;
|
||||
import work.slhaf.snippet.entity.file.AddEntity;
|
||||
import work.slhaf.snippet.entity.file.MetaDataEntity;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import static work.slhaf.snippet.common.SnippetUtil.extractJson;
|
||||
|
||||
public class MetaDataExtractor {
|
||||
private final ChatClient chatClient = new ChatClient(
|
||||
System.getenv(Constant.Property.BASE_URL),
|
||||
System.getenv(Constant.Property.API_KEY),
|
||||
System.getenv(Constant.Property.MODEL)
|
||||
);
|
||||
private final List<Message> messages = List.of(
|
||||
new Message(ChatConstant.Character.SYSTEM, """
|
||||
你需要对接下来发送的代码相关内容进行提取,提取出‘描述’、‘标签’等内容。
|
||||
|
||||
输入格式示例:
|
||||
```json
|
||||
{
|
||||
"name": "ByteBuddy用法示例", //代码片段名称
|
||||
"language": "Java", //代码片段所属语言
|
||||
"content": "Class<? extends Module> clazz = module.getClazz();\\nClass<? extends Module> proxyClass = new ByteBuddy()\\n .subclass(clazz)\\n .method(ElementMatchers.isOverriddenFrom(overrideSource))\\n .intercept(MethodDelegation.to(new ModuleProxyInterceptor(record.post, record.pre)))\\n .make()\\n .load(ModuleProxyFactory.class.getClassLoader())\\n .getLoaded();" //代码片段内容
|
||||
}
|
||||
```
|
||||
|
||||
输出格式示例
|
||||
```json
|
||||
{
|
||||
"tags": [
|
||||
"ByteBuddy",
|
||||
"动态代理"
|
||||
],
|
||||
"description": "通过ByteBuddy创建动态代理类的示例"
|
||||
}
|
||||
```
|
||||
""")
|
||||
);
|
||||
|
||||
public MetaDataEntity extract(AddEntity entity) {
|
||||
List<Message> temp = new ArrayList<>(messages);
|
||||
temp.add(new Message(ChatConstant.Character.USER, JSONUtil.toJsonStr(entity)));
|
||||
ChatResponse response = chatClient.runChat(temp);
|
||||
return JSONUtil.toBean(extractJson(response.getMessage()), MetaDataEntity.class);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,144 @@
|
||||
package work.slhaf.snippet.service;
|
||||
|
||||
import cn.hutool.core.bean.BeanUtil;
|
||||
import cn.hutool.core.io.file.FileNameUtil;
|
||||
import cn.hutool.crypto.digest.DigestUtil;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import work.slhaf.snippet.common.Constant;
|
||||
import work.slhaf.snippet.entity.Snippet;
|
||||
import work.slhaf.snippet.entity.file.EditEntity;
|
||||
import work.slhaf.snippet.entity.file.RebuildEntity;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileReader;
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.nio.file.StandardCopyOption;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
|
||||
@Slf4j
|
||||
public class SnippetManager {
|
||||
|
||||
private final SnippetReader snippetReader = new SnippetReader();
|
||||
|
||||
public HashMap<String, String> getFileStatus() {
|
||||
File file = new File(System.getenv(Constant.Property.DIR));
|
||||
HashMap<String, String> map = new HashMap<>();
|
||||
listFileStatus(file, map);
|
||||
return map;
|
||||
}
|
||||
|
||||
private void listFileStatus(File file, HashMap<String, String> map) {
|
||||
File[] files = file.listFiles();
|
||||
if (files == null) {
|
||||
return;
|
||||
}
|
||||
for (File f : files) {
|
||||
if (f.isDirectory()) {
|
||||
listFileStatus(f, map);
|
||||
} else {
|
||||
String sha = DigestUtil.sha256Hex(f);
|
||||
map.put(f.getAbsolutePath(), sha);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void add(Snippet snippet, Path path) throws IOException {
|
||||
String markdown = snippet.toMarkdown();
|
||||
File file = path.toFile();
|
||||
if (file.exists()) {
|
||||
throw new RuntimeException("文件已存在: " + file.getAbsolutePath());
|
||||
}
|
||||
file.getParentFile().mkdirs();
|
||||
Files.writeString(file.toPath(), markdown, StandardCharsets.UTF_8);
|
||||
}
|
||||
|
||||
public void update(EditEntity entity, UpdateAction action) throws IOException {
|
||||
log.info("编辑操作: {}, {}", entity.getPath(), action);
|
||||
switch (action) {
|
||||
case EDIT -> edit(entity);
|
||||
case CONFIRM -> confirm(entity);
|
||||
case FALLBACK -> fallback(entity);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private void fallback(EditEntity entity) throws IOException {
|
||||
String path = entity.getPath();
|
||||
String tempPath = getTempPath(path);
|
||||
Files.move(Paths.get(tempPath), Paths.get(path), StandardCopyOption.REPLACE_EXISTING);
|
||||
}
|
||||
|
||||
private void confirm(EditEntity entity) throws IOException {
|
||||
String tempPath = getTempPath(entity.getPath());
|
||||
Files.deleteIfExists(Paths.get(tempPath));
|
||||
}
|
||||
|
||||
private void edit(EditEntity entity) throws IOException {
|
||||
String path = entity.getPath();
|
||||
String tempPath = getTempPath(path);
|
||||
Path p = Paths.get(path);
|
||||
|
||||
Snippet snippet = new Snippet();
|
||||
BeanUtil.copyProperties(entity, snippet);
|
||||
|
||||
Files.move(p, Paths.get(tempPath), StandardCopyOption.REPLACE_EXISTING);
|
||||
Files.writeString(p, snippet.toMarkdown(), StandardCharsets.UTF_8);
|
||||
}
|
||||
|
||||
private String getTempPath(String path) {
|
||||
return path + ".tmp";
|
||||
}
|
||||
|
||||
public void delete(String path) throws IOException {
|
||||
Files.deleteIfExists(Paths.get(path));
|
||||
}
|
||||
|
||||
public String sha(String filePath) {
|
||||
File file = new File(filePath);
|
||||
if (!file.exists()) {
|
||||
throw new RuntimeException("文件不存在: " + filePath);
|
||||
}
|
||||
return DigestUtil.sha256Hex(file);
|
||||
}
|
||||
|
||||
public List<RebuildEntity> listAll() throws IOException {
|
||||
log.info("获取数据目录[{}]文件信息", System.getenv(Constant.Property.DIR));
|
||||
List<RebuildEntity> list = new ArrayList<>();
|
||||
File file = new File(System.getenv(Constant.Property.DIR));
|
||||
listAll(file, list);
|
||||
return list;
|
||||
}
|
||||
|
||||
private void listAll(File file, List<RebuildEntity> list) throws IOException {
|
||||
File[] files = file.listFiles();
|
||||
if (files == null) {
|
||||
return;
|
||||
}
|
||||
for (File f : files) {
|
||||
if (f.isDirectory()) {
|
||||
listAll(f, list);
|
||||
} else {
|
||||
FileReader reader = new FileReader(f, StandardCharsets.UTF_8);
|
||||
String s = reader.readAllAsString();
|
||||
Snippet snippet = snippetReader.visit(s);
|
||||
RebuildEntity rebuildEntity = new RebuildEntity();
|
||||
BeanUtil.copyProperties(snippet, rebuildEntity);
|
||||
rebuildEntity.setPath(f.getAbsolutePath());
|
||||
rebuildEntity.setSha(DigestUtil.sha256Hex(f));
|
||||
rebuildEntity.setName(FileNameUtil.mainName(f));
|
||||
list.add(rebuildEntity);
|
||||
reader.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public enum UpdateAction {
|
||||
EDIT, CONFIRM, FALLBACK
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,134 @@
|
||||
package work.slhaf.snippet.service;
|
||||
|
||||
import org.commonmark.node.*;
|
||||
import org.commonmark.parser.Parser;
|
||||
import work.slhaf.snippet.entity.Snippet;
|
||||
|
||||
public class SnippetReader {
|
||||
|
||||
public Snippet visit(String text){
|
||||
Snippet snippet = new Snippet();
|
||||
Node document = Parser.builder().build().parse(text);
|
||||
document.accept(new SnippetVisitor(snippet));
|
||||
return snippet;
|
||||
}
|
||||
|
||||
private static class SnippetVisitor extends AbstractVisitor {
|
||||
|
||||
private final Snippet snippet;
|
||||
private String currentSection = "";
|
||||
private String lastMetadataKey = "";
|
||||
|
||||
public SnippetVisitor(Snippet snippet) {
|
||||
this.snippet = snippet;
|
||||
}
|
||||
|
||||
private String extractText(Node node) {
|
||||
StringBuilder text = new StringBuilder();
|
||||
Node child = node.getFirstChild();
|
||||
while (child != null) {
|
||||
if (child instanceof Text) {
|
||||
text.append(((Text) child).getLiteral());
|
||||
} else if (child instanceof Paragraph) {
|
||||
text.append(extractText(child));
|
||||
}
|
||||
child = child.getNext();
|
||||
}
|
||||
return text.toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visit(FencedCodeBlock fencedCodeBlock) {
|
||||
// Extract the code content from the fenced code block
|
||||
String codeContent = fencedCodeBlock.getLiteral();
|
||||
if (codeContent != null) {
|
||||
snippet.setContent(codeContent);
|
||||
}
|
||||
|
||||
// Extract the language from the info string
|
||||
String info = fencedCodeBlock.getInfo();
|
||||
if (info != null && !info.isEmpty()) {
|
||||
snippet.setLanguage(info);
|
||||
}
|
||||
|
||||
super.visit(fencedCodeBlock);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visit(Heading heading) {
|
||||
// Extract text content from the heading to identify the section
|
||||
StringBuilder headingText = new StringBuilder();
|
||||
Node child = heading.getFirstChild();
|
||||
while (child != null) {
|
||||
if (child instanceof Text) {
|
||||
headingText.append(((Text) child).getLiteral());
|
||||
}
|
||||
child = child.getNext();
|
||||
}
|
||||
|
||||
// Set the current section based on the heading text
|
||||
currentSection = headingText.toString();
|
||||
|
||||
super.visit(heading);
|
||||
}
|
||||
|
||||
private void addTag(String tag) {
|
||||
String[] existingTags = snippet.getTags();
|
||||
if (existingTags == null) {
|
||||
snippet.setTags(new String[]{tag});
|
||||
} else {
|
||||
String[] newTags = new String[existingTags.length + 1];
|
||||
System.arraycopy(existingTags, 0, newTags, 0, existingTags.length);
|
||||
newTags[existingTags.length] = tag;
|
||||
snippet.setTags(newTags);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visit(BulletList bulletList) {
|
||||
super.visit(bulletList);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visit(ListItem listItem) {
|
||||
// Extract text content from the list item
|
||||
String itemText = extractText(listItem);
|
||||
|
||||
// Remove leading/trailing whitespace
|
||||
itemText = itemText.trim();
|
||||
|
||||
// Process metadata based on the current section and item text
|
||||
if ("MetaData".equals(currentSection)) {
|
||||
if (itemText.equals("Language")) {
|
||||
lastMetadataKey = "Language";
|
||||
} else if (itemText.equals("Tags")) {
|
||||
lastMetadataKey = "Tags";
|
||||
} else if (itemText.equals("Description")) {
|
||||
lastMetadataKey = "description";
|
||||
} else {
|
||||
// This is a value for the previous metadata key
|
||||
if ("Language".equals(lastMetadataKey)) {
|
||||
snippet.setLanguage(itemText);
|
||||
} else if ("description".equals(lastMetadataKey)) {
|
||||
snippet.setDescription(itemText);
|
||||
} else if ("Tags".equals(lastMetadataKey)) {
|
||||
// Collect tags
|
||||
addTag(itemText);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
super.visit(listItem);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visit(Paragraph paragraph) {
|
||||
super.visit(paragraph);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visit(Text text) {
|
||||
super.visit(text);
|
||||
}
|
||||
}
|
||||
}
|
||||
11
CodeSnippetDaemon/src/test/java/MarkdownTest.java
Normal file
11
CodeSnippetDaemon/src/test/java/MarkdownTest.java
Normal file
@@ -0,0 +1,11 @@
|
||||
import work.slhaf.snippet.entity.Snippet;
|
||||
import work.slhaf.snippet.service.SnippetReader;
|
||||
|
||||
void main() throws IOException {
|
||||
// Read the test file
|
||||
FileReader reader = new FileReader("../test/test.md", StandardCharsets.UTF_8);
|
||||
String s = reader.readAllAsString();
|
||||
SnippetReader snippetReader = new SnippetReader();
|
||||
Snippet visit = snippetReader.visit(s);
|
||||
System.out.println(visit);
|
||||
}
|
||||
29
CodeSnippetDaemon/src/test/java/SnippetReaderTest.java
Normal file
29
CodeSnippetDaemon/src/test/java/SnippetReaderTest.java
Normal file
@@ -0,0 +1,29 @@
|
||||
import work.slhaf.snippet.entity.Snippet;
|
||||
import work.slhaf.snippet.service.SnippetReader;
|
||||
|
||||
void main() throws IOException {
|
||||
// Read the test file
|
||||
FileReader reader = new FileReader("../test/test.md", StandardCharsets.UTF_8);
|
||||
SnippetReader snippetReader = new SnippetReader();
|
||||
StringBuilder content = new StringBuilder();
|
||||
int ch;
|
||||
while ((ch = reader.read()) != -1) {
|
||||
content.append((char) ch);
|
||||
}
|
||||
reader.close();
|
||||
|
||||
// Parse the content using SnippetReader
|
||||
Snippet snippet = snippetReader.visit(content.toString());
|
||||
|
||||
// Print the extracted information
|
||||
System.out.println("Language: " + snippet.getLanguage());
|
||||
System.out.println("Description: " + snippet.getDescription());
|
||||
System.out.println("Tags: ");
|
||||
if (snippet.getTags() != null) {
|
||||
for (String tag : snippet.getTags()) {
|
||||
System.out.println(" - " + tag);
|
||||
}
|
||||
}
|
||||
System.out.println("Content: ");
|
||||
System.out.println(snippet.getContent());
|
||||
}
|
||||
Reference in New Issue
Block a user