GitRoot

craft your forge, build your project, grow your community freely
  1// SPDX-FileCopyrightText: 2025 Romain Maneschi <romain@gitroot.dev>
  2//
  3// SPDX-License-Identifier: MIT
  4
  5use std::{
  6    collections::HashMap,
  7    sync::{OnceLock, RwLock},
  8};
  9
 10use crate::{
 11    Call, Commit, Exec, ExecStatus, ForgeConf, PluginRun, ReportLevel, ReportToGitroot,
 12    fs::{Fs, FsBase},
 13    imports::{self, call, can_call},
 14};
 15
 16pub type CallbackFn =
 17    Box<dyn Fn(HashMap<String, String>) -> Result<HashMap<String, String>, String> + Send + Sync>;
 18
 19pub struct Server {
 20    at_least_one_change: bool,
 21    pub run: Vec<PluginRun>,
 22    exported_funcs: RwLock<HashMap<String, CallbackFn>>,
 23    worktree_fs: OnceLock<Fs>,
 24    webcontent_fs: OnceLock<Fs>,
 25    cache_fs: OnceLock<Fs>,
 26}
 27
 28impl Server {
 29    pub fn new(run: Vec<PluginRun>) -> Self {
 30        Server {
 31            at_least_one_change: false,
 32            run,
 33            exported_funcs: RwLock::new(HashMap::new()),
 34            worktree_fs: OnceLock::new(),
 35            webcontent_fs: OnceLock::new(),
 36            cache_fs: OnceLock::new(),
 37        }
 38    }
 39
 40    pub fn forge_conf(&self) -> Result<ForgeConf, String> {
 41        imports::forge_conf()
 42    }
 43
 44    pub fn worktree(&self) -> &Fs {
 45        self.worktree_fs.get_or_init(|| Fs::new(FsBase::WORKTREE))
 46    }
 47
 48    pub fn webcontent(&self) -> &Fs {
 49        self.webcontent_fs
 50            .get_or_init(|| Fs::new(FsBase::WEBCONTENT))
 51    }
 52
 53    pub fn cache(&self) -> &Fs {
 54        self.cache_fs.get_or_init(|| Fs::new(FsBase::CACHE))
 55    }
 56
 57    #[deprecated(note = "use worktree().WriteContent")]
 58    pub fn modify_content(&mut self, filepath: String, content: String) {
 59        self.at_least_one_change = true;
 60        imports::modify_content(filepath, content);
 61    }
 62
 63    #[deprecated(note = "use webcontent().WriteContent")]
 64    pub fn modify_web_content(&self, filepath: String, content: String) {
 65        imports::modify_web_content(filepath, content);
 66    }
 67
 68    #[deprecated(note = "use webcontent().ReplaceContent")]
 69    pub fn replace_web_content(&self, filepath: String, old_content: String, content: String) {
 70        imports::replace_web_content(filepath, old_content, content);
 71    }
 72
 73    #[deprecated(note = "use cache().WriteContent")]
 74    pub fn modify_cache_content(&self, filepath: String, content: String) {
 75        imports::modify_cache_content(filepath, content);
 76    }
 77
 78    pub fn commit_all_if_needed(&self, message: String) {
 79        if self.at_least_one_change {
 80            imports::commit_all(message);
 81        }
 82    }
 83
 84    pub fn diff_with_parent(
 85        &self,
 86        hash: String,
 87        old_filepath: String,
 88        new_filepath: String,
 89    ) -> Result<String, String> {
 90        imports::diff_with_parent(hash, old_filepath, new_filepath)
 91    }
 92
 93    pub fn log<S>(&self, message: S)
 94    where
 95        S: AsRef<str>,
 96    {
 97        imports::log(message.as_ref());
 98    }
 99
100    pub fn log_error<S, K>(&self, message: S, err: K)
101    where
102        S: AsRef<str>,
103        K: AsRef<str>,
104    {
105        imports::log_error(message.as_ref(), err.as_ref());
106    }
107
108    pub fn merge(&self, from: String, to: String) {
109        imports::merge(from, to);
110    }
111
112    pub fn commits(&self, from: String, to: String) -> Result<Vec<Commit>, String> {
113        imports::commits(from, to)
114    }
115
116    pub fn exec(&self, cmd: &Exec) -> Result<ExecStatus, String> {
117        imports::exec(cmd)
118    }
119
120    pub fn report(&self, level: ReportLevel, content: Vec<String>) {
121        imports::report_to_gitroot(ReportToGitroot { level, content })
122    }
123
124    pub fn export_func<S, F>(&self, name: S, callback: F)
125    where
126        S: Into<String>,
127        F: Fn(HashMap<String, String>) -> Result<HashMap<String, String>, String>
128            + Send
129            + Sync
130            + 'static,
131    {
132        let mut cb_map = self.exported_funcs.write().unwrap();
133        cb_map.insert(name.into(), Box::new(callback));
134    }
135
136    pub(crate) fn call_internal_func(&self, c: Call) -> Result<HashMap<String, String>, String> {
137        let cb_map = self.exported_funcs.read().unwrap();
138        match cb_map.get(&c.name) {
139            Some(func) => func(c.args),
140            None => Err("func not found".to_string()),
141        }
142    }
143
144    pub fn can_call_func(&self, c: Call) -> bool {
145        can_call(&c) != 0
146    }
147
148    pub fn call_func(&self, c: Call) -> Result<HashMap<String, String>, String> {
149        call(&c).map(|r| r.res)
150    }
151}