Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Architecture

scuv is built in Rust using a modular architecture.

Module Structure

src/
├── cli/              # Command-line interface
│   ├── mod.rs        # Cli struct, Commands enum, ShellType
│   └── commands/     # Individual command handlers
├── core/             # Domain logic
│   ├── metadata.rs   # Virtualenv metadata (JSON)
│   ├── version.rs    # Version file resolution
│   ├── virtualenv/   # Virtualenv entity (mod.rs + tests.rs)
│   ├── doctor/       # Health check system (engine, types, checks/)
│   ├── manifest.rs   # Sync manifest (.scuv.toml, backs `scuv sync`)
│   ├── export_schema.rs  # Export/import schema (backs `scuv export`/`import`)
│   └── migrate/      # Migration from pyenv/conda/virtualenvwrapper
│       ├── mod.rs
│       ├── discovery.rs  # Source detection
│       ├── migrator.rs   # Migration orchestrator
│       └── ...
├── shell/            # Shell integration
│   ├── mod.rs        # Shell module exports & detection
│   ├── common.rs     # Shared shell utilities & macros
│   ├── bash.rs       # Bash init script
│   ├── zsh.rs        # Zsh init script
│   ├── fish.rs       # Fish init script
│   └── powershell.rs # PowerShell init script
├── output/           # Terminal UI and JSON output
│   └── time.rs       # last_used fuzzy-age formatter
├── uv/               # uv CLI wrapper
├── error/            # ScoopError module
│   └── exit.rs       # Exit code policy
├── paths.rs          # Path utilities
├── validate.rs       # Input validation
├── i18n.rs           # Internationalization
└── config.rs         # Configuration management

Module Dependency Graph

graph TB
    CLI[cli/] --> Core[core/]
    CLI --> Shell[shell/]
    CLI --> Output[output/]
    CLI --> I18N[i18n]
    CLI --> Config[config]

    Core --> UV[uv/]
    Core --> Paths[paths]
    Core --> Error[error]
    Core --> Validate[validate]
    Core --> Config

    Shell --> Paths

    Output --> Error
    Output --> I18N

    UV --> Error

    Config --> Paths
    Config --> Error

    style CLI fill:#e1f5ff
    style Core fill:#fff3e0
    style Shell fill:#f3e5f5
    style Output fill:#e8f5e9

Key Components

CLI Layer (cli/)

  • Uses clap for argument parsing
  • Cli struct defines global options
  • Commands enum defines subcommands
  • Each command has an execute function in commands/

Core Layer (core/)

ModulePurpose
doctorHealth check system with Check trait
metadataJSON metadata for virtualenvs
versionVersion file discovery and parsing
virtualenvVirtualenv entity and operations

Shell Layer (shell/)

Generates shell scripts for integration:

  • init_script() - Returns shell initialization code
  • Wrapper function for scuv command
  • Auto-activation hooks
  • Tab completion definitions

Output Layer (output/)

Handles terminal output formatting:

UV Layer (uv/)

Wraps the uv CLI for Python/virtualenv operations:

  • Python installation
  • Virtualenv creation
  • Version listing

Design Patterns

Shell Eval Pattern

The CLI outputs shell code to stdout, which the shell evaluates:

# User runs
scuv activate myenv

# CLI outputs
export VIRTUAL_ENV="/Users/x/.scuv/virtualenvs/myenv"
export PATH="/Users/x/.scuv/virtualenvs/myenv/bin:$PATH"
export SCUV_ACTIVE="myenv"

# Shell wrapper evaluates this output
eval "$(command scuv activate --shell bash myenv)"

This pattern is used by pyenv, rbenv, and other version managers.

Error Handling

Uses thiserror for error types:

#![allow(unused)]
fn main() {
// Uses thiserror for Error trait derive
// Display impl is manual (not #[error] attributes) for i18n support
#[derive(Debug, Error)]
pub enum ScoopError {
    VirtualenvNotFound { name: String },
    VirtualenvExists { name: String },
    // ...
}

// Manual Display implementation using rust-i18n
impl std::fmt::Display for ScoopError {
    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
        match self {
            Self::VirtualenvNotFound { name } => {
                write!(f, "{}", t!("error.virtualenv_not_found", name = name))
            }
            // ...
        }
    }
}
}

