---
title: "Packaging Your Code in R and Python with Rix"
author: "Bruno Rodrigues"
date: "`r Sys.Date()`"
output: rmarkdown::html_vignette
vignette: >
  %\VignetteIndexEntry{Packaging Your Code in R and Python with rix}
  %\VignetteEngine{knitr::rmarkdown}
  %\VignetteEncoding{UTF-8}
---

```{r, include = FALSE}
knitr::opts_chunk$set(
  collapse = TRUE,
  comment = "#>"
)
```

Packaging your code is the most reliable way to ensure reproducibility and
reusability. Instead of relying on loose scripts, packaging encourages you to
organize your functions, document them, and test them. This vignette
demonstrates how to create packages in R and Python within a Nix environment
managed by `{rix}`.

We will cover:
1.  Creating an R package using `{devtools}` and `{usethis}`.
2.  Creating a Python package using `uv`.
3.  Defining the correct Nix environments to develop and use these packages.

## Part 1: R Packages with `{usethis}` and `{devtools}`

The R ecosystem provides excellent tools for package development. We will use
`{usethis}` to set up the package structure and `{devtools}` for documentation
and testing.

### Setting up the Development Environment

Start by generating a `default.nix` that includes the necessary development
tools:

```r
library(rix)

rix(
  r_ver = "frozen-edge",
  r_pkgs = c("devtools", "usethis", "roxygen2", "testthat"),
  ide = "rstudio",  # or "code", "none"
  project_path = ".",
  overwrite = TRUE
)
```

Build the environment with `nix-shell`.

### Initializing the Package

From inside your Nix shell (or RStudio started from Nix), create a new package:

```r
usethis::create_package("~/Documents/projects/cleanR")
```

This creates the standard R package directory structure (`R/`, `DESCRIPTION`,
`NAMESPACE`).

### Workflow Basics

1.  **Create a function**:
    ```r
    usethis::use_r("clean_names")
    ```
    Add your function to `R/clean_names.R` and use `roxygen2` comments (`#'`)
    for documentation.

2.  **Document**:
    ```r
    devtools::document()
    ```
    This generates the `man/*.Rd` files and updates `NAMESPACE`.

3.  **Test**:
    ```r
    usethis::use_testthat()
    usethis::use_test("clean_names")
    ```
    Write unit tests in `tests/testthat/test-clean_names.R`, then run them:
    ```r
    devtools::test()
    ```

4.  **Check**:
    ```r
    devtools::check()
    ```
    This runs the standard R package check to ensure everything is correct.

### Using Your Package in a Nix Environment

Once your package is hosted on GitHub, you can use it in other project-specific
Nix environments by adding it to the `git_pkgs` argument in `rix()`:

```r
rix(
  r_ver = "frozen-edge",
  r_pkgs = c("dplyr"),
  git_pkgs = list(
    package_name = "cleanR",
    repo_url = "https://github.com/yourusername/cleanR",
    commit = "commit_hash_here"
  ),
  ide = "rstudio",
  project_path = ".",
  overwrite = TRUE
)
```

## Part 2: Python Packages with `uv`

For Python, we will use `uv` to manage the project configuration (`pyproject.toml`) and build process, while Nix manages the actual Python interpreter and environment.

### Setting up the Development Environment

Generate a `default.nix` with Python, `uv`, and your package's dependencies:

```r
library(rix)

rix(
  date = "2025-10-07",
  py_conf = list(
    py_version = "3.12",
    py_pkgs = c("pytest", "pandas", "numpy") # Runtime and test deps
  ),
  system_pkgs = "uv",
  ide = "none",
  project_path = ".",
  overwrite = TRUE
)
```

### Initializing the Package

Inside the Nix shell, create a directory and initialize the project components:

```bash
mkdir pyclean
cd pyclean
uv init --bare
mkdir -p src/pyclean tests
touch src/pyclean/__init__.py
```

`uv init --bare` creates a `pyproject.toml` without creating a virtual
environment (since we are using Nix).

### Configuring `pyproject.toml`

Edit `pyproject.toml` to define dependencies and test configuration. Critical
sections include:

```toml
[project]
name = "pyclean"
version = "0.1.0"
dependencies = [
    "pandas>=2.0.0",
]

[project.optional-dependencies]
test = [
    "pytest",
]

# Essential for running pytest from source layout
[tool.pytest.ini_options]
pythonpath = ["src"]
```

### Development Workflow

1.  **Write Code**: Add functions to `src/pyclean/`.
2.  **Write Tests**: Add tests to `tests/`.
3.  **Run Tests**:
    ```bash
    pytest
    ```
4.  **Build**:
    ```bash
    uv build
    ```
    This produces a wheel in `dist/`.

### Local Development Loop

When developing a Python package locally within a Nix shell, you want changes to
be reflected immediately. Update your `default.nix` `shellHook` to add your
source directory to `PYTHONPATH`.

`rix` (>= 0.17.4) makes this easy with the `py_src_dir` argument:

```r
rix(
  date = "2025-10-07",
  py_conf = list(
    py_version = "3.12",
    py_pkgs = c("pytest", "pandas"),
    py_src_dir = "src"  # Adds src/ to PYTHONPATH automatically
  ),
  system_pkgs = "uv",
  # ...
)
```

This injects `export PYTHONPATH=$PWD/src:$PYTHONPATH` into the shell hook,
allowing you to `import pyclean` and run tests against the live source code.

### Using Your Package in a Nix Environment

To use your Python package in another Nix environment (e.g., for an analysis
project), use `pkgs.python3Packages.buildPythonPackage` in your `default.nix`.
You can point to a GitHub repository:

```nix
pyclean = pkgs.python313Packages.buildPythonPackage rec {
  pname = "pyclean";
  version = "0.1.0";
  src = pkgs.fetchgit {
    url = "https://github.com/yourusername/pyclean";
    rev = "commit_hash";
    sha256 = "sha256-hash";
  };
  pyproject = true;
  propagatedBuildInputs = [
    pkgs.python313Packages.pandas
  ];
};
```

Then add `pyclean` to the `buildInputs` list alongside your other Python packages.
