| Crates.io | metarepo-core |
| lib.rs | metarepo-core |
| version | 0.11.0 |
| created_at | 2025-09-05 22:34:32.50426+00 |
| updated_at | 2025-11-21 23:21:13.100137+00 |
| description | Core interfaces and types for the metarepo multi-project management tool |
| homepage | https://github.com/codyaverett/metarepo |
| repository | https://github.com/codyaverett/metarepo |
| max_upload_size | |
| id | 1826354 |
| size | 174,653 |
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.
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.
Add this to your Cargo.toml:
[dependencies]
metarepo-core = "0.3"
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(())
}
}
}
The MetaPlugin trait is the main interface that all plugins must implement:
pub trait MetaPlugin: Send + Sync {
/// Returns the plugin name (used for command routing)
fn name(&self) -> &str;
/// Register CLI commands for this plugin
fn register_commands(&self, app: Command) -> Command;
/// Handle a command for this plugin
fn handle_command(&self, matches: &ArgMatches, config: &RuntimeConfig) -> Result<()>;
/// Returns true if this plugin is experimental (default: false)
fn is_experimental(&self) -> bool {
false
}
}
The RuntimeConfig struct provides access to the meta repository configuration and environment:
pub struct RuntimeConfig {
pub meta_config: MetaConfig, // The loaded .meta file configuration
pub working_dir: PathBuf, // Current working directory
pub meta_file_path: Option<PathBuf>, // Path to the .meta file (if found)
pub experimental: bool, // Whether experimental features are enabled
}
Key methods:
has_meta_file() - Check if a .meta file was foundmeta_root() - Get the root directory of the meta repositoryis_experimental() - Check if experimental features are enabledThe MetaConfig struct represents the contents of a .meta file:
pub struct MetaConfig {
pub ignore: Vec<String>, // Patterns to ignore
pub projects: HashMap<String, String>, // Project paths -> repository URLs
pub plugins: Option<HashMap<String, String>>, // Plugin configurations
pub nested: Option<NestedConfig>, // Nested repository settings
}
Methods for working with configurations:
load() - Load from the nearest .meta fileload_from_file() - Load from a specific file pathsave_to_file() - Save configuration to a filefind_meta_file() - Search for .meta file in parent directoriesFor a complete working example of a metarepo plugin, see the metarepo-plugin-example in the main repository.
This example demonstrates:
cargo new --lib my-metarepo-plugin
cd my-metarepo-plugin
[dependencies]
metarepo-core = "0.3"
anyhow = "1.0"
clap = "4.0"
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);
// Implementation here
Ok(())
}
_ => Ok(())
}
}
}
You can test your plugin by creating a binary that uses it:
// src/main.rs
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();
// Create a test runtime config
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(())
}
Mark your plugin as experimental to require the --experimental flag:
impl MetaPlugin for MyPlugin {
fn is_experimental(&self) -> bool {
true
}
// ... other methods
}
The NestedConfig struct provides configuration for handling nested repositories:
pub struct NestedConfig {
pub recursive_import: bool, // Import nested repos recursively
pub max_depth: usize, // Maximum nesting depth
pub flatten: bool, // Flatten nested structure
pub cycle_detection: bool, // Detect circular dependencies
pub ignore_nested: Vec<String>, // Patterns to ignore
pub namespace_separator: Option<String>, // Separator for namespaces
pub preserve_structure: bool, // Preserve directory structure
}
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(())
}
Once your plugin is built, it can be integrated with the metarepo CLI in several ways:
Refer to the main metarepo documentation for details on plugin integration.
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.
Contributions are welcome! Please see the main repository for contribution guidelines.
This project is licensed under the MIT License - see the LICENSE file for details.