Compare commits
2 Commits
48408d2763
...
1c6385187d
Author | SHA1 | Date |
---|---|---|
|
1c6385187d | |
|
b3a417a072 |
57
README.md
57
README.md
|
@ -241,6 +241,63 @@ Examples:
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
## http_client
|
||||||
|
|
||||||
|
The inverse of `http-driver`.
|
||||||
|
|
||||||
|
### Caveats
|
||||||
|
- HTTPS is assumed unless the request is to port 80.
|
||||||
|
- If the request or response sets `Content-Type` to `application/json` or `…/preserves`
|
||||||
|
the body will be a parsed Preserves value.
|
||||||
|
- No cache support.
|
||||||
|
- Internal errors propagate using a `400 Internal client error` response.
|
||||||
|
|
||||||
|
Sample Syndicate server script:
|
||||||
|
```
|
||||||
|
# A top-level dataspace
|
||||||
|
let ?ds = dataspace
|
||||||
|
|
||||||
|
# A dataspace for handling the HTTP response.
|
||||||
|
let ?response = dataspace
|
||||||
|
$response [
|
||||||
|
?? <done { "code": "EUR" "exchange_middle": ?middle } > [
|
||||||
|
$ds <exchange EUR RSD $middle>
|
||||||
|
]
|
||||||
|
]
|
||||||
|
|
||||||
|
$ds [
|
||||||
|
<request
|
||||||
|
# Request Euro to Dinar exchange rate.
|
||||||
|
<http-request 0 "kurs.resenje.org" 443
|
||||||
|
get ["api" "v1" "currencies" "eur" "rates" "today"]
|
||||||
|
{Content-Type: "application/json"} {} #f
|
||||||
|
>
|
||||||
|
$response
|
||||||
|
>
|
||||||
|
|
||||||
|
# Log all assertions.
|
||||||
|
? ?any [
|
||||||
|
$log ! <log "-" { forex: $any }>
|
||||||
|
]
|
||||||
|
]
|
||||||
|
|
||||||
|
? <service-object <daemon http-client> ?cap> [
|
||||||
|
$cap <http-client {
|
||||||
|
dataspace: $ds
|
||||||
|
}>
|
||||||
|
]
|
||||||
|
|
||||||
|
<require-service <daemon http-client>>
|
||||||
|
|
||||||
|
? <built http-client ?path ?sum> [
|
||||||
|
<daemon http-client {
|
||||||
|
argv: [ "/bin/http_client" ]
|
||||||
|
clearEnv: #t
|
||||||
|
protocol: application/syndicate
|
||||||
|
}>
|
||||||
|
]
|
||||||
|
```
|
||||||
|
|
||||||
## mintsturdyref
|
## mintsturdyref
|
||||||
|
|
||||||
A utility for minting [Sturdyrefs](https://synit.org/book/operation/builtin/gatekeeper.html#sturdyrefs).
|
A utility for minting [Sturdyrefs](https://synit.org/book/operation/builtin/gatekeeper.html#sturdyrefs).
|
||||||
|
|
|
@ -26,6 +26,10 @@ UnixAddress = <unix @path string>.
|
||||||
|
|
||||||
SocketAddress = TcpAddress / UnixAddress .
|
SocketAddress = TcpAddress / UnixAddress .
|
||||||
|
|
||||||
|
HttpClientArguments = <http-client {
|
||||||
|
dataspace: #:any
|
||||||
|
}>.
|
||||||
|
|
||||||
HttpDriverArguments = <http-driver {
|
HttpDriverArguments = <http-driver {
|
||||||
dataspace: #:any
|
dataspace: #:any
|
||||||
}>.
|
}>.
|
||||||
|
|
|
@ -0,0 +1,90 @@
|
||||||
|
# SPDX-FileCopyrightText: ☭ Emery Hemingway
|
||||||
|
# SPDX-License-Identifier: Unlicense
|
||||||
|
|
||||||
|
import
|
||||||
|
std/[httpclient, options, streams, strutils, tables, uri],
|
||||||
|
pkg/taps,
|
||||||
|
pkg/preserves,
|
||||||
|
pkg/syndicate, pkg/syndicate/protocols/http,
|
||||||
|
../schema/config
|
||||||
|
|
||||||
|
proc url(req: HttpRequest): Uri =
|
||||||
|
result.scheme = if req.port == 80: "http" else: "https"
|
||||||
|
result.hostname = req.host.present
|
||||||
|
result.port = $req.port
|
||||||
|
for i, p in req.path:
|
||||||
|
if 0 < i: result.path.add '/'
|
||||||
|
result.path.add p.encodeUrl
|
||||||
|
for key, vals in req.query:
|
||||||
|
if result.query.len > 0:
|
||||||
|
result.query.add '&'
|
||||||
|
result.query.add key.string.encodeUrl
|
||||||
|
for i, val in vals:
|
||||||
|
if i == 0: result.query.add '='
|
||||||
|
elif i < vals.high: result.query.add ','
|
||||||
|
result.query.add val.string.encodeUrl
|
||||||
|
stderr.writeLine result
|
||||||
|
|
||||||
|
proc bodyString(req: HttpRequest): string =
|
||||||
|
if req.body.orKind == RequestBodyKind.present:
|
||||||
|
return cast[string](req.body.present)
|
||||||
|
|
||||||
|
proc spawnHttpClient*(turn: Turn; root: Cap): Actor {.discardable.} =
|
||||||
|
|
||||||
|
during(turn, root, ?:HttpClientArguments) do (ds: Cap):
|
||||||
|
spawn("http-client", turn) do (turn: Turn):
|
||||||
|
during(turn, ds, HttpContext.grabType) do (ctx: HttpContext):
|
||||||
|
let peer = ctx.res.unembed(Cap).get
|
||||||
|
var client = newHttpClient()
|
||||||
|
try:
|
||||||
|
var
|
||||||
|
headers = newHttpHeaders()
|
||||||
|
contentType = ""
|
||||||
|
for key, val in ctx.req.headers:
|
||||||
|
if key == Symbol"Content-Type":
|
||||||
|
contentType = val
|
||||||
|
client.headers[key.string] = val
|
||||||
|
let stdRes = client.request(
|
||||||
|
ctx.req.url,
|
||||||
|
ctx.req.method.string.toUpper,
|
||||||
|
ctx.req.bodyString, headers
|
||||||
|
)
|
||||||
|
var resp = HttpResponse(orKind: HttpResponseKind.status)
|
||||||
|
resp.status.code = stdRes.status[0 .. 2].parseInt
|
||||||
|
resp.status.message = stdRes.status[3 .. ^1]
|
||||||
|
message(turn, peer, resp)
|
||||||
|
resp = HttpResponse(orKind: HttpResponseKind.header)
|
||||||
|
for key, val in stdRes.headers:
|
||||||
|
if key == "Content-Type":
|
||||||
|
contentType = val
|
||||||
|
resp.header.name = key.Symbol
|
||||||
|
resp.header.value = val
|
||||||
|
message(turn, peer, resp)
|
||||||
|
case contentType
|
||||||
|
of "application/json", "text/preserves":
|
||||||
|
message(turn, peer,
|
||||||
|
initRecord("done", stdRes.bodyStream.readAll.parsePreserves))
|
||||||
|
of "application/preserves":
|
||||||
|
message(turn, peer,
|
||||||
|
initRecord("done", stdRes.bodyStream.decodePreserves))
|
||||||
|
else:
|
||||||
|
resp = HttpResponse(orKind: HttpResponseKind.done)
|
||||||
|
resp.done.chunk.string = stdRes.bodyStream.readAll()
|
||||||
|
message(turn, peer, resp)
|
||||||
|
except CatchableError as err:
|
||||||
|
var resp = HttpResponse(orKind: HttpResponseKind.status)
|
||||||
|
resp.status.code = 400
|
||||||
|
resp.status.message = "Internal client error"
|
||||||
|
message(turn, peer, resp)
|
||||||
|
resp = HttpResponse(orKind: HttpResponseKind.done)
|
||||||
|
resp.done.chunk.string = err.msg
|
||||||
|
message(turn, peer, resp)
|
||||||
|
client.close()
|
||||||
|
do:
|
||||||
|
client.close()
|
||||||
|
|
||||||
|
when isMainModule:
|
||||||
|
import syndicate/relays
|
||||||
|
runActor("main") do (turn: Turn):
|
||||||
|
resolveEnvironment(turn) do (turn: Turn; ds: Cap):
|
||||||
|
spawnHttpClient(turn, ds)
|
|
@ -0,0 +1 @@
|
||||||
|
define:ssl
|
|
@ -20,8 +20,8 @@ proc mount(source, target, fsType: cstring; flags: culong; data: pointer): cint
|
||||||
proc umount(target: cstring): cint {.importc, header: "<sys/mount.h>".}
|
proc umount(target: cstring): cint {.importc, header: "<sys/mount.h>".}
|
||||||
## `umount(2)`
|
## `umount(2)`
|
||||||
|
|
||||||
proc spawnMountActor*(turn: var Turn; ds: Cap): Actor {.discardable.} =
|
proc spawnMountActor*(turn: Turn; ds: Cap): Actor {.discardable.} =
|
||||||
spawnActor(turn, "mount_actor") do (turn: var Turn):
|
spawnActor(turn, "mount_actor") do (turn: Turn):
|
||||||
let
|
let
|
||||||
targetPat = observePattern(!Mountpoint, { @[%1]: grabLit() })
|
targetPat = observePattern(!Mountpoint, { @[%1]: grabLit() })
|
||||||
sourcePat = observePattern(!Mountpoint, {
|
sourcePat = observePattern(!Mountpoint, {
|
||||||
|
@ -48,6 +48,6 @@ proc spawnMountActor*(turn: var Turn; ds: Cap): Actor {.discardable.} =
|
||||||
|
|
||||||
when isMainModule:
|
when isMainModule:
|
||||||
import syndicate/relays
|
import syndicate/relays
|
||||||
runActor("main") do (turn: var Turn):
|
runActor("main") do (turn: Turn):
|
||||||
resolveEnvironment(turn) do (turn: var Turn; ds: Cap):
|
resolveEnvironment(turn) do (turn: Turn; ds: Cap):
|
||||||
discard spawnMountActor(turn, ds)
|
discard spawnMountActor(turn, ds)
|
||||||
|
|
|
@ -6,11 +6,11 @@ import preserves, syndicate, syndicate/relays
|
||||||
|
|
||||||
setControlCHook(proc () {.noconv.} = quit())
|
setControlCHook(proc () {.noconv.} = quit())
|
||||||
|
|
||||||
runActor("msg") do (turn: var Turn):
|
runActor("msg") do (turn: Turn):
|
||||||
let
|
let
|
||||||
data = map(commandLineParams(), parsePreserves)
|
data = map(commandLineParams(), parsePreserves)
|
||||||
cmd = paramStr(0).extractFilename.normalize
|
cmd = paramStr(0).extractFilename.normalize
|
||||||
resolveEnvironment(turn) do (turn: var Turn; ds: Cap):
|
resolveEnvironment(turn) do (turn: Turn; ds: Cap):
|
||||||
case cmd
|
case cmd
|
||||||
of "assert":
|
of "assert":
|
||||||
for e in data:
|
for e in data:
|
||||||
|
@ -18,5 +18,5 @@ runActor("msg") do (turn: var Turn):
|
||||||
else: # "msg"
|
else: # "msg"
|
||||||
for e in data:
|
for e in data:
|
||||||
message(turn, ds, e)
|
message(turn, ds, e)
|
||||||
sync(turn, ds) do (turn: var Turn):
|
sync(turn, ds) do (turn: Turn):
|
||||||
stopActor(turn)
|
stopActor(turn)
|
||||||
|
|
|
@ -105,8 +105,8 @@ proc renderSql(tokens: openarray[Value]): string =
|
||||||
else:
|
else:
|
||||||
return ""
|
return ""
|
||||||
|
|
||||||
proc spawnPostgreActor*(turn: var Turn; root: Cap): Actor {.discardable.} =
|
proc spawnPostgreActor*(turn: Turn; root: Cap): Actor {.discardable.} =
|
||||||
spawn("postgre", turn) do (turn: var Turn):
|
spawn("postgre", turn) do (turn: Turn):
|
||||||
during(turn, root, ?:PostgreArguments) do (params: StringPairs, ds: Cap):
|
during(turn, root, ?:PostgreArguments) do (params: StringPairs, ds: Cap):
|
||||||
var
|
var
|
||||||
conn: PGconn
|
conn: PGconn
|
||||||
|
@ -153,6 +153,6 @@ proc spawnPostgreActor*(turn: var Turn; root: Cap): Actor {.discardable.} =
|
||||||
when isMainModule:
|
when isMainModule:
|
||||||
import syndicate/relays
|
import syndicate/relays
|
||||||
|
|
||||||
runActor("main") do (turn: var Turn):
|
runActor("main") do (turn: Turn):
|
||||||
resolveEnvironment(turn) do (turn: var Turn; ds: Cap):
|
resolveEnvironment(turn) do (turn: Turn; ds: Cap):
|
||||||
spawnPostgreActor(turn, ds)
|
spawnPostgreActor(turn, ds)
|
||||||
|
|
|
@ -10,8 +10,8 @@ import ./schema/rofi
|
||||||
if getEnv("ROFI_OUTSIDE") == "":
|
if getEnv("ROFI_OUTSIDE") == "":
|
||||||
quit("run this program in rofi")
|
quit("run this program in rofi")
|
||||||
|
|
||||||
runActor("rofi_script_actor") do (turn: var Turn):
|
runActor("rofi_script_actor") do (turn: Turn):
|
||||||
resolveEnvironment(turn) do (turn: var Turn; ds: Cap):
|
resolveEnvironment(turn) do (turn: Turn; ds: Cap):
|
||||||
case paramCount()
|
case paramCount()
|
||||||
of 0:
|
of 0:
|
||||||
let pat = ?:Options
|
let pat = ?:Options
|
||||||
|
|
|
@ -10,6 +10,12 @@ type
|
||||||
WebsocketArguments* {.preservesRecord: "websocket".} = object
|
WebsocketArguments* {.preservesRecord: "websocket".} = object
|
||||||
`field0`*: WebsocketArgumentsField0
|
`field0`*: WebsocketArgumentsField0
|
||||||
|
|
||||||
|
HttpClientArgumentsField0* {.preservesDictionary.} = object
|
||||||
|
`dataspace`* {.preservesEmbedded.}: EmbeddedRef
|
||||||
|
|
||||||
|
HttpClientArguments* {.preservesRecord: "http-client".} = object
|
||||||
|
`field0`*: HttpClientArgumentsField0
|
||||||
|
|
||||||
JsonTranslatorArgumentsField0* {.preservesDictionary.} = object
|
JsonTranslatorArgumentsField0* {.preservesDictionary.} = object
|
||||||
`argv`*: seq[string]
|
`argv`*: seq[string]
|
||||||
`dataspace`* {.preservesEmbedded.}: EmbeddedRef
|
`dataspace`* {.preservesEmbedded.}: EmbeddedRef
|
||||||
|
@ -117,7 +123,8 @@ type
|
||||||
`host`*: string
|
`host`*: string
|
||||||
`port`*: BiggestInt
|
`port`*: BiggestInt
|
||||||
|
|
||||||
proc `$`*(x: WebsocketArguments | JsonTranslatorArguments | SocketAddress |
|
proc `$`*(x: WebsocketArguments | HttpClientArguments | JsonTranslatorArguments |
|
||||||
|
SocketAddress |
|
||||||
Base64DecoderArguments |
|
Base64DecoderArguments |
|
||||||
JsonTranslatorConnected |
|
JsonTranslatorConnected |
|
||||||
JsonSocketTranslatorArguments |
|
JsonSocketTranslatorArguments |
|
||||||
|
@ -136,7 +143,9 @@ proc `$`*(x: WebsocketArguments | JsonTranslatorArguments | SocketAddress |
|
||||||
Tcp): string =
|
Tcp): string =
|
||||||
`$`(toPreserves(x))
|
`$`(toPreserves(x))
|
||||||
|
|
||||||
proc encode*(x: WebsocketArguments | JsonTranslatorArguments | SocketAddress |
|
proc encode*(x: WebsocketArguments | HttpClientArguments |
|
||||||
|
JsonTranslatorArguments |
|
||||||
|
SocketAddress |
|
||||||
Base64DecoderArguments |
|
Base64DecoderArguments |
|
||||||
JsonTranslatorConnected |
|
JsonTranslatorConnected |
|
||||||
JsonSocketTranslatorArguments |
|
JsonSocketTranslatorArguments |
|
||||||
|
|
|
@ -55,14 +55,14 @@ proc finalize(stmt: Stmt): cint {.importSqlite3.}
|
||||||
doAssert libversion_number() == SQLITE_VERSION_NUMBER
|
doAssert libversion_number() == SQLITE_VERSION_NUMBER
|
||||||
|
|
||||||
proc assertError(facet: Facet; cap: Cap; db: Sqlite3; context: string) =
|
proc assertError(facet: Facet; cap: Cap; db: Sqlite3; context: string) =
|
||||||
run(facet) do (turn: var Turn):
|
run(facet) do (turn: Turn):
|
||||||
publish(turn, cap, SqlError(
|
publish(turn, cap, SqlError(
|
||||||
msg: $errmsg(db),
|
msg: $errmsg(db),
|
||||||
context: context,
|
context: context,
|
||||||
))
|
))
|
||||||
|
|
||||||
proc assertError(facet: Facet; cap: Cap; msg, context: string) =
|
proc assertError(facet: Facet; cap: Cap; msg, context: string) =
|
||||||
run(facet) do (turn: var Turn):
|
run(facet) do (turn: Turn):
|
||||||
publish(turn, cap, SqlError(
|
publish(turn, cap, SqlError(
|
||||||
msg: msg,
|
msg: msg,
|
||||||
context: context,
|
context: context,
|
||||||
|
@ -107,17 +107,17 @@ proc renderSql(tokens: openarray[Value]): string =
|
||||||
else:
|
else:
|
||||||
return ""
|
return ""
|
||||||
|
|
||||||
proc spawnSqliteActor*(turn: var Turn; root: Cap): Actor {.discardable.} =
|
proc spawnSqliteActor*(turn: Turn; root: Cap): Actor {.discardable.} =
|
||||||
spawn("sqlite-actor", turn) do (turn: var Turn):
|
spawn("sqlite-actor", turn) do (turn: Turn):
|
||||||
during(turn, root, ?:SqliteArguments) do (path: string, ds: Cap):
|
during(turn, root, ?:SqliteArguments) do (path: string, ds: Cap):
|
||||||
linkActor(turn, path) do (turn: var Turn):
|
linkActor(turn, path) do (turn: Turn):
|
||||||
let facet = turn.facet
|
let facet = turn.facet
|
||||||
stderr.writeLine("opening SQLite database ", path)
|
stderr.writeLine("opening SQLite database ", path)
|
||||||
var db: Sqlite3
|
var db: Sqlite3
|
||||||
if open_v2(path, addr db, SQLITE_OPEN_READONLY, nil) != SQLITE_OK:
|
if open_v2(path, addr db, SQLITE_OPEN_READONLY, nil) != SQLITE_OK:
|
||||||
assertError(facet, ds, db, path)
|
assertError(facet, ds, db, path)
|
||||||
else:
|
else:
|
||||||
turn.onStop do (turn: var Turn):
|
turn.onStop do (turn: Turn):
|
||||||
close(db)
|
close(db)
|
||||||
stderr.writeLine("closed SQLite database ", path)
|
stderr.writeLine("closed SQLite database ", path)
|
||||||
during(turn, ds, ?:Query) do (statement: seq[Value], target: Cap):
|
during(turn, ds, ?:Query) do (statement: seq[Value], target: Cap):
|
||||||
|
@ -144,6 +144,6 @@ proc spawnSqliteActor*(turn: var Turn; root: Cap): Actor {.discardable.} =
|
||||||
|
|
||||||
when isMainModule:
|
when isMainModule:
|
||||||
import syndicate/relays
|
import syndicate/relays
|
||||||
runActor("main") do (turn: var Turn):
|
runActor("main") do (turn: Turn):
|
||||||
resolveEnvironment(turn) do (turn: var Turn; ds: Cap):
|
resolveEnvironment(turn) do (turn: Turn; ds: Cap):
|
||||||
spawnSqliteActor(turn, ds)
|
spawnSqliteActor(turn, ds)
|
||||||
|
|
|
@ -15,8 +15,8 @@ import ./syndesizer/[
|
||||||
pulses,
|
pulses,
|
||||||
xml_translator]
|
xml_translator]
|
||||||
|
|
||||||
runActor("syndesizer") do (turn: var Turn):
|
runActor("syndesizer") do (turn: Turn):
|
||||||
resolveEnvironment(turn) do (turn: var Turn; ds: Cap):
|
resolveEnvironment(turn) do (turn: Turn; ds: Cap):
|
||||||
discard spawnTimerDriver(turn, ds)
|
discard spawnTimerDriver(turn, ds)
|
||||||
discard spawnBase64Decoder(turn, ds)
|
discard spawnBase64Decoder(turn, ds)
|
||||||
discard spawnCacheActor(turn, ds)
|
discard spawnCacheActor(turn, ds)
|
||||||
|
|
|
@ -10,8 +10,8 @@ import ../schema/base64 as schema
|
||||||
export Base64DecoderArguments
|
export Base64DecoderArguments
|
||||||
export schema
|
export schema
|
||||||
|
|
||||||
proc spawnBase64Decoder*(turn: var Turn; root: Cap): Actor {.discardable.} =
|
proc spawnBase64Decoder*(turn: Turn; root: Cap): Actor {.discardable.} =
|
||||||
spawnActor(turn, "base64-decoder") do (turn: var Turn):
|
spawnActor(turn, "base64-decoder") do (turn: Turn):
|
||||||
let tmpDir = getTempDir()
|
let tmpDir = getTempDir()
|
||||||
during(turn, root, ?:Base64DecoderArguments) do (ds: Cap):
|
during(turn, root, ?:Base64DecoderArguments) do (ds: Cap):
|
||||||
|
|
||||||
|
@ -46,6 +46,6 @@ proc spawnBase64Decoder*(turn: var Turn; root: Cap): Actor {.discardable.} =
|
||||||
|
|
||||||
when isMainModule:
|
when isMainModule:
|
||||||
import syndicate/relays
|
import syndicate/relays
|
||||||
runActor("main") do (turn: var Turn):
|
runActor("main") do (turn: Turn):
|
||||||
resolveEnvironment(turn) do (turn: var Turn; ds: Cap):
|
resolveEnvironment(turn) do (turn: Turn; ds: Cap):
|
||||||
spawnBase64Decoder(turn, ds)
|
spawnBase64Decoder(turn, ds)
|
||||||
|
|
|
@ -18,9 +18,9 @@ type CacheEntity {.final.} = ref object of Entity
|
||||||
pattern: Pattern
|
pattern: Pattern
|
||||||
lifetime: float64
|
lifetime: float64
|
||||||
|
|
||||||
method publish(cache: CacheEntity; turn: var Turn; ass: AssertionRef; h: Handle) =
|
method publish(cache: CacheEntity; turn: Turn; ass: AssertionRef; h: Handle) =
|
||||||
## Re-assert pattern captures in a sub-facet.
|
## Re-assert pattern captures in a sub-facet.
|
||||||
discard inFacet(turn) do (turn: var Turn):
|
discard inFacet(turn) do (turn: Turn):
|
||||||
# TODO: a seperate facet for every assertion, too much?
|
# TODO: a seperate facet for every assertion, too much?
|
||||||
var ass = depattern(cache.pattern, ass.value.sequence)
|
var ass = depattern(cache.pattern, ass.value.sequence)
|
||||||
# Build an assertion with what he have of the pattern and capture.
|
# Build an assertion with what he have of the pattern and capture.
|
||||||
|
@ -34,8 +34,8 @@ proc isObserve(pat: Pattern): bool =
|
||||||
pat.group.type.orKind == GroupTypeKind.rec and
|
pat.group.type.orKind == GroupTypeKind.rec and
|
||||||
pat.group.type.rec.label.isSymbol"Observe"
|
pat.group.type.rec.label.isSymbol"Observe"
|
||||||
|
|
||||||
proc spawnCacheActor*(turn: var Turn; root: Cap): Actor =
|
proc spawnCacheActor*(turn: Turn; root: Cap): Actor =
|
||||||
spawnActor(turn, "cache_actor") do (turn: var Turn):
|
spawnActor(turn, "cache_actor") do (turn: Turn):
|
||||||
during(turn, root, ?:CacheArguments) do (ds: Cap, lifetime: float64):
|
during(turn, root, ?:CacheArguments) do (ds: Cap, lifetime: float64):
|
||||||
onPublish(turn, ds, ?:Observe) do (pat: Pattern, obs: Cap):
|
onPublish(turn, ds, ?:Observe) do (pat: Pattern, obs: Cap):
|
||||||
var cache: CacheEntity
|
var cache: CacheEntity
|
||||||
|
@ -52,7 +52,7 @@ proc spawnCacheActor*(turn: var Turn; root: Cap): Actor =
|
||||||
|
|
||||||
when isMainModule:
|
when isMainModule:
|
||||||
import syndicate/relays
|
import syndicate/relays
|
||||||
runActor("main") do (turn: var Turn):
|
runActor("main") do (turn: Turn):
|
||||||
resolveEnvironment(turn) do (turn: var Turn; ds: Cap):
|
resolveEnvironment(turn) do (turn: Turn; ds: Cap):
|
||||||
discard spawnTimerDriver(turn, ds)
|
discard spawnTimerDriver(turn, ds)
|
||||||
discard spawnCacheActor(turn, ds)
|
discard spawnCacheActor(turn, ds)
|
||||||
|
|
|
@ -7,8 +7,8 @@ import syndicate
|
||||||
|
|
||||||
import ../schema/[assertions, config]
|
import ../schema/[assertions, config]
|
||||||
|
|
||||||
proc spawnFileSystemUsageActor*(turn: var Turn; root: Cap): Actor {.discardable.} =
|
proc spawnFileSystemUsageActor*(turn: Turn; root: Cap): Actor {.discardable.} =
|
||||||
spawn("file-system-usage", turn) do (turn: var Turn):
|
spawn("file-system-usage", turn) do (turn: Turn):
|
||||||
during(turn, root, ?:FileSystemUsageArguments) do (ds: Cap):
|
during(turn, root, ?:FileSystemUsageArguments) do (ds: Cap):
|
||||||
let pat = observePattern(!FileSystemUsage, { @[%0]: grab() })
|
let pat = observePattern(!FileSystemUsage, { @[%0]: grab() })
|
||||||
during(turn, ds, pat) do (lit: Literal[string]):
|
during(turn, ds, pat) do (lit: Literal[string]):
|
||||||
|
@ -23,6 +23,6 @@ proc spawnFileSystemUsageActor*(turn: var Turn; root: Cap): Actor {.discardable.
|
||||||
|
|
||||||
when isMainModule:
|
when isMainModule:
|
||||||
import syndicate/relays
|
import syndicate/relays
|
||||||
runActor("main") do (turn: var Turn):
|
runActor("main") do (turn: Turn):
|
||||||
resolveEnvironment(turn) do (turn: var Turn; ds: Cap):
|
resolveEnvironment(turn) do (turn: Turn; ds: Cap):
|
||||||
discard spawnFileSystemUsageActor(turn, ds)
|
discard spawnFileSystemUsageActor(turn, ds)
|
||||||
|
|
|
@ -8,7 +8,7 @@ import pkg/syndicate/drivers/http_driver
|
||||||
import pkg/taps
|
import pkg/taps
|
||||||
import ../schema/config
|
import ../schema/config
|
||||||
|
|
||||||
proc spawnHttpDriver*(turn: var Turn; ds: Cap): Actor {.discardable.}=
|
proc spawnHttpDriver*(turn: Turn; ds: Cap): Actor {.discardable.}=
|
||||||
http_driver.spawnHttpDriver(turn, ds)
|
http_driver.spawnHttpDriver(turn, ds)
|
||||||
during(turn, ds, ?:HttpDriverArguments) do (ds: Cap):
|
during(turn, ds, ?:HttpDriverArguments) do (ds: Cap):
|
||||||
http_driver.spawnHttpDriver(turn, ds)
|
http_driver.spawnHttpDriver(turn, ds)
|
||||||
|
@ -30,13 +30,13 @@ when isMainModule:
|
||||||
return
|
return
|
||||||
quit("failed to parse command line for route to Syndicate gatekeeper")
|
quit("failed to parse command line for route to Syndicate gatekeeper")
|
||||||
|
|
||||||
runActor("main") do (turn: var Turn):
|
runActor("main") do (turn: Turn):
|
||||||
let ds = newDataspace(turn)
|
let ds = newDataspace(turn)
|
||||||
spawnRelays(turn, ds)
|
spawnRelays(turn, ds)
|
||||||
resolve(turn, ds, envRoute(), spawnHttpDriver)
|
resolve(turn, ds, envRoute(), spawnHttpDriver)
|
||||||
|
|
||||||
else:
|
else:
|
||||||
|
|
||||||
runActor("main") do (turn: var Turn):
|
runActor("main") do (turn: Turn):
|
||||||
resolveEnvironment(turn) do (turn: var Turn; ds: Cap):
|
resolveEnvironment(turn) do (turn: Turn; ds: Cap):
|
||||||
spawnHttpDriver(turn, ds)
|
spawnHttpDriver(turn, ds)
|
||||||
|
|
|
@ -14,9 +14,9 @@ template translateSocketBody {.dirty.} =
|
||||||
dec = newBufferedDecoder(0)
|
dec = newBufferedDecoder(0)
|
||||||
buf = new string #TODO: get a pointer into the decoder
|
buf = new string #TODO: get a pointer into the decoder
|
||||||
alive = true
|
alive = true
|
||||||
proc kill(turn: var Turn) =
|
proc kill(turn: Turn) =
|
||||||
alive = false
|
alive = false
|
||||||
proc setup(turn: var Turn) =
|
proc setup(turn: Turn) =
|
||||||
# Closure, not CPS.
|
# Closure, not CPS.
|
||||||
onMessage(turn, ds, ?:SendJson) do (data: JsonNode):
|
onMessage(turn, ds, ?:SendJson) do (data: JsonNode):
|
||||||
if alive:
|
if alive:
|
||||||
|
@ -38,7 +38,7 @@ template translateSocketBody {.dirty.} =
|
||||||
dec.feed(buf[])
|
dec.feed(buf[])
|
||||||
var data = dec.parse()
|
var data = dec.parse()
|
||||||
if data.isSome:
|
if data.isSome:
|
||||||
proc send(turn: var Turn) =
|
proc send(turn: Turn) =
|
||||||
# Closure, not CPS.
|
# Closure, not CPS.
|
||||||
message(turn, ds, initRecord("recv", data.get))
|
message(turn, ds, initRecord("recv", data.get))
|
||||||
run(facet, send)
|
run(facet, send)
|
||||||
|
@ -59,19 +59,19 @@ proc translateSocket(facet: Facet; ds: Cap; sa: UnixAddress) {.asyncio.} =
|
||||||
socket[] = conn
|
socket[] = conn
|
||||||
translateSocketBody()
|
translateSocketBody()
|
||||||
|
|
||||||
proc spawnJsonSocketTranslator*(turn: var Turn; root: Cap): Actor {.discardable.} =
|
proc spawnJsonSocketTranslator*(turn: Turn; root: Cap): Actor {.discardable.} =
|
||||||
spawnActor(turn, "json-socket-translator") do (turn: var Turn):
|
spawnActor(turn, "json-socket-translator") do (turn: Turn):
|
||||||
during(turn, root, ?:JsonSocketTranslatorArguments) do (ds: Cap, sa: TcpAddress):
|
during(turn, root, ?:JsonSocketTranslatorArguments) do (ds: Cap, sa: TcpAddress):
|
||||||
linkActor(turn, "json-socket-translator") do (turn: var Turn):
|
linkActor(turn, "json-socket-translator") do (turn: Turn):
|
||||||
discard trampoline:
|
discard trampoline:
|
||||||
whelp translateSocket(turn.facet, ds, sa)
|
whelp translateSocket(turn.facet, ds, sa)
|
||||||
during(turn, root, ?:JsonSocketTranslatorArguments) do (ds: Cap, sa: UnixAddress):
|
during(turn, root, ?:JsonSocketTranslatorArguments) do (ds: Cap, sa: UnixAddress):
|
||||||
linkActor(turn, "json-socket-translator") do (turn: var Turn):
|
linkActor(turn, "json-socket-translator") do (turn: Turn):
|
||||||
discard trampoline:
|
discard trampoline:
|
||||||
whelp translateSocket(turn.facet, ds, sa)
|
whelp translateSocket(turn.facet, ds, sa)
|
||||||
|
|
||||||
when isMainModule:
|
when isMainModule:
|
||||||
import syndicate/relays
|
import syndicate/relays
|
||||||
runActor("main") do (turn: var Turn):
|
runActor("main") do (turn: Turn):
|
||||||
resolveEnvironment(turn) do (turn: var Turn; ds: Cap):
|
resolveEnvironment(turn) do (turn: Turn; ds: Cap):
|
||||||
spawnJsonSocketTranslator(turn, ds)
|
spawnJsonSocketTranslator(turn, ds)
|
||||||
|
|
|
@ -19,8 +19,8 @@ proc runChild(params: seq[string]): string =
|
||||||
if result == "":
|
if result == "":
|
||||||
stderr.writeLine "no ouput"
|
stderr.writeLine "no ouput"
|
||||||
|
|
||||||
proc spawnJsonStdioTranslator*(turn: var Turn; root: Cap): Actor {.discardable.} =
|
proc spawnJsonStdioTranslator*(turn: Turn; root: Cap): Actor {.discardable.} =
|
||||||
spawnActor(turn, "json-stdio-translator") do (turn: var Turn):
|
spawnActor(turn, "json-stdio-translator") do (turn: Turn):
|
||||||
during(turn, root, ?:JsonTranslatorArguments) do (argv: seq[string], ds: Cap):
|
during(turn, root, ?:JsonTranslatorArguments) do (argv: seq[string], ds: Cap):
|
||||||
var js = parseJson(runChild(argv))
|
var js = parseJson(runChild(argv))
|
||||||
message(turn, ds, RecvJson(data: js))
|
message(turn, ds, RecvJson(data: js))
|
||||||
|
@ -28,6 +28,6 @@ proc spawnJsonStdioTranslator*(turn: var Turn; root: Cap): Actor {.discardable.}
|
||||||
|
|
||||||
when isMainModule:
|
when isMainModule:
|
||||||
import syndicate/relays
|
import syndicate/relays
|
||||||
runActor("main") do (turn: var Turn):
|
runActor("main") do (turn: Turn):
|
||||||
resolveEnvironment(turn) do (turn: var Turn; ds: Cap):
|
resolveEnvironment(turn) do (turn: Turn; ds: Cap):
|
||||||
spawnJsonStdioTranslator(turn, ds)
|
spawnJsonStdioTranslator(turn, ds)
|
||||||
|
|
|
@ -17,7 +17,7 @@ type PulseEntity {.final.} = ref object of Entity
|
||||||
observePattern: Pattern
|
observePattern: Pattern
|
||||||
observing: bool
|
observing: bool
|
||||||
|
|
||||||
proc schedule(turn: var Turn; pulse: PulseEntity) =
|
proc schedule(turn: Turn; pulse: PulseEntity) =
|
||||||
## Schedule the next pulse.
|
## Schedule the next pulse.
|
||||||
## The next pulse will be schedule using the current time as
|
## The next pulse will be schedule using the current time as
|
||||||
## reference point and not the moment of the previous pulse.
|
## reference point and not the moment of the previous pulse.
|
||||||
|
@ -27,7 +27,7 @@ proc schedule(turn: var Turn; pulse: PulseEntity) =
|
||||||
observer: pulse.self,
|
observer: pulse.self,
|
||||||
))
|
))
|
||||||
|
|
||||||
method publish(pulse: PulseEntity; turn: var Turn; ass: AssertionRef; h: Handle) =
|
method publish(pulse: PulseEntity; turn: Turn; ass: AssertionRef; h: Handle) =
|
||||||
## Publish observers in reponse to <later-than …> assertions.
|
## Publish observers in reponse to <later-than …> assertions.
|
||||||
pulse.timers.target.retract(turn, pulse.timerHandle)
|
pulse.timers.target.retract(turn, pulse.timerHandle)
|
||||||
schedule(turn, pulse)
|
schedule(turn, pulse)
|
||||||
|
@ -36,7 +36,7 @@ method publish(pulse: PulseEntity; turn: var Turn; ass: AssertionRef; h: Handle)
|
||||||
pulse.target.publish(turn, a, h)
|
pulse.target.publish(turn, a, h)
|
||||||
pulse.target.sync(turn, pulse.self)
|
pulse.target.sync(turn, pulse.self)
|
||||||
|
|
||||||
method message(pulse: PulseEntity; turn: var Turn; v: AssertionRef) =
|
method message(pulse: PulseEntity; turn: Turn; v: AssertionRef) =
|
||||||
## Retract observers in response to a sync message.
|
## Retract observers in response to a sync message.
|
||||||
pulse.observing = false
|
pulse.observing = false
|
||||||
for h in pulse.observers.keys:
|
for h in pulse.observers.keys:
|
||||||
|
@ -46,7 +46,7 @@ type ProxyEntity {.final.} = ref object of Entity
|
||||||
## A proxy `Entity` that diverts observers to a `PulseEntity`.
|
## A proxy `Entity` that diverts observers to a `PulseEntity`.
|
||||||
pulse: PulseEntity
|
pulse: PulseEntity
|
||||||
|
|
||||||
method publish(proxy: ProxyEntity; turn: var Turn; ass: AssertionRef; h: Handle) =
|
method publish(proxy: ProxyEntity; turn: Turn; ass: AssertionRef; h: Handle) =
|
||||||
## Proxy assertions that are not observations.
|
## Proxy assertions that are not observations.
|
||||||
if proxy.pulse.observePattern.matches ass.value:
|
if proxy.pulse.observePattern.matches ass.value:
|
||||||
if proxy.pulse.observers.len == 0:
|
if proxy.pulse.observers.len == 0:
|
||||||
|
@ -55,7 +55,7 @@ method publish(proxy: ProxyEntity; turn: var Turn; ass: AssertionRef; h: Handle)
|
||||||
else:
|
else:
|
||||||
proxy.pulse.target.publish(turn, ass, h)
|
proxy.pulse.target.publish(turn, ass, h)
|
||||||
|
|
||||||
method retract(proxy: ProxyEntity; turn: var Turn; h: Handle) =
|
method retract(proxy: ProxyEntity; turn: Turn; h: Handle) =
|
||||||
## Retract proxied assertions.
|
## Retract proxied assertions.
|
||||||
var obs: AssertionRef
|
var obs: AssertionRef
|
||||||
if proxy.pulse.observers.pop(h, obs):
|
if proxy.pulse.observers.pop(h, obs):
|
||||||
|
@ -66,15 +66,15 @@ method retract(proxy: ProxyEntity; turn: var Turn; h: Handle) =
|
||||||
else:
|
else:
|
||||||
proxy.pulse.target.retract(turn, h)
|
proxy.pulse.target.retract(turn, h)
|
||||||
|
|
||||||
method message(proxy: ProxyEntity; turn: var Turn; v: AssertionRef) =
|
method message(proxy: ProxyEntity; turn: Turn; v: AssertionRef) =
|
||||||
## Proxy mesages.
|
## Proxy mesages.
|
||||||
proxy.pulse.target.message(turn, v)
|
proxy.pulse.target.message(turn, v)
|
||||||
|
|
||||||
method sync(proxy: ProxyEntity; turn: var Turn; peer: Cap) =
|
method sync(proxy: ProxyEntity; turn: Turn; peer: Cap) =
|
||||||
## Proxy sync.
|
## Proxy sync.
|
||||||
proxy.pulse.target.sync(turn, peer)
|
proxy.pulse.target.sync(turn, peer)
|
||||||
|
|
||||||
proc newProxyEntity(turn: var Turn; timers, ds: Cap; period: float): ProxyEntity =
|
proc newProxyEntity(turn: Turn; timers, ds: Cap; period: float): ProxyEntity =
|
||||||
new result
|
new result
|
||||||
result.pulse = PulseEntity(
|
result.pulse = PulseEntity(
|
||||||
target: ds.target,
|
target: ds.target,
|
||||||
|
@ -84,10 +84,10 @@ proc newProxyEntity(turn: var Turn; timers, ds: Cap; period: float): ProxyEntity
|
||||||
)
|
)
|
||||||
result.pulse.self = newCap(turn, result.pulse)
|
result.pulse.self = newCap(turn, result.pulse)
|
||||||
|
|
||||||
proc spawnPulseActor*(turn: var Turn; root: Cap): Actor =
|
proc spawnPulseActor*(turn: Turn; root: Cap): Actor =
|
||||||
## Spawn an actor that retracts and re-asserts observers on
|
## Spawn an actor that retracts and re-asserts observers on
|
||||||
## a timed pulse. Requires a timer service on the `root` capability.
|
## a timed pulse. Requires a timer service on the `root` capability.
|
||||||
spawnActor(turn, "pulse") do (turn: var Turn):
|
spawnActor(turn, "pulse") do (turn: Turn):
|
||||||
let grabPeriod = observePattern(!Pulse, { @[%0]: grab() })
|
let grabPeriod = observePattern(!Pulse, { @[%0]: grab() })
|
||||||
during(turn, root, ?:PulseArguments) do (ds: Cap):
|
during(turn, root, ?:PulseArguments) do (ds: Cap):
|
||||||
during(turn, ds, grabPeriod) do (lit: Literal[float]):
|
during(turn, ds, grabPeriod) do (lit: Literal[float]):
|
||||||
|
@ -100,6 +100,6 @@ proc spawnPulseActor*(turn: var Turn; root: Cap): Actor =
|
||||||
|
|
||||||
when isMainModule:
|
when isMainModule:
|
||||||
import syndicate/relays
|
import syndicate/relays
|
||||||
runActor("main") do (turn: var Turn):
|
runActor("main") do (turn: Turn):
|
||||||
resolveEnvironment(turn) do (turn: var Turn; ds: Cap):
|
resolveEnvironment(turn) do (turn: Turn; ds: Cap):
|
||||||
discard spawnPulseActor(turn, ds)
|
discard spawnPulseActor(turn, ds)
|
||||||
|
|
|
@ -17,8 +17,8 @@ proc translatePreserves(pr: Value): XmlTranslation {.gcsafe.} =
|
||||||
var xn = result.pr.preservesTo(XmlNode)
|
var xn = result.pr.preservesTo(XmlNode)
|
||||||
if xn.isSome: result.xml = $get(xn)
|
if xn.isSome: result.xml = $get(xn)
|
||||||
|
|
||||||
proc spawnXmlTranslator*(turn: var Turn; root: Cap): Actor {.discardable.} =
|
proc spawnXmlTranslator*(turn: Turn; root: Cap): Actor {.discardable.} =
|
||||||
spawnActor(turn, "xml-translator") do (turn: var Turn):
|
spawnActor(turn, "xml-translator") do (turn: Turn):
|
||||||
during(turn, root, ?:XmlTranslatorArguments) do (ds: Cap):
|
during(turn, root, ?:XmlTranslatorArguments) do (ds: Cap):
|
||||||
let xmlPat = observePattern(!XmlTranslation, {@[%0]:grab()})
|
let xmlPat = observePattern(!XmlTranslation, {@[%0]:grab()})
|
||||||
during(turn, ds, xmlPat) do (xs: Literal[string]):
|
during(turn, ds, xmlPat) do (xs: Literal[string]):
|
||||||
|
@ -29,6 +29,6 @@ proc spawnXmlTranslator*(turn: var Turn; root: Cap): Actor {.discardable.} =
|
||||||
|
|
||||||
when isMainModule:
|
when isMainModule:
|
||||||
import syndicate/relays
|
import syndicate/relays
|
||||||
runActor("main") do (turn: var Turn):
|
runActor("main") do (turn: Turn):
|
||||||
resolveEnvironment(turn) do (turn: var Turn; ds: Cap):
|
resolveEnvironment(turn) do (turn: Turn; ds: Cap):
|
||||||
spawnXmlTranslator(turn, ds)
|
spawnXmlTranslator(turn, ds)
|
||||||
|
|
|
@ -36,19 +36,19 @@ proc toLine(values: seq[Value]; prefix: char): string =
|
||||||
add(result, $v)
|
add(result, $v)
|
||||||
add(result, '\n')
|
add(result, '\n')
|
||||||
|
|
||||||
method publish(dump: DumpEntity; turn: var Turn; ass: AssertionRef; h: Handle) =
|
method publish(dump: DumpEntity; turn: Turn; ass: AssertionRef; h: Handle) =
|
||||||
var values = ass.value.sequence
|
var values = ass.value.sequence
|
||||||
stdout.write(values.toLine('+'))
|
stdout.write(values.toLine('+'))
|
||||||
stdout.flushFile()
|
stdout.flushFile()
|
||||||
dump.assertions[h] = values
|
dump.assertions[h] = values
|
||||||
|
|
||||||
method retract(dump: DumpEntity; turn: var Turn; h: Handle) =
|
method retract(dump: DumpEntity; turn: Turn; h: Handle) =
|
||||||
var values: seq[Value]
|
var values: seq[Value]
|
||||||
if dump.assertions.pop(h, values):
|
if dump.assertions.pop(h, values):
|
||||||
stdout.write(values.toLine('-'))
|
stdout.write(values.toLine('-'))
|
||||||
stdout.flushFile()
|
stdout.flushFile()
|
||||||
|
|
||||||
method message*(dump: DumpEntity; turn: var Turn; ass: AssertionRef) =
|
method message*(dump: DumpEntity; turn: Turn; ass: AssertionRef) =
|
||||||
stdout.write(ass.value.sequence.toLine('!'))
|
stdout.write(ass.value.sequence.toLine('!'))
|
||||||
stdout.flushFile()
|
stdout.flushFile()
|
||||||
|
|
||||||
|
@ -60,8 +60,8 @@ proc main =
|
||||||
let
|
let
|
||||||
patterns = inputPatterns()
|
patterns = inputPatterns()
|
||||||
entity = DumpEntity()
|
entity = DumpEntity()
|
||||||
runActor("syndex_card") do (turn: var Turn):
|
runActor("syndex_card") do (turn: Turn):
|
||||||
resolveEnvironment(turn) do (turn: var Turn; ds: Cap):
|
resolveEnvironment(turn) do (turn: Turn; ds: Cap):
|
||||||
for pat in patterns:
|
for pat in patterns:
|
||||||
discard observe(turn, ds, pat, entity)
|
discard observe(turn, ds, pat, entity)
|
||||||
|
|
||||||
|
|
|
@ -173,8 +173,8 @@ proc toPreservesHook*(xn: xmlNodePtr): Value =
|
||||||
preserveSiblings(items, xn)
|
preserveSiblings(items, xn)
|
||||||
items[0]
|
items[0]
|
||||||
|
|
||||||
proc spawnXsltActor*(turn: var Turn; root: Cap): Actor {.discardable.} =
|
proc spawnXsltActor*(turn: Turn; root: Cap): Actor {.discardable.} =
|
||||||
spawnActor(turn, "xslt") do (turn: var Turn):
|
spawnActor(turn, "xslt") do (turn: Turn):
|
||||||
initLibXml()
|
initLibXml()
|
||||||
during(turn, root, ?:XsltArguments) do (ds: Cap):
|
during(turn, root, ?:XsltArguments) do (ds: Cap):
|
||||||
let sheetsPat = observePattern(!XsltTransform, {@[%0]: grab(), @[%1]: grab()})
|
let sheetsPat = observePattern(!XsltTransform, {@[%0]: grab(), @[%1]: grab()})
|
||||||
|
@ -206,6 +206,6 @@ proc spawnXsltActor*(turn: var Turn; root: Cap): Actor {.discardable.} =
|
||||||
|
|
||||||
when isMainModule:
|
when isMainModule:
|
||||||
import syndicate/relays
|
import syndicate/relays
|
||||||
runActor("main") do (turn: var Turn):
|
runActor("main") do (turn: Turn):
|
||||||
resolveEnvironment(turn) do (turn: var Turn; ds: Cap):
|
resolveEnvironment(turn) do (turn: Turn; ds: Cap):
|
||||||
spawnXsltActor(turn, ds)
|
spawnXsltActor(turn, ds)
|
||||||
|
|
|
@ -1,13 +1,13 @@
|
||||||
# Package
|
# Package
|
||||||
|
|
||||||
version = "20240422"
|
version = "20240509"
|
||||||
author = "Emery Hemingway"
|
author = "Emery Hemingway"
|
||||||
description = "Utilites for Syndicated Actors and Synit"
|
description = "Utilites for Syndicated Actors and Synit"
|
||||||
license = "unlicense"
|
license = "unlicense"
|
||||||
srcDir = "src"
|
srcDir = "src"
|
||||||
bin = @["mintsturdyref", "mount_actor", "msg", "postgre_actor", "preserve_process_environment", "rofi_script_actor", "sqlite_actor", "syndesizer", "syndump", "xslt_actor"]
|
bin = @["http_client", "mintsturdyref", "mount_actor", "msg", "postgre_actor", "preserve_process_environment", "rofi_script_actor", "sqlite_actor", "syndesizer", "syndump", "xslt_actor", "webextension_native_messager"]
|
||||||
|
|
||||||
|
|
||||||
# Dependencies
|
# Dependencies
|
||||||
|
|
||||||
requires "http://git.syndicate-lang.org/ehmry/syndicate-nim.git >= 20240422", "https://github.com/ehmry/nim-sys.git#4ef3b624db86e331ba334e705c1aa235d55b05e1", "https://git.sr.ht/~ehmry/nim_taps >= 20240405"
|
requires "https://git.syndicate-lang.org/ehmry/syndicate-nim.git >= 20240430", "https://github.com/ehmry/nim-sys.git#4ef3b624db86e331ba334e705c1aa235d55b05e1", "https://git.sr.ht/~ehmry/nim_taps >= 20240405"
|
||||||
|
|
Loading…
Reference in New Issue