diff --git a/README.md b/README.md index 67ae874..d8a8b9b 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,8 @@ Matching patterns to actions is done with `action-handler` records: ```preserves > ``` - In the preceding example the URI `foo://bar:42` would cause the message `` to be sent to `$entity`. +See [handlers-example.pr](./handlers-example.pr) for more information. + The [protocol.nim](./src/protocol.nim) file is generated from the [protocol.prs](./protocol.prs) schema, a [Tupfile](https://gittup.org/tup/) file is provided to do this. diff --git a/handlers-example.pr b/handlers-example.pr new file mode 100644 index 0000000..3aaf6da --- /dev/null +++ b/handlers-example.pr @@ -0,0 +1,27 @@ +; the dataspace starts programs when it receives exec messages +? [ + + > + + > + + > + + > + + > + + > + +] + +; when mpv is available send it commands directly +? [ + > +] diff --git a/protocol.prs b/protocol.prs index e0756ab..4960834 100644 --- a/protocol.prs +++ b/protocol.prs @@ -1,7 +1,10 @@ version 1 . -XdgOpen = . +XdgOpen = . + +UriRunnerConfig = { + handlerspace: #!any + urispace: #!any +} . -UriRunnerConfig = ListenOn / ActionHandler . -ListenOn = . ActionHandler = . diff --git a/src/protocol.nim b/src/protocol.nim index 2d7d398..c7313e3 100644 --- a/src/protocol.nim +++ b/src/protocol.nim @@ -1,32 +1,22 @@ import - std/typetraits, preserves + preserves type XdgOpen* {.preservesRecord: "xdg-open".} = object - `uris`*: seq[string] + `uri`*: string - UriRunnerConfigKind* {.pure.} = enum - `ListenOn`, `ActionHandler` - `UriRunnerConfig`* {.preservesOr.} = object - case orKind*: UriRunnerConfigKind - of UriRunnerConfigKind.`ListenOn`: - `listenon`*: ListenOn - - of UriRunnerConfigKind.`ActionHandler`: - `actionhandler`*: ActionHandler - - - ListenOn* {.preservesRecord: "listen-on".} = object - `dataspace`* {.preservesEmbedded.}: Preserve[void] + UriRunnerConfig* {.preservesDictionary.} = object + `handlerspace`* {.preservesEmbedded.}: Preserve[void] + `urispace`* {.preservesEmbedded.}: Preserve[void] ActionHandler* {.preservesRecord: "action-handler".} = object `pat`*: string `entity`* {.preservesEmbedded.}: Preserve[void] `action`*: Preserve[void] -proc `$`*(x: XdgOpen | UriRunnerConfig | ListenOn | ActionHandler): string = +proc `$`*(x: XdgOpen | UriRunnerConfig | ActionHandler): string = `$`(toPreserve(x)) -proc encode*(x: XdgOpen | UriRunnerConfig | ListenOn | ActionHandler): seq[byte] = +proc encode*(x: XdgOpen | UriRunnerConfig | ActionHandler): seq[byte] = encode(toPreserve(x)) diff --git a/src/uri_runner.nim b/src/uri_runner.nim index 53a8ca1..76aa5dd 100644 --- a/src/uri_runner.nim +++ b/src/uri_runner.nim @@ -1,11 +1,15 @@ # SPDX-FileCopyrightText: ☭ Emery Hemingway # SPDX-License-Identifier: Unlicense -import std/[asyncdispatch, re] -import preserves, syndicate +import std/[re, tables] +import preserves, syndicate, syndicate/patterns import ./protocol -type RegexAction = tuple[regex: Regex; entity: Ref; action: Assertion] +# importing std/re doesn't actually link against PCRE +{.passC: staticExec("pkg-config --cflags libpcre").} +{.passL: staticExec("pkg-config --libs libpcre").} + +type RegexAction = tuple[regex: Regex, entity: Ref, action: Assertion] proc rewrite(result: var Assertion; uri: string; regex: Regex) = proc op(pr: var Assertion) = @@ -15,24 +19,36 @@ proc rewrite(result: var Assertion; uri: string; regex: Regex) = runActor("main") do (root: Ref; turn: var Turn): connectStdio(root, turn) - var handlers: seq[RegexAction] + during(turn, root, ?UriRunnerConfig) do (handlerspace: Ref, urispace: Ref): - onPublish(turn, root, ?ActionHandler) do (pat: string; entity: Ref; response: Assertion): - handlers.add (re(pat, {reIgnoreCase, reStudy}), entity, response,) + # sanity chek + onMessage(turn, handlerspace, ?XdgOpen) do (uri: string): + raiseAssert "got a message in the wrong dataspace!" + onPublish(turn, urispace, ?ActionHandler) do (pat: string; entity: Ref; act: Assertion): + raiseAssert "got an assertion in the wrong dataspace!" - during(turn, root, ?ListenOn) do (ds: Ref): - onMessage(turn, ds, ?XdgOpen) do (uris: seq[string]): - for uri in uris: - var matched: bool - for handler in handlers: + during(turn, handlerspace, dropType(ActionHandler)) do: + + var handlers: Table[Handle, RegexAction] + during(turn, handlerspace, ?ActionHandler) do (pat: string; entity: Ref; act: Assertion): + # `duringHandle` is a symbol exposed by the `during` macro + handlers[duringHandle] = (re(pat, {reIgnoreCase, reStudy}), entity, act,) + do: + del(handlers, duringHandle) + + onMessage(turn, urispace, ?XdgOpen) do (uri: string): + assert len(handlers) > 0 + var matched = false + for handler in handlers.values: if match(uri, handler.regex): matched = true - var response = handler.action - rewrite(response, uri, handler.regex) - message(turn, handler.entity, response) + var action = handler.action + try: + rewrite(action, uri, handler.regex) + message(turn, handler.entity, action) + except CatchableError: + stderr.writeLine "rewrite failed on ", action if not matched: stderr.writeLine "no actions matched for ", uri - do: - # The Syndicate server retracts all assertions when - # the config is rewritten. - handlers.setLen 0 + + diff --git a/src/xdg_open.nim b/src/xdg_open.nim index 397ba7a..710a383 100644 --- a/src/xdg_open.nim +++ b/src/xdg_open.nim @@ -1,24 +1,29 @@ -# SPDX-FileCopyrightText: ☭ 2022 Emery Hemingway +# SPDX-FileCopyrightText: ☭ Emery Hemingway # SPDX-License-Identifier: Unlicense +## Facsimile of xdg-open from xdg-utils. +## https://portland.freedesktop.org/doc/xdg-open.html + import std/[asyncdispatch, os] -from std/sequtils import map -import syndicate, syndicate/capabilities +import preserves, syndicate, syndicate/capabilities import ./protocol -proc unixSocketPath: string = - result = getEnv("SYNDICATE_SOCK") - if result == "": - result = getEnv("XDG_RUNTIME_DIR", "/run/user/1000") / "dataspace" +proc publishUri(turn: var Turn; ds: Ref) = + for arg in commandLineParams(): + var xo = XdgOpen(uri: arg) + if fileExists(xo.uri): + xo.uri = "file://" & absolutePath(arg) + message(turn, ds, xo) + stop(turn) -bootDataspace("main") do (root: Ref; turn: var Turn): - connectUnix(turn, unixSocketPath(), mint()) do (turn: var Turn; ds: Ref): - var uris = commandLineParams().map do (param: string) -> string: - if fileExists param: - "file://" & absolutePath(param) - else: - param - message(turn, ds, XdgOpen(uris: uris)) +proc unixSocketPath: Unix = + result.path = getEnv("SYNDICATE_SOCK") + if result.path == "": + result.path = getEnv("XDG_RUNTIME_DIR", "/run/user/1000") / "dataspace" -for i in 0..7: poll(20) +bootDataspace("xdg-open") do (root: Ref; turn: var Turn): + let cap = mint().toPreserve(Ref) + connect(turn, unixSocketPath(), cap, publishUri) + +for i in 0..4: poll(20) # A hack to exit diff --git a/uri_runner.pr b/uri_runner.pr index edbca7f..65e5246 100644 --- a/uri_runner.pr +++ b/uri_runner.pr @@ -1,68 +1,30 @@ -; Expose a dataspace over a unix socket -let ?socketspace = dataspace - $gatekeeper>> - +> -; Create a new dataspace for receiving "exec" messages -; from the uri_runner -let ?execspace = dataspace -$execspace ?? $config [ - let ?id = timestamp - let ?facet = facet - let ?d = - > - [ + - ? complete> [$facet ! stop] - ? failed> [$facet ! stop] ] -; Start the uri_runner -> - +; grab a dataspace for observing messages +? [ -? ?cap> $cap [ + ; log xdg-open messages + $socketspace ?? [ + $log ! }> + ] - ; send configuration to uri_runner - - - ; When http* is matched send a message to $execspace - ; that indicates the Syndicate server should start Firefox. - > - - ; Capture a pattern within a pattern - > - - ; Local file-system paths should always be prefixed - ; by file:// but that can be removed after matching - > - - > - - > - - > - - > - - ; when the mpv-transator is available send it commands directly - $config ? ?mpv> $mpv [ - $cap > + ; configure the uri_runner + ? ?cap> [ + $cap { + ; watch the config dataspace for handler configuration + handlerspace: $config + urispace: $socketspace + } ] ] diff --git a/xdg_open_ng.nimble b/xdg_open_ng.nimble index 7f8df07..f196554 100644 --- a/xdg_open_ng.nimble +++ b/xdg_open_ng.nimble @@ -1,6 +1,6 @@ # Package -version = "20230506" +version = "20230518" author = "Emery" description = "A better xdg-open" license = "Unlicense" @@ -10,4 +10,4 @@ bin = @[ "uri_runner", "xdg_open"] # Dependencies -requires "nim >= 1.6.4", "syndicate >= 1.3.0" +requires "nim >= 1.6.4", "syndicate >= 20230518"