metarepo-core
Core library for building metarepo plugins. This crate provides the essential interfaces and types needed to create custom plugins that extend the metarepo CLI functionality.
Overview
metarepo-core is the foundation for the metarepo plugin system. It defines the MetaPlugin trait that all plugins must implement, along with configuration structures and utilities for working with meta repositories.
Installation
Add this to your Cargo.toml:
[dependencies]
metarepo-core = "0.3"
Quick Start
Here's a minimal example of creating a metarepo plugin:
use anyhow::Result;
use clap::{ArgMatches, Command};
use metarepo_core::{MetaPlugin, RuntimeConfig};
pub struct MyPlugin {
name: String,
}
impl MyPlugin {
pub fn new() -> Self {
Self {
name: "myplugin".to_string(),
}
}
}
impl MetaPlugin for MyPlugin {
fn name(&self) -> &str {
&self.name
}
fn register_commands(&self, app: Command) -> Command {
app.subcommand(
Command::new("myplugin")
.about("My custom plugin")
.subcommand(
Command::new("hello")
.about("Say hello")
)
)
}
fn handle_command(&self, matches: &ArgMatches, config: &RuntimeConfig) -> Result<()> {
match matches.subcommand() {
Some(("hello", _)) => {
println!("Hello from my plugin!");
println!("Working directory: {:?}", config.working_dir);
Ok(())
}
_ => Ok(())
}
}
}
Core Components
MetaPlugin Trait
The MetaPlugin trait is the main interface that all plugins must implement:
pub trait MetaPlugin: Send + Sync {
fn name(&self) -> &str;
fn register_commands(&self, app: Command) -> Command;
fn handle_command(&self, matches: &ArgMatches, config: &RuntimeConfig) -> Result<()>;
fn is_experimental(&self) -> bool {
false
}
}
RuntimeConfig
The RuntimeConfig struct provides access to the meta repository configuration and environment:
pub struct RuntimeConfig {
pub meta_config: MetaConfig, pub working_dir: PathBuf, pub meta_file_path: Option<PathBuf>, pub experimental: bool, }
Key methods:
has_meta_file() - Check if a .meta file was found
meta_root() - Get the root directory of the meta repository
is_experimental() - Check if experimental features are enabled
MetaConfig
The MetaConfig struct represents the contents of a .meta file:
pub struct MetaConfig {
pub ignore: Vec<String>, pub projects: HashMap<String, String>, pub plugins: Option<HashMap<String, String>>, pub nested: Option<NestedConfig>, }
Methods for working with configurations:
load() - Load from the nearest .meta file
load_from_file() - Load from a specific file path
save_to_file() - Save configuration to a file
find_meta_file() - Search for .meta file in parent directories
Complete Example
For a complete working example of a metarepo plugin, see the metarepo-plugin-example in the main repository.
This example demonstrates:
- Command registration with subcommands and arguments
- Accessing the runtime configuration
- Working with meta repository projects
- Handling different command patterns
Plugin Development Guide
1. Create a New Plugin Project
cargo new --lib my-metarepo-plugin
cd my-metarepo-plugin
2. Add Dependencies
[dependencies]
metarepo-core = "0.3"
anyhow = "1.0"
clap = "4.0"
3. Implement the Plugin
Create your plugin implementation in src/lib.rs:
use anyhow::Result;
use clap::{ArgMatches, Command, Arg};
use metarepo_core::{MetaPlugin, RuntimeConfig};
pub struct MyPlugin;
impl MetaPlugin for MyPlugin {
fn name(&self) -> &str {
"myplugin"
}
fn register_commands(&self, app: Command) -> Command {
app.subcommand(
Command::new("myplugin")
.about("My custom metarepo plugin")
.subcommand(
Command::new("list")
.about("List all projects")
)
.subcommand(
Command::new("add")
.about("Add a new project")
.arg(
Arg::new("path")
.required(true)
.help("Path to the project")
)
)
)
}
fn handle_command(&self, matches: &ArgMatches, config: &RuntimeConfig) -> Result<()> {
match matches.subcommand() {
Some(("list", _)) => {
for (path, url) in &config.meta_config.projects {
println!("{}: {}", path, url);
}
Ok(())
}
Some(("add", sub_matches)) => {
let path = sub_matches.get_one::<String>("path").unwrap();
println!("Adding project at: {}", path);
Ok(())
}
_ => Ok(())
}
}
}
4. Testing Your Plugin
You can test your plugin by creating a binary that uses it:
use anyhow::Result;
use clap::Command;
use metarepo_core::{MetaConfig, RuntimeConfig};
use my_metarepo_plugin::MyPlugin;
fn main() -> Result<()> {
let plugin = MyPlugin;
let app = Command::new("test-plugin");
let app = plugin.register_commands(app);
let matches = app.get_matches();
let config = RuntimeConfig {
meta_config: MetaConfig::default(),
working_dir: std::env::current_dir()?,
meta_file_path: None,
experimental: false,
};
plugin.handle_command(&matches, &config)?;
Ok(())
}
Advanced Features
Experimental Plugins
Mark your plugin as experimental to require the --experimental flag:
impl MetaPlugin for MyPlugin {
fn is_experimental(&self) -> bool {
true
}
}
Working with Nested Repositories
The NestedConfig struct provides configuration for handling nested repositories:
pub struct NestedConfig {
pub recursive_import: bool, pub max_depth: usize, pub flatten: bool, pub cycle_detection: bool, pub ignore_nested: Vec<String>, pub namespace_separator: Option<String>, pub preserve_structure: bool, }
Error Handling
Use anyhow::Result for error handling:
use anyhow::{anyhow, Context};
fn handle_command(&self, matches: &ArgMatches, config: &RuntimeConfig) -> Result<()> {
let project_path = matches
.get_one::<String>("path")
.ok_or_else(|| anyhow!("Project path is required"))?;
std::fs::read_dir(project_path)
.context("Failed to read project directory")?;
Ok(())
}
Integration with Metarepo CLI
Once your plugin is built, it can be integrated with the metarepo CLI in several ways:
- Built-in plugins: Compile directly into the metarepo binary
- Dynamic plugins: Load at runtime (requires dynamic loading support)
- External plugins: Run as separate processes communicating via IPC
Refer to the main metarepo documentation for details on plugin integration.
API Stability
The metarepo-core API follows semantic versioning. The MetaPlugin trait and core types are considered stable from version 1.0 onwards. Minor versions may add new optional methods with default implementations.
Contributing
Contributions are welcome! Please see the main repository for contribution guidelines.
License
This project is licensed under the MIT License - see the LICENSE file for details.
Resources