on master help sign in
61 files changed+4506−395
since splitsnapshots
unifiedsplit
.gitignore+2 LICENSE+7 README.md+43 dev/sink-elm-live.sh+7 dev/vanilla-elm-live.sh+7 docs/sink.md+101 elm.json+25−4 example/elm.json+53 example/fetch-event-source.js+2 example/index.html+13 example/script.js+42 example/src/Sink.elm+199 example/src/Vanilla.elm+251 example/urbit/app/journal.hoon+133 example/urbit/desk.bill+2 example/urbit/desk.docket-0+12 example/urbit/gen/journal/add.hoon+37 example/urbit/lib/agentio.hoon+139 example/urbit/lib/dbug.hoon+155 example/urbit/lib/default-agent.hoon+69 example/urbit/lib/docket.hoon+223 example/urbit/lib/journal.hoon+66 example/urbit/lib/noun-diff.hoon+227 example/urbit/lib/sink.hoon+26 example/urbit/lib/skeleton.hoon+51 example/urbit/mar/bill.hoon+34 example/urbit/mar/docket-0.hoon+25 example/urbit/mar/hoon.hoon+49 example/urbit/mar/jam.hoon+17 example/urbit/mar/journal/action.hoon+14 example/urbit/mar/journal/update.hoon+16 example/urbit/mar/json.hoon+26 example/urbit/mar/kelvin.hoon+28 example/urbit/mar/mime.hoon+32 example/urbit/mar/noun.hoon+19 example/urbit/mar/txt-diff.hoon+16 example/urbit/mar/txt.hoon+275 example/urbit/sur/docket.hoon+82 example/urbit/sur/journal.hoon+19 example/urbit/sys.kelvin+1 res/airlock.jpeg src/BigInt/Bytes.elm+35 src/BitWriter.elm+5−5 src/Ur.elm+119 src/Ur/Cmd.elm+52 src/Ur/Cmd/Internal.elm+8 src/Ur/Constructor.elm+151 src/Ur/Da.elm+33 src/Ur/Deconstructor.elm+378 src/Urbit.elm → src/Ur/Jam.elm+15−16 src/Ur/NounDiff.elm+115 src/Ur/Phonemic.elm+25 src/Ur/Requests.elm+118 src/Ur/Run.elm+510 src/Ur/Sub.elm+61 src/Ur/Sub/Internal.elm+16 src/Ur/Types.elm+60 src/Ur/Uw.elm+204 src/Urbit/Constructor.elm−93 src/Urbit/Deconstructor.elm−261 tests/Test/Urbit.elm+56−16
.gitignore+2
@@ -1 +1,3 @@
11elm-stuff
2+elm.js
3+zod
LICENSEnew file+7
@@ -0,0 +1,7 @@
1+Copyright 2023 Ilia Kostiuchenko
2+
3+Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
4+
5+The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
6+
7+THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
README.mdnew file+43
@@ -0,0 +1,43 @@
1+# Airlock
2+
3+![](res/airlock.jpeg)
4+
5+## Usage
6+
7+### Demo
8+
9+[![DEMO](https://img.youtube.com/vi/tynCzifvIzE/0.jpg)](https://www.youtube.com/watch?v=tynCzifvIzE)
10+
11+
12+## Getting started
13+
14+The easiest way of using this library is by using the [airlock template](https://github.com/ilyakooo0/airlock-template).
15+
16+### FFI
17+
18+This is for setting up this library from scratch.
19+
20+Urbit subscriptions require [server-side event (SSE)](https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events).
21+Unfortunately, Elm does not provide a native implementation.
22+This means that we will need to use [JS FFI (aka ports)](https://guide.elm-lang.org/interop/ports.html).
23+
24+#### Setting up the Elm side
25+
26+You would need to declare two ports for Airlock to use:
27+
28+1. `port createEventSource : String -> Cmd msg`
29+
30+ This sets up the SSE channel.
31+2. `port onEventSourceMessage : (JD.Value -> msg) -> Sub msg`
32+ This recieves the actual messages from the SSE channel.
33+
34+These two functions need to be passed to one of the functions from `Ur.Run` module.
35+The functions in `Ur.Run` are the same as functions from `Browser`, but have a couple of extra urbit-specific fields.
36+
37+#### Setting up the JS side
38+
39+The SSE JS code is neatly packed into [example/fetch-event-source.js](example/fetch-event-source.js).
40+All you need to do is pass them to Elm ports like in [example/script.js](example/script.js).
41+
42+You can see a full working example in [example](example).
43+
dev/sink-elm-live.shnew file+7
@@ -0,0 +1,7 @@
1+#!/bin/sh
2+
3+set -e
4+
5+cd "$(dirname "$0")/../example"
6+
7+nix run nixpkgs#elmPackages.elm-live -- src/Sink.elm --start-page=index.html -- --output=elm.js --debug "$@"
dev/vanilla-elm-live.shnew file+7
@@ -0,0 +1,7 @@
1+#!/bin/sh
2+
3+set -e
4+
5+cd "$(dirname "$0")/../example"
6+
7+nix run nixpkgs#elmPackages.elm-live -- src/Vanilla.elm --start-page=index.html -- --output=elm.js --debug "$@"
docs/sink.mdnew file+101
@@ -0,0 +1,101 @@
1+# %sink
2+
3+%sink is a state replication system between your %gall agent and your Elm airlock.
4+
5+%sink allows you to avoid having to manually write subscriptions which send semantic diffs between your %gall agent and the frontend.
6+
7+## How it works
8+
9+%sink consists of two parts:
10+1. The %sink library you import into your %gall agent and call appropriate functions at the right points in your agent lifecycle. More on this in a bit.
11+2. The `Ur.Sub.sink` urbit subscription which reconciles the state and hands you the latest version of %gall agent state that you can then `Deconstruct` into Elm data structures.
12+
13+The whole system works by saving the previous version your %gall agent state and diffing the raw nouns that compose your old state and your new state.
14+On first initialization of the %sink system the whole state you supplied is sent to the frontend. Any subsequent changes are sent as a diff between your old state and your new state.
15+
16+## How to use it
17+
18+### The %gall agent part
19+
20+#### Copying library files
21+
22+In order to use %sink you have to copy the following two files into your agent desk into the `lib` directory:
23+1. [example/urbit/lib/noun-diff.hoon](../example/urbit/lib/noun-diff.hoon) -- contains the algorithm for diffing raw nouns.
24+2. [example/urbit/lib/sink.hoon](../example/urbit/lib/sink.hoon) -- contains the logic of syncing the state between your agent and your frontend.
25+
26+#### Adding sync points into your agent
27+
28+First you need to import the %sink library into your agent:
29+
30+```hoon
31+/+ *sink
32+```
33+
34+Next you need to initialize the sink. Put the following declaration somewhere before your agent door:
35+
36+```hoon
37+::
38+:: A deferred expression of the state you want to sync to the frontend.
39+=* entries (tap:j-orm journal.stat)
40+::
41+:: Replace /sync with whatever path you want to use for syncing your state.
42+=/ snik (sink ~[/sync])
43+::
44+:: Next you initialize your sink with your initial agent state.
45+=/ sink (snik entries)
46+```
47+
48+You can have multiple sinks in the same application to sync different parts of your state on different paths.
49+
50+Don't forget to reinitialize your `sink` when your restore your agent state in the `++on-load` arm:
51+
52+```hoon
53+++ on-load
54+ |= old-vase=vase
55+ ^- (quip card _this)
56+ =/ state !<(versioned-state old-vase)
57+ ::
58+ :: the `sink (snik entries)` is the important bit.
59+ `this(state state, sink (snik entries))
60+```
61+
62+Lastly, you need to send sink updates whenever you change your state. Most likely this will be in your `++on-poke` arm:
63+
64+```hoon
65+::
66+:: This line generates a `card` that you need to pass to arvo and updates
67+:: the `sink` to reference the latest state of your agent.
68+=^ card sink (sync:sink entries)
69+```
70+
71+You can look at the [journal.hoon](../example/urbit/app/journal.hoon) for a full example.
72+
73+#### Adding sink the the frontend
74+
75+For your frontend to recieve %sink updates you need to pass the result of calling `Ur.Sub.sink` to the `urbitSubscriptions` field of `Ur.Run.application` or similar function from `Ur.Run`:
76+
77+```elm
78+main : Ur.Run.Program Model Msg
79+main =
80+ Ur.Run.application
81+ {
82+ -- ...
83+ urbitSubscriptions =
84+ -- ...
85+ Ur.Sub.sink
86+ { ship = ship
87+ , app = "journal"
88+ , path = [ "sync" ]
89+ , deconstructor =
90+ D.list (D.cell D.bigint D.cord |> D.map (\a b -> ( a, b )))
91+ |> D.map GotListings
92+ }
93+ -- ...
94+ }
95+```
96+
97+In the `deconstructor` field you specify a `Deconstructor` the deconstructs _the whole_ state that is being synced form the %gall agent.
98+
99+You do not have to deal with diffs. It is handled automatically
100+
101+You can look at [example/src/Sink.elm](../example/src/Sink.elm) for a full example.
elm.json+25−4
@@ -3,16 +3,37 @@
33 "name": "ilyakooo0/airlock",
44 "summary": "Urbit airlock",
55 "license": "MIT",
6− "version": "1.0.0",
6+ "version": "1.1.1",
77 "exposed-modules": [
8− "Urbit"
8+ "Ur",
9+ "Ur.Cmd",
10+ "Ur.Sub",
11+ "Ur.Constructor",
12+ "Ur.Deconstructor",
13+ "Ur.Phonemic",
14+ "Ur.Da",
15+ "Ur.Uw",
16+ "Ur.Run",
17+ "Ur.Types"
918 ],
1019 "elm-version": "0.19.0 <= v < 0.20.0",
1120 "dependencies": {
1221 "TSFoster/elm-bytes-extra": "1.3.0 <= v < 1.4.0",
22+ "chelovek0v/bbase64": "1.0.1 <= v < 2.0.0",
23+ "cmditch/elm-bigint": "2.0.1 <= v < 3.0.0",
24+ "elm/browser": "1.0.2 <= v < 2.0.0",
1325 "elm/bytes": "1.0.8 <= v < 2.0.0",
14− "elm/core": "1.0.0 <= v < 1.0.5",
15− "elm-community/list-extra": "8.7.0 <= v < 9.0.0"
26+ "elm/core": "1.0.0 <= v < 2.0.0",
27+ "elm/html": "1.0.0 <= v < 2.0.0",
28+ "elm/http": "2.0.0 <= v < 3.0.0",
29+ "elm/json": "1.1.3 <= v < 2.0.0",
30+ "elm/time": "1.0.0 <= v < 2.0.0",
31+ "elm/url": "1.0.0 <= v < 2.0.0",
32+ "elm-community/list-extra": "8.7.0 <= v < 9.0.0",
33+ "elm-community/maybe-extra": "5.3.0 <= v < 6.0.0",
34+ "figbus/elm-urbit-api": "4.0.1 <= v < 5.0.0",
35+ "jxxcarlson/hex": "4.0.0 <= v < 5.0.0",
36+ "toastal/either": "3.6.3 <= v < 4.0.0"
1637 },
1738 "test-dependencies": {
1839 "elm-explorations/test": "2.1.1 <= v < 3.0.0"
example/elm.jsonnew file+53
@@ -0,0 +1,53 @@
1+{
2+ "type": "application",
3+ "source-directories": [
4+ "src",
5+ "../src"
6+ ],
7+ "elm-version": "0.19.1",
8+ "dependencies": {
9+ "direct": {
10+ "Orasund/elm-ui-widgets": "3.4.0",
11+ "TSFoster/elm-bytes-extra": "1.3.0",
12+ "chelovek0v/bbase64": "1.0.1",
13+ "cmditch/elm-bigint": "2.0.1",
14+ "elm/browser": "1.0.2",
15+ "elm/bytes": "1.0.8",
16+ "elm/core": "1.0.5",
17+ "elm/html": "1.0.0",
18+ "elm/http": "2.0.0",
19+ "elm/json": "1.1.3",
20+ "elm/parser": "1.1.0",
21+ "elm/time": "1.0.0",
22+ "elm/url": "1.0.0",
23+ "elm-community/list-extra": "8.7.0",
24+ "elm-community/maybe-extra": "5.3.0",
25+ "figbus/elm-urbit-api": "4.0.1",
26+ "jasonliang-dev/elm-heroicons": "2.0.0",
27+ "jxxcarlson/hex": "4.0.0",
28+ "ktonon/elm-word": "2.1.2",
29+ "mdgriffith/elm-ui": "1.1.8",
30+ "robinheghan/murmur3": "1.0.0",
31+ "toastal/either": "3.6.3"
32+ },
33+ "indirect": {
34+ "AdrianRibao/elm-derberos-date": "1.2.3",
35+ "avh4/elm-color": "1.0.0",
36+ "bitsoflogic/elm-radixint": "2.0.0",
37+ "elm/file": "1.0.5",
38+ "elm/random": "1.0.0",
39+ "elm/regex": "1.0.0",
40+ "elm/svg": "1.0.1",
41+ "elm/virtual-dom": "1.0.3",
42+ "elm-community/intdict": "3.0.0",
43+ "fredcy/elm-parseint": "2.0.1",
44+ "noahzgordon/elm-color-extra": "1.0.2",
45+ "rtfeldman/elm-hex": "1.0.0",
46+ "turboMaCk/queue": "1.1.0"
47+ }
48+ },
49+ "test-dependencies": {
50+ "direct": {},
51+ "indirect": {}
52+ }
53+}
example/fetch-event-source.jsnew file+2
@@ -0,0 +1,2 @@
1+var m=class extends Error{};async function A(r,e,t){let n=r.getReader(),o={done:!1,value:new Uint8Array};for(;o&&!o.done;)o=await Promise.race([n.read(),new Promise((g,i)=>{setTimeout(()=>i(new Error("getBytes timed out")),t)})]),e(o.value)}function R(r){let e,t,n,o=!1;return function(i){e===void 0?(e=i,t=0,n=-1):e=L(e,i);let s=e.length,a=0;for(;t<s;){o&&(e[t]===10&&(a=++t),o=!1);let c=-1;for(;t<s&&c===-1;++t)switch(e[t]){case 58:n===-1&&(n=t-a);break;case 13:o=!0;case 10:c=t;break}if(c===-1)break;r(e.subarray(a,c),n),a=t,n=-1}a===s?e=void 0:a!==0&&(e=e.subarray(a),t-=a)}}function P(r,e,t){let n=k(),o=new TextDecoder;return function(i,s){if(i.length===0)r?.(n),n=k();else if(s>0){let a=o.decode(i.subarray(0,s)),c=s+(i[s+1]===32?2:1),p=o.decode(i.subarray(c));switch(a){case"data":n.data=n.data?n.data+`
2+`+p:p;break;case"event":n.event=p;break;case"id":e?.(n.id=p);break;case"retry":let l=parseInt(p,10);isNaN(l)||t?.(n.retry=l);break}}}}function L(r,e){let t=new Uint8Array(r.length+e.length);return t.set(r),t.set(e,r.length),t}function k(){return{data:"",event:"",id:"",retry:void 0}}var b="text/event-stream",N=1e3,T="last-event-id";function q(r,{signal:e,headers:t,onopen:n,onmessage:o,onclose:g,onerror:i,openWhenHidden:s,fetch:a,responseTimeout:c,...p}){return new Promise((l,M)=>{let y={...t};y.accept||(y.accept=b);let f;function w(){f.abort(),document.hidden||E()}typeof document<"u"&&!s&&document.addEventListener("visibilitychange",w);let S=N,h;function x(){typeof document<"u"&&!s&&document.removeEventListener("visibilitychange",w),clearTimeout(h),f.abort()}e?.addEventListener("abort",()=>{x(),l()});let I=a??fetch,U=n??O,v=!1;async function E(){f=new AbortController;try{let u=await Promise.race([I(r,{...p,headers:y,signal:f.signal}),new Promise((d,C)=>{setTimeout(()=>C(new Error("fetch timed out")),c)})]);if(u.status===404)throw new m("Channel reaped");if(u.status<200||u.status>=300)throw new Error(`Invalid server response: ${u.status}`);await U(u,v),v&&(v=!1),await A(u.body,R(P(o,d=>{d?y[T]=d:delete y[T]},d=>{S=d})),c),g?.(),x(),l()}catch(u){if(!f.signal.aborted)try{v=!0;let d=i?.(u)??S;clearTimeout(h),f.abort(),h=setTimeout(E,d)}catch(d){x(),M(d)}}}E()})}function O(r){let e=r.headers.get("content-type");if(!e?.startsWith(b))throw new Error(`Expected content-type to be ${b}, Actual: ${e}`)}export{b as EventStreamContentType,q as fetchEventSource};
example/index.htmlnew file+13
@@ -0,0 +1,13 @@
1+<!DOCTYPE html>
2+<html lang="en">
3+ <head>
4+ <meta charset="UTF-8" />
5+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6+ </head>
7+ <body>
8+ <div id="elm"></div>
9+ <script src="/elm.js"></script>
10+ <script type="module" src="/script.js"></script>
11+ </body>
12+</html>
13+
example/script.jsnew file+42
@@ -0,0 +1,42 @@
1+import { fetchEventSource } from "/fetch-event-source.js"
2+
3+const uid = `${Math.floor(Date.now() / 1000)}-${Math.random()}`;
4+
5+function searchForInit(obj) {
6+ if (obj.init) {
7+ return obj.init;
8+ } else {
9+ for (let key in obj) {
10+ if (obj.hasOwnProperty(key)) {
11+ const result = searchForInit(obj[key]);
12+ if (result) {
13+ return result;
14+ }
15+ }
16+ }
17+ }
18+}
19+
20+let app = searchForInit(Elm)({ node: document.getElementById("elm"), flags: { uid } });
21+
22+app.ports.createEventSource.subscribe((url) => {
23+ fetchEventSource(url, {
24+ headers: {
25+ Accept: 'application/x-urb-jam',
26+ "x-channel-format": 'application/x-urb-jam',
27+ "content-type": 'application/x-urb-jam'
28+ },
29+ credentials: 'include',
30+ responseTimeout: 25000,
31+ openWhenHidden: true,
32+ onmessage(ev) {
33+ console.log(ev)
34+ app.ports.onEventSourceMessage.send({ message: ev.data });
35+ },
36+ onerror(err) {
37+ console.log(err)
38+ app.ports.onEventSourceMessage.send({ error: err });
39+ }
40+ });
41+
42+});
example/src/Sink.elmnew file+199
@@ -0,0 +1,199 @@
1+port module Sink exposing (main)
2+
3+import BigInt exposing (BigInt)
4+import Browser exposing (Document)
5+import Element exposing (..)
6+import Element.Border as Border
7+import Heroicons.Outline
8+import Json.Decode as JD
9+import Task
10+import Time
11+import Ur
12+import Ur.Cmd
13+import Ur.Constructor as C
14+import Ur.Deconstructor as D
15+import Ur.Run
16+import Ur.Sub
17+import Widget
18+import Widget.Icon as Icon
19+import Widget.Material as Material
20+
21+
22+url : String
23+url =
24+ "http://localhost:8080"
25+
26+
27+main : Ur.Run.Program Model Msg
28+main =
29+ Ur.Run.application
30+ { init =
31+ \_ _ ->
32+ ( { error = ""
33+ , entries = Nothing
34+ , newEntry = ""
35+ , shipName = Nothing
36+ }
37+ , Cmd.batch
38+ [ Ur.logIn url "lidlut-tabwed-pillex-ridrup"
39+ |> Cmd.map (result (Debug.toString >> Error) (always Noop))
40+ , Ur.getShipName url |> Cmd.map (result (always Noop) GotShipName)
41+ ]
42+ |> Ur.Cmd.cmd
43+ )
44+ , update = update
45+ , view = view
46+ , subscriptions = always Sub.none
47+ , createEventSource = createEventSource
48+ , urbitSubscriptions =
49+ \{ shipName } ->
50+ case shipName of
51+ Just ship ->
52+ Ur.Sub.batch
53+ [ Ur.Sub.sink
54+ { ship = ship
55+ , app = "journal"
56+ , path = [ "sync" ]
57+ , deconstructor =
58+ D.list (D.cell D.bigint D.cord)
59+ |> D.map GotListings
60+ }
61+ ]
62+
63+ _ ->
64+ Ur.Sub.none
65+ , onEventSourceMsg = onEventSourceMessage
66+ , onUrlChange = \_ -> Noop
67+ , onUrlRequest = \_ -> Noop
68+ , urbitUrl = \_ -> url
69+ }
70+
71+
72+type alias Model =
73+ { error : String
74+ , entries : Maybe (List ( BigInt, String ))
75+ , newEntry : String
76+ , shipName : Maybe String
77+ }
78+
79+
80+type Msg
81+ = Noop
82+ | Error String
83+ | GotListings (List ( BigInt, String ))
84+ | UpdateNewEntry String
85+ | DeleteEntry BigInt
86+ | AddEntry String
87+ | RunCmd (Ur.Cmd.Cmd Msg)
88+ | GotShipName String
89+
90+
91+update : Msg -> Model -> ( Model, Ur.Cmd.Cmd Msg )
92+update msg model =
93+ case msg of
94+ Noop ->
95+ ( model, Ur.Cmd.none )
96+
97+ Error err ->
98+ ( { model | error = err }, Ur.Cmd.none )
99+
100+ GotListings entries ->
101+ ( { model | entries = Just entries }, Ur.Cmd.none )
102+
103+ UpdateNewEntry txt ->
104+ ( { model | newEntry = txt }, Ur.Cmd.none )
105+
106+ DeleteEntry id ->
107+ ( model
108+ , Ur.Cmd.poke
109+ { ship = "~zod"
110+ , agent = "journal"
111+ , mark = "journal-action"
112+ , noun = C.cell (C.cord "del") (C.bigint id)
113+ }
114+ )
115+
116+ AddEntry txt ->
117+ ( { model | newEntry = "" }
118+ , Time.now
119+ |> Task.perform
120+ (\time ->
121+ Ur.Cmd.poke
122+ { ship = "~zod"
123+ , agent = "journal"
124+ , mark = "journal-action"
125+ , noun = C.cell (C.cord "add") (C.cell (time |> Time.posixToMillis |> BigInt.fromInt |> C.bigint) (C.cord txt))
126+ }
127+ |> RunCmd
128+ )
129+ |> Ur.Cmd.cmd
130+ )
131+
132+ RunCmd cmd ->
133+ ( model, cmd )
134+
135+ GotShipName name ->
136+ ( { model | shipName = Just name }, Ur.Cmd.none )
137+
138+
139+view : Model -> Document Msg
140+view model =
141+ { body =
142+ [ layout []
143+ ([ el [ alignTop ] (text model.error)
144+ , row [ spacing 8 ]
145+ [ Widget.textInput (Material.textInput Material.defaultPalette)
146+ { chips = []
147+ , text = model.newEntry
148+ , placeholder = Nothing
149+ , label = "New entry"
150+ , onChange = UpdateNewEntry
151+ }
152+ , Widget.iconButton (Material.containedButton Material.defaultPalette)
153+ { text = "submit"
154+ , icon = Icon.elmHeroicons Heroicons.Outline.check
155+ , onPress = AddEntry model.newEntry |> Just
156+ }
157+ ]
158+ , model.entries
159+ |> Maybe.withDefault []
160+ |> List.map
161+ (\( id, txt ) ->
162+ row
163+ [ Border.rounded 10
164+ , Border.shadow { offset = ( 0, 5 ), size = 1, blur = 10, color = rgba 0 0 0 0.3 }
165+ , centerX
166+ , padding 10
167+ , spacing 12
168+ ]
169+ [ paragraph [] [ text txt ]
170+ , Widget.iconButton (Material.containedButton Material.defaultPalette)
171+ { text = "delete"
172+ , icon = Icon.elmHeroicons Heroicons.Outline.trash
173+ , onPress = DeleteEntry id |> Just
174+ }
175+ ]
176+ )
177+ |> column [ spacing 10, centerX ]
178+ ]
179+ |> column [ spacing 18, centerX ]
180+ )
181+ ]
182+ , title = "Airlock"
183+ }
184+
185+
186+result : (a -> c) -> (b -> c) -> Result a b -> c
187+result f g res =
188+ case res of
189+ Ok b ->
190+ g b
191+
192+ Err a ->
193+ f a
194+
195+
196+port createEventSource : String -> Cmd msg
197+
198+
199+port onEventSourceMessage : (JD.Value -> msg) -> Sub msg
example/src/Vanilla.elmnew file+251
@@ -0,0 +1,251 @@
1+port module Vanilla exposing (main)
2+
3+import BigInt exposing (BigInt)
4+import Browser exposing (Document)
5+import Element exposing (..)
6+import Element.Border as Border
7+import Heroicons.Outline
8+import Json.Decode as JD
9+import Task
10+import Time
11+import Ur
12+import Ur.Cmd
13+import Ur.Constructor as C
14+import Ur.Deconstructor as D
15+import Ur.Run
16+import Ur.Sub
17+import Widget
18+import Widget.Icon as Icon
19+import Widget.Material as Material
20+
21+
22+url : String
23+url =
24+ "http://localhost:8080"
25+
26+
27+main : Ur.Run.Program Model Msg
28+main =
29+ Ur.Run.application
30+ { init =
31+ \_ _ ->
32+ ( { error = ""
33+ , entries = Nothing
34+ , newEntry = ""
35+ , shipName = Nothing
36+ }
37+ , Cmd.batch
38+ [ Ur.logIn url "lidlut-tabwed-pillex-ridrup"
39+ |> Cmd.map (result (Debug.toString >> Error) (always Noop))
40+ , Ur.getShipName url |> Cmd.map (result (always Noop) GotShipName)
41+ , Ur.scry
42+ { url = url
43+ , agent = "journal"
44+ , path = [ "entries", "all" ]
45+ , error = Noop
46+ , success =
47+ D.cell D.ignore
48+ (D.cell (D.const D.cord "jrnl")
49+ (D.list (D.cell D.bigint D.cord))
50+ )
51+ |> D.map (\( (), ( (), listings ) ) -> GotListings listings)
52+ }
53+ ]
54+ |> Ur.Cmd.cmd
55+ )
56+ , update = update
57+ , view = view
58+ , subscriptions = always Sub.none
59+ , createEventSource = createEventSource
60+ , urbitSubscriptions =
61+ \{ entries, shipName } ->
62+ case ( entries, shipName ) of
63+ ( Just _, Just ship ) ->
64+ Ur.Sub.subscribe
65+ { ship = ship
66+ , app = "journal"
67+ , path = [ "updates" ]
68+ , deconstructor = decodeJournalUpdate |> D.map GotUpdate
69+ }
70+
71+ _ ->
72+ Ur.Sub.none
73+ , onEventSourceMsg = onEventSourceMessage
74+ , onUrlChange = \_ -> Noop
75+ , onUrlRequest = \_ -> Noop
76+ , urbitUrl = \_ -> url
77+ }
78+
79+
80+type alias Model =
81+ { error : String
82+ , entries : Maybe (List ( BigInt, String ))
83+ , newEntry : String
84+ , shipName : Maybe String
85+ }
86+
87+
88+type Msg
89+ = Noop
90+ | Error String
91+ | GotListings (List ( BigInt, String ))
92+ | GotUpdate JournalUpdate
93+ | UpdateNewEntry String
94+ | DeleteEntry BigInt
95+ | AddEntry String
96+ | RunCmd (Ur.Cmd.Cmd Msg)
97+ | GotShipName String
98+
99+
100+update : Msg -> Model -> ( Model, Ur.Cmd.Cmd Msg )
101+update msg model =
102+ case msg of
103+ Noop ->
104+ ( model, Ur.Cmd.none )
105+
106+ Error err ->
107+ ( { model | error = err }, Ur.Cmd.none )
108+
109+ GotListings entries ->
110+ ( { model | entries = Just entries }, Ur.Cmd.none )
111+
112+ GotUpdate action ->
113+ let
114+ applyAction oldEntries =
115+ case action of
116+ Add id txt ->
117+ ( id, txt ) :: oldEntries
118+
119+ Edit id txt ->
120+ oldEntries
121+ |> List.map
122+ (\(( key, _ ) as value) ->
123+ if key == id then
124+ ( key, txt )
125+
126+ else
127+ value
128+ )
129+
130+ Delete id ->
131+ oldEntries |> List.filter (\( key, _ ) -> key /= id)
132+
133+ newEntries =
134+ model.entries |> Maybe.map applyAction
135+ in
136+ ( { model | entries = newEntries }, Ur.Cmd.none )
137+
138+ UpdateNewEntry txt ->
139+ ( { model | newEntry = txt }, Ur.Cmd.none )
140+
141+ DeleteEntry id ->
142+ ( model
143+ , Ur.Cmd.poke
144+ { ship = "~zod"
145+ , agent = "journal"
146+ , mark = "journal-action"
147+ , noun = C.cell (C.cord "del") (C.bigint id)
148+ }
149+ )
150+
151+ AddEntry txt ->
152+ ( { model | newEntry = "" }
153+ , Time.now
154+ |> Task.perform
155+ (\time ->
156+ Ur.Cmd.poke
157+ { ship = "~zod"
158+ , agent = "journal"
159+ , mark = "journal-action"
160+ , noun = C.cell (C.cord "add") (C.cell (time |> Time.posixToMillis |> BigInt.fromInt |> C.bigint) (C.cord txt))
161+ }
162+ |> RunCmd
163+ )
164+ |> Ur.Cmd.cmd
165+ )
166+
167+ RunCmd cmd ->
168+ ( model, cmd )
169+
170+ GotShipName name ->
171+ ( { model | shipName = Just name }, Ur.Cmd.none )
172+
173+
174+type JournalUpdate
175+ = Add BigInt String
176+ | Edit BigInt String
177+ | Delete BigInt
178+
179+
180+decodeJournalUpdate : D.Deconstructor JournalUpdate
181+decodeJournalUpdate =
182+ D.oneOf
183+ [ (D.cell (D.const D.cord "add") <| D.cell D.bigint D.cord) |> D.map (\( (), ( id, txt ) ) -> Add id txt)
184+ , D.cell (D.const D.cord "edit") (D.cell D.bigint D.cord) |> D.map (\( (), ( id, txt ) ) -> Edit id txt)
185+ , D.cell (D.const D.cord "del") D.bigint |> D.map (\( (), id ) -> Delete id)
186+ ]
187+ |> D.cell D.ignore
188+ |> D.map (\( (), upd ) -> upd)
189+
190+
191+view : Model -> Document Msg
192+view model =
193+ { body =
194+ [ layout []
195+ ([ el [ alignTop ] (text model.error)
196+ , row [ spacing 8 ]
197+ [ Widget.textInput (Material.textInput Material.defaultPalette)
198+ { chips = []
199+ , text = model.newEntry
200+ , placeholder = Nothing
201+ , label = "New entry"
202+ , onChange = UpdateNewEntry
203+ }
204+ , Widget.iconButton (Material.containedButton Material.defaultPalette)
205+ { text = "submit"
206+ , icon = Icon.elmHeroicons Heroicons.Outline.check
207+ , onPress = AddEntry model.newEntry |> Just
208+ }
209+ ]
210+ , model.entries
211+ |> Maybe.withDefault []
212+ |> List.map
213+ (\( id, txt ) ->
214+ row
215+ [ Border.rounded 10
216+ , Border.shadow { offset = ( 0, 5 ), size = 1, blur = 10, color = rgba 0 0 0 0.3 }
217+ , centerX
218+ , padding 10
219+ , spacing 12
220+ ]
221+ [ paragraph [] [ text txt ]
222+ , Widget.iconButton (Material.containedButton Material.defaultPalette)
223+ { text = "delete"
224+ , icon = Icon.elmHeroicons Heroicons.Outline.trash
225+ , onPress = DeleteEntry id |> Just
226+ }
227+ ]
228+ )
229+ |> column [ spacing 10, centerX ]
230+ ]
231+ |> column [ spacing 18, centerX ]
232+ )
233+ ]
234+ , title = "Airlock"
235+ }
236+
237+
238+result : (a -> c) -> (b -> c) -> Result a b -> c
239+result f g res =
240+ case res of
241+ Ok b ->
242+ g b
243+
244+ Err a ->
245+ f a
246+
247+
248+port createEventSource : String -> Cmd msg
249+
250+
251+port onEventSourceMessage : (JD.Value -> msg) -> Sub msg
example/urbit/app/journal.hoonnew file+133
@@ -0,0 +1,133 @@
1+/- *journal
2+/+ default-agent, dbug, agentio, *sink
3+|%
4++$ versioned-state
5+ $% state-0
6+ ==
7++$ state-0 [%0 =journal =log]
8++$ card card:agent:gall
9+++ j-orm ((on id txt) gth)
10+++ log-orm ((on @ action) lth)
11+++ unique-time
12+ |= [=time =log]
13+ ^- @
14+ =/ unix-ms=@
15+ (unm:chrono:userlib time)
16+ |-
17+ ?. (has:log-orm log unix-ms)
18+ unix-ms
19+ $(time (add unix-ms 1))
20+--
21+
22+%- agent:dbug
23+=/ state *state-0
24+=* entries (tap:j-orm journal.state)
25+=/ snik (sink ~[/sync])
26+=/ sink (snik entries)
27+^- agent:gall
28+::
29+|_ =bowl:gall
30++* this .
31+ def ~(. (default-agent this %|) bowl)
32+ io ~(. agentio bowl)
33+++ on-init on-init:def
34+++ on-save
35+ ^- vase
36+ !>(state)
37+::
38+++ on-load
39+ |= old-vase=vase
40+ ^- (quip card _this)
41+ =/ state !<(versioned-state old-vase)
42+ `this(state state, sink (snik entries))
43+::
44+++ on-poke
45+ |= [=mark =vase]
46+ ^- (quip card _this)
47+ |^
48+ ?> (team:title our.bowl src.bowl)
49+ ?. ?=(%journal-action mark) (on-poke:def mark vase)
50+ =/ now=@ (unique-time now.bowl log.state)
51+ =/ act !<(action vase)
52+ =. state (poke-action act)
53+ =^ card sink (sync:sink entries)
54+ :_ this(log.state (put:log-orm log.state now act))
55+ ~[(fact:io journal-update+!>(`update`[now act]) ~[/updates]) card]
56+ ::
57+ ++ poke-action
58+ |= act=action
59+ ^- _state
60+ ?- -.act
61+ %add
62+ ?< (has:j-orm journal.state id.act)
63+ state(journal (put:j-orm journal.state id.act txt.act))
64+ ::
65+ %edit
66+ ?> (has:j-orm journal.state id.act)
67+ state(journal (put:j-orm journal.state id.act txt.act))
68+ ::
69+ %del
70+ ?> (has:j-orm journal.state id.act)
71+ state(journal +:(del:j-orm journal.state id.act))
72+ ==
73+ --
74+::
75+++ on-watch
76+ |= =path
77+ ^- (quip card _this)
78+ ?> (team:title our.bowl src.bowl)
79+ ?+ path (on-watch:def path)
80+ [%updates ~] `this
81+ [%sync ~] [~[flush:sink] this]
82+ ==
83+::
84+++ on-peek
85+ |= =path
86+ ^- (unit (unit cage))
87+ ?> (team:title our.bowl src.bowl)
88+ =/ now=@ (unm:chrono:userlib now.bowl)
89+ ?+ path (on-peek:def path)
90+ [%x %entries *]
91+ ?+ t.t.path (on-peek:def path)
92+ [%all ~]
93+ :^ ~ ~ %journal-update
94+ !> ^- update
95+ [now %jrnl entries]
96+ ::
97+ [%before @ @ ~]
98+ =/ before=@ (rash i.t.t.t.path dem)
99+ =/ max=@ (rash i.t.t.t.t.path dem)
100+ :^ ~ ~ %journal-update
101+ !> ^- update
102+ [now %jrnl (tab:j-orm journal.state `before max)]
103+ ::
104+ [%between @ @ ~]
105+ =/ start=@
106+ =+ (rash i.t.t.t.path dem)
107+ ?:(=(0 -) - (sub - 1))
108+ =/ end=@ (add 1 (rash i.t.t.t.t.path dem))
109+ :^ ~ ~ %journal-update
110+ !> ^- update
111+ [now %jrnl (tap:j-orm (lot:j-orm journal.state `end `start))]
112+ ==
113+ ::
114+ [%x %updates *]
115+ ?+ t.t.path (on-peek:def path)
116+ [%all ~]
117+ :^ ~ ~ %journal-update
118+ !> ^- update
119+ [now %logs (tap:log-orm log.state)]
120+ ::
121+ [%since @ ~]
122+ =/ since=@ (rash i.t.t.t.path dem)
123+ :^ ~ ~ %journal-update
124+ !> ^- update
125+ [now %logs (tap:log-orm (lot:log-orm log.state `since ~))]
126+ ==
127+ ==
128+::
129+++ on-leave on-leave:def
130+++ on-agent on-agent:def
131+++ on-arvo on-arvo:def
132+++ on-fail on-fail:def
133+--
example/urbit/desk.billnew file+2
@@ -0,0 +1,2 @@
1+:~ %journal
2+==
example/urbit/desk.docket-0new file+12
@@ -0,0 +1,12 @@
1+:~
2+ title+'Journal'
3+ info+'Dear diary...'
4+ color+0xd9.b06d
5+ version+[0 1 1]
6+ website+'https://urbit.org'
7+ license+'MIT'
8+ base+'journal'
9+ :+ %glob-http
10+ 'https://m.tinnus-napbus.xyz/pub/journal-glob-0v3.gjphm.jp4u3.59f0f.a67bf.446k4.glob'
11+ 0v3.gjphm.jp4u3.59f0f.a67bf.446k4
12+==
example/urbit/gen/journal/add.hoonnew file+37
@@ -0,0 +1,37 @@
1+/- *journal, *sole
2+/+ *generators
3+:- %ask
4+|= [[now=@da * bek=beak] *]
5+^- (sole-product (cask action))
6+=| acc=wain
7+=/ off=tape
8+ =/ blit
9+ =+ [our=(scot %p p.bek) now=(scot %da p.r.bek)]
10+ .^(blit:dill %dx /[our]//[now]/sessions//line)
11+ ?. ?=(%lin -.blit) !!
12+ %+ scan (tufa p.blit)
13+ ;~ sfix
14+ %+ cook
15+ |=(t=tape (reap (add 2 (lent t)) ' '))
16+ (star ;~(less gar next))
17+ (star next)
18+ ==
19+|^
20+%+ print leaf+"{off}-------------------------------------\0a"
21+%+ print leaf+"{off}| |"
22+%+ print leaf+"{off}| (\\\\\\ to submit, <BKSP> to cancel) |"
23+%+ print leaf+"{off}| New Journal Entry |"
24+%+ print leaf+"{off}| |"
25+%+ print leaf+"\0a{off}-------------------------------------"
26+line
27+++ line
28+ %+ prompt [%& %prompt ""]
29+ |= t=tape
30+ ?: =(t "\\\\\\")
31+ %+ produce %journal-action
32+ :+ %add
33+ (unm:chrono:userlib now)
34+ (of-wain:format (flop acc))
35+ %+ print leaf+"{off}{t}"
36+ line(acc [(crip t) acc])
37+--
example/urbit/lib/agentio.hoonnew file+139
@@ -0,0 +1,139 @@
1+=>
2+ |%
3+ ++ card card:agent:gall
4+ --
5+::
6+|_ =bowl:gall
7+++ scry
8+ |= [desk=@tas =path]
9+ %+ weld
10+ /(scot %p our.bowl)/[desk]/(scot %da now.bowl)
11+ path
12+::
13+++ pass
14+ |_ =wire
15+ ++ poke
16+ |= [=dock =cage]
17+ [%pass wire %agent dock %poke cage]
18+ ::
19+ ++ poke-our
20+ |= [app=term =cage]
21+ ^- card
22+ (poke [our.bowl app] cage)
23+ ::
24+ ++ poke-self
25+ |= =cage
26+ ^- card
27+ (poke-our dap.bowl cage)
28+ ::
29+ ++ arvo
30+ |= =note-arvo
31+ ^- card
32+ [%pass wire %arvo note-arvo]
33+ ::
34+ ++ watch
35+ |= [=dock =path]
36+ [%pass (watch-wire path) %agent dock %watch path]
37+ ::
38+ ++ watch-our
39+ |= [app=term =path]
40+ (watch [our.bowl app] path)
41+ ::
42+ ++ watch-wire
43+ |= =path
44+ ^+ wire
45+ ?. ?=(~ wire)
46+ wire
47+ agentio-watch+path
48+ ::
49+ ++ leave
50+ |= =dock
51+ [%pass wire %agent dock %leave ~]
52+ ::
53+ ++ leave-our
54+ |= app=term
55+ (leave our.bowl app)
56+ ::
57+ ++ leave-path
58+ |= [=dock =path]
59+ =. wire
60+ (watch-wire path)
61+ (leave dock)
62+ ::
63+ ++ wait
64+ |= p=@da
65+ (arvo %b %wait p)
66+ ::
67+ ++ rest
68+ |= p=@da
69+ (arvo %b %wait p)
70+ ::
71+ ++ warp
72+ |= [wer=ship =riff:clay]
73+ (arvo %c %warp wer riff)
74+ ::
75+ ++ warp-our
76+ |= =riff:clay
77+ (warp our.bowl riff)
78+ ::
79+ :: right here, right now
80+ ++ warp-slim
81+ |= [genre=?(%sing %next) =care:clay =path]
82+ =/ =mood:clay
83+ [care r.byk.bowl path]
84+ =/ =rave:clay
85+ ?:(?=(%sing genre) [genre mood] [genre mood])
86+ (warp-our q.byk.bowl `rave)
87+ ::
88+ ++ connect
89+ |= [=binding:eyre app=term]
90+ (arvo %e %connect binding app)
91+ --
92+::
93+++ fact-curry
94+ |* [=mark =mold]
95+ |= [paths=(list path) fac=mold]
96+ (fact mark^!>(fac) paths)
97+::
98+++ fact-kick
99+ |= [=path =cage]
100+ ^- (list card)
101+ :~ (fact cage ~[path])
102+ (kick ~[path])
103+ ==
104+::
105+++ fact-init
106+ |= =cage
107+ ^- card
108+ [%give %fact ~ cage]
109+::
110+++ fact-init-kick
111+ |= =cage
112+ ^- (list card)
113+ :~ (fact cage ~)
114+ (kick ~)
115+ ==
116+::
117+++ fact
118+ |= [=cage paths=(list path)]
119+ ^- card
120+ [%give %fact paths cage]
121+::
122+++ fact-all
123+ |= =cage
124+ ^- (unit card)
125+ =/ paths=(set path)
126+ %- ~(gas in *(set path))
127+ %+ turn ~(tap by sup.bowl)
128+ |=([duct ship =path] path)
129+ ?: =(~ paths) ~
130+ `(fact cage ~(tap in paths))
131+::
132+++ kick
133+ |= paths=(list path)
134+ [%give %kick paths ~]
135+::
136+++ kick-only
137+ |= [=ship paths=(list path)]
138+ [%give %kick paths `ship]
139+--
example/urbit/lib/dbug.hoonnew file+155
@@ -0,0 +1,155 @@
1+:: dbug: agent wrapper for generic debugging tools
2+::
3+:: usage: %-(agent:dbug your-agent)
4+::
5+|%
6++$ poke
7+ $% [%bowl ~]
8+ [%state grab=cord]
9+ [%incoming =about]
10+ [%outgoing =about]
11+ ==
12+::
13++$ about
14+ $@ ~
15+ $% [%ship =ship]
16+ [%path =path]
17+ [%wire =wire]
18+ [%term =term]
19+ ==
20+::
21+++ agent
22+ |= =agent:gall
23+ ^- agent:gall
24+ !.
25+ |_ =bowl:gall
26+ +* this .
27+ ag ~(. agent bowl)
28+ ::
29+ ++ on-poke
30+ |= [=mark =vase]
31+ ^- (quip card:agent:gall agent:gall)
32+ ?. ?=(%dbug mark)
33+ =^ cards agent (on-poke:ag mark vase)
34+ [cards this]
35+ =/ dbug
36+ !<(poke vase)
37+ =; =tang
38+ ((%*(. slog pri 1) tang) [~ this])
39+ ?- -.dbug
40+ %bowl [(sell !>(bowl))]~
41+ ::
42+ %state
43+ =? grab.dbug =('' grab.dbug) '-'
44+ =; product=^vase
45+ [(sell product)]~
46+ =/ state=^vase
47+ :: if the underlying app has implemented a /dbug/state scry endpoint,
48+ :: use that vase in place of +on-save's.
49+ ::
50+ =/ result=(each ^vase tang)
51+ (mule |.(q:(need (need (on-peek:ag /x/dbug/state)))))
52+ ?:(?=(%& -.result) p.result on-save:ag)
53+ %+ slap
54+ (slop state !>([bowl=bowl ..zuse]))
55+ (ream grab.dbug)
56+ ::
57+ %incoming
58+ =; =tang
59+ ?^ tang tang
60+ [%leaf "no matching subscriptions"]~
61+ %+ murn
62+ %+ sort ~(tap by sup.bowl)
63+ |= [[* a=[=ship =path]] [* b=[=ship =path]]]
64+ (aor [path ship]:a [path ship]:b)
65+ |= [=duct [=ship =path]]
66+ ^- (unit tank)
67+ =; relevant=?
68+ ?. relevant ~
69+ `>[path=path from=ship duct=duct]<
70+ ?: ?=(~ about.dbug) &
71+ ?- -.about.dbug
72+ %ship =(ship ship.about.dbug)
73+ %path ?=(^ (find path.about.dbug path))
74+ %wire %+ lien duct
75+ |=(=wire ?=(^ (find wire.about.dbug wire)))
76+ %term !!
77+ ==
78+ ::
79+ %outgoing
80+ =; =tang
81+ ?^ tang tang
82+ [%leaf "no matching subscriptions"]~
83+ %+ murn
84+ %+ sort ~(tap by wex.bowl)
85+ |= [[[a=wire *] *] [[b=wire *] *]]
86+ (aor a b)
87+ |= [[=wire =ship =term] [acked=? =path]]
88+ ^- (unit tank)
89+ =; relevant=?
90+ ?. relevant ~
91+ `>[wire=wire agnt=[ship term] path=path ackd=acked]<
92+ ?: ?=(~ about.dbug) &
93+ ?- -.about.dbug
94+ %ship =(ship ship.about.dbug)
95+ %path ?=(^ (find path.about.dbug path))
96+ %wire ?=(^ (find wire.about.dbug wire))
97+ %term =(term term.about.dbug)
98+ ==
99+ ==
100+ ::
101+ ++ on-peek
102+ |= =path
103+ ^- (unit (unit cage))
104+ ?. ?=([@ %dbug *] path)
105+ (on-peek:ag path)
106+ ?+ path [~ ~]
107+ [%u %dbug ~] ``noun+!>(&)
108+ [%x %dbug %state ~] ``noun+!>(on-save:ag)
109+ [%x %dbug %subscriptions ~] ``noun+!>([wex sup]:bowl)
110+ ==
111+ ::
112+ ++ on-init
113+ ^- (quip card:agent:gall agent:gall)
114+ =^ cards agent on-init:ag
115+ [cards this]
116+ ::
117+ ++ on-save on-save:ag
118+ ::
119+ ++ on-load
120+ |= old-state=vase
121+ ^- (quip card:agent:gall agent:gall)
122+ =^ cards agent (on-load:ag old-state)
123+ [cards this]
124+ ::
125+ ++ on-watch
126+ |= =path
127+ ^- (quip card:agent:gall agent:gall)
128+ =^ cards agent (on-watch:ag path)
129+ [cards this]
130+ ::
131+ ++ on-leave
132+ |= =path
133+ ^- (quip card:agent:gall agent:gall)
134+ =^ cards agent (on-leave:ag path)
135+ [cards this]
136+ ::
137+ ++ on-agent
138+ |= [=wire =sign:agent:gall]
139+ ^- (quip card:agent:gall agent:gall)
140+ =^ cards agent (on-agent:ag wire sign)
141+ [cards this]
142+ ::
143+ ++ on-arvo
144+ |= [=wire =sign-arvo]
145+ ^- (quip card:agent:gall agent:gall)
146+ =^ cards agent (on-arvo:ag wire sign-arvo)
147+ [cards this]
148+ ::
149+ ++ on-fail
150+ |= [=term =tang]
151+ ^- (quip card:agent:gall agent:gall)
152+ =^ cards agent (on-fail:ag term tang)
153+ [cards this]
154+ --
155+--
example/urbit/lib/default-agent.hoonnew file+69
@@ -0,0 +1,69 @@
1+/+ skeleton
2+|* [agent=* help=*]
3+?: ?=(%& help)
4+ ~| %default-agent-helpfully-crashing
5+ skeleton
6+|_ =bowl:gall
7+++ on-init
8+ `agent
9+::
10+++ on-save
11+ !>(~)
12+::
13+++ on-load
14+ |= old-state=vase
15+ `agent
16+::
17+++ on-poke
18+ |= =cage
19+ ~| "unexpected poke to {<dap.bowl>} with mark {<p.cage>}"
20+ !!
21+::
22+++ on-watch
23+ |= =path
24+ ~| "unexpected subscription to {<dap.bowl>} on path {<path>}"
25+ !!
26+::
27+++ on-leave
28+ |= path
29+ `agent
30+::
31+++ on-peek
32+ |= =path
33+ ~| "unexpected scry into {<dap.bowl>} on path {<path>}"
34+ !!
35+::
36+++ on-agent
37+ |= [=wire =sign:agent:gall]
38+ ^- (quip card:agent:gall _agent)
39+ ?- -.sign
40+ %poke-ack
41+ ?~ p.sign
42+ `agent
43+ %- (slog leaf+"poke failed from {<dap.bowl>} on wire {<wire>}" u.p.sign)
44+ `agent
45+ ::
46+ %watch-ack
47+ ?~ p.sign
48+ `agent
49+ =/ =tank leaf+"subscribe failed from {<dap.bowl>} on wire {<wire>}"
50+ %- (slog tank u.p.sign)
51+ `agent
52+ ::
53+ %kick `agent
54+ %fact
55+ ~| "unexpected subscription update to {<dap.bowl>} on wire {<wire>}"
56+ ~| "with mark {<p.cage.sign>}"
57+ !!
58+ ==
59+::
60+++ on-arvo
61+ |= [=wire =sign-arvo]
62+ ~| "unexpected system response {<-.sign-arvo>} to {<dap.bowl>} on wire {<wire>}"
63+ !!
64+::
65+++ on-fail
66+ |= [=term =tang]
67+ %- (slog leaf+"error in {<dap.bowl>}" >term< tang)
68+ `agent
69+--
example/urbit/lib/docket.hoonnew file+223
@@ -0,0 +1,223 @@
1+/- *docket
2+|%
3+::
4+++ mime
5+ |%
6+ +$ draft
7+ $: title=(unit @t)
8+ info=(unit @t)
9+ color=(unit @ux)
10+ glob-http=(unit [=url hash=@uvH])
11+ glob-ames=(unit [=ship hash=@uvH])
12+ base=(unit term)
13+ site=(unit path)
14+ image=(unit url)
15+ version=(unit version)
16+ website=(unit url)
17+ license=(unit cord)
18+ ==
19+ ::
20+ ++ finalize
21+ |= =draft
22+ ^- (unit docket)
23+ ?~ title.draft ~
24+ ?~ info.draft ~
25+ ?~ color.draft ~
26+ ?~ version.draft ~
27+ ?~ website.draft ~
28+ ?~ license.draft ~
29+ =/ href=(unit href)
30+ ?^ site.draft `[%site u.site.draft]
31+ ?~ base.draft ~
32+ ?^ glob-http.draft
33+ `[%glob u.base hash.u.glob-http %http url.u.glob-http]:draft
34+ ?~ glob-ames.draft
35+ ~
36+ `[%glob u.base hash.u.glob-ames %ames ship.u.glob-ames]:draft
37+ ?~ href ~
38+ =, draft
39+ :- ~
40+ :* %1
41+ u.title
42+ u.info
43+ u.color
44+ u.href
45+ image
46+ u.version
47+ u.website
48+ u.license
49+ ==
50+ ::
51+ ++ from-clauses
52+ =| =draft
53+ |= cls=(list clause)
54+ ^- (unit docket)
55+ =* loop $
56+ ?~ cls (finalize draft)
57+ =* clause i.cls
58+ =. draft
59+ ?- -.clause
60+ %title draft(title `title.clause)
61+ %info draft(info `info.clause)
62+ %color draft(color `color.clause)
63+ %glob-http draft(glob-http `[url hash]:clause)
64+ %glob-ames draft(glob-ames `[ship hash]:clause)
65+ %base draft(base `base.clause)
66+ %site draft(site `path.clause)
67+ %image draft(image `url.clause)
68+ %version draft(version `version.clause)
69+ %website draft(website `website.clause)
70+ %license draft(license `license.clause)
71+ ==
72+ loop(cls t.cls)
73+ ::
74+ ++ to-clauses
75+ |= d=docket
76+ ^- (list clause)
77+ %- zing
78+ :~ :~ title+title.d
79+ info+info.d
80+ color+color.d
81+ version+version.d
82+ website+website.d
83+ license+license.d
84+ ==
85+ ?~ image.d ~ ~[image+u.image.d]
86+ ?: ?=(%site -.href.d) ~[site+path.href.d]
87+ =/ ref=glob-reference glob-reference.href.d
88+ :~ base+base.href.d
89+ ?- -.location.ref
90+ %http [%glob-http url.location.ref hash.ref]
91+ %ames [%glob-ames ship.location.ref hash.ref]
92+ == == ==
93+ ::
94+ ++ spit-clause
95+ |= =clause
96+ ^- tape
97+ %+ weld " {(trip -.clause)}+"
98+ ?+ -.clause "'{(trip +.clause)}'"
99+ %color (scow %ux color.clause)
100+ %site (spud path.clause)
101+ ::
102+ %glob-http
103+ "['{(trip url.clause)}' {(scow %uv hash.clause)}]"
104+ ::
105+ %glob-ames
106+ "[{(scow %p ship.clause)} {(scow %uv hash.clause)}]"
107+ ::
108+ %version
109+ =, version.clause
110+ "[{(scow %ud major)} {(scow %ud minor)} {(scow %ud patch)}]"
111+ ==
112+ ::
113+ ++ spit-docket
114+ |= dock=docket
115+ ^- tape
116+ ;: welp
117+ ":~\0a"
118+ `tape`(zing (join "\0a" (turn (to-clauses dock) spit-clause)))
119+ "\0a=="
120+ ==
121+ --
122+::
123+++ enjs
124+ =, enjs:format
125+ |%
126+ ::
127+ ++ charge-update
128+ |= u=^charge-update
129+ ^- json
130+ %+ frond -.u
131+ ^- json
132+ ?- -.u
133+ %del-charge s+desk.u
134+ ::
135+ %initial
136+ %- pairs
137+ %+ turn ~(tap by initial.u)
138+ |=([=desk c=^charge] [desk (charge c)])
139+ ::
140+ %add-charge
141+ %- pairs
142+ :~ desk+s+desk.u
143+ charge+(charge charge.u)
144+ ==
145+ ==
146+ ::
147+ ++ num
148+ |= a=@u
149+ ^- ^tape
150+ =/ p=json (numb a)
151+ ?> ?=(%n -.p)
152+ (trip p.p)
153+ ::
154+ ++ version
155+ |= v=^version
156+ ^- json
157+ :- %s
158+ %- crip
159+ "{(num major.v)}.{(num minor.v)}.{(num patch.v)}"
160+ ::
161+ ++ merge
162+ |= [a=json b=json]
163+ ^- json
164+ ?> &(?=(%o -.a) ?=(%o -.b))
165+ [%o (~(uni by p.a) p.b)]
166+ ::
167+ ++ href
168+ |= h=^href
169+ %+ frond -.h
170+ ?- -.h
171+ %site s+(spat path.h)
172+ %glob
173+ %- pairs
174+ :~ base+s+base.h
175+ glob-reference+(glob-reference glob-reference.h)
176+ ==
177+ ==
178+ ::
179+ ++ glob-reference
180+ |= ref=^glob-reference
181+ %- pairs
182+ :~ hash+s+(scot %uv hash.ref)
183+ location+(glob-location location.ref)
184+ ==
185+ ::
186+ ++ glob-location
187+ |= loc=^glob-location
188+ ^- json
189+ %+ frond -.loc
190+ ?- -.loc
191+ %http s+url.loc
192+ %ames s+(scot %p ship.loc)
193+ ==
194+ ::
195+ ++ charge
196+ |= c=^charge
197+ %+ merge (docket docket.c)
198+ %- pairs
199+ :~ chad+(chad chad.c)
200+ ==
201+ ::
202+ ++ docket
203+ |= d=^docket
204+ ^- json
205+ %- pairs
206+ :~ title+s+title.d
207+ info+s+info.d
208+ color+s+(scot %ux color.d)
209+ href+(href href.d)
210+ image+?~(image.d ~ s+u.image.d)
211+ version+(version version.d)
212+ license+s+license.d
213+ website+s+website.d
214+ ==
215+ ::
216+ ++ chad
217+ |= c=^chad
218+ %+ frond -.c
219+ ?+ -.c ~
220+ %hung s+err.c
221+ ==
222+ --
223+--
example/urbit/lib/journal.hoonnew file+66
@@ -0,0 +1,66 @@
1+/- *journal
2+|%
3+++ dejs-action
4+ =, dejs:format
5+ |= jon=json
6+ ^- action
7+ %. jon
8+ %- of
9+ :~ [%add (ot ~[id+ni txt+so])]
10+ [%edit (ot ~[id+ni txt+so])]
11+ [%del (ot ~[id+ni])]
12+ ==
13+++ enjs-update
14+ =, enjs:format
15+ |= upd=update
16+ ^- json
17+ |^
18+ ?+ -.q.upd (logged upd)
19+ %jrnl
20+ %- pairs
21+ :~ ['time' (numb p.upd)]
22+ ['entries' a+(turn list.q.upd entry)]
23+ ==
24+ ::
25+ %logs
26+ %- pairs
27+ :~ ['time' (numb p.upd)]
28+ ['logs' a+(turn list.q.upd logged)]
29+ ==
30+ ==
31+ ++ entry
32+ |= ent=^entry
33+ ^- json
34+ %- pairs
35+ :~ ['id' (numb id.ent)]
36+ ['txt' s+txt.ent]
37+ ==
38+ ++ logged
39+ |= lgd=^logged
40+ ^- json
41+ ?- -.q.lgd
42+ %add
43+ %- pairs
44+ :~ ['time' (numb p.lgd)]
45+ :- 'add'
46+ %- pairs
47+ :~ ['id' (numb id.q.lgd)]
48+ ['txt' s+txt.q.lgd]
49+ == ==
50+ %edit
51+ %- pairs
52+ :~ ['time' (numb p.lgd)]
53+ :- 'edit'
54+ %- pairs
55+ :~ ['id' (numb id.q.lgd)]
56+ ['txt' s+txt.q.lgd]
57+ == ==
58+ %del
59+ %- pairs
60+ :~ ['time' (numb p.lgd)]
61+ :- 'del'
62+ (frond 'id' (numb id.q.lgd))
63+ ==
64+ ==
65+ --
66+--
example/urbit/lib/noun-diff.hoonnew file+227
@@ -0,0 +1,227 @@
1+=<
2+|%
3+ ++ diff
4+ |= [old=* new=*]
5+ ^- patch
6+ =/ del (extract-del (oracle old new) old)
7+ =/ ins (extract-ins (oracle old new) new)
8+ =/ allowed-holes (~(int in (find-del-holes del)) (find-ins-holes ins))
9+ =. del (filter-del-holes allowed-holes del)
10+ =/ ins (filter-ins-holes allowed-holes ins)
11+ =/ closed-patch (closure (gcp [del ins]))
12+ ?> =(& +.closed-patch)
13+ -.closed-patch
14+ ++ apply
15+ |= [patch=_id noun=*]
16+ ?- -.patch
17+ %diff
18+ =/ var-map (del del.patch noun)
19+ (ins ins.patch var-map)
20+ %cell
21+ ?> ?=(^ noun)
22+ [$(patch lhs.patch, noun -.noun) $(patch rhs.patch, noun +.noun)]
23+ ==
24+ ++ id
25+ ^- patch
26+ [%diff [%hole ~] [%hole ~]]
27+ +$ patch
28+ $%
29+ [%cell lhs=patch rhs=patch]
30+ [%diff ^diff]
31+ ==
32+--
33+::
34+|%
35+ +$ del-diff
36+ $%
37+ [%hole @]
38+ [%cell lhs=del-diff rhs=del-diff]
39+ [%ignore ~]
40+ ==
41+ +$ ins-diff
42+ $%
43+ [%hole @ original=*]
44+ [%cell lhs=ins-diff rhs=ins-diff]
45+ [%atom @]
46+ ==
47+ +$ final-ins-diff
48+ $%
49+ [%hole @]
50+ [%cell lhs=final-ins-diff rhs=final-ins-diff]
51+ [%atom @]
52+ ==
53+ +$ diff [del=del-diff ins=final-ins-diff]
54+ +$ patch
55+ $%
56+ [%cell lhs=patch rhs=patch]
57+ [%diff diff]
58+ ==
59+ ++ insify-noun
60+ |= noun=*
61+ ^- final-ins-diff
62+ ?- noun
63+ ^ [%cell $(noun -.noun) $(noun +.noun)]
64+ @ [%atom noun]
65+ ==
66+ ++ empty-set (silt `(list @)`~)
67+ ++ find-del-holes
68+ |= diff=del-diff
69+ ~+
70+ ^- (set @)
71+ ?- -.diff
72+ %hole (silt ~[+.diff])
73+ %cell
74+ (~(uni in $(diff lhs.diff)) $(diff rhs.diff))
75+ %ignore empty-set
76+ ==
77+ ++ find-final-ins-holes
78+ |= diff=final-ins-diff
79+ ~+
80+ ^- (set @)
81+ ?- -.diff
82+ %hole (silt ~[+.diff])
83+ %cell
84+ (~(uni in $(diff lhs.diff)) $(diff rhs.diff))
85+ %atom empty-set
86+ ==
87+ ++ find-ins-holes
88+ |= diff=ins-diff
89+ ~+
90+ ^- (set @)
91+ ?- -.diff
92+ %hole (silt ~[+<.diff])
93+ %cell
94+ (~(uni in $(diff lhs.diff)) $(diff rhs.diff))
95+ %atom empty-set
96+ ==
97+ ++ filter-del-holes
98+ |= [allowed-holes=(set @) diff=del-diff]
99+ ^- del-diff
100+ ?: ?=(%ignore -.diff) diff
101+ ?- -.diff
102+ %hole
103+ ?: (~(has in allowed-holes) +.diff) diff
104+ [%ignore ~]
105+ %cell [%cell $(diff +<.diff) $(diff +>.diff)]
106+ ==
107+ ++ filter-ins-holes
108+ |= [allowed-holes=(set @) diff=ins-diff]
109+ ^- final-ins-diff
110+ ?- -.diff
111+ %hole
112+ ?: (~(has in allowed-holes) +<.diff) [%hole +<.diff]
113+ (insify-noun original:diff)
114+ %cell [%cell $(diff +<.diff) $(diff +>.diff)]
115+ %atom diff
116+ ==
117+ ++ gcp
118+ |= diff=diff
119+ ^- patch
120+ ?- -.ins.diff
121+ %atom
122+ [%diff diff]
123+ %cell
124+ ?: ?=(%cell -.del.diff)
125+ [%cell $(diff [+<.del.diff +<.ins.diff]) $(diff [+>.del.diff +>.ins.diff])]
126+ [%diff diff]
127+ %hole
128+ [%diff diff]
129+ ==
130+ ++ closure
131+ |= =patch
132+ ^- [^patch ?]
133+ ?- -.patch
134+ %diff
135+ =/ del-holes (find-del-holes del:patch)
136+ =/ ins-holes (find-final-ins-holes ins:patch)
137+ =/ difference (~(dif in ins-holes) del-holes)
138+ [patch =(difference empty-set)]
139+ %cell
140+ =/ lhs $(patch lhs:patch)
141+ =/ rhs $(patch rhs:patch)
142+ ?: ?&(+.lhs +.rhs) [[%cell -.lhs -.rhs] &]
143+ $(patch (pull-diff [%cell -.lhs -.rhs]))
144+ ==
145+ ++ pull-diff
146+ |= =patch
147+ ^- [%diff del=del-diff ins=final-ins-diff]
148+ ?- -.patch
149+ %diff patch
150+ %cell
151+ =/ pulled-lhs $(patch lhs:patch)
152+ =/ pulled-rhs $(patch rhs:patch)
153+ :+
154+ %diff
155+ [%cell del:pulled-lhs del:pulled-rhs]
156+ [%cell ins:pulled-lhs ins:pulled-rhs]
157+ ==
158+ ++ is-subtree
159+ |= [tree=* subtree=*]
160+ ~+
161+ ^- ?
162+ ?: =(tree subtree) &
163+ ?@ tree |
164+ ?|
165+ (is-subtree -.tree subtree)
166+ (is-subtree +.tree subtree)
167+ ==
168+ ++ oracle
169+ |= [a=* b=*]
170+ |= subtree=*
171+ ^- (unit @)
172+ ?:
173+ ?&
174+ (is-subtree a subtree)
175+ (is-subtree b subtree)
176+ ==
177+ `(mug subtree)
178+ ~
179+ ++ extract-del
180+ |= [oracle=$-(* (unit @)) subtree=*]
181+ ~+
182+ ^- del-diff
183+ =/ hash (oracle subtree)
184+ ?^ hash [%hole +.hash]
185+ ?@ subtree [%ignore ~]
186+ [%cell (extract-del oracle -.subtree) (extract-del oracle +.subtree)]
187+ ++ extract-ins
188+ |= [oracle=$-(* (unit @)) subtree=*]
189+ ~+
190+ ^- ins-diff
191+ =/ hash (oracle subtree)
192+ ?^ hash [%hole +.hash subtree]
193+ ?@ subtree [%atom subtree]
194+ [%cell (extract-ins oracle -.subtree) (extract-ins oracle +.subtree)]
195+ ++ ins
196+ |= [diff=final-ins-diff var-map=(map @ *)]
197+ ^- *
198+ ?- -.diff
199+ %atom
200+ +.diff
201+ %cell
202+ [$(diff +<.diff) $(diff +>.diff)]
203+ %hole
204+ (~(got by var-map) +.diff)
205+ ==
206+ ++ del
207+ |= [diff=del-diff noun=*]
208+ ^- (map @ *)
209+ |^ (go diff noun ((map @ *) ~))
210+ ++ go
211+ |= [diff=del-diff noun=* var-map=(map @ *)]
212+ ^- (map @ *)
213+ ?- -.diff
214+ %ignore var-map
215+ %hole
216+ =/ subtree (~(get by var-map) +.diff)
217+ ?~ subtree (~(put by var-map) +.diff noun)
218+ ?> =(+.subtree noun)
219+ var-map
220+ %cell
221+ ?> ?=(^ noun)
222+ =/ lhs-var-map $(diff +<.diff, noun -.noun)
223+ =/ rhs-var-map $(diff +>.diff, noun +.noun, var-map lhs-var-map)
224+ rhs-var-map
225+ ==
226+ --
227+--
example/urbit/lib/sink.hoonnew file+26
@@ -0,0 +1,26 @@
1+/+ noun-diff
2+|%
3+++ clog
4+ |$ [stut]
5+ $%
6+ [%flush stut]
7+ [%drain patch:noun-diff]
8+ ==
9+++ sink
10+ |* pats=(list path)
11+ |* stat=*
12+ |@
13+ ++ sync
14+ |= [stat=_stat]
15+ ^- [card:agent:gall _..sync]
16+ =/ dif
17+ %+ diff:noun-diff ^stat stat
18+ :-
19+ [%give %fact pats %noun !>(^-((clog) [%drain dif]))]
20+ ..sync(stat stat)
21+ ++ paths pats
22+ ++ flush
23+ ^- card:agent:gall
24+ [%give %fact pats %noun !>(^-((clog) [%flush stat]))]
25+ --
26+--
example/urbit/lib/skeleton.hoonnew file+51
@@ -0,0 +1,51 @@
1+:: Similar to default-agent except crashes everywhere
2+^- agent:gall
3+|_ bowl:gall
4+++ on-init
5+ ^- (quip card:agent:gall agent:gall)
6+ !!
7+::
8+++ on-save
9+ ^- vase
10+ !!
11+::
12+++ on-load
13+ |~ old-state=vase
14+ ^- (quip card:agent:gall agent:gall)
15+ !!
16+::
17+++ on-poke
18+ |~ in-poke-data=cage
19+ ^- (quip card:agent:gall agent:gall)
20+ !!
21+::
22+++ on-watch
23+ |~ path
24+ ^- (quip card:agent:gall agent:gall)
25+ !!
26+::
27+++ on-leave
28+ |~ path
29+ ^- (quip card:agent:gall agent:gall)
30+ !!
31+::
32+++ on-peek
33+ |~ path
34+ ^- (unit (unit cage))
35+ !!
36+::
37+++ on-agent
38+ |~ [wire sign:agent:gall]
39+ ^- (quip card:agent:gall agent:gall)
40+ !!
41+::
42+++ on-arvo
43+ |~ [wire =sign-arvo]
44+ ^- (quip card:agent:gall agent:gall)
45+ !!
46+::
47+++ on-fail
48+ |~ [term tang]
49+ ^- (quip card:agent:gall agent:gall)
50+ !!
51+--
example/urbit/mar/bill.hoonnew file+34
@@ -0,0 +1,34 @@
1+|_ bil=(list dude:gall)
2+++ grow
3+ |%
4+ ++ mime `^mime`[/text/x-bill (as-octs:mimes:html hoon)]
5+ ++ noun bil
6+ ++ hoon
7+ ^- @t
8+ |^ (crip (of-wall:format (wrap-lines (spit-duz bil))))
9+ ::
10+ ++ wrap-lines
11+ |= taz=wall
12+ ^- wall
13+ ?~ taz ["~"]~
14+ :- (weld ":~ " i.taz)
15+ %- snoc :_ "=="
16+ (turn t.taz |=(t=tape (weld " " t)))
17+ ::
18+ ++ spit-duz
19+ |= duz=(list dude:gall)
20+ ^- wall
21+ (turn duz |=(=dude:gall ['%' (trip dude)]))
22+ --
23+ ++ txt (to-wain:format hoon)
24+ --
25+++ grab
26+ |%
27+ ++ noun (list dude:gall)
28+ ++ mime
29+ |= [=mite len=@ud tex=@]
30+ ~_ tex
31+ !<((list dude:gall) (slap !>(~) (ream tex)))
32+ --
33+++ grad %noun
34+--
example/urbit/mar/docket-0.hoonnew file+25
@@ -0,0 +1,25 @@
1+/+ dock=docket
2+|_ =docket:dock
3+++ grow
4+ |%
5+ ++ mime
6+ ^- ^mime
7+ [/text/x-docket (as-octt:mimes:html (spit-docket:mime:dock docket))]
8+ ++ noun docket
9+ ++ json (docket:enjs:dock docket)
10+ --
11+++ grab
12+ |%
13+ ::
14+ ++ mime
15+ |= [=mite len=@ud tex=@]
16+ ^- docket:dock
17+ %- need
18+ %- from-clauses:mime:dock
19+ !<((list clause:dock) (slap !>(~) (ream tex)))
20+
21+ ::
22+ ++ noun docket:dock
23+ --
24+++ grad %noun
25+--
example/urbit/mar/hoon.hoonnew file+49
@@ -0,0 +1,49 @@
1+:::: /hoon/hoon/mar
2+ ::
3+/? 310
4+::
5+=, eyre
6+|_ own=@t
7+::
8+++ grow :: convert to
9+ |%
10+ ++ mime `^mime`[/text/x-hoon (as-octs:mimes:html own)] :: convert to %mime
11+ ++ elem :: convert to %html
12+ ;div:pre(urb_codemirror "", mode "hoon"):"{(trip own)}"
13+ :: =+ gen-id="src-{<`@ui`(mug own)>}"
14+ :: ;div
15+ :: ;textarea(id "{gen-id}"):"{(trip own)}"
16+ :: ;script:"""
17+ :: CodeMirror.fromTextArea(
18+ :: window[{<gen-id>}],
19+ :: \{lineNumbers:true, readOnly:true}
20+ :: )
21+ :: """
22+ :: ==
23+ ++ hymn
24+ :: ;html:(head:title:"Source" "+{elem}")
25+ ;html
26+ ;head
27+ ;title:"Source"
28+ ;script@"//cdnjs.cloudflare.com/ajax/libs/codemirror/4.3.0/codemirror.js";
29+ ;script@"/lib/syntax/hoon.js";
30+ ;link(rel "stylesheet", href "//cdnjs.cloudflare.com/ajax/libs/".
31+ "codemirror/4.3.0/codemirror.min.css");
32+ ;link/"/lib/syntax/codemirror.css"(rel "stylesheet");
33+ ==
34+ ;body
35+ ;textarea#src:"{(trip own)}"
36+ ;script:'CodeMirror.fromTextArea(src, {lineNumbers:true, readOnly:true})'
37+ ==
38+ ==
39+ ++ txt
40+ (to-wain:format own)
41+ --
42+++ grab
43+ |% :: convert from
44+ ++ mime |=([p=mite q=octs] q.q)
45+ ++ noun @t :: clam from %noun
46+ ++ txt of-wain:format
47+ --
48+++ grad %txt
49+--
example/urbit/mar/jam.hoonnew file+17
@@ -0,0 +1,17 @@
1+::
2+:::: /hoon/jam/mar
3+ ::
4+/? 310
5+::
6+=, mimes:html
7+|_ mud=@
8+++ grow
9+ |%
10+ ++ mime [/application/octet-stream (as-octs mud)]
11+ --
12+++ grab
13+ |% :: convert from
14+ ++ noun @ :: clam from %noun
15+ --
16+++ grad %mime
17+--
example/urbit/mar/journal/action.hoonnew file+14
@@ -0,0 +1,14 @@
1+/- *journal
2+/+ *journal
3+|_ act=action
4+++ grow
5+ |%
6+ ++ noun act
7+ --
8+++ grab
9+ |%
10+ ++ noun action
11+ ++ json dejs-action
12+ --
13+++ grad %noun
14+--
example/urbit/mar/journal/update.hoonnew file+16
@@ -0,0 +1,16 @@
1+/- *journal
2+/+ *journal
3+|_ upd=update
4+++ grow
5+ |%
6+ ++ noun upd
7+ ++ json (enjs-update upd)
8+ ++ jam (^jam upd)
9+ --
10+++ grab
11+ |%
12+ ++ noun update
13+ ++ jam cue
14+ --
15+++ grad %noun
16+--
example/urbit/mar/json.hoonnew file+26
@@ -0,0 +1,26 @@
1+::
2+:::: /hoon/json/mar
3+ ::
4+/? 310
5+ ::
6+:::: compute
7+ ::
8+=, eyre
9+=, format
10+=, html
11+|_ jon=^json
12+::
13+++ grow :: convert to
14+ |%
15+ ++ mime [/application/json (as-octs:mimes -:txt)] :: convert to %mime
16+ ++ txt [(en:json jon)]~
17+ --
18+++ grab
19+ |% :: convert from
20+ ++ mime |=([p=mite q=octs] (fall (de:json (@t q.q)) *^json))
21+ ++ noun ^json :: clam from %noun
22+ ++ numb numb:enjs
23+ ++ time time:enjs
24+ --
25+++ grad %mime
26+--
example/urbit/mar/kelvin.hoonnew file+28
@@ -0,0 +1,28 @@
1+|_ kal=waft:clay
2+++ grow
3+ |%
4+ ++ mime `^mime`[/text/x-kelvin (as-octs:mimes:html hoon)]
5+ ++ noun kal
6+ ++ hoon
7+ %+ rap 3
8+ %+ turn
9+ %+ sort
10+ ~(tap in (waft-to-wefts:clay kal))
11+ |= [a=weft b=weft]
12+ ?: =(lal.a lal.b)
13+ (gte num.a num.b)
14+ (gte lal.a lal.b)
15+ |= =weft
16+ (rap 3 '[%' (scot %tas lal.weft) ' ' (scot %ud num.weft) ']\0a' ~)
17+ ::
18+ ++ txt (to-wain:format hoon)
19+ --
20+++ grab
21+ |%
22+ ++ noun waft:clay
23+ ++ mime
24+ |= [=mite len=@ud tex=@]
25+ (cord-to-waft:clay tex)
26+ --
27+++ grad %noun
28+--
example/urbit/mar/mime.hoonnew file+32
@@ -0,0 +1,32 @@
1+::
2+:::: /hoon/mime/mar
3+ ::
4+/? 310
5+::
6+|_ own=mime
7+++ grow
8+ ^?
9+ |%
10+ ++ jam `@`q.q.own
11+ --
12+::
13+++ grab :: convert from
14+ ^?
15+ |%
16+ ++ noun mime :: clam from %noun
17+ ++ tape
18+ |=(a=_"" [/application/x-urb-unknown (as-octt:mimes:html a)])
19+ --
20+++ grad
21+ ^?
22+ |%
23+ ++ form %mime
24+ ++ diff |=(mime +<)
25+ ++ pact |=(mime +<)
26+ ++ join |=([mime mime] `(unit mime)`~)
27+ ++ mash
28+ |= [[ship desk mime] [ship desk mime]]
29+ ^- mime
30+ ~|(%mime-mash !!)
31+ --
32+--
example/urbit/mar/noun.hoonnew file+19
@@ -0,0 +1,19 @@
1+::
2+:::: /hoon/noun/mar
3+ ::
4+/? 310
5+!:
6+:::: A minimal noun mark
7+|_ non=*
8+++ grab |%
9+ ++ noun *
10+ --
11+++ grad
12+ |%
13+ ++ form %noun
14+ ++ diff |=(* +<)
15+ ++ pact |=(* +<)
16+ ++ join |=([* *] *(unit *))
17+ ++ mash |=([[ship desk *] [ship desk *]] `*`~|(%noun-mash !!))
18+ --
19+--
example/urbit/mar/txt-diff.hoonnew file+16
@@ -0,0 +1,16 @@
1+::
2+:::: /hoon/txt-diff/mar
3+ ::
4+/? 310
5+|_ txt-diff=(urge:clay cord)
6+::
7+++ grad %noun
8+++ grow
9+ |%
10+ ++ noun txt-diff
11+ --
12+++ grab :: convert from
13+ |%
14+ ++ noun (urge:clay cord) :: make from %noun
15+ --
16+--
example/urbit/mar/txt.hoonnew file+275
@@ -0,0 +1,275 @@
1+::
2+:::: /hoon/txt/mar
3+ ::
4+/? 310
5+::
6+=, clay
7+=, differ
8+=, format
9+=, mimes:html
10+|_ txt=wain
11+::
12+++ grab :: convert from
13+ |%
14+ ++ mime |=((pair mite octs) (to-wain q.q))
15+ ++ noun wain :: clam from %noun
16+ --
17+++ grow
18+ => v=.
19+ |%
20+ ++ mime => v [/text/plain (as-octs (of-wain txt))]
21+ ++ elem => v ;pre: {(trip (of-wain txt))}
22+ --
23+++ grad
24+ |%
25+ ++ form %txt-diff
26+ ++ diff
27+ |= tyt=wain
28+ ^- (urge cord)
29+ (lusk txt tyt (loss txt tyt))
30+ ::
31+ ++ pact
32+ |= dif=(urge cord)
33+ ~| [%pacting dif]
34+ ^- wain
35+ (lurk txt dif)
36+ ::
37+ ++ join
38+ |= [ali=(urge cord) bob=(urge cord)]
39+ ^- (unit (urge cord))
40+ |^
41+ =. ali (clean ali)
42+ =. bob (clean bob)
43+ |- ^- (unit (urge cord))
44+ ?~ ali `bob
45+ ?~ bob `ali
46+ ?- -.i.ali
47+ %&
48+ ?- -.i.bob
49+ %&
50+ ?: =(p.i.ali p.i.bob)
51+ %+ bind $(ali t.ali, bob t.bob)
52+ |=(cud=(urge cord) [i.ali cud])
53+ ?: (gth p.i.ali p.i.bob)
54+ %+ bind $(p.i.ali (sub p.i.ali p.i.bob), bob t.bob)
55+ |=(cud=(urge cord) [i.bob cud])
56+ %+ bind $(ali t.ali, p.i.bob (sub p.i.bob p.i.ali))
57+ |=(cud=(urge cord) [i.ali cud])
58+ ::
59+ %|
60+ ?: =(p.i.ali (lent p.i.bob))
61+ %+ bind $(ali t.ali, bob t.bob)
62+ |=(cud=(urge cord) [i.bob cud])
63+ ?: (gth p.i.ali (lent p.i.bob))
64+ %+ bind $(p.i.ali (sub p.i.ali (lent p.i.bob)), bob t.bob)
65+ |=(cud=(urge cord) [i.bob cud])
66+ ~
67+ ==
68+ ::
69+ %|
70+ ?- -.i.bob
71+ %|
72+ ?. =(i.ali i.bob)
73+ ~
74+ %+ bind $(ali t.ali, bob t.bob)
75+ |=(cud=(urge cord) [i.ali cud])
76+ ::
77+ %&
78+ ?: =(p.i.bob (lent p.i.ali))
79+ %+ bind $(ali t.ali, bob t.bob)
80+ |=(cud=(urge cord) [i.ali cud])
81+ ?: (gth p.i.bob (lent p.i.ali))
82+ %+ bind $(ali t.ali, p.i.bob (sub p.i.bob (lent p.i.ali)))
83+ |=(cud=(urge cord) [i.ali cud])
84+ ~
85+ ==
86+ ==
87+ ++ clean :: clean
88+ |= wig=(urge cord)
89+ ^- (urge cord)
90+ ?~ wig ~
91+ ?~ t.wig wig
92+ ?: ?=(%& -.i.wig)
93+ ?: ?=(%& -.i.t.wig)
94+ $(wig [[%& (add p.i.wig p.i.t.wig)] t.t.wig])
95+ [i.wig $(wig t.wig)]
96+ ?: ?=(%| -.i.t.wig)
97+ $(wig [[%| (welp p.i.wig p.i.t.wig) (welp q.i.wig q.i.t.wig)] t.t.wig])
98+ [i.wig $(wig t.wig)]
99+ --
100+ ::
101+ ++ mash
102+ |= $: [als=ship ald=desk ali=(urge cord)]
103+ [bos=ship bod=desk bob=(urge cord)]
104+ ==
105+ ^- (urge cord)
106+ |^
107+ =. ali (clean ali)
108+ =. bob (clean bob)
109+ |- ^- (urge cord)
110+ ?~ ali bob
111+ ?~ bob ali
112+ ?- -.i.ali
113+ %&
114+ ?- -.i.bob
115+ %&
116+ ?: =(p.i.ali p.i.bob)
117+ [i.ali $(ali t.ali, bob t.bob)]
118+ ?: (gth p.i.ali p.i.bob)
119+ [i.bob $(p.i.ali (sub p.i.ali p.i.bob), bob t.bob)]
120+ [i.ali $(ali t.ali, p.i.bob (sub p.i.bob p.i.ali))]
121+ ::
122+ %|
123+ ?: =(p.i.ali (lent p.i.bob))
124+ [i.bob $(ali t.ali, bob t.bob)]
125+ ?: (gth p.i.ali (lent p.i.bob))
126+ [i.bob $(p.i.ali (sub p.i.ali (lent p.i.bob)), bob t.bob)]
127+ =/ [fic=(unce cord) ali=(urge cord) bob=(urge cord)]
128+ (resolve ali bob)
129+ [fic $(ali ali, bob bob)]
130+ :: ~ :: here, alice is good for a while, but not for the whole
131+ == :: length of bob's changes
132+ ::
133+ %|
134+ ?- -.i.bob
135+ %|
136+ =/ [fic=(unce cord) ali=(urge cord) bob=(urge cord)]
137+ (resolve ali bob)
138+ [fic $(ali ali, bob bob)]
139+ ::
140+ %&
141+ ?: =(p.i.bob (lent p.i.ali))
142+ [i.ali $(ali t.ali, bob t.bob)]
143+ ?: (gth p.i.bob (lent p.i.ali))
144+ [i.ali $(ali t.ali, p.i.bob (sub p.i.bob (lent p.i.ali)))]
145+ =/ [fic=(unce cord) ali=(urge cord) bob=(urge cord)]
146+ (resolve ali bob)
147+ [fic $(ali ali, bob bob)]
148+ ==
149+ ==
150+ ::
151+ ++ annotate :: annotate conflict
152+ |= $: ali=(list @t)
153+ bob=(list @t)
154+ bas=(list @t)
155+ ==
156+ ^- (list @t)
157+ %- zing
158+ ^- (list (list @t))
159+ %- flop
160+ ^- (list (list @t))
161+ :- :_ ~
162+ %^ cat 3 '<<<<<<<<<<<<'
163+ %^ cat 3 ' '
164+ %^ cat 3 `@t`(scot %p bos)
165+ %^ cat 3 '/'
166+ bod
167+
168+ :- bob
169+ :- ~['------------']
170+ :- bas
171+ :- ~['++++++++++++']
172+ :- ali
173+ :- :_ ~
174+ %^ cat 3 '>>>>>>>>>>>>'
175+ %^ cat 3 ' '
176+ %^ cat 3 `@t`(scot %p als)
177+ %^ cat 3 '/'
178+ ald
179+ ~
180+ ::
181+ ++ clean :: clean
182+ |= wig=(urge cord)
183+ ^- (urge cord)
184+ ?~ wig ~
185+ ?~ t.wig wig
186+ ?: ?=(%& -.i.wig)
187+ ?: ?=(%& -.i.t.wig)
188+ $(wig [[%& (add p.i.wig p.i.t.wig)] t.t.wig])
189+ [i.wig $(wig t.wig)]
190+ ?: ?=(%| -.i.t.wig)
191+ $(wig [[%| (welp p.i.wig p.i.t.wig) (welp q.i.wig q.i.t.wig)] t.t.wig])
192+ [i.wig $(wig t.wig)]
193+ ::
194+ ++ resolve
195+ |= [ali=(urge cord) bob=(urge cord)]
196+ ^- [fic=[%| p=(list cord) q=(list cord)] ali=(urge cord) bob=(urge cord)]
197+ =- [[%| bac (annotate alc boc bac)] ali bob]
198+ |- ^- $: $: bac=(list cord)
199+ alc=(list cord)
200+ boc=(list cord)
201+ ==
202+ ali=(urge cord)
203+ bob=(urge cord)
204+ ==
205+ ?~ ali [[~ ~ ~] ali bob]
206+ ?~ bob [[~ ~ ~] ali bob]
207+ ?- -.i.ali
208+ %&
209+ ?- -.i.bob
210+ %& [[~ ~ ~] ali bob] :: no conflict
211+ %|
212+ =+ lob=(lent p.i.bob)
213+ ?: =(lob p.i.ali)
214+ [[p.i.bob p.i.bob q.i.bob] t.ali t.bob]
215+ ?: (lth lob p.i.ali)
216+ [[p.i.bob p.i.bob q.i.bob] [[%& (sub p.i.ali lob)] t.ali] t.bob]
217+ =+ wat=(scag (sub lob p.i.ali) p.i.bob)
218+ =+ ^= res
219+ %= $
220+ ali t.ali
221+ bob [[%| (scag (sub lob p.i.ali) p.i.bob) ~] t.bob]
222+ ==
223+ :* :* (welp bac.res wat)
224+ (welp alc.res wat)
225+ (welp boc.res q.i.bob)
226+ ==
227+ ali.res
228+ bob.res
229+ ==
230+ ==
231+ ::
232+ %|
233+ ?- -.i.bob
234+ %&
235+ =+ loa=(lent p.i.ali)
236+ ?: =(loa p.i.bob)
237+ [[p.i.ali q.i.ali p.i.ali] t.ali t.bob]
238+ ?: (lth loa p.i.bob)
239+ [[p.i.ali q.i.ali p.i.ali] t.ali [[%& (sub p.i.bob loa)] t.bob]]
240+ =+ wat=(slag (sub loa p.i.bob) p.i.ali)
241+ =+ ^= res
242+ %= $
243+ ali [[%| (scag (sub loa p.i.bob) p.i.ali) ~] t.ali]
244+ bob t.bob
245+ ==
246+ :* :* (welp bac.res wat)
247+ (welp alc.res q.i.ali)
248+ (welp boc.res wat)
249+ ==
250+ ali.res
251+ bob.res
252+ ==
253+ ::
254+ %|
255+ =+ loa=(lent p.i.ali)
256+ =+ lob=(lent p.i.bob)
257+ ?: =(loa lob)
258+ [[p.i.ali q.i.ali q.i.bob] t.ali t.bob]
259+ =+ ^= res
260+ ?: (gth loa lob)
261+ $(ali [[%| (scag (sub loa lob) p.i.ali) ~] t.ali], bob t.bob)
262+ ~& [%scagging loa=loa pibob=p.i.bob slag=(scag loa p.i.bob)]
263+ $(ali t.ali, bob [[%| (scag (sub lob loa) p.i.bob) ~] t.bob])
264+ :* :* (welp bac.res ?:((gth loa lob) p.i.bob p.i.ali))
265+ (welp alc.res q.i.ali)
266+ (welp boc.res q.i.bob)
267+ ==
268+ ali.res
269+ bob.res
270+ ==
271+ ==
272+ ==
273+ --
274+ --
275+--
example/urbit/sur/docket.hoonnew file+82
@@ -0,0 +1,82 @@
1+|%
2+::
3++$ version
4+ [major=@ud minor=@ud patch=@ud]
5+::
6++$ glob (map path mime)
7+::
8++$ url cord
9+:: $glob-location: How to retrieve a glob
10+::
11++$ glob-reference
12+ [hash=@uvH location=glob-location]
13+::
14++$ glob-location
15+ $% [%http =url]
16+ [%ames =ship]
17+ ==
18+:: $href: Where a tile links to
19+::
20++$ href
21+ $% [%glob base=term =glob-reference]
22+ [%site =path]
23+ ==
24+:: $chad: State of a docket
25+::
26++$ chad
27+ $~ [%install ~]
28+ $% :: Done
29+ [%glob =glob]
30+ [%site ~]
31+ :: Waiting
32+ [%install ~]
33+ [%suspend glob=(unit glob)]
34+ :: Error
35+ [%hung err=cord]
36+ ==
37+::
38+:: $charge: A realized $docket
39+::
40++$ charge
41+ $: =docket
42+ =chad
43+ ==
44+::
45+:: $clause: A key and value, as part of a docket
46+::
47+:: Only used to parse $docket
48+::
49++$ clause
50+ $% [%title title=@t]
51+ [%info info=@t]
52+ [%color color=@ux]
53+ [%glob-http url=cord hash=@uvH]
54+ [%glob-ames =ship hash=@uvH]
55+ [%image =url]
56+ [%site =path]
57+ [%base base=term]
58+ [%version =version]
59+ [%website website=url]
60+ [%license license=cord]
61+ ==
62+::
63+:: $docket: A description of JS bundles for a desk
64+::
65++$ docket
66+ $: %1
67+ title=@t
68+ info=@t
69+ color=@ux
70+ =href
71+ image=(unit url)
72+ =version
73+ website=url
74+ license=cord
75+ ==
76+::
77++$ charge-update
78+ $% [%initial initial=(map desk charge)]
79+ [%add-charge =desk =charge]
80+ [%del-charge =desk]
81+ ==
82+--
example/urbit/sur/journal.hoonnew file+19
@@ -0,0 +1,19 @@
1+|%
2++$ id @
3++$ txt @t
4++$ action
5+ $% [%add =id =txt]
6+ [%edit =id =txt]
7+ [%del =id]
8+ ==
9++$ entry [=id =txt]
10++$ logged (pair @ action)
11++$ update
12+ %+ pair @
13+ $% action
14+ [%jrnl list=(list entry)]
15+ [%logs list=(list logged)]
16+ ==
17++$ journal ((mop id txt) gth)
18++$ log ((mop @ action) lth)
19+--
example/urbit/sys.kelvinnew file+1
@@ -0,0 +1 @@
1+[%zuse 413]
res/airlock.jpegnew file

Binary file not shown.

src/BigInt/Bytes.elmnew file+35
@@ -0,0 +1,35 @@
1+module BigInt.Bytes exposing (decode, encode)
2+
3+import BigInt exposing (BigInt, toHexString)
4+import Bytes exposing (Bytes)
5+import Bytes.Extra
6+import Hex.Convert as Hex
7+
8+
9+encode : BigInt -> Bytes
10+encode x =
11+ let
12+ hexString =
13+ toHexString x
14+
15+ paddedHexString =
16+ if modBy 2 (String.length hexString) == 0 then
17+ hexString
18+
19+ else
20+ "0" ++ hexString
21+ in
22+ Hex.toBytes paddedHexString
23+ |> Maybe.map (Bytes.Extra.toByteValues >> List.reverse >> Bytes.Extra.fromByteValues)
24+ |> Maybe.withDefault Bytes.Extra.empty
25+
26+
27+decode : Bytes -> BigInt
28+decode bs =
29+ bs
30+ |> Bytes.Extra.toByteValues
31+ |> List.reverse
32+ |> Bytes.Extra.fromByteValues
33+ |> Hex.toString
34+ |> BigInt.fromHexString
35+ |> Maybe.withDefault (BigInt.fromInt 0)
src/BitWriter.elm+5−5
@@ -14,17 +14,17 @@ import Bytes.Encode as BE
1414
1515empty : BitWriter
1616empty =
17− BitWriter { running = Nothing, collected = BE.sequence [], offset = 0 }
17+ BitWriter { running = Nothing, collected = [], offset = 0 }
1818
1919
2020run : BitWriter -> Bytes
2121run (BitWriter { running, collected }) =
2222 case running of
2323 Nothing ->
24− BE.encode collected
24+ BE.encode (collected |> List.reverse |> BE.sequence)
2525
2626 Just { value } ->
27− BE.sequence [ collected, BE.unsignedInt8 value ] |> BE.encode
27+ BE.sequence (BE.unsignedInt8 value :: collected |> List.reverse) |> BE.encode
2828
2929
3030type BitWriter
@@ -35,7 +35,7 @@ type BitWriter
3535 , length : Int
3636 }
3737 , offset : Int
38− , collected : BE.Encoder
38+ , collected : List BE.Encoder
3939 }
4040
4141
@@ -68,7 +68,7 @@ bit b (BitWriter { running, collected, offset }) =
6868 if length == 7 then
6969 BitWriter
7070 { running = Nothing
71− , collected = BE.sequence [ collected, BE.unsignedInt8 newValue ]
71+ , collected = BE.unsignedInt8 newValue :: collected
7272 , offset = newOffset
7373 }
7474
src/Ur.elmnew file+119
@@ -0,0 +1,119 @@
1+module Ur exposing (logIn, getShipName, scry, scryTask)
2+
3+{-|
4+
5+
6+# Simple requests
7+
8+@docs logIn, getShipName, scry, scryTask
9+
10+-}
11+
12+import Http
13+import Platform exposing (Task)
14+import Task
15+import Ur.Deconstructor as D
16+import Ur.Types exposing (..)
17+
18+
19+{-| Logs into your Urbit at the given root path and password.
20+
21+ logIn "http://localhost:8080" "lidlut-tabwed-pillex-ridrup"
22+
23+-}
24+logIn : String -> String -> Cmd (Result Http.Error ())
25+logIn root password =
26+ Http.riskyRequest
27+ { url = root ++ "/~/login"
28+ , method = "POST"
29+ , headers = []
30+ , timeout = Nothing
31+ , tracker = Nothing
32+ , body =
33+ Http.stringBody
34+ "application/x-www-form-urlencoded; charset=utf-8"
35+ ("password=" ++ password)
36+ , expect = Http.expectWhatever identity
37+ }
38+
39+
40+{-| Queries the name of the current ship.
41+Return strings like `~zod` or `~racfer-hattes`.
42+
43+ getShipName "http://localhost:8080"
44+
45+-}
46+getShipName : String -> Cmd (Result Http.Error Ship)
47+getShipName root =
48+ Http.riskyRequest
49+ { url = root ++ "/~/name"
50+ , method = "GET"
51+ , headers = []
52+ , timeout = Nothing
53+ , tracker = Nothing
54+ , body = Http.emptyBody
55+ , expect = Http.expectString identity
56+ }
57+
58+
59+{-| Scry an agent at some path
60+
61+ scry
62+ { url = "http://localhost:8080"
63+ , agent = "journal"
64+ , path = [ "entries", "all" ]
65+ , error = Noop
66+ , success =
67+ D.cell D.ignore
68+ (D.cell (D.const D.cord "jrnl")
69+ (D.list (D.cell D.bigint D.cord |> D.map (\a b -> ( a, b ))))
70+ |> D.map GotListings
71+ )
72+ }
73+
74+-}
75+scry :
76+ { url : String
77+ , agent : Agent
78+ , path : Path
79+ , error : msg
80+ , success : D.Deconstructor msg
81+ }
82+ -> Cmd msg
83+scry args =
84+ scryTask args |> Task.perform identity
85+
86+
87+{-| Same as `scry` but returns a `Task` instead of a `Cmd`.
88+-}
89+scryTask :
90+ { url : String
91+ , agent : Agent
92+ , path : Path
93+ , error : msg
94+ , success : D.Deconstructor msg
95+ }
96+ -> Task a msg
97+scryTask { url, agent, path, error, success } =
98+ Http.riskyTask
99+ { method = "GET"
100+ , headers = []
101+ , url = url ++ "/~/scry/" ++ agent ++ "/" ++ String.join "/" path ++ ".jam"
102+ , body = Http.emptyBody
103+ , resolver =
104+ Http.bytesResolver
105+ (\resp ->
106+ case resp of
107+ Http.GoodStatus_ _ bytes ->
108+ case D.runBytes success bytes of
109+ Just msg ->
110+ Ok msg
111+
112+ Nothing ->
113+ Ok error
114+
115+ _ ->
116+ Ok error
117+ )
118+ , timeout = Nothing
119+ }
src/Ur/Cmd.elmnew file+52
@@ -0,0 +1,52 @@
1+module Ur.Cmd exposing (Cmd, poke, cmd, none, batch)
2+
3+{-| This module is conceptually similar to `Platform.Cmd`, but also allows you to send Urbit requests.
4+
5+@docs Cmd, poke, cmd, none, batch
6+
7+-}
8+
9+import Ur.Cmd.Internal
10+import Ur.Types exposing (Agent, Mark, Noun, Ship)
11+
12+
13+{-| Like `Cmd` from `Platform.Cmd`, but for Urbit commands.
14+-}
15+type alias Cmd msg =
16+ List (Ur.Cmd.Internal.Cmd msg)
17+
18+
19+{-| A command that does exactly nothing.
20+-}
21+none : Cmd msg
22+none =
23+ []
24+
25+
26+{-| Sends a %poke to a Gall Agent.
27+
28+ poke
29+ { ship = "~zod"
30+ , agent = "journal"
31+ , mark = "journal-action"
32+ , noun = C.cell (C.cord "del") (C.bigint id)
33+ }
34+
35+-}
36+poke : { ship : Ship, agent : Agent, mark : Mark, noun : Noun } -> Cmd msg
37+poke p =
38+ [ Ur.Cmd.Internal.Poke p ]
39+
40+
41+{-| Turns a `Platform.Cmd` command into a `Ur.Cmd` command.
42+-}
43+cmd : Cmd.Cmd msg -> Cmd msg
44+cmd c =
45+ [ Ur.Cmd.Internal.Cmd c ]
46+
47+
48+{-| Batches multiple commands into one. Similar to `Platform.Cmd.batch`.
49+-}
50+batch : List (Cmd msg) -> Cmd msg
51+batch =
52+ List.concat
src/Ur/Cmd/Internal.elmnew file+8
@@ -0,0 +1,8 @@
1+module Ur.Cmd.Internal exposing (Cmd(..))
2+
3+import Ur.Types exposing (Agent, Mark, Noun, Ship)
4+
5+
6+type Cmd msg
7+ = Poke { ship : Ship, agent : Agent, mark : Mark, noun : Noun }
8+ | Cmd (Cmd.Cmd msg)
src/Ur/Constructor.elmnew file+151
@@ -0,0 +1,151 @@
1+module Ur.Constructor exposing
2+ ( cell, sig, bytes
3+ , int, signedInt, bigint
4+ , float32, float64
5+ , cord, tape
6+ , listOf
7+ )
8+
9+{-| Different ways of constructing `Noun` from Elm types.
10+
11+
12+# Primitive constructors
13+
14+@docs cell, sig, bytes
15+
16+
17+# Integers
18+
19+@docs int, signedInt, bigint
20+
21+
22+# Floats
23+
24+@docs float32, float64
25+
26+
27+# String
28+
29+@docs cord, tape
30+
31+
32+# Higher-order constructors
33+
34+@docs listOf
35+
36+-}
37+
38+import BigInt exposing (BigInt)
39+import BigInt.Bytes
40+import Bitwise
41+import Bytes exposing (Bytes, Endianness(..))
42+import Bytes.Encode as BE
43+import Ur.Types exposing (..)
44+
45+
46+{-| Constructs a [Cell](https://developers.urbit.org/reference/glossary/cell) from two Nouns.
47+-}
48+cell : Noun -> Noun -> Noun
49+cell a b =
50+ Cell ( a, b )
51+
52+
53+{-| Constructs an `Atom` from an unsigned 32-bit `Int`.
54+If you pass something other than an unsigned 32-bit integer the behaviour is undefined.
55+-}
56+int : Int -> Noun
57+int i =
58+ Atom
59+ (BE.encode
60+ (if i <= 255 then
61+ BE.unsignedInt8 i
62+
63+ else if i <= 65535 then
64+ BE.unsignedInt16 LE i
65+
66+ else
67+ BE.unsignedInt32 LE i
68+ )
69+ )
70+
71+
72+{-| Constructs an `Atom` from a `BigInt`.
73+-}
74+bigint : BigInt -> Noun
75+bigint x =
76+ Atom (BigInt.Bytes.encode x)
77+
78+
79+{-| Constructs an `Atom` from a signed `Int`.
80+-}
81+signedInt : Int -> Noun
82+signedInt i =
83+ int
84+ (if i < 0 then
85+ negate (Bitwise.shiftLeftBy 1 i) - 1
86+
87+ else
88+ Bitwise.shiftLeftBy 1 i
89+ )
90+
91+
92+{-| Constructs an `Atom` from `Bytes`.
93+-}
94+bytes : Bytes -> Noun
95+bytes =
96+ Atom
97+
98+
99+{-| Constructs a [`cord`](https://developers.urbit.org/reference/glossary/cord), [`knot`](https://developers.urbit.org/guides/additional/strings#knot) or a [`term`](https://developers.urbit.org/guides/additional/strings#term) from a String.
100+-}
101+cord : String -> Noun
102+cord s =
103+ Atom (BE.encode (BE.string s))
104+
105+
106+{-| Constructs a [`tape`](https://developers.urbit.org/reference/glossary/tape) from a `String`.
107+-}
108+tape : String -> Noun
109+tape s =
110+ String.toList s |> List.map String.fromChar |> listOf cord
111+
112+
113+{-| Constructs a `sig`-terminated list.
114+
115+The first argument is a constructor for the elements of the list.
116+
117+The second argument is the list of values to encode.
118+
119+-}
120+listOf : (a -> Noun) -> List a -> Noun
121+listOf encEl list =
122+ case list of
123+ [] ->
124+ sig
125+
126+ x :: xs ->
127+ cell (encEl x) (listOf encEl xs)
128+
129+
130+{-| The [`sig`](https://developers.urbit.org/reference/glossary/tape) value. Also known as `~`.
131+
132+It is equivalent to `0`.
133+
134+-}
135+sig : Noun
136+sig =
137+ Atom (BE.encode (BE.sequence []))
138+
139+
140+{-| Constructs an `Atom` from a 32-bit float.
141+-}
142+float32 : Float -> Noun
143+float32 f =
144+ Atom (BE.encode (BE.float32 LE f))
145+
146+
147+{-| Constructs an `Atom` from a 64-bit float.
148+-}
149+float64 : Float -> Noun
150+float64 f =
151+ Atom (BE.encode (BE.float64 LE f))
src/Ur/Da.elmnew file+33
@@ -0,0 +1,33 @@
1+module Ur.Da exposing (Da, posixToDa)
2+
3+{-|
4+
5+@docs Da, posixToDa
6+
7+-}
8+
9+import BigInt exposing (BigInt)
10+import Time exposing (Posix)
11+
12+
13+{-| Represents a `@da`.
14+-}
15+type alias Da =
16+ BigInt
17+
18+
19+unixEpochStart : BigInt
20+unixEpochStart =
21+ BigInt.fromIntString "170141184475152167957503069145530368000" |> Maybe.withDefault (BigInt.fromInt 0)
22+
23+
24+second : BigInt
25+second =
26+ BigInt.fromIntString "18446744073709551616" |> Maybe.withDefault (BigInt.fromInt 0)
27+
28+
29+{-| Converts `Posix` to a `@da`.
30+-}
31+posixToDa : Posix -> Da
32+posixToDa p =
33+ BigInt.add (BigInt.div (BigInt.mul (BigInt.fromInt (Time.posixToMillis p)) second) (BigInt.fromInt 1000000)) unixEpochStart
src/Ur/Deconstructor.elmnew file+378
@@ -0,0 +1,378 @@
1+module Ur.Deconstructor exposing
2+ ( Deconstructor
3+ , run, runBytes
4+ , cell, list, oneOf, const
5+ , map
6+ , int, signedInt, bigint
7+ , float32, float64
8+ , cord, tape
9+ , bytes, sig, ignore, tar, lazy, debug
10+ )
11+
12+{-| This module provides an API to deconstruct `Noun`s into arbitrary Elm data structures.
13+
14+You would parse a `[%edit @ cord]` like this:
15+
16+ type alias Edit = {id: Int, text : String}
17+
18+ (D.cell (D.const D.cord "edit") (D.cell D.bigint D.cord)) |> D.map (\( (), ( id, txt ) ) -> Edit id txt)
19+
20+@docs Deconstructor
21+
22+
23+# Running a `Deconstructor`
24+
25+@docs run, runBytes
26+
27+
28+# Higher-order `Deconstructor`s
29+
30+@docs cell, list, oneOf, const
31+
32+
33+# Mapping a `Deconstructor`
34+
35+@docs map
36+
37+
38+# Numbers
39+
40+
41+## `Int`a
42+
43+@docs int, signedInt, bigint
44+
45+
46+## `Float`s
47+
48+@docs float32, float64
49+
50+
51+# `String`s
52+
53+@docs cord, tape
54+
55+
56+# Miscellaneous
57+
58+@docs bytes, sig, ignore, tar, lazy, debug
59+
60+-}
61+
62+import BigInt exposing (BigInt)
63+import BigInt.Bytes
64+import Bitwise
65+import Bytes exposing (Bytes, Endianness(..))
66+import Bytes.Decode as BD
67+import Bytes.Encode as BE
68+import Bytes.Extra
69+import Ur.Jam exposing (cue, isSig)
70+import Ur.Types exposing (..)
71+
72+
73+{-| A `Noun` deconstructor.
74+-}
75+type alias Deconstructor a =
76+ Noun -> Maybe a
77+
78+
79+{-| Executes a `Deconstructor` on a `Noun`.
80+-}
81+run : Deconstructor a -> Noun -> Maybe a
82+run f noun =
83+ f noun
84+
85+
86+{-| Executes a deconstructor on a `jam`med `Noun`.
87+-}
88+runBytes : Deconstructor a -> Bytes -> Maybe a
89+runBytes f bs =
90+ cue bs |> Maybe.andThen f
91+
92+
93+{-| Asserts that the value should be exactly equal to the second argument.
94+
95+The first argument is a `Deconstructor` for the given type.
96+
97+The second argument is the value to compare with.
98+
99+This is useful to match on `term`s when there are multiple possible cases in a head-tagged union.
100+
101+-}
102+const : Deconstructor a -> a -> Deconstructor ()
103+const f value noun =
104+ case f noun of
105+ Just a ->
106+ if a == value then
107+ Just ()
108+
109+ else
110+ Nothing
111+
112+ Nothing ->
113+ Nothing
114+
115+
116+{-| Extracts a `BigInt`.
117+-}
118+bigint : Deconstructor BigInt
119+bigint x =
120+ case x of
121+ Atom bs ->
122+ BigInt.Bytes.decode bs |> Just
123+
124+ Cell _ ->
125+ Nothing
126+
127+
128+{-| Extracts a 32-bit signed `Int`.
129+
130+If the `Atom` is larger than 32 bits the the behaviour is undefined.
131+
132+-}
133+signedInt : Deconstructor Int
134+signedInt =
135+ int
136+ |> map
137+ (\i ->
138+ if Bitwise.and 1 i == 1 then
139+ -(Bitwise.shiftRightBy 1 (1 + i))
140+
141+ else
142+ Bitwise.shiftRightBy 1 i
143+ )
144+
145+
146+{-| Extracts a 32-bit `Float`.
147+-}
148+float32 : Deconstructor Float
149+float32 x =
150+ case x of
151+ Atom bs ->
152+ BD.decode (BD.float32 LE) bs
153+
154+ Cell _ ->
155+ Nothing
156+
157+
158+{-| Extracts a 64-bit `Float`.
159+-}
160+float64 : Deconstructor Float
161+float64 x =
162+ case x of
163+ Atom bs ->
164+ BD.decode (BD.float64 LE) bs
165+
166+ Cell _ ->
167+ Nothing
168+
169+
170+{-| Extracts the raw `Byte`s of an `Atom`.
171+-}
172+bytes : Deconstructor Bytes
173+bytes n =
174+ case n of
175+ Atom bs ->
176+ Just bs
177+
178+ Cell _ ->
179+ Nothing
180+
181+
182+{-| Asserts the the `Atom` should be exactly `sig` (`~`).
183+-}
184+sig : Deconstructor ()
185+sig n =
186+ case n of
187+ Atom b ->
188+ if (Bytes.Extra.toByteValues b |> List.filter (\x -> x /= 0)) == [] then
189+ Just ()
190+
191+ else
192+ Nothing
193+
194+ Cell _ ->
195+ Nothing
196+
197+
198+{-| Extracts a sig-terminated list of arbitrary elements.
199+
200+The first argument is a `Deconstructor` of the elements of the list.
201+
202+-}
203+list : Deconstructor a -> Deconstructor (List a)
204+list f n =
205+ let
206+ go n_ =
207+ case n_ of
208+ Atom _ ->
209+ Just []
210+
211+ Cell ( el, tail ) ->
212+ Maybe.map2 (\a b -> a :: b) (f el) (go tail)
213+ in
214+ go n
215+
216+
217+alt : Deconstructor a -> Deconstructor a -> Deconstructor a
218+alt f g n =
219+ case f n of
220+ Just x ->
221+ Just x
222+
223+ Nothing ->
224+ g n
225+
226+
227+{-| Try to execute all of the `Deconstructor`s in order until one succeeds.
228+
229+This is especially useful for deconstructing head-tagged unions from Hoon.
230+
231+-}
232+oneOf : List (Deconstructor a) -> Deconstructor a
233+oneOf l =
234+ case l of
235+ [] ->
236+ always Nothing
237+
238+ x :: xs ->
239+ alt x (oneOf xs)
240+
241+
242+{-| Ignore any value.
243+
244+This is useful when you have a value you don't care about. `ignore` allows you to just skip the value.
245+
246+-}
247+ignore : Deconstructor ()
248+ignore =
249+ always (Just ())
250+
251+
252+{-| Extracts a [`Cell`](https://developers.urbit.org/reference/glossary/cell) (pair) of two arbitrary values.
253+
254+Accepts two arbitrary `Deconstructor`s that form a Cell.
255+
256+-}
257+cell : Deconstructor a -> Deconstructor b -> Deconstructor ( a, b )
258+cell l r noun =
259+ case noun of
260+ Cell ( lhs, rhs ) ->
261+ l lhs |> Maybe.andThen (\a -> r rhs |> Maybe.map (\b -> ( a, b )))
262+
263+ Atom _ ->
264+ Nothing
265+
266+
267+{-| Maps (applies) a function to all of the values deconstructed.
268+
269+This is useful when you want to create a data type with extracted values as fields.
270+
271+-}
272+lazy : (() -> Deconstructor a) -> Deconstructor a
273+lazy f noun =
274+ f () noun
275+
276+
277+{-| Maps over the result of the deconstructor.
278+-}
279+map : (a -> b) -> Deconstructor a -> Deconstructor b
280+map g f noun =
281+ f noun |> Maybe.map g
282+
283+
284+{-| Extracts a `cord`.
285+-}
286+cord : Deconstructor String
287+cord x =
288+ case x of
289+ Atom bs ->
290+ BD.decode (BD.string (Bytes.width bs)) bs
291+
292+ Cell _ ->
293+ Nothing
294+
295+
296+{-| Extracts a `tape`.
297+-}
298+tape : Deconstructor String
299+tape =
300+ list cord |> map String.concat
301+
302+
303+{-| Extracts a 32-bit unsigned `Int`.
304+
305+If the `Atom` is larger than 32 bits the the behaviour is undefined.
306+
307+-}
308+int : Deconstructor Int
309+int x =
310+ case x of
311+ Atom bs ->
312+ case Bytes.width bs of
313+ 1 ->
314+ BD.decode BD.unsignedInt8 bs
315+
316+ 2 ->
317+ BD.decode (BD.unsignedInt16 LE) bs
318+
319+ 3 ->
320+ BD.decode (BD.unsignedInt32 LE) (BE.encode (BE.sequence [ BE.bytes bs, BE.unsignedInt8 0 ]))
321+
322+ 4 ->
323+ BD.decode (BD.unsignedInt32 LE) bs
324+
325+ _ ->
326+ Nothing
327+
328+ Cell _ ->
329+ Nothing
330+
331+
332+{-| Extract the raw `Noun`.
333+
334+Always succeeds.
335+
336+-}
337+tar : Deconstructor Noun
338+tar =
339+ Just
340+
341+
342+{-| Debug a deconstructor.
343+If the deconstructor from the second argument fails then the failed nock will be printed to the console.
344+-}
345+debug : (String -> String) -> Deconstructor a -> Deconstructor a
346+debug log f noun =
347+ case f noun of
348+ Just x ->
349+ Just x
350+
351+ Nothing ->
352+ let
353+ _ =
354+ log (prettyNoun noun)
355+ in
356+ Nothing
357+
358+
359+prettyNoun : Noun -> String
360+prettyNoun noun =
361+ let
362+ go isRhs n =
363+ case n of
364+ Atom a ->
365+ if isSig a then
366+ "~"
367+
368+ else
369+ "@"
370+
371+ Cell ( lhs, rhs ) ->
372+ if isRhs then
373+ go False lhs ++ " " ++ go True rhs
374+
375+ else
376+ "[" ++ go False lhs ++ " " ++ go True rhs ++ "]"
377+ in
378+ go False noun
src/Urbit.elm → src/Ur/Jam.elmrenamed+15−16
@@ -1,10 +1,13 @@
1−module Urbit exposing
2− ( Noun(..)
3− , cue
4− , jam
5− , mat
6− , rub
7− )
1+module Ur.Jam exposing (..)
2+
3+{-|
4+
5+
6+# Encoding and decoding Nouns
7+
8+@docs jam, cue
9+
10+-}
811
912import BitParser as BP exposing (BitParser)
1013import BitWriter as BW exposing (BitWriter)
@@ -13,17 +16,11 @@ import Bytes exposing (Bytes)
1316import Bytes.Extra as Bytes
1417import Dict exposing (Dict)
1518import List.Extra as List
19+import Ur.Types exposing (..)
1620
1721
18−type Noun
19− = Cell ( Noun, Noun )
20− | Atom Bytes
21−
22−
23−type alias Atom =
24− Bytes
25−
26−
22+{-| `jam` encodes arbitrary Nouns into an Atom.
23+-}
2724jam : Noun -> Bytes
2825jam n =
2926 BW.run (jamWriter n BW.empty)
@@ -50,6 +47,8 @@ jamWriter noun writer =
5047 |> jamWriter b
5148
5249
50+{-| Decodes a `jam`med Noun (Atom) into the original Noun.
51+-}
5352cue : Bytes -> Maybe Noun
5453cue =
5554 BP.run (cueParser Dict.empty) >> Maybe.map Tuple.second
src/Ur/NounDiff.elmnew file+115
@@ -0,0 +1,115 @@
1+module Ur.NounDiff exposing (Patch, apply, deconstructPatch)
2+
3+import Bytes.Extra
4+import Dict exposing (Dict)
5+import Ur.Deconstructor as D
6+import Ur.Types exposing (..)
7+
8+
9+type Patch
10+ = PatchCell Patch Patch
11+ | Diff DelDiff InsDiff
12+
13+
14+deconstructPatch : () -> D.Deconstructor Patch
15+deconstructPatch () =
16+ D.oneOf
17+ [ D.cell (D.const D.cord "cell")
18+ (D.cell
19+ (D.lazy deconstructPatch)
20+ (D.lazy deconstructPatch)
21+ )
22+ |> D.map (\( (), ( a, b ) ) -> PatchCell a b)
23+ , D.cell (D.const D.cord "diff") (D.cell (D.lazy deconstructDel) (D.lazy deconstructIns))
24+ |> D.map (\( (), ( a, b ) ) -> Diff a b)
25+ ]
26+
27+
28+apply : Patch -> Noun -> Noun
29+apply patch noun =
30+ case ( patch, noun ) of
31+ ( PatchCell lhs rhs, Cell ( lhs_, rhs_ ) ) ->
32+ Cell ( apply lhs lhs_, apply rhs rhs_ )
33+
34+ ( Diff delDiff insDiff, _ ) ->
35+ ins insDiff (del delDiff noun)
36+
37+ _ ->
38+ noun
39+
40+
41+del : DelDiff -> Noun -> Dict Int Noun
42+del diff noun =
43+ let
44+ go : DelDiff -> Noun -> Dict Int Noun -> Dict Int Noun
45+ go diff_ noun_ dict =
46+ case ( diff_, noun_ ) of
47+ ( Ignore, _ ) ->
48+ dict
49+
50+ ( DelCell lhsDiff rhsDiff, Cell ( lhs, rhs ) ) ->
51+ go lhsDiff lhs (go rhsDiff rhs dict)
52+
53+ ( DelHole hole, _ ) ->
54+ -- There should be a continuity check here.
55+ -- Check that the noun_ is equal to whatever the hole maps to in dict.
56+ -- I don't include it for speed and laziness reasons.
57+ Dict.insert hole noun_ dict
58+
59+ _ ->
60+ dict
61+ in
62+ go diff noun Dict.empty
63+
64+
65+ins : InsDiff -> Dict Int Noun -> Noun
66+ins diff dict =
67+ case diff of
68+ InsAtom a ->
69+ Atom a
70+
71+ InsHole hole ->
72+ Dict.get hole dict |> Maybe.withDefault (Atom Bytes.Extra.empty)
73+
74+ InsCell lhs rhs ->
75+ Cell ( ins lhs dict, ins rhs dict )
76+
77+
78+type DelDiff
79+ = Ignore
80+ | DelHole Int
81+ | DelCell DelDiff DelDiff
82+
83+
84+deconstructDel : () -> D.Deconstructor DelDiff
85+deconstructDel () =
86+ D.oneOf
87+ [ D.cell (D.const D.cord "ignore") D.ignore |> D.map (\( (), () ) -> Ignore)
88+ , D.cell (D.const D.cord "hole") D.int |> D.map (\( (), x ) -> DelHole x)
89+ , D.cell (D.const D.cord "cell")
90+ (D.cell
91+ (D.lazy deconstructDel)
92+ (D.lazy deconstructDel)
93+ )
94+ |> D.map (\( (), ( lhs, rhs ) ) -> DelCell lhs rhs)
95+ ]
96+
97+
98+type InsDiff
99+ = InsAtom Atom
100+ | InsHole Int
101+ | InsCell InsDiff InsDiff
102+
103+
104+deconstructIns : () -> D.Deconstructor InsDiff
105+deconstructIns () =
106+ D.oneOf
107+ [ D.cell (D.const D.cord "hole") D.int |> D.map (\( (), x ) -> InsHole x)
108+ , D.cell (D.const D.cord "atom") D.bytes |> D.map (\( (), bs ) -> InsAtom bs)
109+ , D.cell (D.const D.cord "cell")
110+ (D.cell
111+ (D.lazy deconstructIns)
112+ (D.lazy deconstructIns)
113+ )
114+ |> D.map (\( (), ( a, b ) ) -> InsCell a b)
115+ ]
src/Ur/Phonemic.elmnew file+25
@@ -0,0 +1,25 @@
1+module Ur.Phonemic exposing (fromString)
2+
3+{-|
4+
5+@docs fromString
6+
7+-}
8+
9+import BigInt.Bytes
10+import Bytes.Extra
11+import Ur.Types exposing (Atom, Noun(..), Ship)
12+import Urbit.Encoding.Atom exposing (toBigInt)
13+import Urbit.Encoding.Phonemic exposing (..)
14+
15+
16+{-| Converts a ship name like `~zod` into an `Atom`.
17+-}
18+fromString : Ship -> Atom
19+fromString s =
20+ case fromPatp s of
21+ Ok atom ->
22+ toBigInt atom |> BigInt.Bytes.encode
23+
24+ Err _ ->
25+ Bytes.Extra.empty
src/Ur/Requests.elmnew file+118
@@ -0,0 +1,118 @@
1+module Ur.Requests exposing
2+ ( EventId
3+ , UrbitRequest(..)
4+ , send
5+ , sendTask
6+ , tag
7+ , toNoun
8+ )
9+
10+import Http
11+import Task exposing (Task)
12+import Ur exposing (..)
13+import Ur.Constructor as C
14+import Ur.Jam exposing (jam)
15+import Ur.Phonemic
16+import Ur.Types exposing (..)
17+import Ur.Uw
18+
19+
20+type UrbitRequest
21+ = Subscribe ( Ship, Agent, Path )
22+ | Unsubscribe EventId
23+ | Poke { ship : Ship, agent : Agent, mark : Mark, noun : Noun }
24+ | Ack Int
25+
26+
27+tag : EventId -> List x -> ( EventId, List ( EventId, x ) )
28+tag eventId reqs =
29+ case reqs of
30+ [] ->
31+ ( eventId, [] )
32+
33+ req :: rest ->
34+ tag (eventId + 1) rest |> Tuple.mapSecond (\xs -> ( eventId, req ) :: xs)
35+
36+
37+send :
38+ { url : String
39+ , error : msg
40+ , success : msg
41+ , requests : List ( EventId, UrbitRequest )
42+ }
43+ -> Cmd msg
44+send inp =
45+ sendTask inp |> Task.perform identity
46+
47+
48+{-| `requests` should the result of calling `tag`
49+-}
50+sendTask :
51+ { url : String
52+ , error : msg
53+ , success : msg
54+ , requests : List ( EventId, UrbitRequest )
55+ }
56+ -> Task a msg
57+sendTask { url, error, success, requests } =
58+ if List.isEmpty requests then
59+ Task.succeed success
60+
61+ else
62+ Http.riskyTask
63+ { method = "PUT"
64+ , headers = []
65+ , url = url
66+ , body =
67+ requests
68+ |> List.map (uncurry toNoun)
69+ |> C.listOf identity
70+ |> jam
71+ |> Ur.Uw.encode
72+ |> Http.stringBody "application/x-urb-jam"
73+ , resolver =
74+ Http.bytesResolver
75+ (\resp ->
76+ case resp of
77+ Http.GoodStatus_ _ _ ->
78+ Ok success
79+
80+ _ ->
81+ Ok error
82+ )
83+ , timeout = Nothing
84+ }
85+
86+
87+type alias EventId =
88+ Int
89+
90+
91+toNoun : EventId -> UrbitRequest -> Noun
92+toNoun eventId req =
93+ case req of
94+ Subscribe ( ship, app, path ) ->
95+ C.cell (C.cord "subscribe") <|
96+ C.cell (C.int eventId) <|
97+ C.cell (Atom (Ur.Phonemic.fromString ship)) <|
98+ C.cell (C.cord app) (C.listOf C.cord path)
99+
100+ Unsubscribe subId ->
101+ C.cell (C.cord "unsubscribe") <|
102+ C.cell (C.int eventId) (C.int subId)
103+
104+ Poke { ship, agent, mark, noun } ->
105+ C.cell (C.cord "poke") <|
106+ C.cell (C.int eventId) <|
107+ C.cell (Atom (Ur.Phonemic.fromString ship)) <|
108+ C.cell (C.cord agent) <|
109+ C.cell (C.cord mark) <|
110+ noun
111+
112+ Ack number ->
113+ C.cell (C.cord "ack") (C.int number)
114+
115+
116+uncurry : (a -> b -> c) -> (( a, b ) -> c)
117+uncurry f ( a, b ) =
118+ f a b
src/Ur/Run.elmnew file+510
@@ -0,0 +1,510 @@
1+module Ur.Run exposing (element, document, application, Program, Model, Msg)
2+
3+{-| This module contains functions to run your application with Urbit integration.
4+
5+@docs element, document, application, Program, Model, Msg
6+
7+-}
8+
9+import Browser exposing (Document, UrlRequest)
10+import Browser.Navigation as Nav
11+import Dict exposing (Dict)
12+import Either exposing (Either(..))
13+import Html exposing (Html)
14+import Json.Decode as JD
15+import Task
16+import Time
17+import Ur.Cmd
18+import Ur.Cmd.Internal
19+import Ur.Constructor as C
20+import Ur.Deconstructor as D
21+import Ur.NounDiff exposing (Patch, deconstructPatch)
22+import Ur.Requests exposing (..)
23+import Ur.Sub
24+import Ur.Sub.Internal
25+import Ur.Types exposing (..)
26+import Ur.Uw
27+import Url exposing (Url)
28+
29+
30+type alias SubDict msg =
31+ Dict
32+ -- (ship, agent, path)
33+ ( String, String, List String )
34+ { deconstructor : D.Deconstructor msg
35+ , number : Int
36+ , sink : Bool
37+ }
38+
39+
40+type alias Flags =
41+ { uid : String }
42+
43+
44+{-| An Urbit wrapper around your application model.
45+-}
46+type alias Model app msg =
47+ { subscriptions : SubDict msg
48+ , subscriptionIntMapping : Dict Int ( String, String, List String )
49+ , app : app
50+ , connected : Bool
51+ , eventId : Int
52+ , flags : Flags
53+ , requestsToRetry : List UrbitRequest
54+ , sinks : Dict Int Noun
55+ }
56+
57+
58+{-| An Urbit wrapper around your application messages.
59+-}
60+type Msg msg
61+ = AppMsg msg
62+ | EventSourceMsg JD.Value
63+ | FailedRequest (List UrbitRequest)
64+ | Noop
65+ | OpenConnection
66+ | NeedsActivation
67+ | RetryRequests
68+
69+
70+{-| A wrapper around your application `Program`.
71+-}
72+type alias Program model msg =
73+ Platform.Program Flags (Model model msg) (Msg msg)
74+
75+
76+{-| The same as `Browser.element` but with urbit stuff added around it.
77+-}
78+element :
79+ { init : ( model, Ur.Cmd.Cmd msg )
80+ , view : model -> Html msg
81+ , update : msg -> model -> ( model, Ur.Cmd.Cmd msg )
82+ , subscriptions : model -> Sub msg
83+ , urbitSubscriptions : model -> Ur.Sub.Sub msg
84+ , createEventSource : String -> Cmd (Msg msg)
85+ , onEventSourceMsg : (JD.Value -> Msg msg) -> Sub (Msg msg)
86+ , urbitUrl : model -> String
87+ }
88+ -> Program model msg
89+element inp =
90+ Browser.element
91+ { init = init inp inp.init
92+ , view = \model -> inp.view model.app |> Html.map AppMsg
93+ , update = update inp
94+ , subscriptions = subscriptions inp
95+ }
96+
97+
98+{-| The same as `Browser.element` but with urbit stuff added around it.
99+-}
100+document :
101+ { init : ( model, Ur.Cmd.Cmd msg )
102+ , view : model -> Document msg
103+ , update : msg -> model -> ( model, Ur.Cmd.Cmd msg )
104+ , subscriptions : model -> Sub msg
105+ , urbitSubscriptions : model -> Ur.Sub.Sub msg
106+ , createEventSource : String -> Cmd (Msg msg)
107+ , onEventSourceMsg : (JD.Value -> Msg msg) -> Sub (Msg msg)
108+ , urbitUrl : model -> String
109+ }
110+ -> Program model msg
111+document inp =
112+ Browser.document
113+ { init = init inp inp.init
114+ , view =
115+ \model ->
116+ inp.view model.app
117+ |> (\{ body, title } -> { title = title, body = body |> List.map (Html.map AppMsg) })
118+ , update = update inp
119+ , subscriptions = subscriptions inp
120+ }
121+
122+
123+{-| The same as `Browser.element` but with urbit stuff added around it.
124+-}
125+application :
126+ { init : Url -> Nav.Key -> ( model, Ur.Cmd.Cmd msg )
127+ , view : model -> Document msg
128+ , update : msg -> model -> ( model, Ur.Cmd.Cmd msg )
129+ , subscriptions : model -> Sub msg
130+ , urbitSubscriptions : model -> Ur.Sub.Sub msg
131+ , onUrlRequest : UrlRequest -> msg
132+ , onUrlChange : Url -> msg
133+ , createEventSource : String -> Cmd (Msg msg)
134+ , onEventSourceMsg : (JD.Value -> Msg msg) -> Sub (Msg msg)
135+ , urbitUrl : model -> String
136+ }
137+ -> Program model msg
138+application inp =
139+ let
140+ { view, onUrlRequest, onUrlChange } =
141+ inp
142+ in
143+ Browser.application
144+ { init = \flags url key -> init inp (inp.init url key) flags
145+ , view =
146+ \model ->
147+ view model.app
148+ |> (\{ body, title } -> { title = title, body = body |> List.map (Html.map AppMsg) })
149+ , update = update inp
150+ , subscriptions = subscriptions inp
151+ , onUrlRequest = \req -> onUrlRequest req |> AppMsg
152+ , onUrlChange = \url -> onUrlChange url |> AppMsg
153+ }
154+
155+
156+update :
157+ { r
158+ | update : msg -> app -> ( app, Ur.Cmd.Cmd msg )
159+ , createEventSource : String -> Cmd (Msg msg)
160+ , urbitUrl : app -> String
161+ , urbitSubscriptions : app -> Ur.Sub.Sub msg
162+ }
163+ -> Msg msg
164+ -> Model app msg
165+ -> ( Model app msg, Cmd (Msg msg) )
166+update inp msg model =
167+ let
168+ url =
169+ inp.urbitUrl model.app ++ "/~/channel/" ++ model.flags.uid
170+ in
171+ case msg of
172+ AppMsg msg_ ->
173+ let
174+ ( appModel, appCmds ) =
175+ inp.update msg_ model.app
176+
177+ subsResult =
178+ processUrSubs
179+ model.eventId
180+ model.subscriptions
181+ (inp.urbitSubscriptions appModel |> (\(Ur.Sub.Internal.Sub x) -> x))
182+
183+ ( eventId_, cmds, urReqs ) =
184+ processCmd subsResult.eventId appCmds
185+ in
186+ ( { model
187+ | app = appModel
188+ , eventId = eventId_
189+ , subscriptions = subsResult.subscriptions
190+ , subscriptionIntMapping = model.subscriptionIntMapping |> Dict.union subsResult.subscriptionIntMapping
191+ }
192+ , Cmd.batch
193+ [ cmds
194+ , Ur.Requests.send
195+ { url = url
196+ , error = Noop
197+ , success = Noop
198+ , requests = urReqs
199+ }
200+ , send
201+ { requests = subsResult.subscriptionRequests
202+ , url = url
203+ , error = subsResult.subscriptionRequests |> List.map (\( _, x ) -> x) |> FailedRequest
204+ , success = Noop
205+ }
206+ ]
207+ )
208+
209+ EventSourceMsg value ->
210+ case JD.decodeValue (JD.field "message" JD.string) value of
211+ Ok string ->
212+ case
213+ D.runBytes
214+ (D.cell D.int (D.cell D.cord D.tar))
215+ (Ur.Uw.decode string)
216+ of
217+ Just ( messageId, ( messageType, rest ) ) ->
218+ let
219+ ( eventId, ackReqs ) =
220+ tag model.eventId [ Ack messageId ]
221+
222+ ackCmd =
223+ send
224+ { requests = ackReqs
225+ , url = url
226+ , success = Noop
227+ , error = ackReqs |> List.map (\( _, x ) -> x) |> FailedRequest
228+ }
229+
230+ model_ =
231+ { model | eventId = eventId }
232+ in
233+ case messageType of
234+ "watch-ack" ->
235+ -- Not sure what to do. Assume things are fine.
236+ ( model_, ackCmd )
237+
238+ "poke-ack" ->
239+ -- Not sure what to do.
240+ ( model_, ackCmd )
241+
242+ "fact" ->
243+ case
244+ Dict.get messageId model.subscriptionIntMapping
245+ |> Maybe.andThen (\key -> Dict.get key model.subscriptions)
246+ of
247+ Just { deconstructor, sink } ->
248+ if sink then
249+ case D.run (D.cell D.ignore (D.cell D.ignore deconstructSink) |> D.map (\( (), ( (), s ) ) -> s)) rest of
250+ Just (Flush noun) ->
251+ ( { model | sinks = Dict.insert messageId noun model.sinks }
252+ , case D.run deconstructor noun of
253+ Just subMsg ->
254+ pureCmd (AppMsg subMsg)
255+
256+ -- Got garbage
257+ Nothing ->
258+ Cmd.none
259+ )
260+
261+ Just (Drain patch) ->
262+ case Dict.get messageId model.sinks of
263+ Just oldNoun ->
264+ let
265+ newNoun =
266+ Ur.NounDiff.apply patch oldNoun
267+ in
268+ ( { model | sinks = Dict.insert messageId newNoun model.sinks }
269+ , case D.run deconstructor newNoun of
270+ Just subMsg ->
271+ pureCmd (AppMsg subMsg)
272+
273+ Nothing ->
274+ Cmd.none
275+ )
276+
277+ Nothing ->
278+ ( model, Cmd.none )
279+
280+ -- Got garbage
281+ Nothing ->
282+ ( model, Cmd.none )
283+
284+ else
285+ case D.run (D.cell D.ignore (D.cell D.ignore deconstructor) |> D.map (\( (), ( (), m ) ) -> m)) rest of
286+ Just subMsg ->
287+ ( model_, pureCmd (AppMsg subMsg) )
288+
289+ -- Got gargbage
290+ Nothing ->
291+ ( model_, ackCmd )
292+
293+ -- Got a fact for a subscription we do not hold
294+ Nothing ->
295+ ( model_, ackCmd )
296+
297+ _ ->
298+ ( model_, ackCmd )
299+
300+ -- got something we don't expect
301+ Nothing ->
302+ ( model, Cmd.none )
303+
304+ Err _ ->
305+ case JD.decodeValue (JD.field "error" JD.value) value of
306+ Ok _ ->
307+ ( { model | connected = False }, Cmd.none )
308+
309+ Err _ ->
310+ -- we got garbage
311+ ( model, Cmd.none )
312+
313+ NeedsActivation ->
314+ let
315+ ( eventId, reqs ) =
316+ [ Poke { ship = "", agent = "hood", mark = "helm-hi", noun = C.cord "Opening airlock!" } ]
317+ |> tag model.eventId
318+ in
319+ ( { model | eventId = eventId }
320+ , send { url = url, requests = reqs, success = OpenConnection, error = Noop }
321+ )
322+
323+ Noop ->
324+ ( model, Cmd.none )
325+
326+ FailedRequest reqs ->
327+ ( { model | requestsToRetry = reqs ++ model.requestsToRetry }, Cmd.none )
328+
329+ OpenConnection ->
330+ ( { model | connected = True }, inp.createEventSource url )
331+
332+ RetryRequests ->
333+ let
334+ ( eventId, reqs ) =
335+ model.requestsToRetry |> tag model.eventId
336+ in
337+ ( { model | eventId = eventId, requestsToRetry = [] }
338+ , send
339+ { url = url
340+ , error = FailedRequest model.requestsToRetry
341+ , success = Noop
342+ , requests = reqs
343+ }
344+ )
345+
346+
347+type Sink
348+ = Flush Noun
349+ | Drain Patch
350+
351+
352+deconstructSink : D.Deconstructor Sink
353+deconstructSink =
354+ D.oneOf
355+ [ D.cell (D.const D.cord "flush") D.tar |> D.map (\( (), n ) -> Flush n)
356+ , D.cell (D.const D.cord "drain") (D.lazy deconstructPatch) |> D.map (\( (), patch ) -> Drain patch)
357+ ]
358+
359+
360+processCmd : EventId -> Ur.Cmd.Cmd msg -> ( EventId, Cmd (Msg msg), List ( EventId, UrbitRequest ) )
361+processCmd eventId urCmds =
362+ let
363+ ( cmds, reqs ) =
364+ urCmds
365+ |> List.map
366+ (\x ->
367+ case x of
368+ Ur.Cmd.Internal.Cmd cmd ->
369+ cmd |> Cmd.map AppMsg |> Left
370+
371+ Ur.Cmd.Internal.Poke p ->
372+ Ur.Requests.Poke p |> Right
373+ )
374+ |> Either.partition
375+
376+ ( newEventId, urReqs ) =
377+ reqs |> tag eventId
378+ in
379+ ( newEventId, Cmd.batch cmds, urReqs )
380+
381+
382+pureCmd : msg -> Cmd msg
383+pureCmd msg =
384+ Task.succeed msg |> Task.perform identity
385+
386+
387+processUrSubs :
388+ EventId
389+ -> Dict ( Ship, Agent, Path ) { deconstructor : d, number : EventId, sink : Bool }
390+ -> Dict ( Ship, Agent, Path ) { deconstructor : d, sink : Bool }
391+ ->
392+ { subscriptions : Dict ( Ship, Agent, Path ) { deconstructor : d, number : EventId, sink : Bool }
393+ , eventId : EventId
394+ , subscriptionRequests : List ( EventId, UrbitRequest )
395+ , subscriptionIntMapping : Dict EventId ( Ship, Agent, Path )
396+ }
397+processUrSubs eventId existingSubscriptions urbitSubs_ =
398+ let
399+ urbitSubs =
400+ urbitSubs_
401+ |> Dict.map (\_ { deconstructor, sink } -> { deconstructor = deconstructor, sink = sink })
402+
403+ ( eventId_, newSubscriptionActions ) =
404+ Dict.diff urbitSubs existingSubscriptions
405+ |> Dict.toList
406+ |> List.map (\( address, _ ) -> ( Subscribe address, address ))
407+ |> tag eventId
408+
409+ removedSubscriptions =
410+ Dict.diff existingSubscriptions urbitSubs
411+
412+ ( eventId__, removedSubscriptionActions ) =
413+ removedSubscriptions
414+ |> Dict.toList
415+ |> List.map (\( _, { number } ) -> Unsubscribe number)
416+ |> tag eventId_
417+
418+ keyToNumber =
419+ newSubscriptionActions |> List.map (\( a, ( _, b ) ) -> ( b, a )) |> Dict.fromList
420+
421+ newSubscriptions =
422+ Dict.merge
423+ (\_ _ x -> x)
424+ (\key number { deconstructor, sink } ->
425+ Dict.insert key
426+ { deconstructor = deconstructor
427+ , number = number
428+ , sink = sink
429+ }
430+ )
431+ (\_ _ x -> x)
432+ keyToNumber
433+ urbitSubs
434+ Dict.empty
435+ in
436+ { subscriptions = Dict.diff existingSubscriptions removedSubscriptions |> Dict.union newSubscriptions
437+ , subscriptionIntMapping =
438+ newSubscriptions
439+ |> Dict.toList
440+ |> List.map (\( key, { number } ) -> ( number, key ))
441+ |> Dict.fromList
442+ , eventId = eventId__
443+ , subscriptionRequests =
444+ removedSubscriptionActions ++ (newSubscriptionActions |> List.map (\( id, ( req, _ ) ) -> ( id, req )))
445+ }
446+
447+
448+init :
449+ { a | urbitSubscriptions : model -> Ur.Sub.Sub msg, urbitUrl : model -> String }
450+ -> ( model, Ur.Cmd.Cmd msg )
451+ -> Flags
452+ ->
453+ ( Model model msg
454+ , Cmd (Msg msg)
455+ )
456+init inp ( app, appCmds ) flags =
457+ let
458+ subsResult =
459+ processUrSubs
460+ 0
461+ Dict.empty
462+ (inp.urbitSubscriptions app |> (\(Ur.Sub.Internal.Sub x) -> x))
463+
464+ ( eventId_, cmds, urReqs ) =
465+ processCmd subsResult.eventId appCmds
466+
467+ url =
468+ inp.urbitUrl app ++ "/~/channel/" ++ flags.uid
469+ in
470+ ( { subscriptions = subsResult.subscriptions
471+ , sinks = Dict.empty
472+ , subscriptionIntMapping = subsResult.subscriptionIntMapping
473+ , app = app
474+ , connected = False
475+ , eventId = eventId_
476+ , flags = flags
477+ , requestsToRetry = []
478+ }
479+ , [ cmds
480+ , pureCmd NeedsActivation
481+ , send
482+ { requests = urReqs ++ subsResult.subscriptionRequests
483+ , url = url
484+ , error = Noop
485+ , success = Noop
486+ }
487+ ]
488+ |> Cmd.batch
489+ )
490+
491+
492+subscriptions :
493+ { a | subscriptions : b -> Sub msg, onEventSourceMsg : (JD.Value -> Msg c) -> Sub (Msg msg) }
494+ -> { d | app : b, requestsToRetry : List e, connected : Bool }
495+ -> Sub (Msg msg)
496+subscriptions inp model =
497+ Sub.batch
498+ [ inp.subscriptions model.app |> Sub.map AppMsg
499+ , inp.onEventSourceMsg EventSourceMsg
500+ , if List.isEmpty model.requestsToRetry then
501+ Sub.none
502+
503+ else
504+ Time.every 1000 (always RetryRequests)
505+ , if not model.connected then
506+ Time.every 10000 (always NeedsActivation)
507+
508+ else
509+ Sub.none
510+ ]
src/Ur/Sub.elmnew file+61
@@ -0,0 +1,61 @@
1+module Ur.Sub exposing (Sub, subscribe, none, batch, sink)
2+
3+{-| This module is conceptually similar to `Platform.Sub`, but allows you to subscribe to Urbit channels.
4+
5+@docs Sub, subscribe, none, batch, sink
6+
7+-}
8+
9+import Dict
10+import Ur.Deconstructor as D
11+import Ur.Sub.Internal
12+
13+
14+{-| Like `Sub` from `Platform.Sub` but for Urbit subscriptions.
15+-}
16+type alias Sub msg =
17+ Ur.Sub.Internal.Sub msg
18+
19+
20+{-| Creates an Urbit subscription.
21+
22+ subscribe
23+ { ship = ship
24+ , app = "journal"
25+ , path = [ "updates" ]
26+ , deconstructor = decodeJournalUpdate |> D.map GotUpdate
27+ }
28+
29+-}
30+subscribe : { ship : String, app : String, path : List String, deconstructor : D.Deconstructor msg } -> Sub msg
31+subscribe { ship, app, path, deconstructor } =
32+ Dict.singleton ( ship, app, path )
33+ { deconstructor = deconstructor
34+ , sink = False
35+ }
36+ |> Ur.Sub.Internal.Sub
37+
38+
39+{-| Creates a %sink subscription.
40+-}
41+sink : { ship : String, app : String, path : List String, deconstructor : D.Deconstructor msg } -> Sub msg
42+sink { ship, app, path, deconstructor } =
43+ Dict.singleton ( ship, app, path )
44+ { deconstructor = deconstructor
45+ , sink = True
46+ }
47+ |> Ur.Sub.Internal.Sub
48+
49+
50+{-| A subscription that does exactly nothing. (Does not subscribe to anything)
51+-}
52+none : Sub msg
53+none =
54+ Ur.Sub.Internal.Sub Dict.empty
55+
56+
57+{-| Batch multiple subscriptions into one.
58+-}
59+batch : List (Sub msg) -> Sub msg
60+batch subs =
61+ subs |> List.map (\(Ur.Sub.Internal.Sub dict) -> dict) |> List.foldl Dict.union Dict.empty |> Ur.Sub.Internal.Sub
src/Ur/Sub/Internal.elmnew file+16
@@ -0,0 +1,16 @@
1+module Ur.Sub.Internal exposing (Sub(..))
2+
3+import Dict exposing (Dict)
4+import Ur.Deconstructor as D
5+import Ur.Types exposing (Agent, Path, Ship)
6+
7+
8+type Sub msg
9+ = Sub
10+ (Dict
11+ -- key is (ship, app, path)
12+ ( Ship, Agent, Path )
13+ { deconstructor : D.Deconstructor msg
14+ , sink : Bool
15+ }
16+ )
src/Ur/Types.elmnew file+60
@@ -0,0 +1,60 @@
1+module Ur.Types exposing
2+ ( Noun(..), Atom
3+ , Agent, Mark, Path, Ship
4+ )
5+
6+{-|
7+
8+
9+# Nouns
10+
11+@docs Noun, Atom
12+
13+
14+# Types
15+
16+@docs Agent, Mark, Path, Ship
17+
18+-}
19+
20+import Bytes exposing (Bytes)
21+
22+
23+{-| An [Urbit agent](https://developers.urbit.org/reference/glossary/agent) (app) name like `journal` or `groups`.
24+-}
25+type alias Agent =
26+ String
27+
28+
29+{-| An [Urbit subscription path](https://developers.urbit.org/reference/glossary/path).
30+-}
31+type alias Path =
32+ List String
33+
34+
35+{-| An [Urbit mark](https://developers.urbit.org/reference/arvo/clay/marks/marks)
36+-}
37+type alias Mark =
38+ String
39+
40+
41+{-| A ship name like `~zod` or `~racfer-hattes`.
42+
43+Also know as `@p`.
44+
45+-}
46+type alias Ship =
47+ String
48+
49+
50+{-| An [Urbit Noun](https://developers.urbit.org/reference/glossary/noun).
51+-}
52+type Noun
53+ = Cell ( Noun, Noun )
54+ | Atom Atom
55+
56+
57+{-| An [Urbit Atom](https://developers.urbit.org/reference/glossary/atom)
58+-}
59+type alias Atom =
60+ Bytes
src/Ur/Uw.elmnew file+204
@@ -0,0 +1,204 @@
1+module Ur.Uw exposing (decode, encode)
2+
3+{-| This module works with Urbit base-64 encoded strings aka `@uw`.
4+
5+@docs decode, encode
6+
7+-}
8+
9+import BitParser as BP
10+import BitWriter as BW
11+import Bitwise
12+import Bytes exposing (Bytes)
13+import Bytes.Extra
14+import Dict
15+import List.Extra
16+
17+
18+{-| -}
19+decode : String -> Bytes
20+decode string =
21+ if string == "0w0" then
22+ Bytes.Extra.empty
23+
24+ else
25+ let
26+ chars =
27+ string
28+ |> String.toList
29+ -- 0w
30+ |> List.drop 2
31+
32+ go : List Char -> List Int -> BW.BitWriter -> BW.BitWriter
33+ go cs append writer =
34+ case cs of
35+ [] ->
36+ BW.bits append writer
37+
38+ '.' :: rest ->
39+ go rest append writer
40+
41+ c :: rest ->
42+ case Dict.get c charToBits of
43+ Just bits ->
44+ go rest (bits ++ append) writer
45+
46+ Nothing ->
47+ go rest [] writer
48+ in
49+ BW.run (go chars [] BW.empty)
50+
51+
52+{-| -}
53+encode : Bytes -> String
54+encode bytes =
55+ let
56+ w =
57+ Bytes.width bytes * 8
58+
59+ go () =
60+ take w 6
61+ |> BP.andThen
62+ (\bits ->
63+ if List.isEmpty bits then
64+ BP.succeed []
65+
66+ else
67+ case Dict.get (BP.bitsToInt bits) intToChar of
68+ Nothing ->
69+ BP.fail
70+
71+ Just char ->
72+ go () |> BP.map (\chars -> char :: chars)
73+ )
74+
75+ encoded =
76+ BP.run (go ()) bytes
77+ |> Maybe.withDefault []
78+ |> List.Extra.dropWhileRight (\x -> x == '0')
79+ |> dot
80+ |> List.reverse
81+ in
82+ if List.isEmpty encoded then
83+ "0w0"
84+
85+ else
86+ String.fromList ('0' :: 'w' :: encoded)
87+
88+
89+dot : List Char -> List Char
90+dot chars =
91+ if List.isEmpty chars then
92+ []
93+
94+ else if List.length chars <= 5 then
95+ chars
96+
97+ else
98+ List.take 5 chars ++ ('.' :: dot (List.drop 5 chars))
99+
100+
101+take : Int -> Int -> BP.BitParser (List Int)
102+take width n =
103+ if n <= 0 then
104+ BP.succeed []
105+
106+ else
107+ BP.getOffset
108+ |> BP.andThen
109+ (\offset ->
110+ if offset >= width then
111+ BP.succeed []
112+
113+ else
114+ BP.bit
115+ |> BP.andThen
116+ (\bit -> take width (n - 1) |> BP.map (\bits -> bit :: bits))
117+ )
118+
119+
120+intToChar : Dict.Dict Int Char
121+intToChar =
122+ Dict.fromList mapping
123+
124+
125+charToBits : Dict.Dict Char (List Int)
126+charToBits =
127+ let
128+ intToBits bitsLeft n =
129+ if bitsLeft > 0 then
130+ Bitwise.and 1 n :: intToBits (bitsLeft - 1) (Bitwise.shiftRightBy 1 n)
131+
132+ else
133+ []
134+ in
135+ mapping |> List.map (\( x, y ) -> ( y, intToBits 6 x )) |> Dict.fromList
136+
137+
138+mapping : List ( number, Char )
139+mapping =
140+ [ ( 0, '0' )
141+ , ( 1, '1' )
142+ , ( 2, '2' )
143+ , ( 3, '3' )
144+ , ( 4, '4' )
145+ , ( 5, '5' )
146+ , ( 6, '6' )
147+ , ( 7, '7' )
148+ , ( 8, '8' )
149+ , ( 9, '9' )
150+ , ( 10, 'a' )
151+ , ( 11, 'b' )
152+ , ( 12, 'c' )
153+ , ( 13, 'd' )
154+ , ( 14, 'e' )
155+ , ( 15, 'f' )
156+ , ( 16, 'g' )
157+ , ( 17, 'h' )
158+ , ( 18, 'i' )
159+ , ( 19, 'j' )
160+ , ( 20, 'k' )
161+ , ( 21, 'l' )
162+ , ( 22, 'm' )
163+ , ( 23, 'n' )
164+ , ( 24, 'o' )
165+ , ( 25, 'p' )
166+ , ( 26, 'q' )
167+ , ( 27, 'r' )
168+ , ( 28, 's' )
169+ , ( 29, 't' )
170+ , ( 30, 'u' )
171+ , ( 31, 'v' )
172+ , ( 32, 'w' )
173+ , ( 33, 'x' )
174+ , ( 34, 'y' )
175+ , ( 35, 'z' )
176+ , ( 36, 'A' )
177+ , ( 37, 'B' )
178+ , ( 38, 'C' )
179+ , ( 39, 'D' )
180+ , ( 40, 'E' )
181+ , ( 41, 'F' )
182+ , ( 42, 'G' )
183+ , ( 43, 'H' )
184+ , ( 44, 'I' )
185+ , ( 45, 'J' )
186+ , ( 46, 'K' )
187+ , ( 47, 'L' )
188+ , ( 48, 'M' )
189+ , ( 49, 'N' )
190+ , ( 50, 'O' )
191+ , ( 51, 'P' )
192+ , ( 52, 'Q' )
193+ , ( 53, 'R' )
194+ , ( 54, 'S' )
195+ , ( 55, 'T' )
196+ , ( 56, 'U' )
197+ , ( 57, 'V' )
198+ , ( 58, 'W' )
199+ , ( 59, 'X' )
200+ , ( 60, 'Y' )
201+ , ( 61, 'Z' )
202+ , ( 62, '-' )
203+ , ( 63, '~' )
204+ ]
src/Urbit/Constructor.elmdeleted−93
@@ -1,93 +0,0 @@
1−module Urbit.Constructor exposing
2− ( Constructor
3− , cell
4− , cord
5− , float32
6− , float64
7− , int
8− , listOf
9− , sig
10− , signedInt
11− , tape
12− )
13−
14−import Bitwise
15−import Bytes exposing (Bytes, Endianness(..))
16−import Bytes.Encode as BE
17−import Urbit exposing (..)
18−
19−
20−type alias Constructor a =
21− a -> Noun
22−
23−
24−cell : Noun -> Noun -> Noun
25−cell a b =
26− Cell ( a, b )
27−
28−
29−int : Int -> Noun
30−int i =
31− Atom
32− (BE.encode
33− (if i <= 255 then
34− BE.unsignedInt8 i
35−
36− else if i <= 65535 then
37− BE.unsignedInt16 LE i
38−
39− else
40− BE.unsignedInt32 LE i
41− )
42− )
43−
44−
45−signedInt : Int -> Noun
46−signedInt i =
47− int
48− (if i < 0 then
49− negate (Bitwise.shiftLeftBy 1 i) - 1
50−
51− else
52− Bitwise.shiftLeftBy 1 i
53− )
54−
55−
56−bytes : Bytes -> Noun
57−bytes =
58− Atom
59−
60−
61−cord : String -> Noun
62−cord s =
63− Atom (BE.encode (BE.string s))
64−
65−
66−tape : String -> Noun
67−tape s =
68− String.toList s |> List.map String.fromChar |> listOf cord
69−
70−
71−listOf : (a -> Noun) -> List a -> Noun
72−listOf encEl list =
73− case list of
74− [] ->
75− sig
76−
77− x :: xs ->
78− cell (encEl x) (listOf encEl xs)
79−
80−
81−sig : Noun
82−sig =
83− Atom (BE.encode (BE.sequence []))
84−
85−
86−float32 : Float -> Noun
87−float32 f =
88− Atom (BE.encode (BE.float32 LE f))
89−
90−
91−float64 : Float -> Noun
92−float64 f =
93− Atom (BE.encode (BE.float64 LE f))
src/Urbit/Deconstructor.elmdeleted−261
@@ -1,261 +0,0 @@
1−module Urbit.Deconstructor exposing
2− ( Deconstructor
3− , alt
4− , bytes
5− , cell
6− , const
7− , cord
8− , float32
9− , float64
10− , int
11− , list
12− , llec
13− , map
14− , oneOf
15− , run
16− , runBytes
17− , sig
18− , signedInt
19− , tape
20− , tar
21− )
22−
23−import Bitwise
24−import Bytes exposing (Bytes, Endianness(..))
25−import Bytes.Decode as BD
26−import Bytes.Encode as BE
27−import Bytes.Extra
28−import Urbit exposing (..)
29−
30−
31−type Deconstructor a b
32− = Deconstructor (Noun -> a -> Maybe b)
33−
34−
35−run : Deconstructor (a -> a) a -> Noun -> Maybe a
36−run (Deconstructor f) noun =
37− f noun identity
38−
39−
40−runBytes : Deconstructor (a -> a) a -> Bytes -> Maybe a
41−runBytes (Deconstructor f) bs =
42− cue bs
43− |> Maybe.andThen
44− (\noun -> f noun identity)
45−
46−
47−const : Deconstructor (a -> a) a -> a -> Deconstructor c c
48−const (Deconstructor f) value =
49− Deconstructor
50− (\noun c ->
51− case f noun identity of
52− Just a ->
53− if a == value then
54− Just c
55−
56− else
57− Nothing
58−
59− Nothing ->
60− Nothing
61− )
62−
63−
64−cord : Deconstructor (String -> a) a
65−cord =
66− Deconstructor
67− (\x f ->
68− case x of
69− Atom bs ->
70− BD.decode (BD.string (Bytes.width bs)) bs
71− |> Maybe.map f
72−
73− Cell _ ->
74− Nothing
75− )
76−
77−
78−tape : Deconstructor (String -> a) a
79−tape =
80− list cord |> fmap String.concat
81−
82−
83−int : Deconstructor (Int -> a) a
84−int =
85− Deconstructor
86− (\x f ->
87− case x of
88− Atom bs ->
89− (case Bytes.width bs of
90− 1 ->
91− BD.decode BD.unsignedInt8 bs
92−
93− 2 ->
94− BD.decode (BD.unsignedInt16 LE) bs
95−
96− 3 ->
97− BD.decode (BD.unsignedInt32 LE) (BE.encode (BE.sequence [ BE.bytes bs, BE.unsignedInt8 0 ]))
98−
99− 4 ->
100− BD.decode (BD.unsignedInt32 LE) bs
101−
102− _ ->
103− Nothing
104− )
105− |> Maybe.map f
106−
107− Cell _ ->
108− Nothing
109− )
110−
111−
112−signedInt : Deconstructor (Int -> a) a
113−signedInt =
114− int
115− |> fmap
116− (\i ->
117− if Bitwise.and 1 i == 1 then
118− -(Bitwise.shiftRightBy 1 (1 + i))
119−
120− else
121− Bitwise.shiftRightBy 1 i
122− )
123−
124−
125−float32 : Deconstructor (Float -> a) a
126−float32 =
127− Deconstructor
128− (\x f ->
129− case x of
130− Atom bs ->
131− BD.decode (BD.float32 LE) bs
132− |> Maybe.map f
133−
134− Cell _ ->
135− Nothing
136− )
137−
138−
139−float64 : Deconstructor (Float -> a) a
140−float64 =
141− Deconstructor
142− (\x f ->
143− case x of
144− Atom bs ->
145− BD.decode (BD.float64 LE) bs
146− |> Maybe.map f
147−
148− Cell _ ->
149− Nothing
150− )
151−
152−
153−bytes : Deconstructor (Bytes -> a) a
154−bytes =
155− Deconstructor
156− (\n f ->
157− case n of
158− Atom bs ->
159− f bs |> Just
160−
161− Cell _ ->
162− Nothing
163− )
164−
165−
166−sig : Deconstructor a a
167−sig =
168− Deconstructor
169− (\n a ->
170− case n of
171− Atom b ->
172− if (Bytes.Extra.toByteValues b |> List.filter (\x -> x /= 0)) == [] then
173− Just a
174−
175− else
176− Nothing
177−
178− Cell _ ->
179− Nothing
180− )
181−
182−
183−list : Deconstructor (a -> a) a -> Deconstructor (List a -> b) b
184−list (Deconstructor f) =
185− Deconstructor
186− (\n g ->
187− let
188− go n_ =
189− case n_ of
190− Atom _ ->
191− Just []
192−
193− Cell ( el, tail ) ->
194− Maybe.map2 (\a b -> a :: b) (f el identity) (go tail)
195− in
196− go n |> Maybe.map g
197− )
198−
199−
200−alt : Deconstructor a b -> Deconstructor a b -> Deconstructor a b
201−alt (Deconstructor f) (Deconstructor g) =
202− Deconstructor
203− (\n a ->
204− case f n a of
205− Just x ->
206− Just x
207−
208− Nothing ->
209− g n a
210− )
211−
212−
213−oneOf : List (Deconstructor a b) -> Deconstructor a b
214−oneOf l =
215− case l of
216− [] ->
217− Deconstructor (\_ _ -> Nothing)
218−
219− x :: xs ->
220− alt x (oneOf xs)
221−
222−
223−tar : Deconstructor a a
224−tar =
225− Deconstructor (\_ a -> Just a)
226−
227−
228−llec : Deconstructor a b -> Deconstructor b c -> Deconstructor a c
229−llec (Deconstructor r) (Deconstructor l) =
230− Deconstructor
231− (\noun a ->
232− case noun of
233− Cell ( lhs, rhs ) ->
234− r rhs a |> Maybe.andThen (\b -> l lhs b)
235−
236− Atom _ ->
237− Nothing
238− )
239−
240−
241−cell : Deconstructor a b -> Deconstructor b c -> Deconstructor a c
242−cell (Deconstructor l) (Deconstructor r) =
243− Deconstructor
244− (\noun a ->
245− case noun of
246− Cell ( lhs, rhs ) ->
247− l lhs a |> Maybe.andThen (\b -> r rhs b)
248−
249− Atom _ ->
250− Nothing
251− )
252−
253−
254−map : a -> Deconstructor a b -> Deconstructor (b -> c) c
255−map a (Deconstructor f) =
256− Deconstructor (\noun g -> f noun a |> Maybe.map g)
257−
258−
259−fmap : (a -> b) -> Deconstructor (a -> c) c -> Deconstructor (b -> c) c
260−fmap f (Deconstructor g) =
261− Deconstructor (\noun h -> g noun (f >> h))
tests/Test/Urbit.elm+56−16
@@ -1,5 +1,7 @@
11module Test.Urbit exposing (tests)
22
3+import BigInt exposing (BigInt)
4+import BigInt.Bytes
35import BitParser
46import BitWriter
57import Bytes exposing (Bytes)
@@ -9,9 +11,12 @@ import Fuzz exposing (Fuzzer)
911import List.Extra as List
1012import Test exposing (..)
1113import Test.Utils exposing (..)
12−import Urbit exposing (..)
13−import Urbit.Constructor as C
14−import Urbit.Deconstructor as D
14+import Ur exposing (..)
15+import Ur.Constructor as C
16+import Ur.Deconstructor as D
17+import Ur.Jam exposing (cue, jam, mat, rub)
18+import Ur.Types exposing (..)
19+import Ur.Uw
1520
1621
1722tests : Test
@@ -72,16 +77,26 @@ tests =
7277 Expect.equal
7378 (Just ( 1, 2 ))
7479 (D.runBytes
75− (D.cell D.int D.int |> D.map Tuple.pair)
80+ (D.cell D.int D.int)
7681 (Bytes.fromByteValues [ 0x31, 0x12 ])
7782 )
7883 )
84+ , test "1.686.761.906.334"
85+ (\() ->
86+ Expect.equal
87+ (Just "1686761906334")
88+ (D.runBytes
89+ D.bigint
90+ (Bytes.fromByteValues [ 0x80, 0xC9, 0x13, 0x04, 0x5B, 0x17, 0x31 ])
91+ |> Maybe.map BigInt.toString
92+ )
93+ )
7994 , test "[4 ~[1 2 3]]"
8095 (\() ->
8196 Expect.equal
8297 (Just ( 4, [ 1, 2, 3 ] ))
8398 (D.runBytes
84− (D.cell D.int (D.list D.int) |> D.map Tuple.pair)
99+ (D.cell D.int (D.list D.int))
85100 (Bytes.fromByteValues [ 0x61, 0xC6, 0x21, 0x43, 0x0B ])
86101 )
87102 )
@@ -90,7 +105,7 @@ tests =
90105 Expect.equal
91106 (Just ( 8, 11 ))
92107 (D.runBytes
93− (D.cell D.float32 D.float32 |> D.map Tuple.pair)
108+ (D.cell D.float32 D.float32)
94109 (Bytes.fromByteValues [ 0x01, 0x1F, 0x00, 0x00, 0x20, 0x08, 0x7C, 0x00, 0x00, 0x98, 0x20 ])
95110 )
96111 )
@@ -99,7 +114,7 @@ tests =
99114 Expect.equal
100115 (Just ( 8, "hi" ))
101116 (D.runBytes
102− (D.cell D.int D.cord |> D.map Tuple.pair)
117+ (D.cell D.int D.cord)
103118 (Bytes.fromByteValues [ 0x41, 0x10, 0x3C, 0x5A, 0x1A ])
104119 )
105120 )
@@ -108,7 +123,7 @@ tests =
108123 Expect.equal
109124 (Just ( 8, "hi" ))
110125 (D.runBytes
111− (D.cell D.int D.tape |> D.map Tuple.pair)
126+ (D.cell D.int D.tape)
112127 (Bytes.fromByteValues [ 0x41, 0x30, 0x38, 0x3A, 0x78, 0x5A ])
113128 )
114129 )
@@ -119,8 +134,8 @@ tests =
119134 (Just "hi")
120135 (D.runBytes
121136 (D.oneOf
122− [ D.cell (D.const D.cord "tape") D.tape
123− , D.cell (D.const D.cord "cord") D.cord
137+ [ D.cell (D.const D.cord "tape") D.tape |> D.map (\((), t) -> t)
138+ , D.cell (D.const D.cord "cord") D.cord |> D.map (\((), c) -> c)
124139 ]
125140 )
126141 (Bytes.fromByteValues [ 0x01, 0x9F, 0x2E, 0x0C, 0xAE, 0x1C, 0x1C, 0x1D, 0x3C, 0x2D ])
@@ -132,8 +147,8 @@ tests =
132147 (Just "hi")
133148 (D.runBytes
134149 (D.oneOf
135− [ D.cell (D.const D.cord "tape") D.tape
136− , D.cell (D.const D.cord "cord") D.cord
150+ [ D.cell (D.const D.cord "tape") D.tape |> D.map (\((), t) -> t)
151+ , D.cell (D.const D.cord "cord") D.cord |> D.map (\((), c) -> c)
137152 ]
138153 )
139154 (Bytes.fromByteValues [ 0x01, 0x7F, 0xEC, 0x4D, 0x8E, 0x0C, 0x1E, 0x2D, 0x0D ])
@@ -145,11 +160,11 @@ tests =
145160 Expect.equal
146161 (Just ( 8, -8 ))
147162 (D.runBytes
148− (D.cell D.signedInt D.signedInt |> D.map Tuple.pair)
163+ (D.cell D.signedInt D.signedInt)
149164 (Bytes.fromByteValues [ 0xC1, 0x20, 0xE4, 0x01 ])
150165 )
151166 )
152− , test "65.600"
167+ , test "Int 65.600"
153168 (\() ->
154169 Expect.equal
155170 (Just 65600)
@@ -158,6 +173,15 @@ tests =
158173 (Bytes.fromByteValues [ 0xC0, 0x00, 0x02, 0x08 ])
159174 )
160175 )
176+ , test "BigInt 65.600"
177+ (\() ->
178+ Expect.equal
179+ (Just (BigInt.fromInt 65600))
180+ (D.runBytes
181+ D.bigint
182+ (Bytes.fromByteValues [ 0xC0, 0x00, 0x02, 0x08 ])
183+ )
184+ )
161185 ]
162186 , describe "Constructor <-> Deconstructor"
163187 [ Test.fuzz
@@ -166,7 +190,7 @@ tests =
166190 (\( f, i, ui ) ->
167191 Expect.equal (Just ( f, i, ui ))
168192 (D.run
169− (D.cell D.float64 (D.cell D.signedInt D.int) |> D.map (\a b c -> ( a, b, c )))
193+ (D.cell D.float64 (D.cell D.signedInt D.int) |> D.map (\(a, (b, c)) -> ( a, b, c )))
170194 (C.cell (C.float64 f) (C.cell (C.signedInt i) (C.int ui)))
171195 )
172196 )
@@ -203,6 +227,12 @@ tests =
203227 (C.tape x)
204228 )
205229 )
230+ , Test.fuzz bigint "bigint" (\x -> Expect.equal (Just x) (D.run D.bigint (C.bigint x)))
231+ ]
232+ , Test.describe "Ur.Uw"
233+ [ Test.fuzz atom
234+ "encode <-> decode"
235+ (\bs -> bytesEq bs (bs |> Ur.Uw.encode |> Ur.Uw.decode |> stripTrailingZeros))
206236 ]
207237 ]
208238
@@ -234,7 +264,12 @@ atom : Fuzzer Bytes
234264atom =
235265 bytes
236266 |> Fuzz.map
237− (\a -> a |> Bytes.toByteValues |> List.dropWhileRight (\x -> x == 0) |> Bytes.fromByteValues)
267+ stripTrailingZeros
268+
269+
270+stripTrailingZeros : Bytes -> Bytes
271+stripTrailingZeros =
272+ Bytes.toByteValues >> List.dropWhileRight (\x -> x == 0) >> Bytes.fromByteValues
238273
239274
240275noun : () -> Fuzzer Noun
@@ -243,3 +278,8 @@ noun () =
243278 [ ( 0.6, atom |> Fuzz.map Atom )
244279 , ( 0.4, Fuzz.map2 (\a b -> Cell ( a, b )) (Fuzz.lazy noun) (Fuzz.lazy noun) )
245280 ]
281+
282+
283+bigint : Fuzzer BigInt
284+bigint =
285+ bytes |> Fuzz.map BigInt.Bytes.decode