with one click
parsley-coding
How to write Parsley code for tests, examples and documentation.
Install with Codex or Claude Copy this prompt, paste it into Codex, Claude, or another assistant, and let it review the skill page and install it for you.
Menu
How to write Parsley code for tests, examples and documentation.
Install with Codex or Claude Copy this prompt, paste it into Codex, Claude, or another assistant, and let it review the skill page and install it for you.
Based on SOC occupation classification
Build and run a Basil server against a scratch site to observe changes at the real HTTP surface.
Run the project Definition of Done for the current unit of work and integrate it — build, test, verify, docs/changelog, commit, rebase, merge to main, push, and remove the worktree. Use when a piece of work is finished and should land on main. Merge model is direct-to-main (no PR).
Develop and test Basil web applications with HTTP handlers, routing, sessions, authentication, databases, and interactive components (Parts).
| name | parsley-coding |
| description | How to write Parsley code for tests, examples and documentation. |
| updated | "2026-01-11T00:00:00.000Z" |
This skill helps you write and debug Parsley code.
Use this skill when you need to:
Important distinction:
./parsBasil-only features (not available in standalone Parsley):
@params - URL/form parametersimport @basil/http - request, response, route, method (query removed; use @params)import @basil/auth - session, auth, userParsley features work everywhere:
import @std/* modules (math, valid, schema, etc.)When writing code, consider:
./pars to test pure Parsley code@basil/* importsdocs/parsley/CHEATSHEET.md to learn Parsley code especially common mistakespars to test and validate Parsley code snippetsif and for are expressions that return valuesfn) return everything by default, so do not need return statement@ constructorsimport @std/….pars file-ending(see docs/parsley/CHEATSHEET.md for more Major Gotchas (Common Mistakes))
export Table = fn({rows, hidden, ...props}){
<table ...props>
<thead>
<tr>
for (k,_ in rows[0]){
if (k not in hidden)
<th class={"th-"+k}>k.toTitle()</th>
}
</tr>
</thead>
<tbody>
for (row in rows){
<tr>
for (k,v in row){
if (k not in hidden)
<td class={"td-"+k}>v</td>
}
</tr>
}
</tbody>
</table>
}
// Standard library (works everywhere)
let {floor, ceil} = import @std/math
let {table} = import @std/table
// Basil context (handler-only)
let {request, response, route, method} = import @basil/http // Basil-only
let {session, auth, user} = import @basil/auth // Basil-only
// Local modules (works everywhere)
let {People} = import @~/schema/birthdays.pars
for returns an array of values, making it more like map and filterlet doubled = for (n in [1,2,3]) { n * 2 } // [2, 4, 6]
// Filter pattern - if returns null, element is omitted from result
let evens = for (n in [1,2,3,4]) {
if (n % 2 == 0) { n }
}
// evens => [2, 4]
if age >= 18 { "adult" }
if (age >= 18) { "adult" }
let status = if (age >= 18) "adult" else "minor"
// ✅ CORRECT
let path = @./config.json // Relative to current file
let rootPath = @~/components/nav // Relative to project root (Basil)
// ❌ WRONG
let path = "./config.json" // This is just a string
// Other literals
let url = @https://example.com
let date = @2024-11-29
let time = @14:30
let duration = @1d
// ❌ WRONG (JavaScript arrow functions)
arr.map(x => x * 2)
// ✅ CORRECT - Use fn() { } syntax
arr.map(fn(x) { x * 2 })
arr.filter(fn(x) { x > 0 })
// Named functions
let double = fn(x) { x * 2 }
<h3>Welcome to Parsley</h3> // ❌ WRONG - bare text in tags
<h3>"Welcome to Parsley"</h3> // ✅ CORRECT - strings need quotes
<h3>`Welcome to {name}`</h3> // Template literal style also works
// ✅ CORRECT - no quotes needed for simple identifiers
<div class=container id=main disabled=true>
<button type=submit disabled={isDisabled}>
// ✅ ALSO CORRECT - quotes when you need them
<div class="user-profile" id={userId}>
<a href="/about">
// Use quotes for:
// - Multi-word values: class="nav item"
// - Values with special chars: onclick="alert('hi')"
// - String literals vs expressions
// Tag attributes have THREE forms:
// 1. Double-quoted strings - literal, no interpolation
<button onclick="alert('hello')">
<a href="/about">
// 2. Single-quoted strings - RAW, for embedding JavaScript
<button onclick='fetch("/api/items", {method: "POST"})'>
// ^ Double quotes and braces stay literal - perfect for JS!
// Use @{} for dynamic values:
<button onclick='fetch("/api/items/@{myId}", {method: "DELETE"})'>
// 3. Expression braces - Parsley code
<div class={`user-{id}`}> // Template string for dynamic class
<button disabled={!isValid}> // Boolean expression
<img width={width} height={height}>
// ❌ WRONG - interpolation in quoted strings
<div class="user-{id}"> // {id} is literal text
// ✅ CORRECT - use expression braces with template string
<div class={`user-{id}`}>
// Double quotes: Plain strings (no interpolation)
let msg = "Hello {name}" // literal braces, no interpolation
// Backticks: Template literals (JavaScript style, with interpolation)
let msg = `Hello {name}`
// Single quotes: RAW strings - {braces} stay literal
let js = 'fetch("/api/items", {method: "POST"})'
let regex = '\d+\.\d+' // Backslashes stay literal
// Use @{} for interpolation inside raw strings
let id = 42
let js = 'fetch("/api/items/@{id}", {method: "DELETE"})' // id interpolated
// Perfect for onclick handlers with dynamic values:
let myId = 5
<button onclick='fetch("/api/items/@{myId}", {method: "DELETE"})'/>
// Static JS (no interpolation needed):
<button onclick='document.getElementById("output").innerHTML = "done"'/>
// Escape @ with \@ if you need a literal @
'email: user\@domain.com' // literal @
// Singleton tags (HTML void elements) MUST self-close:
// ❌ WRONG
<br>
<img src="photo.jpg">
<input type="text">
<Part src={@./foo.part}>
// ✅ CORRECT - singleton tags need />
<br/>
<img src="photo.jpg"/>
<input type="text"/>
<Part src={@./foo.part}/>
// Paired tags can be empty (no /> needed):
// ✅ CORRECT
<div></div>
<script></script>
<button></button>
// Dictionaries iterate in insertion order by default
for (k, v in {b: 2, a: 1}) { k } // ["b", "a"]
// To iterate in sorted key order, extract and sort keys first
let d = {b: 2, a: 1}
let sortedKeys = (for (k, _ in d) { k }).sort()
for (k in sortedKeys) { d[k] } // values in key order: [1, 2]
[1,2,3].length() // => 3
"ABC".length() // => 3
123.type() // => "integer"
[1,2,3].type() // => "array"
"hello".type() // => "string"
@1968-11-21.type() // => "datetime"
$5.type() // => "money"
@now.type() // => "datetime"
// Export functions/values to make them available to importers
export myFunc = fn(x) { x * 2 }
export myVar = 42
// Each export must be declared separately
// (no "export {a, b}" shorthand)
// let without export is file-local (private)
let private = 123 // Not available to importers
export public = 456 // Available to importers
// Default export
export default = fn(props) { <div>props.text</div> }
// Works everywhere (standalone pars and Basil handlers)
import @std/mdDoc // Markdown (mdDoc pseudo-type)
import @std/table // Table DSL
import @std/math // Math functions
import @std/valid // Validation functions
import @std/schema // Schema validation
import @std/id // ID generation (UUID, ULID, etc)
import @std/dev // Dev tools
// Requires Basil server (not available in standalone pars)
import @std/api // API helpers (redirect, notFound, etc)
import @std/html // HTML components (Page, Button, etc.)
// ❌ DEPRECATED/REMOVED - don't use these:
// @std/markdown - removed, use @std/mdDoc
// now() builtin - removed, use @now
ONLY available in Basil handlers, NOT in standalone pars:
import @basil/http // request, response, route, method
import @basil/auth // session, auth, user
// Works everywhere
@now // Current datetime
@now.year // 2026
@now.format() // "January 11, 2026" (human-readable)
@env.HOME // Environment variables
@env.PATH
// Basil-only (ONLY in handlers)
@params // URL/form parameters
@params.id // Individual parameter
To test Parsley code locally:
./pars # Start interactive REPL
./pars script.pars # Execute a script
./pars -pp page.pars # Pretty-print HTML output
./pars -x script.pars # Allow unrestricted script execution
./pars --no-write script.pars # Deny file writes
./pars --restrict-read=/etc script.pars # Deny reads from path
NOTE: that .pars converts values to their default string representation and concatenates them:
[1,2,3] -> 123["a","b","c"] - > abc``s = fn(){"Sam" "Was" "Here"}; s() -> SamWasHereWhen writing tests and examples:
pkg/parsley/tests/*.pars or *_test.goexamples/*/handlers/*.pars./pars to test snippets before documenting./basil --dev and test in browserFor comprehensive guide to:
@schema{}@query()@insert()@update()@delete()@transaction{}See the Query DSL SKILL:
.../parsley-query-dsl/SKILL.mdFor comprehensive guides, see:
docs/parsley/CHEATSHEET.md and docs/parsley/reference.mddocs/basil/reference.md - Full language reference with all operators and builtinspars before using in documentation