# Arc Package Manager Library

A Rust library for managing both pacman and flatpak packages with intelligent package matching and filtering capabilities.

## Features

- 🔍 **Unified Package Search**: Search across both pacman and flatpak repositories
- 📦 **Smart Package Installation**: Automatically prefer pacman packages over flatpak when configured
- 🚫 **Blacklist Support**: Block specific flatpak packages from being installed
- ✅ **Whitelist Support**: Restrict pacman packages to only approved packages
- 🔄 **Package Mapping**: Map flatpak applications to their pacman equivalents
- 🏷️ **Pacman Aliases**: Define alternative names for pacman packages
- 🤝 **Companion Packages**: Automatically install/uninstall related packages together
- 📋 **Package Listing**: View all installed packages from both sources

## Installation

Add this to your `Cargo.toml`:

```toml
[dependencies]
arc = { path = "/path/to/arc" }
```

## Configuration

The library reads its configuration from `/etc/arc.json`. The configuration file has five main sections:

```json
{
  "flatpak_to_pacman": {
    "firefox": "firefox",
    "chromium": "chromium",
    "vlc": "vlc"
  },
  "flatpak_blacklist": [
    "steam",
    "discord"
  ],
  "pacman_whitelist": [
    "firefox",
    "chromium",
    "vlc",
    "git",
    "vim"
  ],
  "pacman_aliases": {
    "vim-nightly": "vim-git",
    "python3": "python",
    "node": "nodejs"
  },
  "pacman_companions": {
    "steam": ["steam-native-runtime", "lib32-nvidia-utils"],
    "wine": ["wine-mono", "wine-gecko"]
  }
}
```

### Configuration Fields

#### `flatpak_to_pacman`
Maps flatpak application names to preferred pacman packages. When installing a flatpak, if a mapping exists, the pacman package will be installed instead.

**Note**: Flatpak names are extracted from bundle IDs by taking the last component after splitting by dots and converting to lowercase. For example, `org.mozilla.Firefox` becomes `firefox`.

#### `flatpak_blacklist`
List of flatpak application names that cannot be installed. These packages will be filtered out from search results and blocked from installation.

#### `pacman_whitelist`
List of pacman packages that are allowed to be installed. If empty, all pacman packages are allowed. If specified, only packages in this list can be installed.

#### `pacman_aliases`
Maps alternative package names to actual pacman package names. This is useful for providing user-friendly aliases or handling common naming variations. When any operation references an alias, it's automatically resolved to the actual package name.

**Examples:**
- `"vim-nightly": "vim-git"` - Install vim-git when user requests vim-nightly
- `"python3": "python"` - Handle the common python3 alias
- `"node": "nodejs"` - Map node to nodejs package

#### `pacman_companions`
Maps packages to companion packages that should be installed or uninstalled together. When you install a package with companions, all companion packages are automatically installed. When you uninstall it, all companions are also uninstalled.

**Use cases:**
- **Dependencies**: `"steam": ["steam-native-runtime"]` - Install runtime with Steam
- **Optional components**: `"wine": ["wine-mono", "wine-gecko"]` - Install Wine dependencies
- **Hardware support**: `"blender": ["cuda", "optix"]` - Install GPU rendering support with Blender
- **Related tools**: `"python": ["pip", "python-setuptools"]` - Install package managers with Python

**Note**: Companion packages also go through alias resolution and whitelist checking. If a companion package is not whitelisted, it will be skipped during installation.

## Usage

### Basic Example

```rust
use libarc::{Package, PackageManager};

fn main() -> Result<(), Box<dyn std::error::Error>> {
    // Create package manager (loads /etc/arc.json)
    let pm = PackageManager::new()?;

    // Search for packages
    let results = pm.search("firefox")?;
    for package in results {
        println!("{:?}", package);
    }

    // Install a package
    let package = Package::Pacman("firefox".to_string());
    pm.install(&package)?;

    // Check if installed
    if pm.is_installed(&package)? {
        println!("Package is installed!");
    }

    // Uninstall a package
    pm.uninstall(&package)?;

    Ok(())
}
```

### Custom Configuration

