extract_relative_path

Function extract_relative_path 

Source
pub fn extract_relative_path(
    path: &Path,
    resource_type: &ResourceType,
) -> PathBuf
Expand description

Extracts the relative path from a resource by removing the resource type directory prefix.

This function preserves directory structure when installing resources from Git sources by intelligently stripping the resource type directory (e.g., “agents/”, “snippets/”) from source repository paths. This allows subdirectories within a resource category to be maintained in the installation target, enabling organized source repositories to retain their structure.

§Path Processing Strategy

The function implements a prefix-aware extraction algorithm:

  1. Converts the resource type string to its expected directory name (e.g., “agent” → “agents”)
  2. Checks if the path starts with this directory name as its first component
  3. If matched, returns the path with the first component stripped
  4. If not matched, returns the original path unchanged

This approach ensures that:

  • Nested directories within a category are preserved (e.g., agents/ai/helper.mdai/helper.md)
  • Paths without the expected prefix remain unchanged (backwards compatibility)
  • Cross-platform path handling works correctly (Windows and Unix separators)

§Arguments

  • path - The original resource path from the dependency specification (e.g., from a Git repository)
  • resource_type - The resource type string: "agent", "snippet", "command", "script", "hook", or "mcp-server"

§Returns

A PathBuf containing:

  • The path with the resource type prefix removed (if the prefix matched)
  • The original path unchanged (if no prefix matched)

§Resource Type Mapping

Input TypeExpected DirectoryExample Input PathExample Output Path
"agent"agents/agents/ai/helper.mdai/helper.md
"snippet"snippets/snippets/tools/fmt.mdtools/fmt.md
"command"commands/commands/build.mdbuild.md
"script"scripts/scripts/test.shtest.sh
"hook"hooks/hooks/pre-commit.jsonpre-commit.json
"mcp-server"mcp-servers/mcp-servers/db.jsondb.json

§Examples

§Basic Path Extraction

use std::path::{Path, PathBuf};

// Resource type prefix is removed
let path = Path::new("snippets/directives/thing.md");
let result = extract_relative_path(path, &ResourceType::Snippet);
assert_eq!(result, PathBuf::from("directives/thing.md"));

// No matching prefix - path unchanged
let path = Path::new("directives/thing.md");
let result = extract_relative_path(path, &ResourceType::Snippet);
assert_eq!(result, PathBuf::from("directives/thing.md"));

// Works with deeply nested directories
let path = Path::new("agents/ai/helper.md");
let result = extract_relative_path(path, &ResourceType::Agent);
assert_eq!(result, PathBuf::from("ai/helper.md"));

§Preserving Directory Structure


// Multi-level nested directories are fully preserved
let path = Path::new("agents/languages/rust/expert.md");
let result = extract_relative_path(path, &ResourceType::Agent);
assert_eq!(result, PathBuf::from("languages/rust/expert.md"));
// This will install to: .claude/agents/languages/rust/expert.md

§Pattern Matching Use Case

When used with glob patterns like agents/**/*.md, this function ensures each matched file preserves its subdirectory structure:


// Example: glob pattern "agents/**/*.md" matches these paths
let matched_paths = vec![
    "agents/rust/expert.md",
    "agents/rust/testing.md",
    "agents/python/async.md",
    "agents/go/concurrency.md",
];

for path_str in matched_paths {
    let path = Path::new(path_str);
    let relative = extract_relative_path(path, &ResourceType::Agent);
    // Produces: "rust/expert.md", "rust/testing.md", "python/async.md", "go/concurrency.md"
    // Each installs to: .claude/agents/<relative_path>
}

§Integration with Custom Targets

Custom targets work in conjunction with relative path extraction:

# In agpm.toml
[agents]
# Path: agents/rust/expert.md → extract → rust/expert.md
# Target: custom → combined → custom/rust/expert.md
# Final: .claude/agents/custom/rust/expert.md
rust-agents = {
    source = "community",
    path = "agents/rust/*.md",
    target = "custom",
    version = "v1.0.0"
}

§Use Cases

§Organized Source Repository

For a source repository with categorized resources:

agpm-community/
├── agents/
│   ├── languages/
│   │   ├── rust/
│   │   │   ├── expert.md
│   │   │   └── testing.md
│   │   └── python/
│   │       └── async.md
│   └── tools/
│       └── git-helper.md
└── snippets/
    ├── directives/
    │   └── custom.md
    └── templates/
        └── api.md

After installation, the structure is preserved:

.claude/
├── agents/
│   ├── languages/
│   │   ├── rust/
│   │   │   ├── expert.md
│   │   │   └── testing.md
│   │   └── python/
│   │       └── async.md
│   └── tools/
│       └── git-helper.md
└── snippets/
    ├── directives/
    │   └── custom.md
    └── templates/
        └── api.md

§Pattern-Based Installation

Bulk installation with patterns preserves organization:

[agents]
# Installs all Rust agents with subdirectory structure intact
rust-tools = { source = "community", path = "agents/languages/rust/**/*.md", version = "v1.0.0" }
# Results in: .claude/agents/<files from rust/ and subdirectories>

§Implementation Notes

  • Uses path component analysis for cross-platform compatibility
  • Only examines the first path component to determine prefix match
  • Empty paths or invalid components are handled gracefully
  • Unknown resource types cause the path to be returned unchanged
  • Works with both absolute and relative paths from source repositories

§Version History

  • v0.3.18: Introduced to support relative path preservation during installation
  • Works in conjunction with updated lockfile installed_at path generation