diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/main.rs | 41 | ||||
| -rw-r--r-- | src/mem_mgr.rs | 241 |
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))) +} |