```rust
use libarc::{ArcConfig, PackageManager};
use std::collections::HashMap;

fn main() {
    let mut config = ArcConfig::default();
    
    // Add flatpak to pacman mappings
    config.flatpak_to_pacman.insert(
        "firefox".to_string(),
        "firefox".to_string()
    );
    
    // Add to blacklist
    config.flatpak_blacklist.push("steam".to_string());
    
    // Create whitelist
    config.pacman_whitelist = vec![
        "firefox".to_string(),
        "chromium".to_string(),
    ];
    
    // Add pacman aliases
    config.pacman_aliases.insert(
        "vim-nightly".to_string(),
        "vim-git".to_string()
    );
    
    let pm = PackageManager::with_config(config);
    // Use pm...
}
```

### Using Pacman Aliases

```rust
use libarc::{Package, PackageManager};

fn main() -> Result<(), Box<dyn std::error::Error>> {
    let pm = PackageManager::new()?;

    // Install using an alias - "vim-nightly" will resolve to "vim-git"
    let package = Package::Pacman("vim-nightly".to_string());
    pm.install(&package)?;  // Actually installs "vim-git"

    // The alias is also resolved for checking installation
    if pm.is_installed(&package)? {
        println!("vim-git is installed!");
    }

    Ok(())
}
```

### Using Companion Packages

```rust
use libarc::{ArcConfig, Package, PackageManager};

fn main() -> Result<(), Box<dyn std::error::Error>> {
    let mut config = ArcConfig::default();
    
    // Configure Steam with companion packages
    config.pacman_companions.insert(
        "steam".to_string(),
        vec![
            "steam-native-runtime".to_string(),
            "lib32-nvidia-utils".to_string(),
        ]
    );
    
    config.pacman_whitelist = vec![
        "steam".to_string(),
        "steam-native-runtime".to_string(),
        "lib32-nvidia-utils".to_string(),
    ];
    
    let pm = PackageManager::with_config(config);
    
    // Install Steam - automatically installs companions too
    let steam = Package::Pacman("steam".to_string());
    pm.install(&steam)?;
    // This installs: steam, steam-native-runtime, lib32-nvidia-utils
    
    // Uninstall Steam - automatically uninstalls companions
    pm.uninstall(&steam)?;
    // This uninstalls all three packages
    
    Ok(())
}
```

### Working with Packages

```rust
use libarc::{Package, PackageManager};

fn main() -> Result<(), Box<dyn std::error::Error>> {
    let pm = PackageManager::new()?;

    // Create package instances
    let pacman_pkg = Package::Pacman("vim".to_string());
    let flatpak_pkg = Package::Flatpak("org.gimp.GIMP".to_string());

    // Check package type
    if pacman_pkg.is_pacman() {
        println!("This is a pacman package");
    }

    // Get package name
    println!("Package name: {}", pacman_pkg.name());

    // List all installed packages
    let installed = pm.list_installed()?;
    for pkg in installed {
        match pkg {
            Package::Pacman(name) => println!("[pacman] {}", name),
            Package::Flatpak(bundle_id) => println!("[flatpak] {}", bundle_id),
        }
    }

    Ok(())
}
```

## API Reference

### `PackageManager`

Main interface for package management operations.

#### Methods

- `new() -> Result<Self>` - Create a new PackageManager with default config
- `with_config(config: ArcConfig) -> Self` - Create with custom config
- `search(&self, query: &str) -> Result<Vec<Package>>` - Search for packages
- `install(&self, package: &Package) -> Result<()>` - Install a package (also installs companions)
- `uninstall(&self, package: &Package) -> Result<()>` - Uninstall a package (also uninstalls companions)
- `is_installed(&self, package: &Package) -> Result<bool>` - Check if installed
- `list_installed(&self) -> Result<Vec<Package>>` - List all installed packages
- `config(&self) -> &ArcConfig` - Get the current configuration

### `Package`

Represents a package from either pacman or flatpak.

```rust
pub enum Package {
    Pacman(String),
    Flatpak(String),
}
```

#### Methods

- `name(&self) -> &str` - Get the package name
- `is_pacman(&self) -> bool` - Check if it's a pacman package
- `is_flatpak(&self) -> bool` - Check if it's a flatpak package

### `ArcConfig`

Configuration structure for the package manager.

