Rust Library Guide
SwissArmyHammer is available as a Rust library (swissarmyhammer
) that you can integrate into your own applications. This guide covers installation, basic usage, and advanced integration patterns.
Installation
Add SwissArmyHammer to your Cargo.toml
:
[dependencies]
swissarmyhammer = { git = "https://github.com/wballard/swissarmyhammer", features = ["full"] }
Feature Flags
Control which functionality to include:
[dependencies]
swissarmyhammer = {
git = "https://github.com/wballard/swissarmyhammer",
features = ["prompts", "templates", "search", "mcp"]
}
Available features:
prompts
- Core prompt management (always enabled)templates
- Liquid template engine with custom filterssearch
- Full-text search capabilitiesmcp
- Model Context Protocol server supportstorage
- Advanced storage backendsfull
- All features enabled
Quick Start
Basic Prompt Library
use swissarmyhammer::{PromptLibrary, ArgumentSpec};
use std::collections::HashMap;
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
// Create a new prompt library
let mut library = PromptLibrary::new();
// Add prompts from a directory
library.add_directory("./prompts").await?;
// List available prompts
for prompt_id in library.list_prompts() {
println!("Available prompt: {}", prompt_id);
}
// Get a specific prompt
let prompt = library.get("code-review")?;
println!("Title: {}", prompt.title());
println!("Description: {}", prompt.description());
// Prepare arguments
let mut args = HashMap::new();
args.insert("code".to_string(), "fn main() { println!(\"Hello\"); }".to_string());
args.insert("language".to_string(), "rust".to_string());
// Render the prompt
let rendered = prompt.render(&args)?;
println!("Rendered prompt:\n{}", rendered);
Ok(())
}
Custom Prompt Creation
use swissarmyhammer::{Prompt, ArgumentSpec, PromptMetadata};
fn create_custom_prompt() -> Result<Prompt, Box<dyn std::error::Error>> {
let metadata = PromptMetadata {
title: "Custom Code Review".to_string(),
description: "A custom code review prompt".to_string(),
arguments: vec![
ArgumentSpec {
name: "code".to_string(),
description: "Code to review".to_string(),
required: true,
default: None,
},
ArgumentSpec {
name: "focus".to_string(),
description: "Review focus area".to_string(),
required: false,
default: Some("general".to_string()),
},
],
};
let template = r#"
Code Review: {{ focus | capitalize }}
Please review this code:
{{ code }}
{% if focus == "security" %}
Focus specifically on security vulnerabilities and best practices.
{% elsif focus == "performance" %}
Focus on performance optimizations and efficiency.
{% else %}
Perform a general code review covering style, bugs, and maintainability.
{% endif %}
"#;
Prompt::from_parts(metadata, template)
}
Core Components
PromptLibrary
The main interface for managing collections of prompts.
use swissarmyhammer::PromptLibrary;
let mut library = PromptLibrary::new();
// Add prompts from various sources
library.add_directory("./prompts").await?;
library.add_file("./special-prompt.md").await?;
library.add_builtin_prompts();
// Query prompts
let prompts = library.list_prompts();
let prompt = library.get("prompt-id")?;
let filtered = library.filter_by_category("review");
// Search prompts
let results = library.search("code review")?;
Prompt
Individual prompt with metadata and template.
use swissarmyhammer::Prompt;
// Load from file
let prompt = Prompt::from_file("./prompts/review.md").await?;
// Access metadata
println!("Title: {}", prompt.title());
println!("Description: {}", prompt.description());
for arg in prompt.arguments() {
println!("Argument: {} (required: {})", arg.name, arg.required);
}
// Render with arguments
let mut args = HashMap::new();
args.insert("code".to_string(), "example code".to_string());
let rendered = prompt.render(&args)?;
Template Engine
Advanced template processing with custom filters.
use swissarmyhammer::template::{TemplateEngine, TemplateContext};
let engine = TemplateEngine::new();
let template = "Hello {{ name | capitalize }}! Today is {{ 'now' | format_date: '%Y-%m-%d' }}.";
let mut context = TemplateContext::new();
context.insert("name", "alice");
let result = engine.render(template, &context)?;
println!("{}", result); // "Hello Alice! Today is 2024-01-15."
Advanced Usage
Custom Storage Backend
Implement your own storage for prompts:
use swissarmyhammer::storage::{StorageBackend, PromptSource};
use async_trait::async_trait;
struct DatabaseStorage {
// Your database connection
db: Database,
}
#[async_trait]
impl StorageBackend for DatabaseStorage {
async fn list_prompts(&self) -> Result<Vec<String>, StorageError> {
// Implement database query
todo!()
}
async fn get_prompt(&self, id: &str) -> Result<PromptSource, StorageError> {
// Implement database retrieval
todo!()
}
async fn save_prompt(&mut self, id: &str, source: &PromptSource) -> Result<(), StorageError> {
// Implement database storage
todo!()
}
}
// Use custom storage
let storage = DatabaseStorage::new(db);
let mut library = PromptLibrary::with_storage(storage);
Search Integration
Advanced search capabilities:
use swissarmyhammer::search::{SearchEngine, SearchQuery, SearchResult};
let mut search_engine = SearchEngine::new();
// Index prompts
search_engine.index_prompt("code-review", &prompt).await?;
// Perform searches
let query = SearchQuery::new("code review")
.with_field("title")
.with_limit(10)
.case_sensitive(false);
let results: Vec<SearchResult> = search_engine.search(&query)?;
for result in results {
println!("Found: {} (score: {:.2})", result.id, result.score);
}
MCP Server Integration
Embed MCP server functionality:
use swissarmyhammer::mcp::{McpServer, McpConfig};
let config = McpConfig {
name: "my-prompt-server".to_string(),
version: "1.0.0".to_string(),
// ... other config
};
let mut library = PromptLibrary::new();
library.add_directory("./prompts").await?;
let server = McpServer::new(config, library);
// Run MCP server
server.serve().await?;
Event System
React to library events:
use swissarmyhammer::events::{EventHandler, PromptEvent};
struct MyEventHandler;
impl EventHandler for MyEventHandler {
fn handle_prompt_added(&self, id: &str) {
println!("Prompt added: {}", id);
}
fn handle_prompt_updated(&self, id: &str) {
println!("Prompt updated: {}", id);
}
fn handle_prompt_removed(&self, id: &str) {
println!("Prompt removed: {}", id);
}
}
let mut library = PromptLibrary::new();
library.add_event_handler(Box::new(MyEventHandler));
File Watching
Automatically reload prompts when files change:
use swissarmyhammer::watcher::FileWatcher;
let mut library = PromptLibrary::new();
library.add_directory("./prompts").await?;
// Start watching for file changes
let _watcher = FileWatcher::new("./prompts", move |event| {
match event {
FileEvent::Created(path) => {
if let Err(e) = library.reload_file(&path) {
eprintln!("Failed to load {}: {}", path.display(), e);
}
}
FileEvent::Modified(path) => {
if let Err(e) = library.reload_file(&path) {
eprintln!("Failed to reload {}: {}", path.display(), e);
}
}
FileEvent::Deleted(path) => {
library.remove_file(&path);
}
}
});
// Keep the watcher alive
std::thread::sleep(std::time::Duration::from_secs(60));
Integration Examples
Web Server Integration
use axum::{extract::Path, http::StatusCode, response::Json, routing::get, Router};
use swissarmyhammer::PromptLibrary;
use std::sync::Arc;
use tokio::sync::RwLock;
type SharedLibrary = Arc<RwLock<PromptLibrary>>;
async fn list_prompts(library: SharedLibrary) -> Json<Vec<String>> {
let lib = library.read().await;
Json(lib.list_prompts())
}
async fn get_prompt(
Path(id): Path<String>,
library: SharedLibrary,
) -> Result<Json<String>, StatusCode> {
let lib = library.read().await;
match lib.get(&id) {
Ok(prompt) => Ok(Json(prompt.title().to_string())),
Err(_) => Err(StatusCode::NOT_FOUND),
}
}
#[tokio::main]
async fn main() {
let mut library = PromptLibrary::new();
library.add_directory("./prompts").await.unwrap();
let shared_library = Arc::new(RwLock::new(library));
let app = Router::new()
.route("/prompts", get({
let lib = shared_library.clone();
move || list_prompts(lib)
}))
.route("/prompts/:id", get({
let lib = shared_library.clone();
move |path| get_prompt(path, lib)
}));
axum::Server::bind(&"0.0.0.0:3000".parse().unwrap())
.serve(app.into_make_service())
.await
.unwrap();
}
CLI Tool Integration
use clap::{Arg, Command};
use swissarmyhammer::PromptLibrary;
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let matches = Command::new("my-prompt-tool")
.arg(Arg::new("prompt")
.help("Prompt ID to render")
.required(true)
.index(1))
.arg(Arg::new("args")
.help("Template arguments as key=value pairs")
.multiple_values(true)
.short('a')
.long("arg"))
.get_matches();
let mut library = PromptLibrary::new();
library.add_directory("./prompts").await?;
let prompt_id = matches.value_of("prompt").unwrap();
let prompt = library.get(prompt_id)?;
let mut args = std::collections::HashMap::new();
if let Some(arg_values) = matches.values_of("args") {
for arg in arg_values {
if let Some((key, value)) = arg.split_once('=') {
args.insert(key.to_string(), value.to_string());
}
}
}
let rendered = prompt.render(&args)?;
println!("{}", rendered);
Ok(())
}
Configuration Management
use serde::{Deserialize, Serialize};
use swissarmyhammer::{PromptLibrary, storage::FileSystemStorage};
#[derive(Serialize, Deserialize)]
struct AppConfig {
prompt_directories: Vec<String>,
default_arguments: std::collections::HashMap<String, String>,
search_enabled: bool,
}
impl Default for AppConfig {
fn default() -> Self {
Self {
prompt_directories: vec!["./prompts".to_string()],
default_arguments: std::collections::HashMap::new(),
search_enabled: true,
}
}
}
async fn setup_library(config: &AppConfig) -> Result<PromptLibrary, Box<dyn std::error::Error>> {
let mut library = PromptLibrary::new();
for dir in &config.prompt_directories {
library.add_directory(dir).await?;
}
if config.search_enabled {
library.enable_search()?;
}
Ok(library)
}
Error Handling
SwissArmyHammer uses comprehensive error types:
use swissarmyhammer::error::{SwissArmyHammerError, PromptError, TemplateError};
match library.get("nonexistent") {
Ok(prompt) => {
// Handle success
}
Err(SwissArmyHammerError::PromptNotFound(id)) => {
eprintln!("Prompt '{}' not found", id);
}
Err(SwissArmyHammerError::Template(TemplateError::RenderError(msg))) => {
eprintln!("Template rendering failed: {}", msg);
}
Err(SwissArmyHammerError::Io(io_err)) => {
eprintln!("I/O error: {}", io_err);
}
Err(e) => {
eprintln!("Unexpected error: {}", e);
}
}
Testing
SwissArmyHammer provides testing utilities:
use swissarmyhammer::testing::{MockPromptLibrary, PromptTestCase};
#[tokio::test]
async fn test_prompt_rendering() {
let mut library = MockPromptLibrary::new();
let test_case = PromptTestCase::new("test-prompt")
.with_template("Hello {{ name }}!")
.with_argument("name", "World")
.expect_output("Hello World!");
library.add_test_prompt(test_case);
let prompt = library.get("test-prompt").unwrap();
let mut args = std::collections::HashMap::new();
args.insert("name".to_string(), "World".to_string());
let result = prompt.render(&args).unwrap();
assert_eq!(result, "Hello World!");
}
Performance Considerations
Memory Usage
- Prompt libraries cache parsed templates in memory
- Large collections may require custom storage backends
- Use lazy loading for better memory efficiency
Concurrency
PromptLibrary
isSend + Sync
when used with appropriate storage- Template rendering is thread-safe
- Consider using
Arc<RwLock<PromptLibrary>>
for shared access
Best Practices
- Prefer batch operations for multiple prompts
- Cache rendered templates when arguments don’t change
- Use feature flags to include only needed functionality
- Implement proper error handling for production use
Migration from CLI
If you’re migrating from using the CLI to the library:
// CLI equivalent: swissarmyhammer search "code review"
let results = library.search("code review")?;
// CLI equivalent: swissarmyhammer test prompt-id --arg key=value
let prompt = library.get("prompt-id")?;
let mut args = HashMap::new();
args.insert("key".to_string(), "value".to_string());
let rendered = prompt.render(&args)?;
// CLI equivalent: swissarmyhammer export --all output.tar.gz
library.export_all("output.tar.gz", ExportFormat::TarGz)?;
See Also
- Library API Reference - Complete API documentation
- Integration Examples - More integration patterns
- Custom Filters - Template customization
- Advanced Prompts - Complex template patterns