pkgs-cli 0.2.0

A simple cli tool to manage packages.
Documentation
# pkgs

[English]./README.md | [简体中文]./README.zh-CN.md

Use symbolic links to manage the installation of different packages.

## Development background

### Managing configuration files

There has long been no unified solution for managing configuration files on Linux systems.
The mainstream solutions today are [Stow](https://www.gnu.org/software/stow/manual/stow.html) and [Nix Home Manager](https://github.com/nix-community/home-manager). However, both have some unsatisfactory drawbacks.

Stow’s management approach is somewhat rigid — it requires storing full filesystem paths and lacks customization options.
Nix Home Manager requires the Nix ecosystem and has a relatively high learning curve.

### Managing Git packages

> [!caution]
> This feature is still planned and has not yet been implemented.

Tools like [mpv](https://mpv.io/) lack suitable package management when installing plugins, forcing users to manage them manually, which is cumbersome.

This project also plans to integrate Git version management to provide a way to manage Git-based packages.

## Installation

```bash
cargo install pkgs-cli --locked
```

## Usage guide

Create a descriptor file in the directory where the packages exist. Supports both TOML and YAML formats, as shown below:

<details>
<summary>pkgs.toml</summary>

```toml
# Optional `vars` section, used to define variables
# Use the ${var} syntax to reference variables
# If you reference other variables, they must be declared in order
[vars]
CONFIG_DIR = "${HOME}/.config" # HOME variable is built-in
APP_DIR = "${HOME}/Apps"
NU_DIR = "${CONFIG_DIR}/nushell"

# `packages` section is required; each table under it corresponds to a package,
# and should match a directory with the same name in the current directory
[packages.yazi]
kind = "local" # Package type, optional; defaults to "local". Currently only "local" is supported.

[packages.yazi.vars] # Package-local variables, visible only within the package
YAZI_DIR = "${CONFIG_DIR}/yazi"

[packages.yazi.maps] # Each entry under `maps` represents a mapping
"yazi.toml" = "${YAZI_DIR}/yazi.toml"         # Left side can be a file inside the package
"my-custom" = "${YAZI_DIR}/plugins/my-plugin" # It can also be a directory
"keymap.toml" = "${YAZI_DIR}/keymap.toml"     # Right side is the path where the symlink will be created

"yazi.nu" = "${NU_DIR}/autoload/"             # If the mapped file has the same name,
                                              # you can end the path with '/' to omit the filename.

[packages.nu.maps]
"config.nu" = "${NU_DIR}/"
```

</details>

<details>
<summary>pkgs.yaml / pkgs.yml</summary>

```yaml
# Optional `vars` section, used to define variables
# Use the ${var} syntax to reference variables
# If you reference other variables, they must be declared in order
vars:
  CONFIG_DIR: ${HOME}/.config # HOME variable is built-in
  APP_DIR: ${HOME}/Apps
  NU_DIR: ${CONFIG_DIR}/nushell

# `packages` section is required; each table under it corresponds to a package,
# and should match a directory with the same name in the current directory
packages:
  yazi:
    kind: local # Package type, optional; defaults to "local". Currently only "local" is supported.

    vars: # Package-local variables, visible only within the package
      YAZI_DIR: ${CONFIG_DIR}/yazi

    maps: # Each entry under `maps` represents a mapping
      yazi.toml: ${YAZI_DIR}/yazi.toml         # Left side can be a file inside the package
      my-custom: ${YAZI_DIR}/plugins/my-plugin # It can also be a directory
      keymap.toml: ${YAZI_DIR}/keymap.toml     # Right side is the path where the symlink will be created

      yazi.nu: ${NU_DIR}/autoload/             # If the mapped file has the same name,
                                               # you may end with / and omit the filename

  nu:
    maps:
      config.nu: ${NU_DIR}/
```

</details>

The following commands are supported:

```bash
pkgs list # List all packages

pkgs load --all # Load all packages
pkgs load yazi nu # Load only yazi and nu
# After `load`, if you modify the configuration file you can run `load` again to reapply; `unload` is not required

pkgs unload --all # Unload all packages
pkgs unload yazi nu # Unload only yazi and nu

pkgs schema # Generate json schema for descriptor file
```

### Behavior

The `load` command creates symbolic links at the specified locations that point to the corresponding files' **absolute paths** according to the configuration file (so if a file path changes because of variables, you must `load` again). If an error occurs while loading a package, the operation for that package will be **rolled back**. After loading completes, the created symlinks are recorded in `.pkgs/trace.toml` in the current directory — please **do not** modify or delete this file.

If a parent directory for a target path does not exist during loading, the tool will **create all missing parent directories** and notify the user.

The `unload` command removes packages by reading `.pkgs/trace.toml`. If an error occurs during unload, a **rollback** will also be performed.

> [!warning]
> The order of variable loading and the creation of mappings within a package follow the order in the description file.
>
> However, when loading or unloading packages, different packages are processed in lexicographical order
> rather than the order in the description file. Therefore, do not rely on the order between packages.

## License

This project is open-source under the [GPL-3.0 License](https://www.gnu.org/licenses/gpl-3.0.en.html). See [LICENSE](./LICENSE) for details.