syntaxis/shared/rust/INTEGRATION_GUIDE.md
Jesús Pérez 9cef9b8d57 refactor: consolidate configuration directories
Merge _configs/ into config/ for single configuration directory.
Update all path references.

Changes:
- Move _configs/* to config/
- Update .gitignore for new patterns
- No code references to _configs/ found

Impact: -1 root directory (layout_conventions.md compliance)
2025-12-26 18:36:23 +00:00

12 KiB

Tools Shared Utilities Integration Guide

This guide explains how to integrate shared utilities into your Tools ecosystem Rust CLI.

Quick Start

1. Add Dependency to Cargo.toml

In your CLI crate (e.g., crates/your-cli/Cargo.toml):

[dependencies]
tools-shared = { path = "../../../shared/rust" }

# For interactive project selection
tools-shared = { path = "../../../shared/rust", features = ["interactive"] }

# For manifest management
tools-shared = { path = "../../../shared/rust", features = ["manifest"] }

# All features
tools-shared = { path = "../../../shared/rust", features = ["interactive", "manifest"] }

2. Import in main.rs

use tools_shared::{
    // Configuration discovery
    find_config_path, find_db_path,

    // Project features
    detect_project_from_cwd, SelectableProject, select_project,

    // Database migrations
    find_migration_files, run_migration_file,

    // XDG directories
    tool_data_dir, tool_config_dir, ensure_dir,

    // Global database
    find_global_db_path,
};

3. Use in Your Code

See examples below for each feature.

Feature Integration Examples

Configuration Discovery

Find configuration files automatically:

use tools_shared::find_config_path;

#[derive(Parser)]
struct Cli {
    #[arg(short, long)]
    config: Option<String>,
}

#[tokio::main]
async fn main() -> Result<()> {
    let cli = Cli::parse();

    // Use provided config or search
    let config_path = cli.config
        .map(PathBuf::from)
        .or_else(|| find_config_path("my-tool.toml"))
        .ok_or_else(|| anyhow::anyhow!("No config found"))?;

    let config = Config::load(&config_path)?;

    Ok(())
}

Project Auto-Detection

Add a global --project flag that auto-detects projects:

use tools_shared::detect_project_from_cwd;

#[derive(Parser)]
struct Cli {
    #[arg(short, long, global = true, help = "Project name (auto-detected if omitted)")]
    project: Option<String>,

    #[command(subcommand)]
    command: Commands,
}

#[tokio::main]
async fn main() -> Result<()> {
    let cli = Cli::parse();

    // Auto-detect if not specified
    let project_name = cli.project
        .clone()
        .or_else(|| {
            detect_project_from_cwd("config.toml")
                .map(|p| p.name)
        });

    if let Some(name) = project_name {
        println!("Working on project: {}", name);
    } else {
        println!("No project specified or detected");
    }

    Ok(())
}

Interactive Project Selection

Add a command that lets users select a project:

use tools_shared::{select_project, SelectableProject};

#[derive(Subcommand)]
enum Commands {
    /// Select a project interactively
    SelectProject,
}

async fn select_project_command(projects: Vec<ProjectEntity>) -> Result<String> {
    let selectable: Vec<SelectableProject> = projects
        .into_iter()
        .map(|p| SelectableProject {
            id: p.id.clone(),
            name: p.name,
            path: None,
            metadata: Some(format!("Phase: {:?}", p.current_phase)),
        })
        .collect();

    select_project(selectable, "Select a project:", None)
        .map_err(|e| anyhow::anyhow!("Selection failed: {}", e))
}

Database Migrations

Add migration support:

use tools_shared::{find_migration_files, run_migration_file};

#[derive(Subcommand)]
enum Commands {
    /// Run migrations
    Migrate {
        /// Path to migration file
        #[arg(long)]
        file: Option<String>,

        /// Find and run all migrations in data/migration/
        #[arg(long)]
        all: Option<String>,
    },
}

async fn handle_migrate(file: Option<String>, all: Option<String>) -> Result<()> {
    if let Some(sql_file) = file {
        // Run single migration
        run_migration_file(&sql_file)?;
        println!("✅ Migration executed");
    } else if let Some(root) = all {
        // Find and run all migrations
        let migrations = find_migration_files(&root)?;
        println!("Found {} migrations", migrations.len());

        for migration in migrations {
            if let Some(name) = migration.file_name() {
                println!("Running: {:?}", name);
                // Execute migration (requires sqlx or other DB driver)
            }
        }
    }

    Ok(())
}

XDG Directories for Data Storage

Use standard directories for your tool's data:

use tools_shared::{tool_data_dir, tool_config_dir, ensure_dir};
use std::path::PathBuf;

#[tokio::main]
async fn main() -> Result<()> {
    let tool_name = "my-tool";

    // Create standard directories
    let data_dir = tool_data_dir(tool_name);
    let config_dir = tool_config_dir(tool_name);

    ensure_dir(&data_dir)?;
    ensure_dir(&config_dir)?;

    // Now use them
    let db_path = data_dir.join("my-tool.db");
    let config_path = config_dir.join("config.toml");

    println!("Data: {}", db_path.display());
    println!("Config: {}", config_path.display());

    Ok(())
}

Global Multi-Project Database

Use a single global database to manage multiple projects:

use tools_shared::find_global_db_path;
use sqlx::sqlite::SqlitePool;

#[tokio::main]
async fn main() -> Result<()> {
    // Get global database path
    let db_path = find_global_db_path("my-tool");
    let db_url = format!("sqlite://{}", db_path.display());

    // Create database if it doesn't exist
    if !db_path.exists() {
        std::fs::create_dir_all(db_path.parent().unwrap())?;
    }

    // Connect to database
    let pool = SqlitePool::connect(&db_url).await?;

    // Run migrations
    sqlx::query("CREATE TABLE IF NOT EXISTS projects (
        id TEXT PRIMARY KEY,
        name TEXT NOT NULL,
        phase TEXT NOT NULL
    )")
    .execute(&pool)
    .await?;

    Ok(())
}

Integration Checklist

When adding shared features to your CLI:

Configuration Discovery

  • Add tools-shared dependency
  • Update Cargo.toml help text to document search order
  • Use find_config_path() to discover config files
  • Test configuration discovery from multiple locations

Project Detection

  • Add global --project flag to CLI struct
  • Implement fallback to detect_project_from_cwd()
  • Add project detect command to show detected project
  • Cache project selection with save_last_project()

Project Selection

  • Add features = ["interactive"] to tools-shared
  • Create struct list of available projects
  • Implement project selection command
  • Use select_project() for interactive picker

Database Migrations

  • Add migration file discovery
  • Create data/migration/ directory in examples
  • Implement migration commands (migrate --file and migrate --all)
  • Create sample migration files for testing

XDG Directories

  • Use tool_data_dir() for user data
  • Use tool_config_dir() for configuration
  • Use tool_cache_dir() for temporary data
  • Call ensure_dir() before writing files

Global Database

  • Use find_global_db_path() for database location
  • Create database parent directories if missing
  • Store per-project data with project IDs
  • Document multi-project schema

Testing

  • Test all configuration discovery paths
  • Test project detection in various directories
  • Test migration file discovery and execution
  • Test directory creation with ensure_dir()
  • Test database connectivity

Documentation

  • Update help text with configuration search order
  • Document --project flag usage
  • Add examples to README.md
  • Document migration file format

File Structure for a New Tool

When creating a new tool with shared features:

my-tool/
├── Cargo.toml                       # Workspace root
├── README.md                        # Tool documentation
├── QUICKSTART.md                   # 5-minute setup guide
├── crates/
│   ├── my-core/                    # Core library
│   │   ├── src/
│   │   │   ├── lib.rs
│   │   │   ├── error.rs
│   │   │   └── service.rs
│   │   └── Cargo.toml
│   │
│   └── my-cli/                     # CLI using shared features
│       ├── src/
│       │   ├── main.rs             # Imports tools-shared
│       │   └── handlers/           # Command handlers
│       └── Cargo.toml              # Has tools-shared dependency
│
├── data/
│   └── migration/                  # SQL migrations
│       ├── 001_initial.sql
│       └── 002_add_index.sql
│
└── examples/
    └── config.toml                 # Example configuration

Examples in Tools Ecosystem

Study these existing implementations:

Simple: hello-tool

  • Basic project detection
  • Migration file discovery
  • Interactive selection example
# View implementation
cat /Users/Akasha/Tools/hello-tool/crates/hello-cli/src/main.rs

Advanced: syntaxis

  • Global --project flag
  • Project commands (list, show, detect, select)
  • Database migration handler
  • Multi-project global database
# View implementation
cat /Users/Akasha/Development/syntaxis/crates/syntaxis-cli/src/main.rs

Migration Examples

  • Check how project-lifecycle uses migrations:
cat /Users/Akasha/Development/syntaxis/crates/syntaxis-cli/src/handlers/migrate_db.rs

Testing Integration

Before publishing, verify:

# Build your tool
cargo build -p my-cli

# Test project detection
./target/debug/my-tool project detect

# Test configuration discovery
./target/debug/my-tool --config ./my-tool.toml <command>

# Test migration discovery
./target/debug/my-tool migrate --all .

# Run full test suite
cargo test --workspace

Common Issues

Configuration Not Found

Problem: find_config_path() returns None even though file exists

Solution:

  • Check exact filename (case-sensitive)
  • Ensure file is in one of the search locations
  • Run from project root directory
  • Use find_config_path_warn_conflicts() to debug

Project Not Detected

Problem: detect_project_from_cwd() returns None

Solution:

  • Verify .project/lifecycle.toml or .vapora/lifecycle.toml exists
  • Check current working directory
  • Run from project subdirectory (detection searches parents)
  • Use detect_project_from_path() with explicit path

Interactive Selection Not Working

Problem: select_project() shows no options

Solution:

  • Ensure features = ["interactive"] in Cargo.toml
  • Pass non-empty project list
  • Check terminal supports ANSI (for colors/formatting)
  • Test with --no-interactive flag if available

Migrations Not Found

Problem: find_migration_files() returns empty vector

Solution:

  • Create data/migration/ directory
  • Add .sql files (lowercase extension)
  • Ensure files are readable
  • Run from correct directory or use absolute path

Migration Guide

From Local Database to Global

If your tool previously used per-project databases:

// Old way (per-project)
let db_path = format!(".project/data/{}.db", project_name);
let db = Database::open(&db_path)?;

// New way (global, multi-project)
let db_path = find_global_db_path("my-tool");
let db = Database::open(&db_path)?;

// Store project context in data
let projects = db.get_projects()?;
let project = projects.iter().find(|p| p.name == project_name)?;
let entries = db.get_entries(project.id)?;

Best Practices

  1. Always handle missing files gracefully - Don't assume configuration exists
  2. Provide good error messages - Tell users where to create config
  3. Cache project selection - Use save_last_project() for quick re-runs
  4. Support explicit overrides - Allow --project flag even when auto-detected
  5. Document search order - Show users in help text where config is searched
  6. Use XDG directories - Don't hardcode paths like ~/.myapp/
  7. Support global database - Use single DB for multi-project management
  8. Plan migrations - Create data/migration/ structure from start

Getting Help

  • Read source code: /Users/Akasha/Tools/shared/rust/
  • Check examples: /Users/Akasha/Tools/hello-tool/crates/hello-cli/src/main.rs
  • Study advanced usage: /Users/Akasha/Development/syntaxis/
  • Run tests: cargo test -p tools-shared
  • API docs: cargo doc -p tools-shared --open

Next Steps

  1. Add shared dependency to your CLI
  2. Follow "Quick Start" section above
  3. Implement features incrementally
  4. Test each feature thoroughly
  5. Update documentation
  6. Commit with clear commit message
  7. Share with the team!