Packages
Looking for packages? Browse and search the registry at sspurlang.github.io/sspur-packages, and add one with
sspur add NAME. To publish yours, see Publishing a package.
A package is a directory with an sspur.toml. This page builds two: a library, money, and an app, shop, that depends on it.
A library
[package]
name = "money"
version = "0.1.0"
src = "lib.ssp"
// Amounts are in cents. Only `pub` definitions are visible to other packages.
pub type Money = {cents: Int} derive Eq, Ord, Show
pub impl Add for Money
fn add(a: Money, b: Money) -> Money = Money{cents: a.cents + b.cents}
pub type MoneyErr = Negative{cents: Int}
pub fn cents(n: Int) -> Money ! fail[MoneyErr]
= if n < 0 then raise Negative{cents: n} else Money{cents: n}
pub fn fmt(m: Money) -> Str
= do
c = m.cents % 100
pad = if c < 10 then "0" else ""
"${m.cents / 100}.{pad}{c}"
test fmt_pads = fmt(Money{cents: 105}) == "$1.05"
pub exports a definition. A pub sum type exports its variants, a pub trait its methods, and pub impl makes an impl visible to packages that use the type. Everything else is private to the package.
An app that uses it
[package]
name = "shop"
version = "0.1.0"
src = "main.ssp"
use money.{Money, fmt}
fn price(n: Int) -> Str
= catch fmt(money.cents(n))
| money.Negative{cents} => "invalid: {cents}"
fn main() -> Unit ! log
= do
log(price(1999))
log(price(-5))
test shows_dollars = price(250) == "$2.50"
test rejects_negative = price(-1) == "invalid: -1"
test adds = Money{cents: 150} + Money{cents: 250} == Money{cents: 400}
use money.{Money, fmt} imports two names. Everything else is written with the package name: money.cents, money.Negative{cents}. A dependency’s effects are part of its signatures, so calling money.cents means handling fail[money.MoneyErr] or declaring it. Money’s + works here because the library made its impl pub.
Adding the dependency
$ sspur add ../money
added money 0.1.0 #hfxpwwzddzox (path+../money): Money, MoneyErr, cents, fmt, impl Add for money.Money
$ cat sspur.toml
[package]
name = "shop"
version = "0.1.0"
src = "main.ssp"
[deps]
money = { path = "../money" }
$ sspur run
$19.99
invalid: -5
$ sspur test
3 passed, 0 failed
$ sspur deps tree
shop 0.1.0
money 0.1.0 #hfxpwwzddzox path+../money
sspur add writes the dependency to sspur.toml and pins it in sspur.lock by the hash of its exports, so your build doesn’t change until you run sspur deps update. That command prints a semantic diff of each changed export (~ money.fmt effects: + log, ~ money.cents contracts: + pre n > 0) and refuses the upgrade if your code no longer checks. A dependency can also be a git URL with a tag: sspur add https://example.com/money.git@v0.1.0.
A package can also keep packages inside its own directory, models = { path = "models" }, to split a program into modules. Those follow their source: edit one and the next build uses the change, without sspur deps update. A path dependency outside the package stays pinned like any other: when its source changes, sspur run, test and check say so (note: lib at ../lib changed since sspur.lock pinned it ...), and an error about a name only the new version has carries the same hint. The web app that sspur new web-app creates is built this way.
A dependency can also be a published package from the registry, sspur add money@^1.2 or a scoped sspur add acme/money, which the next chapter, Publishing a package, covers. A published package can contain module packages like these, path dependencies inside its own directory. The manifest and lock formats are on the sspur.toml and sspur.lock pages.