summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorWeicao-CatilGrass <1992414357@qq.com>2026-05-24 03:13:00 +0800
committerWeicao-CatilGrass <1992414357@qq.com>2026-05-24 03:13:00 +0800
commit289aa96f02f99bb724013e473725760a86ba6190 (patch)
treecaa818e0fbb66a778b5ad4af61588dfd8b9a3f0f /src
Initialize Rust project with Cargo and implement memory management CLI
Diffstat (limited to 'src')
-rw-r--r--src/main.rs41
-rw-r--r--src/mem_mgr.rs241
2 files changed, 282 insertions, 0 deletions
diff --git a/src/main.rs b/src/main.rs
new file mode 100644
index 0000000..480bc49
--- /dev/null
+++ b/src/main.rs
@@ -0,0 +1,41 @@
+use std::path::PathBuf;
+
+use mingling::{prelude::*, setup::BasicProgramSetup};
+
+mod mem_mgr;
+pub use mem_mgr::*;
+
+#[derive(Debug, Default, Clone)]
+pub struct Constants {
+ pub store_root: PathBuf,
+}
+
+fn main() {
+ let mut program = ThisProgram::new();
+
+ // Setup
+ program.with_setup(BasicProgramSetup);
+
+ // Json
+ if program.pick_global_flag("--pretty") {
+ program.general_renderer_name = mingling::GeneralRendererSetting::JsonPretty;
+ } else {
+ program.general_renderer_name = mingling::GeneralRendererSetting::Json;
+ }
+
+ // Resource
+ let store_root = dirs::data_dir().unwrap().join("memm/");
+ std::fs::create_dir_all(&store_root).unwrap();
+ program.with_resource(Constants { store_root });
+
+ program.exec_and_exit();
+}
+
+dispatcher!("prompt", CMDPromptDisplay => EntryPromptDisplay);
+
+#[chain]
+pub fn render_prompt(_prev: EntryPromptDisplay) {
+ println!("{}", include_str!("../builtin/PROMPT.md"))
+}
+
+gen_program!();
diff --git a/src/mem_mgr.rs b/src/mem_mgr.rs
new file mode 100644
index 0000000..8b9d950
--- /dev/null
+++ b/src/mem_mgr.rs
@@ -0,0 +1,241 @@
+use std::path::PathBuf;
+
+use just_fmt::kebab_case;
+use mingling::{
+ Groupped,
+ macros::{chain, dispatcher, renderer},
+ parser::AsPicker,
+};
+use serde::Serialize;
+
+use crate::Constants;
+
+const SUGGEST_READ: &str = "Please use `memm read {}` to view the memory again";
+const SUGGEST_EXPLORE: &str = "Please use `memm explore` to view available memories";
+
+dispatcher!("remember", CMDRemember => EntryRemember);
+dispatcher!("rewrite", CMDRewrite => EntryRewrite);
+dispatcher!("forget", CMDForget => EntryForget);
+dispatcher!("explore", CMDExplore => EntryExplore);
+dispatcher!("read", CMDRead => EntryRead);
+dispatcher!("dumpall", CMDDumpAll => EntryDumpAll);
+
+#[chain]
+pub fn handle_remember(args: EntryRemember, constants: &Constants) -> Next {
+ let (title, content) = args.pick(()).pick::<Vec<String>>(()).unpack();
+ remember(constants, title, content.join(" ")).to_render()
+}
+
+#[chain]
+pub fn handle_rewrite(args: EntryRewrite, constants: &Constants) -> Next {
+ let (title, content) = args.pick(()).pick::<Vec<String>>(()).unpack();
+ rewrite(constants, title, content.join(" ")).to_render()
+}
+
+#[chain]
+pub fn handle_forget(args: EntryForget, constants: &Constants) -> Next {
+ let title = args.pick(()).unpack();
+ forget(constants, title).to_render()
+}
+
+#[chain]
+pub fn handle_explore(_p: EntryExplore, constants: &Constants) -> Next {
+ explore(constants).to_render()
+}
+
+#[chain]
+pub fn handle_read(args: EntryRead, constants: &Constants) -> Next {
+ let title = args.pick(()).unpack();
+ read(constants, title).to_render()
+}
+
+#[chain]
+pub fn handle_dumpall(_p: EntryDumpAll, constants: &Constants) -> Next {
+ dumpall(constants).to_render()
+}
+
+#[derive(Serialize, Groupped)]
+pub struct ResultExplore {
+ pub titles: Vec<String>,
+}
+#[renderer]
+pub fn phantom_render_result_explore(_p: ResultExplore) {}
+
+#[derive(Serialize, Groupped)]
+pub struct ResultRead {
+ pub exist: bool,
+ pub read_success: bool,
+ pub content_lines: Vec<String>,
+}
+#[renderer]
+pub fn phantom_render_result_read(_p: ResultRead) {}
+
+#[derive(Serialize, Groupped)]
+pub struct ResultWritten {
+ pub exist: bool,
+ pub write_success: bool,
+ pub suggest: String,
+}
+#[renderer]
+pub fn phantom_render_result_written(_p: ResultWritten) {}
+
+#[derive(Serialize, Groupped)]
+pub struct ResultForgotten {
+ pub exist: bool,
+ pub forget_success: bool,
+ pub suggest: String,
+}
+#[renderer]
+pub fn phantom_render_result_forgotten(_p: ResultForgotten) {}
+
+#[derive(Serialize, Groupped)]
+pub struct ResultDumpAll {
+ pub entries: Vec<DumpEntry>,
+}
+
+#[derive(Serialize)]
+pub struct DumpEntry {
+ pub title: String,
+ pub content: String,
+}
+
+#[renderer]
+pub fn phantom_render_result_dumpall(_p: ResultDumpAll) {}
+
+fn explore(constants: &Constants) -> ResultExplore {
+ let store_root = &constants.store_root;
+ let mut titles = Vec::new();
+ if let Ok(entries) = std::fs::read_dir(store_root) {
+ for entry in entries.flatten() {
+ let path = entry.path();
+ if let Some(ext) = path.extension() {
+ if ext == "md" {
+ if let Some(stem) = path.file_stem() {
+ titles.push(stem.to_string_lossy().to_string());
+ }
+ }
+ }
+ }
+ }
+ ResultExplore { titles }
+}
+
+fn read(constants: &Constants, title: String) -> ResultRead {
+ let target_file = item_path(constants, &title);
+ if !target_file.exists() {
+ return ResultRead {
+ exist: false,
+ read_success: false,
+ content_lines: Vec::new(),
+ };
+ }
+ match std::fs::read_to_string(&target_file) {
+ Ok(content) => ResultRead {
+ exist: true,
+ read_success: true,
+ content_lines: content.lines().map(|line| line.to_string()).collect(),
+ },
+ Err(_) => ResultRead {
+ exist: true,
+ read_success: false,
+ content_lines: Vec::new(),
+ },
+ }
+}
+
+fn rewrite(constants: &Constants, title: String, content: String) -> ResultWritten {
+ let content = content.replace("\\n", "\n");
+ let target_file = item_path(constants, &title);
+ match std::fs::write(&target_file, content) {
+ Ok(_) => ResultWritten {
+ exist: true,
+ write_success: true,
+ suggest: SUGGEST_READ.replace("{}", &title),
+ },
+ Err(_) => ResultWritten {
+ exist: true,
+ write_success: false,
+ suggest: SUGGEST_READ.replace("{}", &title),
+ },
+ }
+}
+
+fn remember(constants: &Constants, title: String, content: String) -> ResultWritten {
+ let content = content.replace("\\n", "\n");
+ let target_file = item_path(constants, &title);
+ match std::fs::OpenOptions::new()
+ .append(true)
+ .create(true)
+ .open(&target_file)
+ {
+ Ok(mut file) => {
+ use std::io::Write;
+ match writeln!(file, "{}", content) {
+ Ok(_) => ResultWritten {
+ exist: true,
+ write_success: true,
+ suggest: SUGGEST_READ.replace("{}", &title),
+ },
+ Err(_) => ResultWritten {
+ exist: true,
+ write_success: false,
+ suggest: SUGGEST_READ.replace("{}", &title),
+ },
+ }
+ }
+ Err(_) => ResultWritten {
+ exist: false,
+ write_success: false,
+ suggest: String::new(),
+ },
+ }
+}
+
+fn forget(constants: &Constants, title: String) -> ResultForgotten {
+ let target_file = item_path(constants, &title);
+ if !target_file.exists() {
+ return ResultForgotten {
+ exist: false,
+ forget_success: false,
+ suggest: SUGGEST_EXPLORE.to_string(),
+ };
+ }
+ match std::fs::remove_file(&target_file) {
+ Ok(_) => ResultForgotten {
+ exist: true,
+ forget_success: true,
+ suggest: SUGGEST_EXPLORE.to_string(),
+ },
+ Err(_) => ResultForgotten {
+ exist: true,
+ forget_success: false,
+ suggest: SUGGEST_EXPLORE.to_string(),
+ },
+ }
+}
+
+fn dumpall(constants: &Constants) -> ResultDumpAll {
+ let store_root = &constants.store_root;
+ let mut entries = Vec::new();
+ if let Ok(dir_entries) = std::fs::read_dir(store_root) {
+ for entry in dir_entries.flatten() {
+ let path = entry.path();
+ if let Some(ext) = path.extension() {
+ if ext == "md" {
+ if let Some(stem) = path.file_stem() {
+ let title = stem.to_string_lossy().to_string();
+ let content = std::fs::read_to_string(&path).unwrap_or_default();
+ entries.push(DumpEntry { title, content });
+ }
+ }
+ }
+ }
+ }
+ ResultDumpAll { entries }
+}
+
+fn item_path(constants: &Constants, title: &String) -> PathBuf {
+ constants
+ .store_root
+ .join(format!("{}.md", kebab_case!(title)))
+}