nibb_core/snippets/
repo.rs1use std::fs;
2use std::path::{Path, PathBuf};
3use slug::slugify;
4use crate::config::config::Config;
5use crate::get_nibb_dir;
6use crate::git::git_integration::GitRepo;
7use crate::result::{NibbError, NibbResult};
8use crate::snippets::snippet::{Meta, Snippet};
9pub trait SnippetRepository {
14 fn load_all(&self) -> NibbResult<Vec<Snippet>>;
16 fn load(&self, slug: &str) -> NibbResult<Snippet>;
18 fn save(&self, snippet: &Snippet) -> NibbResult<()>;
20 fn save_all(&self, snippets: &[Snippet]) -> NibbResult<()>;
22 fn delete(&self, slug: &str) -> NibbResult<()>;
24}
25pub struct FSRepo {
36 pub base_dir: PathBuf,
38 pub config: Config,
39 pub git_repo: GitRepo,
40}
41
42impl FSRepo {
43 pub fn new<P: AsRef<Path>>(path: P) -> NibbResult<Self> {
47 let config = Config::load(&path.as_ref().join("config.toml"))?;
48 let git_repo = GitRepo::init_or_open(get_nibb_dir()?)?;
49 let repo = Self {
50 git_repo,
51 config,
52 base_dir: path.as_ref().to_path_buf(),
53 };
54 repo.ensure_structure()?;
55 Ok(repo)
56 }
57 pub fn snippet_path(&self, slug: &str) -> PathBuf {
59 self.snippets_dir().join(slug)
60 }
61 fn snippets_dir(&self) -> PathBuf {
62 self.base_dir.join("snippets")
63 }
64 fn history_dir(&self) -> PathBuf {
65 self.base_dir.join("history")
66 }
67 fn config_path(&self) -> PathBuf {
68 self.base_dir.join("config.toml")
69 }
70 fn ensure_structure(&self) -> NibbResult<()> {
71 fs::create_dir_all(&self.base_dir)?;
72 fs::create_dir_all(self.snippets_dir())?;
73 fs::create_dir_all(self.history_dir())?;
74 let config_path = self.config_path();
75 if !config_path.exists() {
76 fs::File::create(config_path)?;
77 }
78 Ok(())
79 }
80 fn auto_commit(&self, snippet: &Snippet) -> NibbResult<()> {
81 if !self.config.git.enabled || !self.config.git.auto_commit {
82 return Ok(());
83 }
84 self.git_repo.add_and_commit(snippet, &self.config)?;
85 self.auto_push()?;
86 Ok(())
87 }
88 fn auto_push(&self) -> NibbResult<()> {
89 if !self.config.git.enabled || !self.config.git.push_on_commit {
90 return Ok(());
91 }
92 match &self.config.git.remote {
93 Some(remote) => {
94 Ok(self.git_repo.push(remote, &self.config.git.branch)?)
95 },
96 None => Ok(())
97 }
98 }
99 fn get_content_path(&self, slug: &str, extension: &str) -> PathBuf {
100 let snippet_path = self.snippet_path(slug);
101 snippet_path.join(format!("content.{}", extension))
102 }
103 fn get_meta_path(&self, slug: &str) -> PathBuf {
104 let snippet_path = self.snippet_path(slug);
105 snippet_path.join("meta.toml")
106 }
107}
108
109impl SnippetRepository for FSRepo {
110 fn load_all(&self) -> NibbResult<Vec<Snippet>> {
113 let entries = std::fs::read_dir(self.snippets_dir())
114 .map_err(|e| NibbError::NotFound(format!("{}:{:?}", e.to_string(), self.snippets_dir())))?;
115 let mut snippets = Vec::new();
116 for entry in entries {
117 let entry = entry.map_err(|e| NibbError::NotFound(format!("{}:{:?}", e.to_string(), self.snippets_dir())))?;
118 let slug = slugify(entry.file_name().to_str().unwrap());
119 snippets.push(self.load(&slug)?);
120 }
121 Ok(snippets)
122 }
123 fn load(&self, slug: &str) -> NibbResult<Snippet> {
127 let slug = slugify(slug); let meta_path = self.get_meta_path(&slug);
129 let meta_str = std::fs::read_to_string(&meta_path)
130 .map_err(|e| NibbError::NotFound(format!("{}:{:?}", e.to_string(), &meta_path)))?;
131 let meta: Meta = toml::from_str(&meta_str)
132 .map_err(|e| NibbError::NotFound(format!("{}:{:?}", e.to_string(), &meta_path)))?;
133
134 let content_path = self.get_content_path(&slug, &meta.get_content_extension());
135 let content = std::fs::read_to_string(&content_path)
136 .map_err(|e| NibbError::NotFound(format!("{}:{:?}", e.to_string(), &content_path)))?;
137 Ok(Snippet {
138 meta,
139 content,
140 })
141 }
142 fn save(&self, snippet: &Snippet) -> NibbResult<()> {
146 let slug = snippet.meta.get_slug();
147 let snippet_path = self.snippet_path(&slug);
148
149 if !snippet_path.exists() {
150 fs::create_dir_all(&snippet_path)
151 .map_err(|e| NibbError::NotFound(format!("{}:{:?}", e.to_string(), &snippet_path)))?;
152 }
153
154 let meta_path = self.get_meta_path(&slug);
155 fs::write(&meta_path, toml::to_string(&snippet.meta)?)
156 .map_err(|e| NibbError::NotFound(format!("{}:{:?}", e.to_string(), &meta_path)))?;
157
158 for entry in fs::read_dir(&snippet_path)? {
159 let entry = entry?;
160 let path = entry.path();
161
162 if let Some(file_name) = path.file_name().and_then(|n| n.to_str()) {
163 if file_name.starts_with("content.") {
164 fs::remove_file(&path)
165 .map_err(|e| NibbError::NotFound(format!("Failed to remove old content file {}: {}", path.display(), e)))?;
166 }
167 }
168 }
169
170 let extension = snippet.meta.get_content_extension();
171 let content_path = self.get_content_path(&slug, &extension);
172
173 fs::write(&content_path, &snippet.content)
174 .map_err(|e| NibbError::NotFound(format!("{}:{:?}", e.to_string(), &content_path)))?;
175
176 self.auto_commit(snippet)?;
178 Ok(())
179 }
180 fn save_all(&self, snippets: &[Snippet]) -> NibbResult<()> {
184 for snippet in snippets {
185 self.save(snippet)?;
186 }
187 Ok(())
188 }
189 fn delete(&self, slug: &str) -> NibbResult<()> {
191 let slug = slugify(slug); let snippet_path = self.snippet_path(&slug);
193 fs::remove_dir_all(snippet_path)?;
194 Ok(())
195 }
196}