```rust
pub struct ArcConfig {
    pub flatpak_to_pacman: HashMap<String, String>,
    pub flatpak_blacklist: Vec<String>,
    pub pacman_whitelist: Vec<String>,
    pub pacman_aliases: HashMap<String, String>,
    pub pacman_companions: HashMap<String, Vec<String>>,
}
```

#### Methods

- `load() -> Result<Self>` - Load from /etc/arc.json
- `load_from<P: AsRef<Path>>(path: P) -> Result<Self>` - Load from custom path
- `is_flatpak_blacklisted(&self, name: &str) -> bool` - Check if flatpak is blacklisted
- `is_pacman_whitelisted(&self, name: &str) -> bool` - Check if pacman package is whitelisted
- `get_pacman_alternative(&self, flatpak_name: &str) -> Option<&String>` - Get pacman alternative
- `resolve_pacman_alias(&self, name: &str) -> &str` - Resolve a package alias to actual name
- `get_companion_packages(&self, package: &str) -> &[String]` - Get companion packages for a package
- `extract_flatpak_name(bundle_id: &str) -> String` - Extract name from bundle ID

## How It Works

### Pacman Alias Resolution

Pacman aliases are resolved automatically for all operations:

1. When you install/uninstall/check a package with an alias name
2. The alias is looked up in the `pacman_aliases` configuration
3. If found, the actual package name is used for the operation
4. If not found, the original name is used as-is

**Example:** If you have `"python3": "python"` configured and try to install `python3`, the library will automatically install the `python` package instead.

### Companion Packages

Companion packages allow you to automatically install or uninstall related packages together:

**During Installation:**
1. The main package is installed first
2. Each companion package is then installed in order
3. Companions go through alias resolution and whitelist checking
4. If a companion is not whitelisted, it's skipped (no error)

**During Uninstallation:**
1. All companion packages are uninstalled first (in order)
2. If a companion fails to uninstall, the error is ignored (it might not be installed)
3. The main package is uninstalled last

**Example:** When you install `steam` with companions `["steam-native-runtime", "lib32-nvidia-utils"]`:
- First, `steam` is installed
- Then `steam-native-runtime` is installed
- Finally `lib32-nvidia-utils` is installed

When uninstalling, companions are removed first, then the main package.

### Package Name Matching

When you try to install a flatpak like `org.mozilla.Firefox`:

1. The bundle ID is split by dots: `["org", "mozilla", "Firefox"]`
2. The last component is taken: `"Firefox"`
3. It's converted to lowercase: `"firefox"`
4. This name is used to check against mappings and blacklists

### Installation Logic

When installing a package:

1. **For Pacman packages**:
   - Resolve any alias to the actual package name
   - Check if the package is whitelisted (if whitelist is not empty)
   - If not whitelisted, return an error
   - Install the main package via pacman
   - Install all companion packages (if configured)

2. **For Flatpak packages**:
   - Extract the flatpak name from the bundle ID
   - Check if it's blacklisted; if yes, return an error
   - Check if there's a pacman alternative mapping
   - If a pacman alternative exists and is whitelisted, install that instead
   - Otherwise, install the flatpak

### Search Results

Search results are filtered based on configuration:

- Pacman packages are only shown if they're whitelisted
- Flatpak packages are excluded if blacklisted
- If a flatpak has a pacman alternative, the pacman package is shown instead

## Error Handling

The library uses a custom `ArcError` type for all errors:

```rust
pub enum ArcError {
    Io(std::io::Error),
    Json(serde_json::Error),
    PackageNotFound(String),
    PackageBlacklisted(String),
    PackageNotWhitelisted(String),
    CommandFailed(String),
    ConfigError(String),
    InvalidPackageName(String),
}
```

All public functions return `Result<T, ArcError>` (aliased as `Result<T>`).

## Requirements

- Rust 2024 edition or later
- `pacman` command-line tool (for Arch Linux package management)
- `flatpak` command-line tool (for Flatpak package management)
- Root/sudo privileges for installing and uninstalling packages

## License

See the repository LICENSE file for details.

## Contributing

Contributions are welcome! Please ensure all tests pass before submitting pull requests.

Run tests with:
```bash
cargo test
```

Build the library:
```bash
cargo build --release
```
