pinakes/docs/HACKING.md
NotAShelf 520489ab48
docs: finalize hacking guidelines
Signed-off-by: NotAShelf <raf@notashelf.dev>
Change-Id: I2d3a5a9d745630f5da305664f0bdc66e6a6a6964
2026-03-23 03:30:56 +03:00

9 KiB
Raw Blame History

Hacking Pinakes

Pinakes is a lot of things. It also plans to be a lot of things. One of those things, as you might have noticed, is complete. To be complete in features, to be complete in documentation and to be complete in hackability. This document covers, very comprehensively, how you may:

  • Develop Pinakes
  • Build Pinakes
  • Contribute to Pinakes

for developers as well as:

  • Distribute Pinakes

for Pinakes maintainers and packagers.

Development Environment

Pinakes uses Nix with flakes for pure, reproducible developer environments. All of the build dependencies that you need, from Rust to GTK and webkit, are provided in the devshell. In addition to not requiring system libraries, we'd like to actively discourage you from relying on system libraries.

Direnv may be preferred by some users as an alternative to nix develop.

# Enter the dev shell
$ nix develop

# Or with direnv (recommended)
$ direnv allow

Nix is the only supported way to get all dependencies. Distro package managers may work for the core crates but are not supported for the UI.

Building Pinakes

