Roost Script
Roost Script is Roost's JavaScript-style language. If you know JavaScript, you already know most of it. It runs the same commands as Roost Basic, with the same limits and safety rules.
Starting a script
A script is Roost Script when its first line is // roost script. script new tidy --js starts one, and the editor's language menu switches a script between Roost Basic and Roost Script (it changes that first line and the comments, not your code).
// roost script
const yt = tabs({ site: 'youtube.com' })
print(`You have ${yt.length} YouTube tabs`)Script words and memory
Words after a script's name are in args: tidy youtube.com gives args[0] === 'youtube.com'. remember('name', value) keeps a value for the next run, and recall('name', default) reads it back.
// roost script
// visits: count how often I tidy a site
const site = args[0] ?? 'youtube.com'
const n = recall(site, 0) + 1
close(tabs({ site }))
remember(site, n)
print(`Tidied ${site} ${n} times`)Commands are functions
Every Roost command is a function with the same name. Words become arguments; options go in a last { } object, where true gives a flag. Dashes become capitals.
| Roost Script | Runs |
|---|---|
tabs() | tabs |
tabs({ site: 'x.com', older: '3d' }) | tabs --site x.com --older 3d |
open('fruit bat diet', { here: true }) | open "fruit bat diet" --here |
waitFor('Loading', { gone: true }) | wait-for Loading --gone |
click('Reply', { nth: 2 }) | click Reply --nth 2 |
Values are quoted for you, so text from a page can never turn into another command.
Lists and pipes
Commands that find things return a list of items. Each tab has .title, .url, .site, .number, .pinned and more. To pipe a list into a command, pass it as an argument, or call the command on the list with a dot:
const old = tabs({ older: '3d', unpinned: true })
close(old) // $old | close
group(old, 'Old', { color: 'grey' }) // $old | group Old --color grey
links().grep(/pdf/i).head(3).open() // links | grep -i pdf | head 3 | openLists have the usual JavaScript methods (map, filter, find, some, every, reduce, sort, join, slice, includes…) plus Roost's grep, head, tail, uniq, first and last.
Commands with actions
Commands like chirp, wallet, torrent, session and script take an action as their first word. Write it after a dot:
chirp.say('#rust', 'hello from Roost')
const unread = chirp.unread()
print(wallet.balance())
torrent.pause('all')
session.save('research', { close: true })Output
A command on a line by itself shows its output in Roost, just like in Roost Basic. Used as a value (const t = tabs()), it's quiet and returns what it found: a list of items, or text if it only printed one line.
The language
let,const(andvar);if/else,switch, the ternary? :for (const x of list),for (const key in obj),for (;;),while,do … while,break,continue- Functions, arrow functions and closures; default and rest parameters;
return - Objects, arrays, destructuring (
const { title, url } = tab),...spread - Templates (
`Hi ${name}`), regular expressions (/bat/i),?.,??,typeof,=== try/catch/finallyandthrow new Error('why')awaitis accepted but never needed: every command already waits until it's done.
Built in
print(…) console.log(…) | Show values in Roost |
wait('2s') | Pause (500, '2s', '1m'; a minute at most) |
fail('why') stop() | Stop with a message; end the script |
range(5) range(1, 4) | Lists of numbers |
has() exists() read() readAll() value() | Look at the page you're on (see Page checks) |
title() url() site(url) tabCount() | The browser |
hour() minute() weekday() today() now() | The time |
Math JSON Object.keys/values/entries/assign/fromEntries Array.isArray/from Date.now() | The usual helpers |
String() Number() Boolean() parseInt() parseFloat() isNaN() | Conversions |
Text has length, includes, startsWith, endsWith, indexOf, slice, split, replace, replaceAll, trim, toUpperCase, toLowerCase, padStart, padEnd, repeat, match and more.
Classes
Classes work like JavaScript's: fields, a constructor, methods, static members, extends, super(…) and super.method(…), new, this and instanceof. A class can extend Error for your own error types.
// roost script
class Tracker {
seen = []
static made = 0
constructor(site) { this.site = site; Tracker.made++ }
check() {
const now = tabs({ site: this.site }).map(t => t.url)
const fresh = now.filter(u => !this.seen.includes(u))
this.seen.push(...fresh)
return fresh
}
}
class LoudTracker extends Tracker {
check() {
const fresh = super.check()
if (fresh.length) notify(`${fresh.length} new on ${this.site}`)
return fresh
}
}
const t = new LoudTracker('news.example')
t.check()this is the object a method was called on; arrow functions keep the this around them. Methods taken off an object (const f = obj.method) stay attached to it. Getters, setters and #private fields aren't supported: use methods and normal names.
Math and hashes
Math has JavaScript's functions, plus extras for lists of numbers (text like "$18.99" or "1,234" counts too):
Math.sum(list) Math.avg(list) Math.median(list) Math.stdev(list) | Totals and averages |
Math.clamp(x, lo, hi) Math.roundTo(x, 2) Math.percent(part, whole) | Keeping numbers in range and tidy |
Math.format(1234.5) → "1,234.50", (1234.5).toLocaleString() | Numbers for people |
Math.gcd Math.lcm Math.isPrime Math.lerp Math.randomInt(1, 6) | The rest |
Hashes give hex text: md5('hello'), sha1(), sha256(), sha384(), sha512(), or hash(text, 'sha512'). Use them to check text against a published checksum or to notice when part of a page changed (store the hash with remember). md5 and sha1 are fine for that, but not for anything secret.
// roost script
// terms-watch: tell me when a site's terms page changes
open(args[0], { here: true })
const now = sha256(read('main') || '')
if (recall('terms', '') && recall('terms') !== now) notify('The terms changed')
remember('terms', now)What isn’t there
Roost Script is a small language with its own interpreter, not a JavaScript engine. It has no getters or setters, no #private fields, no async, generators, imports or eval, and no way to reach Node, the network or your files. Regular expressions must be simple: patterns that repeat a repeat (like (a+)+) are refused, because they can take forever.
Errors
A failed command throws an error you can catch; e.message says why. Mistakes in the script itself are shown with their line number before anything runs, and the editor marks them as you type.
try {
click('Next page')
} catch (e) {
print('No next page:', e.message)
}At the prompt
js runs one line of Roost Script and shows its value. Variables made with let and const stay for the next js line.
roost:~$ js tabs().length
12
roost:~$ js const yt = tabs({ site: 'youtube.com' })
roost:~$ js yt.map(t => t.title)
["Lo-fi beats", "Bat documentary"]A full example
// roost script
// deals: check the prices on my watch list and tell me about drops
const pages = bookmarks('Watch list')
const found = []
for (const p of pages) {
open(p.url, { here: true })
waitFor('$')
const price = parseFloat((read('.price') || '').replace(/[^0-9.]/g, ''))
if (price && price < 50) found.push(`${title()}: $${price}`)
}
if (found.length) notify(found.join(' · '))
else print('No deals today')