hx-plugins 0.7.9

Plugin system for hx using Steel Scheme
Documentation
# Steel API Reference

The hx plugin system uses [Steel](https://github.com/mattwparas/steel), a Scheme-like language. All hx functions are registered under the `hx/` namespace.

## Project Functions

Access project information and context.

### `(hx/project-name)`

Get the current project name.

```scheme
(define name (hx/project-name))
(hx/info (string-append "Building: " name))
```

**Returns**: String or `#f` if not in a project.

### `(hx/project-root)`

Get the project root directory path.

```scheme
(define root (hx/project-root))
(hx/info (string-append "Root: " root))
```

**Returns**: String (absolute path) or `#f` if not in a project.

### `(hx/ghc-version)`

Get the GHC version being used.

```scheme
(if (hx/ghc-version)
    (hx/info (string-append "Using GHC " (hx/ghc-version)))
    (hx/warn "GHC version unknown"))
```

**Returns**: String (e.g., "9.8.2") or `#f` if unknown.

### `(hx/cabal-file)`

Get the path to the .cabal file.

```scheme
(define cabal (hx/cabal-file))
(if cabal
    (hx/info (string-append "Cabal file: " cabal))
    (hx/warn "No cabal file found"))
```

**Returns**: String (path) or `#f` if not found.

## Shell Functions

Execute external commands.

### `(hx/run cmd args)`

Run a command and get full results.

```scheme
(define result (hx/run "hlint" '("src/")))
;; result is: ((exit-code . 0) (stdout . "...") (stderr . "..."))
```

**Parameters**:
- `cmd` - Command name (String)
- `args` - List of arguments (List of Strings)

**Returns**: Association list with `exit-code`, `stdout`, `stderr`.

**Example - Parse results**:
```scheme
(define (get-exit-code result)
  (cadr (assoc 'exit-code result)))

(define (get-stdout result)
  (cadr (assoc 'stdout result)))

(define result (hx/run "ls" '("-la")))
(if (= 0 (get-exit-code result))
    (hx/info (get-stdout result))
    (hx/error "Command failed"))
```

### `(hx/run-checked cmd args)`

Run a command and return stdout, or raise an error on failure.

```scheme
;; Returns stdout on success, raises error on failure
(define output (hx/run-checked "fourmolu" '("--mode" "check" "src/")))
(hx/info output)
```

**Parameters**:
- `cmd` - Command name (String)
- `args` - List of arguments (List of Strings)

**Returns**: String (stdout) on success.
**Raises**: Error if command fails.

### `(hx/run-silent cmd args)`

Run a command silently and return just the exit code.

```scheme
(define code (hx/run-silent "fourmolu" '("--mode" "check" "src/")))
(if (= 0 code)
    (hx/status "Format" "OK")
    (hx/error "Format check failed"))
```

**Parameters**:
- `cmd` - Command name (String)
- `args` - List of arguments (List of Strings)

**Returns**: Integer (exit code, or -1 on error).

## Filesystem Functions

Read, write, and query files.

### `(hx/read-file path)`

Read a file's contents.

```scheme
(define content (hx/read-file "src/Main.hs"))
(hx/info (string-append "File has "
                        (number->string (string-length content))
                        " characters"))
```

**Parameters**:
- `path` - File path (relative to project root or absolute)

**Returns**: String (file contents).
**Raises**: Error if file cannot be read.

### `(hx/write-file path content)`

Write content to a file. Creates parent directories if needed.

```scheme
(hx/write-file "generated/output.txt" "Hello, World!")
```

**Parameters**:
- `path` - File path (relative to project root or absolute)
- `content` - String to write

**Returns**: void

### `(hx/file-exists? path)`

Check if a file or directory exists.

```scheme
(if (hx/file-exists? "stack.yaml")
    (hx/warn "Found stack.yaml - this project uses Stack")
    (hx/info "No stack.yaml found"))
```

**Parameters**:
- `path` - File path (relative to project root or absolute)

**Returns**: Boolean

### `(hx/glob pattern)`

Find files matching a glob pattern.

```scheme
(define hs-files (hx/glob "src/**/*.hs"))
(hx/info (string-append "Found " (number->string (length hs-files)) " Haskell files"))

(for-each (lambda (f) (hx/debug f)) hs-files)
```

**Parameters**:
- `pattern` - Glob pattern (e.g., `"*.hs"`, `"src/**/*.hs"`)

**Returns**: List of Strings (matching paths).

### `(hx/path-join parts)`

Join path components.

```scheme
(define full-path (hx/path-join (list (hx/project-root) "src" "Main.hs")))
;; e.g., "/home/user/project/src/Main.hs"
```

**Parameters**:
- `parts` - List of path components (List of Strings)

**Returns**: String (joined path).

### `(hx/mkdir path)`

Create a directory (and parents).

```scheme
(hx/mkdir "dist/artifacts")
```

**Parameters**:
- `path` - Directory path (relative to project root or absolute)

**Returns**: void

## Output Functions

Print messages with consistent formatting.

### `(hx/status action message)`

Print a status message with action label.

```scheme
(hx/status "Lint" "Running hlint...")
;; Output: [Lint] Running hlint...
```

**Parameters**:
- `action` - Action name (String, shown in green)
- `message` - Message text (String)

### `(hx/info message)`

Print an informational message.

```scheme
(hx/info "Compiling 15 modules...")
```

### `(hx/warn message)`

Print a warning message (yellow).

```scheme
(hx/warn "Deprecated function used in Main.hs")
;; Output: warning: Deprecated function used in Main.hs
```

### `(hx/error message)`

Print an error message (red).

```scheme
(hx/error "Build failed with 3 errors")
;; Output: error: Build failed with 3 errors
```

### `(hx/debug message)`

Print a debug message (only shown in verbose mode).

```scheme
(hx/debug "Cache hit for aeson-2.2.1.0")
;; Only printed if --verbose flag is set
```

## Complete Example

A full pre-build hook that runs linting and formatting checks:

```scheme
;; plugins/lint.scm

(define (on-pre-build)
  (hx/status "Lint" "Running pre-build checks...")

  ;; Run hlint
  (let ((result (hx/run "hlint" (list "src/"))))
    (let ((exit-code (cadr (assoc 'exit-code result)))
          (stdout (cadr (assoc 'stdout result))))
      (if (= 0 exit-code)
          (hx/info "hlint: OK")
          (begin
            (hx/warn "hlint found issues:")
            (hx/info stdout)))))

  ;; Check formatting
  (let ((format-code (hx/run-silent "fourmolu"
                                    (list "--mode" "check" "src/"))))
    (if (= 0 format-code)
        (hx/info "fourmolu: OK")
        (hx/warn "Code is not formatted - run 'fourmolu -i src/'")))

  ;; Always return success (don't block build)
  #t)
```

## Error Handling

Steel uses `#f` for failure returns and exceptions for errors:

```scheme
;; Check for #f returns
(define ghc (hx/ghc-version))
(if (not ghc)
    (hx/error "GHC version unknown")
    (hx/info ghc))

;; Use try/catch for exceptions
(define (safe-read path)
  (with-exception-handler
    (lambda (e)
      (hx/error (string-append "Failed to read: " path))
      #f)
    (lambda ()
      (hx/read-file path))))
```

## Path Resolution

Relative paths are resolved relative to the project root:

```scheme
;; These are equivalent:
(hx/read-file "src/Main.hs")
(hx/read-file (hx/path-join (list (hx/project-root) "src" "Main.hs")))

;; Absolute paths are used as-is:
(hx/read-file "/etc/hosts")
```