use std::path::PathBuf;
use toml_edit::{value, Array, DocumentMut, InlineTable, Item, Table};
use super::{PluginError, Result};
pub struct PluginConfigManager {
config_path: PathBuf,
}
impl PluginConfigManager {
pub fn project() -> Result<Self> {
let config_path = std::env::current_dir()?
.join(".linthis")
.join("config.toml");
Ok(Self { config_path })
}
pub fn global() -> Result<Self> {
let home = std::env::var("HOME")
.or_else(|_| std::env::var("USERPROFILE"))
.map(std::path::PathBuf::from)
.map_err(|_| PluginError::HomeDirectoryError)?;
let config_dir = home.join(".linthis");
let config_path = config_dir.join("config.toml");
Ok(Self { config_path })
}
pub fn config_path(&self) -> &PathBuf {
&self.config_path
}
fn read_config(&self) -> Result<DocumentMut> {
if !self.config_path.exists() {
return Ok(DocumentMut::new());
}
let content = std::fs::read_to_string(&self.config_path)?;
content.parse::<DocumentMut>().map_err(PluginError::from)
}
fn write_config(&self, doc: &DocumentMut) -> Result<()> {
if let Some(parent) = self.config_path.parent() {
std::fs::create_dir_all(parent)?;
}
std::fs::write(&self.config_path, doc.to_string())?;
Ok(())
}
pub fn add_plugin(&self, alias: &str, url: &str, git_ref: Option<&str>) -> Result<()> {
let mut doc = self.read_config()?;
if !doc.contains_key("plugin") {
doc["plugin"] = Item::Table(Table::new());
}
let plugin_table =
doc["plugin"]
.as_table_mut()
.ok_or_else(|| PluginError::ConfigError {
message: "'plugin' is not a table".to_string(),
})?;
if !plugin_table.contains_key("sources") {
plugin_table["sources"] = value(Array::new());
}
let sources =
plugin_table["sources"]
.as_array_mut()
.ok_or_else(|| PluginError::ConfigError {
message: "'plugin.sources' is not an array".to_string(),
})?;
if self.alias_exists(sources, alias) {
return Err(PluginError::ConfigError {
message: format!(
"Plugin alias '{}' already exists in {}",
alias,
self.config_path.display()
),
});
}
let mut plugin_entry = InlineTable::new();
plugin_entry.insert("name", alias.into());
plugin_entry.insert("url", url.into());
if let Some(ref_) = git_ref {
plugin_entry.insert("ref", ref_.into());
}
sources.push(plugin_entry);
self.write_config(&doc)?;
Ok(())
}
pub fn remove_plugin(&self, alias: &str) -> Result<bool> {
let mut doc = self.read_config()?;
let plugin_table = doc
.get_mut("plugin")
.and_then(|item| item.as_table_mut())
.ok_or_else(|| PluginError::ConfigError {
message: "No [plugin] section found in configuration".to_string(),
})?;
let sources = plugin_table
.get_mut("sources")
.and_then(|item| item.as_array_mut())
.ok_or_else(|| PluginError::ConfigError {
message: "No plugin.sources array found in configuration".to_string(),
})?;
let original_len = sources.len();
sources.retain(|item| {
if let Some(table) = item.as_inline_table() {
if let Some(name) = table.get("name") {
return name.as_str() != Some(alias);
}
}
true
});
let removed = sources.len() < original_len;
if removed {
self.write_config(&doc)?;
}
Ok(removed)
}
fn alias_exists(&self, sources: &Array, alias: &str) -> bool {
sources.iter().any(|item| {
if let Some(table) = item.as_inline_table() {
if let Some(name) = table.get("name") {
return name.as_str() == Some(alias);
}
}
false
})
}
pub fn list_plugins(&self) -> Result<Vec<(String, String, Option<String>)>> {
let doc = self.read_config()?;
let mut plugins = Vec::new();
if let Some(sources) = doc
.get("plugin")
.and_then(|p| p.get("sources"))
.and_then(|s| s.as_array())
{
for item in sources.iter() {
if let Some(table) = item.as_inline_table() {
let name = table.get("name").and_then(|n| n.as_str());
let url = table.get("url").and_then(|u| u.as_str());
let ref_ = table.get("ref").and_then(|r| r.as_str());
if let (Some(name), Some(url)) = (name, url) {
plugins.push((
name.to_string(),
url.to_string(),
ref_.map(|s| s.to_string()),
));
}
}
}
}
Ok(plugins)
}
pub fn get_plugin_by_alias(&self, alias: &str) -> Result<Option<(String, Option<String>)>> {
let plugins = self.list_plugins()?;
Ok(plugins
.into_iter()
.find(|(name, _, _)| name == alias)
.map(|(_, url, ref_)| (url, ref_)))
}
}
#[cfg(test)]
mod tests {
use super::*;
use tempfile::TempDir;
fn create_temp_manager() -> (PluginConfigManager, TempDir) {
let temp_dir = TempDir::new().unwrap();
let config_dir = temp_dir.path().join(".linthis");
std::fs::create_dir_all(&config_dir).unwrap();
let config_path = config_dir.join("config.toml");
let manager = PluginConfigManager { config_path };
(manager, temp_dir)
}
#[test]
fn test_add_plugin() {
let (manager, _temp) = create_temp_manager();
manager
.add_plugin("test", "https://example.com/test.git", None)
.unwrap();
let plugins = manager.list_plugins().unwrap();
assert_eq!(plugins.len(), 1);
assert_eq!(plugins[0].0, "test");
assert_eq!(plugins[0].1, "https://example.com/test.git");
}
#[test]
fn test_add_plugin_with_ref() {
let (manager, _temp) = create_temp_manager();
manager
.add_plugin("test", "https://example.com/test.git", Some("v1.0.0"))
.unwrap();
let plugins = manager.list_plugins().unwrap();
assert_eq!(plugins[0].2, Some("v1.0.0".to_string()));
}
#[test]
fn test_add_duplicate_alias() {
let (manager, _temp) = create_temp_manager();
manager
.add_plugin("test", "https://example.com/test.git", None)
.unwrap();
let result = manager.add_plugin("test", "https://example.com/other.git", None);
assert!(result.is_err());
assert!(result.unwrap_err().to_string().contains("already exists"));
}
#[test]
fn test_remove_plugin() {
let (manager, _temp) = create_temp_manager();
manager
.add_plugin("test", "https://example.com/test.git", None)
.unwrap();
let removed = manager.remove_plugin("test").unwrap();
assert!(removed);
let plugins = manager.list_plugins().unwrap();
assert_eq!(plugins.len(), 0);
}
#[test]
fn test_remove_nonexistent() {
let (manager, _temp) = create_temp_manager();
manager
.add_plugin("test", "https://example.com/test.git", None)
.unwrap();
let removed = manager.remove_plugin("nonexistent").unwrap();
assert!(!removed);
}
#[test]
fn test_get_plugin_by_alias() {
let (manager, _temp) = create_temp_manager();
manager
.add_plugin("test", "https://example.com/test.git", Some("v1.0.0"))
.unwrap();
let result = manager.get_plugin_by_alias("test").unwrap();
assert!(result.is_some());
let (url, ref_) = result.unwrap();
assert_eq!(url, "https://example.com/test.git");
assert_eq!(ref_, Some("v1.0.0".to_string()));
}
}