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.
187 lines
5.3 KiB
Markdown
187 lines
5.3 KiB
Markdown
# 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/`.
|