# Cram

> To force (people or things) into a place or container that is or appears to be too small to contain them.

An alternative to GNU Stow, more some notion of packages with dependencies and install scripts.

Think an Ansible, Puppet or even NixOS but anyarch and lite enough to check in with your dotfiles.

## Overview

Cram operates on a directory of packages called `packages.d/`, and two directories of metapackages called `profiles.d` and `hosts.d`.

### Packages

A Cram package consists of a directory containing a `pkg.toml` file with the following format -

```toml
[cram]
version = 1

[package]
  # The package.require list names depended artifacts.
  [[package.require]]
  name = "packages.d/some-other-package"

  # (optional) The package.build list enumerates either
  # inline scripts or script files. These are run as a
  # package is 'built' before it is installed.
  [[package.build]]
  run = "some-build-command"

  # (optional) Hook script(s) which occur before installation.
  [[package.pre_install]]
  run = "some-hook"

  # (optional) Override installation scrpt(s).
  # By default, everthing under the package directory
  # (the `pkg.toml` excepted) treated is as a file to be
  # installed and stow is emulated using symlinks.
  [[package.install]]
  run = "some-install-command"

  # (optional) Hook script(s) which after installation.
  [[package.post_install]]
  run = "some-other-hook"
```

To take a somewhat real example from my own dotfiles -

```shell
$ tree -a packages.d/tmux
packages.d/tmux
├── pkg.toml
└── .tmux.conf
```

This TMUX package provides only my `.tmux.conf` file, and a stub `pkg.toml` that does nothing.
A fancier setup could use `pkg.toml` to install TMUX either as a `pre_install` task or by using a separate TMUX package and providing the config in a profile.

### Metapackages

Writing lots of packages gets cumbersome quickly, as does managing long lists of explicit dependencies.
To try and manage this, Cram provides metapackages - packages which contain no stowable files, but instad contain subpackages.

To take a somewhat real example from my own dotfiles -

```shell
$ tree -a -L 1 profiles.d/macos
profiles.d/macos
├── pkg.toml
├── emacs/
├── homebrew/
└── zsh/
```

The `profiles.d/macos` package depends AUTOMATICALLY on the contents of the `profiles.d/macos/emacs`, `profiles.d/macos/homebrew` and `profiles.d/macos/zsh` packages, which are normal packages.
These sub-packages can have normal dependencies on other packages both within and without the profile and install files or run scripts.

Profiles allow users to write groups of related packages, especially configs, which go together and allows for scoped reuse of meaningful names.

Likewise the `hosts.d/` tree allows users to store host-specific packages.

## Usage

```
$ cram apply [--dry-run|--execute] [--optimize] [--require <package>] <configdir> <destdir>
```

The `apply` task applies a configuration to a destination directory.
The most common uses of this would be `--dry-run` (the default), which functions as a `diff` or `--execute ~/conf ~/` for emulating Stow and installing dotfiles.

By default `cram` installs two packages - `profiles.d/default` and `hosts.d/$(hostname -s)`.
This default can be overriden by providing `--require <package>` one or more times to enumerate specific packages to install.

Cram always reads the `.cram.log` state file and diffs the current state against the configured state.
Files and directories no longer defined by the configured state are cleaned up automatically.

```
$ cram state <configdir>
```

The `state` task loads up and prints the `.cram.log` state file generated by any previous `cram apply --execute` so you can read a manifest of what cram thinks it did.
This is useful because `cram` attempts to optimize repeated executions and implement change detection using the state file.

This cache can be busted if needed by using `apply --execute --no-optimize`, which will cause cram to take all actions it deems presently required.
This can result in dangling symlinks in the filesystem.

```
$ cram list <configdir> [package]
```

The `list` task lists out all available packages (eg. packages, profiles, hosts, and subpackages) as a dependency graph.
When provided a specific package, the details of that package (its requirements and installation task log) will be printed.

## License

Copyright Reid 'arrdem' McKenzie, 15/02/2022.

Published under the terms of the Anticapitalist Software License (https://anticapitalist.software).

Unlimited commercial licensing is available at nominal pricing.