Pinakes is a Cargo workspace with multiple crates targeting multiple aspects of the projects. The user-facing crates are provided in packages/*. pinakes-server and pinakes-tui can be built normally with cargo while the UI crate, i.e., pinakes-ui must be built with the Dioxus CLI (dx) so that the SCSS stylesheets are compiled correctly. Everything else should work perfectly fine with the standard cargo build.

To make the things a little bit more convenient, this project uses Just as its command runner and provides a few recipes for common tasks.

# Build all crates (server + TUI + UI)
$ just build-all

# Build individual components
$ just build-server   # pinakes-server (HTTP API)
$ just build-tui      # pinakes-tui (terminal UI)
$ just build-ui       # pinakes-ui (Dioxus desktop/web UI, requires dx)

The dependencies required for the recipes, including Just itself, is provided by the Nix devshell. You may also run the cargo equivalents of those commands:

# Core crates
$ cargo build -p pinakes-server
$ cargo build -p pinakes-tui

# The UI crate must be built with dx, not cargo.
$ dx build -p pinakes-ui

Release builds follow the same pattern, but with --release. Nothing else should be required for the most part.

Running Pinakes

For the time being, UI clients require the server to be running before they can function. Offline sync is planned, but likely come later alongside cross-device sync. All UIs connect to the server over HTTP.

# 1. Copy and edit the example config (first time only)
$ cp pinakes.example.toml pinakes.toml

# 2. Start the server (defaults to 127.0.0.1:3000)
$ cargo run -p pinakes-server -- pinakes.toml

# 3a. Run the TUI client
$ cargo run -p pinakes-tui

# 3b. Run the desktop/web UI
$ cargo run -p pinakes-ui

# Connect TUI to a non-default server address
$ cargo run -p pinakes-tui -- --server http://localhost:3000

The GUI reads PINAKES_SERVER_URL if set; otherwise it assumes http://127.0.0.1:3000. You may change the host and port if you require.

Testing

Pinakes boasts, or well, attempts to boast a very comprehensive testing suite to ensure no regressions are introduced while in development. For fast parallel test execution, we use cargo-nextest and while you are recommended to use it over cargo test both should be supported. For CI and package tests, only cargo-nextest is supported.

# Run all workspace tests (preferred)
$ just test

# Equivalent without Just
$ cargo nextest run --workspace

# Tests for a single crate
$ cargo nextest run -p pinakes-core
$ cargo nextest run -p pinakes-server

# Run a specific test by name and show output
$ cargo test -p pinakes-core -- test_name --nocapture

Linting and Formatting

Formatting

There is a treewide formatter provided within the flake.nix that invokes the recommended formatter tooling across the various filetypes, including database migrations and Markdown sources. While a just fmt recipe is provided, it will not cover most of the files. Still, you may format the Rust sources as such:

# Format all code
$ just fmt          # or: cargo fmt

# Check formatting without modifying files
$ cargo fmt --check

Linting

For now lints only cover Rust sources, and are provided by the Clippy tool. You may, as usual, invoke it with just lint.

# Run Clippy
$ just lint                               # or: cargo clippy --workspace

# Treat Clippy warnings as errors (used in CI)
$ cargo clippy --workspace -- -D warnings # `-D warnings` is recommended

All Clippy warnings, besides some of the really annoying ones or false positives, must be resolved at the source. Do not use #[allow(...)] or #[expect(...)] to silence warnings unless there is a documented reason.

It may be okay, at times, to suppress lints but you should focus on resolving them rather than suppressing them. If suppressing, prefer #[expect] and provide the reason parameter.

Generating API Documentation

The REST API documentation is generated from OpenAPI annotations in pinakes-server using cargo xtask:

# Generate the API documentation at docs/api/
$ just docs # or: cargo xtask docs

This writes:

  • docs/api.md - Index linking all generated files
  • docs/api/<tag>.md - One Markdown file per API tag
  • docs/api/openapi.json - Full OpenAPI 3.0 specification

Please do not edit files under docs/api/ by hand; they are regenerated on each run.

Code Style

There are many conventions that are in play within the Pinakes codebase. You'll get used and adjust to most of them naturally, but here are some of the general conventions written down for convenience.

General

  1. Make illegal states unrepresentable; ıse ADTs and newtype wrappers to encode constraints in the type system. Parse and validate at system boundaries (user input, external APIs); trust internal types everywhere else.
  2. This one is kind of obvious, but please use the tracing crate with structured fields. Do not use println! or eprintln! in library or server code.
  3. All storage operations must be implemented for both the SQLite and PostgreSQL backends. Neither backend is optional.

Naming is kind of obvious, but I'd like to remind you that we use snake_case for functions and variables, PascalCase for types and traits, SCREAMING_SNAKE_CASE for constants. There is not much else to it.

Error Handling

  • unwrap() and expect() are banned in non-test code. Use ?, ok_or_else, match, or if let instead.

    • The only accepted exception is when a failure is truly impossible (e.g., a hardcoded regex that is known-valid at compile time). In that case, add #[expect(clippy::expect_used, reason = "...")] with a clear explanation.
  • Wrap storage errors with the db_ctx helper so error messages include the operation and relevant ID:

    stmt.execute(params).map_err(db_ctx("insert_media", &media_id))?;
    

Disallowed Types and APIs

Pinakes uses hashers from rustc_hash, i.e., FxHashMap and FxHashSet over the std equivalents due to their faster nature without random state. This is enforced by clippy.

As Pinakes uses Rust 1.90+, the now-deprecated once_cell crate is also banned. Use the stdlib equivalents:

  • once_cell::unsync::OnceCell -> std::cell::OnceCell
  • once_cell::sync::OnceCell -> std::sync::OnceLock
  • once_cell::unsync::Lazy -> std::cell::LazyCell
  • once_cell::sync::Lazy -> std::sync::LazyLock

Contributing

  1. Fork the repository and create a feature branch.
  2. Enter the dev shell: nix develop (or direnv allow).
  3. Make your changes; keep commits focused and descriptive. We use scoped commits.
  4. Run just fmt and just lint before opening a pull request.
  5. Run just test to verify nothing is broken.
  6. Open a pull request against main. Describe what you changed and why.

For significant changes, consider opening an issue first to discuss the approach.

Packaging and Distribution

Pinakes is released under EUPL v1.2. For distributable release builds, please respect the license. You may create release builds for the user-facing crates using cargo build --release for server and TUI components. Use dx instead for the GUI:

# Server
$ cargo build --release -p pinakes-server

# TUI
$ cargo build --release -p pinakes-tui

# GUI
$ dx build --release -p pinakes-ui

The resulting binaries are self-contained except for system libraries required by the UI (GTK3, libsoup, webkit2gtk). Database migrations run automatically on first server startup via the refinery crate; no manual migration step is needed.

SQLite (the default backend) requires no external database server. PostgreSQL deployments need the pg_trgm extension enabled on the target database. It is generally advisable to document those requirements for the users, even though they do not require additional packaging steps.