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:
commit
5b0e6397dc
53 changed files with 7427 additions and 0 deletions
187
STYLE.md
Normal file
187
STYLE.md
Normal 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/`.
|
||||
Loading…
Add table
Add a link
Reference in a new issue