Risor v1.8.0 Release
Published May 18, 2025
I'm pleased to announce the v1.8.0 release of Risor, which adds a bevy of new modules and features to make your scripting easier, more fun, and perhaps weirder than ever.
This is the first Risor release in 2025. Sorry about that. While I'd prefer the project have a more regular release cadence, this isn't my sole focus, so it is what it is. I count 44 pull requests merged since the last release, so it truly is a substantial body of work.
A big shout out to my fellow contributors who authored multiple PRs:
Worth a quick call out: Risor is a community project with no commercial
backing. It's contributions big and small that keep the project moving and
keep my dreams alive. Consider joining in if you find the project interesting!
And eff why eye, I'm @myzie (opens in a new tab) and I started this project back in 2022 as a way to learn more about interpreters. It's continued growing in fits and starts ever since.
Release Highlights
Here's a quick taste of what's new in v1.8.0.
Scheduled Tasks
The new sched
module provides several ways to schedule both one-shot and
recurring tasks. This lends itself to quickly spinning up a background service
with minimal code.
sched.every("1m", func() { print("hello world!") })
Web Crawling
Several new modules provide tools for web crawling and content extraction:
playwright
for browser automationgoquery
for HTML parsing and DOM manipulationhtmltomarkdown
for HTML to Markdown conversion
For example, here's a one-liner using the Risor CLI to fetch a webpage and convert it to Markdown. I love this one. It captures the essence of what Risor is all about. Put some of the best tools available in the Go ecosystem to work for you in a single, concise command.
risor -c 'fetch("https://risor.io").text() | htmltomarkdown | print'
Install and launch Playwright and use it to read the Hacker News headlines:
playwright.install()
playwright.goto("https://news.ycombinator.com", func(page) {
entries := page.locator(".athing").all()
for i, entry := range entries {
title := entry.locator("td.title > span > a")
printf("%d: %s\n", i+1, title.text_content())
}
})
In fitting with the Risor philosophy, all of these capabilities are baked right
into the risor
binary so you can just get to work. No fiddling with dependencies
or fighting with package managers.
Slack Client
This new module makes it incredibly simple to interact with Slack, including sending messages, querying conversations, uploading files, and more.
client := slack.client(getenv("SLACK_TOKEN"))
client.post_message("#project", {text: "Just a test"})
client.upload_file("C12X73344SJ", {
title: "Example File",
filename: "example.txt",
content: "This is the file content",
})
QR Codes
QR codes are now pretty mainstream. Turns out they can be easy to generate too.
The Risor qrcode
module gives you a one-liner to generate a standard QR code.
It also allows for more customization of color, image patterns, and whatnot if
you like being fancy or unique.
qrcode("https://risor.io").save("qrcode.png")
Now you too can avoid all those horrible ad-filled QR code generator websites. Or make one of your own, high five.
Risor Code Assistant
This isn't technically in the Risor release, but I'm calling it out anyways because it's a recent development and I think it's interesting. Basically, we are at a disadvantage by not being a mainstream language because ChatGPT and Claude know relatively little about Risor. Turns out, they actually do know about it by now, but they don't know it well enough to be useful. So to make LLMs decent at writing Risor, you need to teach them.
The Risor Code Assistant (RCA) (opens in a new tab) is a repository of context files that help LLMs write Risor effectively.
SQL Querying Improvements
The first version of the Risor SQL module would read all rows into memory before returning them to the caller. This release adds an opt-in mode that allows for streaming query results and allows the caller to quit whenever. I know, it should've been this way from the start.
db := sql.connect("sqlite://example.db", {"stream": true}) // <-- opt-in
iterator := db.query("SELECT * FROM events ORDER BY created_at DESC")
for _, row := range iterator {
print("event:", row.id, row.created_at, row.body)
if (row.id == 10) { break }
}
Ok, everything can't be a highlight. Switching gears.
Modules
A number of Risor modules were improved, with a general theme of giving Risor scripts access to more Go standard library functionality. The OS related changes go beyond that and are worth a quick call out. The idea with those is to more fully leverage Risor's ability to provide a mock or virtual operating system. This supports script sandboxing and using alternative stdin/stdout, among other things.
- Added
add_date
method to the Time object for easier date manipulation (#311 (opens in a new tab)) - Added missing documentation for
math.inf
(#361 (opens in a new tab)) - Added
math.atan2
function (#357 (opens in a new tab)) - Added support for more CLI app attributes (#326 (opens in a new tab))
- Use the OS interface in the CLI module (#299 (opens in a new tab))
- Enhanced OS module with more user and group functionality (#348 (opens in a new tab))
- Exposed Go's
os
package errors for more detailed error handling (#373 (opens in a new tab)) - Added
errors.is
function for error comparison (#367 (opens in a new tab)) - shlex: Shell-like string parsing based on github.com/u-root/u-root/pkg/shlex (#298 (opens in a new tab))
- Adjustments to
os.exit
behavior (#296 (opens in a new tab), #327 (opens in a new tab))
Language and Toolchain
In this area, we have some important bug fixes and two improvements that I
consider significant. The first is the ability to import modules by path, which
supports more natural module organization. The second is improving compiler
error messages to make it easier to track down undefined variable
and similar
errors.
- Ability to import modules by path (#337 (opens in a new tab))
- File location information in more compiler error messages (#320 (opens in a new tab))
- Added
vm.ErrGlobalNotFound
(#314 (opens in a new tab)) - Added bitwise and operator (#380 (opens in a new tab))
- Fixed break statement bug with range loops (#336 (opens in a new tab))
- Fixed compound operations with set item (#324 (opens in a new tab)) and other compound operation issues (#325 (opens in a new tab))
- Fixed import handling in REPL and named functions as expressions (#293 (opens in a new tab))
Go Interop
When Risor is used as an embedded library, it has the ability to wrap arbitrary Go types as Risor objects, and to proxy function calls on the types. This allows Risor to be added into existing applications to provide a scripting interface on top of the existing types in the application. Risor's capabilities in this area keep improving. In this release, we added support for a couple less common situations like wrapping struct value types.
- Adjusted proxy behavior on struct value types (#371 (opens in a new tab))
- Adjusted proxying to nested struct fields (#315 (opens in a new tab))
- Fixed issues with proxying to Go functions that have an
any
typed parameter (#323 (opens in a new tab)) - Added mechanism to reuse a global database connection in SQL module (#347 (opens in a new tab))
- Added
FSImporter
for importing modules from a Gofs.FS
filesystem (#366 (opens in a new tab))
Dependencies and Build System
To quickly recap my thinking on Risor's approach to dependencies:
- The Risor CLI (the
risor
binary) is meant to be "batteries included". All modules from the core Risor repository are included in the binary that is available for install via Homebrew. - When using Risor as a library, the developer is able to choose which Risor modules to use. It should be possible to use a minimal form of Risor that is otherwise dependency-free, or very nearly so.
- Additional Risor modules can get installed using
go get
.
This release more fully implements that mindset, at the expense of requiring a
couple minor upgrade steps for some Risor library users, who will need to go get
install a couple more Risor modules.
Specifically, these four modules must be individually go get
installed:
github.com/risor-io/risor/modules/color
github.com/risor-io/risor/modules/isatty
github.com/risor-io/risor/modules/tablewriter
github.com/risor-io/risor/modules/yaml
Then provide them as globals, if desired, using the WithGlobals
option:
risor.WithGlobals(map[string]any{
"color": color.Module(),
"isatty": isatty.Module(),
"tablewriter": tablewriter.Module(),
"yaml": yaml.Module(),
})
Get Started or Upgrade
Thanks for taking a look at Risor. It's really easy to try out. Just run
brew install risor
or build from source:
git clone [email protected]:risor-io/risor.git
cd risor/cmd/risor
go install
I'd love to hear your feedback and see what you're building with Risor. Drop into the
GitHub discussions (opens in a new tab) or join the
#risor
channel on the Gophers Slack (opens in a new tab).
Happy scripting,
@myzie (opens in a new tab)