Initial commit
All checks were successful
/ test (push) Successful in 9s

Safsaf is a Guile web framework, written using Claude Code running
Claude Opus 4.6, based off of the Guix Data Service, Nar Herder and
Guix Build Coordinator codebases.
This commit is contained in:
Christopher Baines 2026-04-13 14:24:19 +03:00
commit 5b0e6397dc
53 changed files with 7427 additions and 0 deletions

187
STYLE.md Normal file
View file

@ -0,0 +1,187 @@
# Guile Style Guide for Safsaf
This guide draws on
[Riastradh's Lisp Style Rules](https://mumble.net/~campbell/scheme/style.txt),
the [Guix Coding Style](https://guix.gnu.org/manual/1.5.0/en/html_node/Coding-Style.html).
---
## Formatting
### Indentation
Use **2-space indentation**, no tabs. When a subform follows the
operator on the same line, align subsequent subforms to that column.
When the first subform is on the next line, align it with the operator.
Special forms (`define`, `let`, `lambda`, `if`, `cond`, `match`, etc.)
follow standard Scheme indentation.
### Line Length
Do not exceed **80 columns**.
### Parentheses
Never place closing parentheses on their own line. Do not put spaces
after opening parentheses or before closing ones.
### Blank Lines
Separate top-level forms with a single blank line. Do not place blank
lines in procedure bodies except to separate internal definitions from
the body.
### Square Brackets
Do not use square brackets. They are non-standard and non-portable.
## Naming
Write names with **English words separated by hyphens**. No underscores,
camelCase, or abbreviations.
- **`?` (predicates):** Boolean-returning questions. E.g., `route?`,
`logged-in?`.
- **`!` (mutation):** Procedures whose primary purpose is destructive
update. E.g., `set-route-handler!`. Do not append to every procedure
with side effects.
- **`%` (private):** Module-private bindings. E.g., `%make-route`,
`%email-rx`.
- **`*` (variants):** Variations on a theme (`let*`, `define*`).
### Records
```scheme
(define-record-type <route>
(%make-route method pattern handler name) ; internal constructor
route? ; predicate
(method route-method) ; accessor
(handler route-handler set-route-handler!)) ; accessor + setter
```
- `make-foo` for public constructors, `%make-foo` for raw constructors.
- `foo?` for predicates, `foo-field` for accessors, `set-foo-field!`
for setters.
### Parameters and Dynamic State
- Guile parameters use the **`current-`** prefix: `current-session`,
`current-csrf-token`.
- **`with-`** establishes dynamic state and calls a thunk.
- **`call-with-`** calls a procedure with arguments, managing resources
or continuations.
### Local Variables
Use meaningful names. Single-letter names only for unambiguous index
variables in tight loops.
## Comments
```scheme
;;;; file-heading.scm — File Heading
;;;
;;; Section Heading
;;;
;;; Top-level explanatory comment.
(define (fnord zarquon)
;; Fragment comment before code.
(quux zot mumble ;margin note
frotz))
```
- **`;;;;`** — File headings.
- **`;;;`** — Section headings (use the sandwich: `;;;` / `;;; Title` /
`;;;`) and top-level explanations.
- **`;;`** — Fragment comments, before the code they describe.
- **`;`** — Margin comments, after code on the same line.
Write comments only where the code cannot explain itself.
## Docstrings
All public procedures must carry a docstring. Place it as the first
expression after the parameter list. Describe what the procedure does,
its parameters, and return values:
```scheme
(define* (route method pattern handler #:key (name #f))
"Create a route. METHOD is a symbol, list of symbols, or '* for any.
HANDLER is a procedure (request body-port) -> (values response body).
NAME is an optional symbol used for reverse routing with path-for."
...)
```
For parameters, use `set-procedure-property!` with `'documentation`.
## Module Definitions
Use `define-module` with `#:use-module` and `#:export`. Group imports:
1. **Standard library**`(ice-9 ...)`, `(web ...)`
2. **SRFIs**`(srfi srfi-1)`, `(srfi srfi-9)`, etc.
3. **External dependencies**`(knots ...)`, `(webutils ...)`, `(json ...)`
4. **Internal modules**`(safsaf ...)`
Prefer `#:use-module`; use `#:autoload` for heavy or circular deps.
Standalone scripts may use `use-modules` instead of `define-module`.
## Procedures
- No more than **four positional parameters**. Use keyword arguments
(via `define*`) beyond that.
- Keep procedures under roughly **21 lines** (excluding docstring).
Break long procedures into meaningfully named helpers.
- Prefer **purely functional** code. Use mutation only for I/O,
performance, and low-level utilities.
- Avoid point-free style and functional combinators. Use explicit
`lambda`. Reserve `compose` for cases where composition is genuinely
the idea being expressed.
## Data Types
- Prefer **records** (`define-record-type` from `(srfi srfi-9)`) over
ad-hoc lists. Do not browse data with `car`/`cdr`/`cadr`.
- Do not export record type descriptors (e.g., `<route>`). Export only
predicates, constructors, and accessors.
- Use `(ice-9 match)` for pattern matching.
- Use **alists** for lightweight key-value data (route params, headers,
config).
## Multiple Return Values
Use `values` for multiple return values. Prefer `(srfi srfi-71)` extended
`let` over `(srfi srfi-11)` `let-values`:
```scheme
(let ((response body (handler request body-port)))
(values response body))
```
## Error Handling
Use `(ice-9 exceptions)` functionality.
## File Organization
- Keep files under **512 lines**. Do not exceed 1024.
- Minimize module dependencies.
- Structure files with `;;;` section headings.
## Testing
Tests use `define-suite`, `suite`, `test`, and `is`. Keep test files
under `tests/`.