Path Management

Centralizes path logic in paths.rs:

  • scoop_home() - Returns SCUV_HOME or ~/.scuv
  • virtualenvs_dir() - Returns virtualenvs directory
  • global_version_file() - Returns global version file path (~/.scuv/version)
  • local_version_file(dir) - Returns local version file path in directory

Data Flow

Command Execution Flow

sequenceDiagram
    participant User
    participant CLI as CLI Parser
    participant Cmd as Command Handler
    participant Core as Core Logic
    participant UV as UV Wrapper
    participant Output as Output Formatter

    User->>CLI: scuv create myenv 3.12
    CLI->>Cmd: parse & dispatch
    Cmd->>Core: VirtualenvService::create()
    Core->>UV: uv venv create
    UV-->>Core: success/error
    Core-->>Cmd: Result<()>
    Cmd->>Output: format response
    Output-->>User: stdout/stderr

Shell Integration Flow

sequenceDiagram
    participant User
    participant Shell as Shell Wrapper
    participant CLI as scuv CLI
    participant Core as Core Logic

    User->>Shell: scuv use myenv
    Shell->>CLI: command scuv use myenv
    CLI->>Core: resolve version & path
    Core-->>CLI: env vars to export
    CLI-->>Shell: echo shell script
    Shell->>Shell: eval output
    Shell-->>User: (myenv) $
    Note over User: Environment activated

Version Resolution Flow

graph LR
    Start([User runs command]) --> Env{SCUV_VERSION<br/>env set?<br/><small>shell hook</small>}
    Env -->|Yes| Use[Use env value]
    Env -->|No| Local{.scuv-version<br/>in current/parent<br/>dirs?}
    Local -->|Yes| Use
    Local -->|No| Global{~/.scuv/version<br/>exists?}
    Global -->|Yes| Use
    Global -->|No| None[No version<br/>system Python]

    style Use fill:#c8e6c9
    style None fill:#fff9c4

Note: .python-version is not supported. Version resolution walks up parent directories to find .scuv-version.

Health Check Flow

flowchart TD
    Start([scuv doctor]) --> Init[Initialize Doctor]
    Init --> Run[Run all checks]

    Run --> UV{UV Check}
    UV -->|Pass| Home{Home Check}
    UV -->|Fail| Fix1[Suggest: install uv]

    Home -->|Pass| Venv{Venv Check}
    Home -->|Fail| Fix2[Auto-fix: mkdir]

    Venv -->|Pass| Link{Symlink Check}
    Venv -->|Warn| Warn1[Warn: corrupted env]

    Link -->|Pass| Shell{Shell Check}
    Link -->|Fail| Fix3[Auto-fix: remove broken links]

    Shell -->|Pass| Ver{Version Check}
    Shell -->|Warn| Warn2[Warn: not initialized]

    Ver -->|Pass| Legacy{Legacy Check}
    Ver -->|Warn| Warn3[Warn: invalid version file]

    Legacy -->|Pass| Done([All checks passed])
    Legacy -->|Warn| Warn4[Warn: leftover scoop state]

    Fix1 --> Report[Generate Report]
    Fix2 --> Report
    Fix3 --> Report
    Warn1 --> Report
    Warn2 --> Report
    Warn3 --> Report
    Done --> Report

    style Done fill:#c8e6c9
    style Fix1 fill:#ffcdd2
    style Fix2 fill:#ffcdd2
    style Fix3 fill:#ffcdd2
    style Warn1 fill:#fff9c4
    style Warn2 fill:#fff9c4
    style Warn3 fill:#fff9c4

Migration Architecture

scuv supports migrating environments from pyenv-virtualenv, virtualenvwrapper, and conda.

