| name | parsley-coding |
| description | How to write Parsley code for tests, examples and documentation. |
| updated | "2026-01-11T00:00:00.000Z" |
Writing Parsley Code
This skill helps you write and debug Parsley code.
When to use this skill
Use this skill when you need to:
- Create new tests in Parsley code
- When debugging functionality during development
- Creating examples of how to use a feature
- Discussing new features and how they might fit into Parsley/Basil ecosystem
- Creating workflow docs: designs, specs, plans, bugs, reports
- Creating documentation that has Parsley code examples
- Creating or editing: *.pars or *.part files
Parsley vs Basil Context
Important distinction:
- Parsley is the language - works standalone with
./pars
- Basil is the web framework - adds server context (request, response, sessions, etc.)
Basil-only features (not available in standalone Parsley):
@params - URL/form parameters
import @basil/http - request, response, route, method (query removed; use @params)
import @basil/auth - session, auth, user
- HTTP-specific functionality (cookies, headers, sessions)
Parsley features work everywhere:
- Core language (for, if, fn, etc.)
import @std/* modules (math, valid, schema, etc.)
- File I/O, JSON, CSV operations
- All basic data types and operators
When writing code, consider:
- For tests/examples: Use
./pars to test pure Parsley code
- For handlers: Basil context available, can use
@basil/* imports
- For documentation: Mark Basil-specific features clearly
Writing code
- Read
docs/parsley/CHEATSHEET.md to learn Parsley code especially common mistakes
- Use
pars to test and validate Parsley code snippets
Notes:
- Parsley code looks like Javascript, but it is expression-based
- Parsley code looks like React/JSX code, but tags and code co-exist — code is not interpolated inside { } brackets
if and for are expressions that return values
- functions (
fn) return everything by default, so do not need return statement
- Parsley has rich pseudo-types for dates, times, money with their own literals with
@ constructors
- Parsley has a standard library
import @std/…
- By convention, Parsley files use
.pars file-ending
Common Pitfalls
(see docs/parsley/CHEATSHEET.md for more Major Gotchas (Common Mistakes))
1. Code within tag pairs needs no { and } brackets
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>
}
2. Import syntax uses path literals and destructuring
// 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
3. for returns an array of values, making it more like map and filter
let 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]
4. If Parentheses are optional but recommended
if age >= 18 { "adult" }
if (age >= 18) { "adult" }
let status = if (age >= 18) "adult" else "minor"
5. Literals Use @ (Path, Date, Time, Duration, ...)
// ✅ 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
6. No Arrow Functions - Use fn() { }
// ❌ 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 }
7. Strings in HTML Must Be Quoted
<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
8. Tag Attributes Don't Need Quotes for Simple Values
// ✅ 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
9. Tag Attributes: Strings 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}`}>
10. Three String Types: "", '', ``
// 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 @
11. Singleton Tags MUST Self-Close, Paired Tags Can Be Empty
// 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>
12. Dictionary Iteration Order
// 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]
13. Use .length() to find the length of something
[1,2,3].length() // => 3
"ABC".length() // => 3
14. Use .type() to get the type name of a value (as a string)
123.type() // => "integer"
[1,2,3].type() // => "array"
"hello".type() // => "string"
@1968-11-21.type() // => "datetime"
$5.type() // => "money"
@now.type() // => "datetime"
15. Module System: let vs export
// 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> }
16. Standard Library Modules (@std/*)
// 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
17. Basil Framework Imports (@basil/*)
ONLY available in Basil handlers, NOT in standalone pars:
import @basil/http // request, response, route, method
import @basil/auth // session, auth, user
18. Magic Variables
// 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
Running pars
To test Parsley code locally:
./pars
./pars script.pars
./pars -pp page.pars
./pars -x script.pars
./pars --no-write script.pars
./pars --restrict-read=/etc script.pars
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() -> SamWasHere
Testing Context
When writing tests and examples:
- Unit tests: Place in
pkg/parsley/tests/*.pars or *_test.go
- Integration tests: Use Go test files with Parsley evaluation
- Examples: Create in
examples/*/handlers/*.pars
- Quick validation: Use
./pars to test snippets before documenting
- Handler testing: Run
./basil --dev and test in browser
Query DSL Documentation for @schema{}, @query(), etc.
For comprehensive guide to:
@schema{}
@query()
@insert()
@update()
@delete()
@transaction{}
See the Query DSL SKILL:
.../parsley-query-dsl/SKILL.md
Detailed Documentation
For comprehensive guides, see:
- Parsley:
docs/parsley/CHEATSHEET.md and docs/parsley/reference.md
- Basil API:
docs/basil/reference.md - Full language reference with all operators and builtins
Best practices
- Consult cheatsheet
- Run code snippets in
pars before using in documentation
- Run all examples to be sure they are valid code