graph TD
    Start([scuv migrate]) --> Detect[Detect Sources]

    Detect --> Pyenv{pyenv-virtualenv}
    Detect --> Venv{virtualenvwrapper}
    Detect --> Conda{conda}

    Pyenv -->|Found| P1[List ~/.pyenv/versions]
    Venv -->|Found| V1[List $WORKON_HOME]
    Conda -->|Found| C1[conda env list]

    P1 --> Parse[Parse metadata]
    V1 --> Parse
    C1 --> Parse

    Parse --> Create[Create in scuv]
    Create --> Copy[Copy packages]
    Copy --> Meta[Write metadata]
    Meta --> Done([Migration complete])

    Pyenv -->|Not found| Skip1[Skip]
    Venv -->|Not found| Skip2[Skip]
    Conda -->|Not found| Skip3[Skip]

    Skip1 --> Check{Any source found?}
    Skip2 --> Check
    Skip3 --> Check

    Check -->|Yes| Done
    Check -->|No| Error[Error: No sources]

    style Done fill:#c8e6c9
    style Error fill:#ffcdd2

Dependencies

CratePurpose
clapArgument parsing & completion
clap_completeShell completion generation
serdeJSON serialization
serde_jsonMetadata persistence
thiserrorError type definitions
owo-colorsTerminal colors
indicatifProgress bars
dialoguerInteractive prompts
dirsHome directory resolution
whichBinary lookup (uv, python)
regexVersion parsing & validation
walkdirDirectory traversal
rust-i18nInternationalization (en, ko, ja, pt-BR, es)
sys-localeSystem locale detection
chronoTimestamp generation
rayonParallel scuv migrate all
toml.scuv.toml manifest parsing (scuv sync)
clap_mangenMan page generation (scuv man)

Extension Points

Adding a New Shell

  1. Create shell/myshell.rs:
#![allow(unused)]
fn main() {
pub fn init_script() -> &'static str {
    // Return shell-specific initialization code as static string
    r#"
    Shell initialization code here
    scuv() { ... }
    "#
}
}

Note: scuv completions <shell> is generated by clap via clap_complete, but the completions built into the init scripts are hand-written per shell (_scuv_complete() in bash.rs, complete -c scuv lines in fish.rs, the langs array in zsh.rs) — those need updating by hand when a subcommand or option changes.

  1. Add to ShellType enum in cli/mod.rs:
#![allow(unused)]
fn main() {
pub enum ShellType {
    // ... existing
    MyShell,
}
}
  1. Add detection to shell::detect_shell() in src/shell/mod.rs:
#![allow(unused)]
fn main() {
pub fn detect_shell() -> ShellType {
    if env::var("MYSHELL_VERSION").is_ok() {
        return ShellType::MyShell;
    }
    // ... existing checks
}
}

Adding a New Migration Source

  1. Create core/migrate/mysource.rs:
#![allow(unused)]
fn main() {
pub struct MySource;

impl MySource {
    pub fn detect() -> bool {
        // Check if source is available
    }

    pub fn list_envs() -> Result<Vec<MigrationCandidate>> {
        // Return list of environments
    }

    pub fn migrate_env(name: &str) -> Result<()> {
        // Perform migration
    }
}
}
  1. Register in cli/commands/migrate/ (its mod.rs):
#![allow(unused)]
fn main() {
let sources = vec![
    // ... existing
    Box::new(MySource),
];
}

Adding a New Doctor Check

See API Reference - Adding a New Health Check for details.

Performance Characteristics

OperationTime ComplexityNotes
List envsO(n)n = number of virtualenvs
Create envO(1)**Depends on uv performance
Delete envO(1)Simple directory removal
Version resolutionO(d)d = directory depth (walks parents)
Doctor checksO(n)n = number of checks (fixed)

Thread Safety

scuv is a single-threaded CLI application. No concurrent operations are performed.

File locking: Not implemented. Assumes single user on single machine. Concurrent operations (e.g., two terminals creating the same env) may result in race conditions.

Security Considerations

  1. Path Traversal: All user-provided names are validated via regex before use in filesystem operations.
  2. Command Injection: uv commands are constructed using typed arguments, not string concatenation.
  3. Symlink Safety: Doctor checks detect and warn about broken symlinks.
  4. Metadata Integrity: JSON parsing errors are gracefully handled without panics.