Merge pull request #1 from nospur-sontud/nospur-sontud-patch-1

Nospur sontud patch 1
This commit is contained in:
nospur-sontud 2023-08-16 11:02:58 -04:00 committed by GitHub
commit 8e58fa5dd9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
122 changed files with 10563 additions and 3155 deletions

View File

@ -165,7 +165,7 @@ Since we're being quite thorough in this article, let's summarize every single a
| `@uw` | unsigned base64 | `0w_____._____` | `0wbnC.8haTg` | | | `@uw` | unsigned base64 | `0w_____._____` | `0wbnC.8haTg` | |
| `@ux` | unsigned hexadecimal | `0x____.____` | `0x5f5.e138` | | | `@ux` | unsigned hexadecimal | `0x____.____` | `0x5f5.e138` | |
You'll also find some irregular auras in use: `%lull`, for instance, has a `@uxblob` type. Nonstandard auras (i.e. those not listed in the table above) render as `@ux` visibly, but are still subject to nesting rules. In fact, the capital-letter suffixes one occasionally encounters (like `@tD` and `@uvJ`) are programmer annotations to mark the intended bit-width of a value. (`A` = {% math %}2^0{% /math %}, `B` = {% math %}2^1%{% /math %}, `C` = {% math %}2^2{% /math %}, `D` = {% math %}2^3{% /math %}, `E` = {% math %}2^5{% /math %}, etc.) You'll also find some irregular auras in use: `%lull`, for instance, has a `@uxblob` type. Nonstandard auras (i.e. those not listed in the table above) render as `@ux` visibly, but are still subject to nesting rules. In fact, the capital-letter suffixes one occasionally encounters (like `@tD` and `@uvJ`) are programmer annotations to mark the intended bit-width of a value. (`A` = {% math %}2^0{% /math %}, `B` = {% math %}2^1%{% /math %}, `C` = {% math %}2^2{% /math %}, `D` = {% math %}2^3{% /math %}, `E` = {% math %}2^4{% /math %}, etc.)
We also include two other literal syntaxes which don't resolve to atoms: We also include two other literal syntaxes which don't resolve to atoms:

View File

@ -74,7 +74,7 @@ For instance, in `/sys/vane/ames.hoon`, we find the definitions
Thus for Ames, the nested core pattern largely consists of building a list of particular moves and then pulling them back out: Thus for Ames, the nested core pattern largely consists of building a list of particular moves and then pulling them back out:
```hoon ```hoon
:: if processing succeded, send positive ack packet and exit :: if processing succeeded, send positive ack packet and exit
++ send-ack ++ send-ack
|= =bone |= =bone
^+ event-core ^+ event-core
@ -121,7 +121,7 @@ Gall uses two nested cores to manage agents: `++mo` handles Arvo-level moves, w
```hoon ```hoon
:: ::
++  mo-core  . ++  mo-core  .
::  +mo-abed: initialise state with the provided duct ::  +mo-abed: initialize state with the provided duct
++  mo-abed  |=(hun=duct mo-core(hen hun)) ++  mo-abed  |=(hun=duct mo-core(hen hun))
::  +mo-abet: finalize, reversing moves ::  +mo-abet: finalize, reversing moves
++  mo-abet  [(flop moves) gall-payload] ++  mo-abet  [(flop moves) gall-payload]

View File

@ -4,29 +4,25 @@ date = "2023-05-01"
weight = 90 weight = 90
next_cohort = "August 2023" next_cohort = "August 2023"
image = "https://storage.googleapis.com/media.urbit.org/developers/images/app-school-live.svg" image = "https://storage.googleapis.com/media.urbit.org/developers/images/app-school-live.svg"
description = "App School focuses on how to build a backend Gall agent, then on connecting it to a React-based front-end. When you're done, you'll be able to produce and distribute your own Urbit apps." description = " App School teaches you all about Urbit-based backend design using Gall agents, as well as interfacing with a React-based front-end. When you're done, you'll be able to produce and distribute your own Urbit apps."
+++ +++
The Urbit Foundation offers cohort classes to cover the [App The Urbit Foundation offers live, cohort-based classes to cover the [App
School](/guides/core/app-school) curriculum jointly. School](/guides/core/app-school) curriculum.
App School focuses on how to build a backend Gall agent, then on connecting it Every userspace Urbit application is a **Gall agent**. App School teaches you all about Urbit-based backend design using Gall agents, as well as interfacing with a React-based front-end. When you're done, you'll be able to produce and distribute your own Urbit apps.
to a React-based front-end. When you're done, you'll be able to produce and
distribute your own Urbit apps.
If you prefer to learn as part of a group with a hands-on instructor, regular If you enjoy learning as part of a group with a hands-on instructor, regular
exercises and discussions, and a completion certification, then App School Live exercises and discussions, and a completion certification, then App School Live
will be a good fit for you. will be a good fit for you.
By completing App School as a cohort student, you should be able to design and By completing App School, you will have the basic skillset to design and implement apps, pick up Urbit userspace bounties, and participate in Hackathons.
implement basic user apps, produce minimal working example applications, and
contribute at the level of basic Urbit userspace bounties.
The next cohort for App School Live will begin the week of August 21st, 2023. The next cohort for App School Live will begin the week of August 21st, 2023.
- [Complete this form](https://forms.gle/3c8xBubvSiQfj7Tr6) to sign up for the upcomming class. - [Complete this form](https://airtable.com/shrdZcSQrIIb6mAmx) to sign up for the upcoming class.
Until then, feel free to work through the [App School Until then, feel free to go through the [App School
docs](/guides/core/app-school). docs](/guides/core/app-school).

View File

@ -1,27 +1,24 @@
+++ +++
title = "Core School Live" title = "Core Academy"
weight = 10 weight = 10
next_cohort = "Late Summer 2023" next_cohort = "August 28th"
image = "https://storage.googleapis.com/media.urbit.org/developers/images/core-school-live.svg" image = "https://storage.googleapis.com/media.urbit.org/developers/images/core-school-live.svg"
description = "Core School prepares experienced Hoon developers to work on the Arvo kernel, the Vere or New Mars runtime, and otherwise build the platform as a senior developer." description = "Core Academy prepares experienced Hoon developers to work on the Arvo kernel, the Vere or New Mars runtime, and otherwise build the platform as a senior developer."
+++ +++
The Urbit Foundation will offer a cohort classes to train new core developers. The Urbit Foundation will offer a cohort classes to train new core developers.
Most core developers have been trained up on-the-job inside of Tlon, but as Most core developers have been trained up on-the-job inside of Tlon, but as
core development expands, other organizations will contribute. core development expands, other organizations will contribute.
Core School prepares experienced Hoon developers to work on the Arvo kernel, Core Academy prepares experienced Hoon developers to work on the Arvo kernel,
the Vere or New Mars runtime, and otherwise build the platform as a senior the Vere or New Mars runtime, and otherwise build the platform as a senior
developer. developer.
By completing Core School as a cohort student, you should be able to compose a By completing Core Academy as a cohort student, you should be able to compose a
new vane, contribute to Urbit as a core platform and infrastructure developer, new vane, contribute to Urbit as a core platform and infrastructure developer,
and guide in-depth projects with minimal oversight (as distinct from and guide in-depth projects with minimal oversight (as distinct from
collaboration). collaboration).
The first cohort for Core School Live will take place in late summer 2023. - [Complete this form](https://airtable.com/shr6V87yXZxJEZqyu) to sign up for Core Academy before August 6th.
- [Complete this form](https://forms.gle/gdDWFLiDV1Te65nH8) to get on our
mailing list about the next opportunity.
Until then, feel free to work through the [Arvo docs](/reference/arvo). Until then, feel free to work through the [Arvo docs](/reference/arvo).

View File

@ -1,7 +1,7 @@
+++ +++
title = "Hoon School Live" title = "Hoon School Live"
weight = 100 weight = 100
next_cohort = "March 29, 2023" next_cohort = "September 2023"
image = "https://storage.googleapis.com/media.urbit.org/developers/images/hoon-school-live.svg" image = "https://storage.googleapis.com/media.urbit.org/developers/images/hoon-school-live.svg"
description = "Hoon School Live teaches the fundamentals of Hoon with a hands-on instructor, regular exercises and discussions, and a completion certification." description = "Hoon School Live teaches the fundamentals of Hoon with a hands-on instructor, regular exercises and discussions, and a completion certification."
+++ +++
@ -19,13 +19,11 @@ explain subject-oriented programming. We omit some of the written Hoon School
content for time, but you should be well-equipped to understand the optional content for time, but you should be well-equipped to understand the optional
lessons. lessons.
The next cohort will begin on March 29, 2023. The cohort after that is The cohort will start in September until October on Thursdays at 1pm EST | 6pm CEST.
scheduled to begin in June 2023.
- [Complete this form](https://forms.gle/kgiPckuHaVtJng9r5) to sign up for the next Hoon School Live. - [Complete this form](https://airtable.com/shrqzaLRLzDsHDnCX) to sign up for the next Hoon School Live.
Until then, feel free to work through the [Hoon School Until then, feel free to work through the [Hoon School docs](/guides/core/hoon-school).
docs](/guides/core/hoon-school).
### What will you learn? ### What will you learn?

View File

@ -5,11 +5,14 @@ weight = 3
insert_anchor_links = "right" insert_anchor_links = "right"
+++ +++
## [`%ahoy`](/guides/additional/app-workbook/ahoy) ## [Building a CLI App with `%rpn`](/guides/additional/app-workbook/rpn)
## [`%dbug`](/guides/additional/app-workbook/dbug) ## [Debugging Wrapper with `%dbug`](/guides/additional/app-workbook/dbug)
## [`%flap`](/guides/additional/app-workbook/flap) ## [Hosting a Website with `%feature`](/guides/additional/app-workbook/feature)
## [`%feature`](/guides/additional/app-workbook/feature) ## [Serving a JS Game with `%flap`](/guides/additional/app-workbook/flap)
## [Ship Monitoring with `%ahoy`](/guides/additional/app-workbook/ahoy)
## [Styled Text with `%track7`](/guides/additional/app-workbook/track7)

View File

@ -1,10 +1,8 @@
+++ +++
title = "%ahoy Ship Monitoring" title = "Ship Monitoring"
weight = 10 weight = 194
+++ +++
# `%ahoy` Ship Monitoring
The `%ahoy` desk by [~midden-fabler](https://urbit.org/ids/~midden-fabler) provides a number of agents to automatically monitor ship activity such as breaching and network uptime. This tutorial examines the `%ahoy` agent specifically with some slight simplifications to demonstrate how an Urbit-native app can be constructed. You will see how to render a front-end using Sail, employ the `++abet` nested core design pattern, construct CLI generators, and set wakeup timers using [Behn](https://developers.urbit.org/reference/glossary/behn). The `%ahoy` desk by [~midden-fabler](https://urbit.org/ids/~midden-fabler) provides a number of agents to automatically monitor ship activity such as breaching and network uptime. This tutorial examines the `%ahoy` agent specifically with some slight simplifications to demonstrate how an Urbit-native app can be constructed. You will see how to render a front-end using Sail, employ the `++abet` nested core design pattern, construct CLI generators, and set wakeup timers using [Behn](https://developers.urbit.org/reference/glossary/behn).
`%ahoy` presents a web UI at `/ahoy` rendered using [Sail](https://developers.urbit.org/guides/additional/sail) and [~paldev](https://urbit.org/ids/~paldev)'s Rudder library alongside command-line generators to add, delete, and modify ship watches. Notifications are sent using `%hark-store` if a ship hasn't been contacted after a specified amount of time. `%ahoy` presents a web UI at `/ahoy` rendered using [Sail](https://developers.urbit.org/guides/additional/sail) and [~paldev](https://urbit.org/ids/~paldev)'s Rudder library alongside command-line generators to add, delete, and modify ship watches. Notifications are sent using `%hark-store` if a ship hasn't been contacted after a specified amount of time.

View File

@ -1,6 +1,6 @@
+++ +++
title = "%dbug Debugging Wrapper" title = "Debugging Wrapper"
weight = 40 weight = 42
+++ +++

View File

@ -1,10 +1,8 @@
+++ +++
title = "%feature Page Hosting" title = "Host a Website"
weight = 160 weight = 85
+++ +++
# `%feature` Page Hosting
[`%feature`](https://github.com/hanfel-dovned/Feature) by [~hanfel-dovned](https://urbit.org/ids/~hanfel-dovned) hosts a simple HTML page from an Urbit ship at an associated URL. This tutorial examines how it uses the middleware [`%schooner`](https://github.com/dalten-collective/schooner/) library by Quartus to return a web page when contacted by a web browser. You will learn how a basic site hosting app can handle HTTP requests and render a page using an `%html` mark. [`%feature`](https://github.com/hanfel-dovned/Feature) by [~hanfel-dovned](https://urbit.org/ids/~hanfel-dovned) hosts a simple HTML page from an Urbit ship at an associated URL. This tutorial examines how it uses the middleware [`%schooner`](https://github.com/dalten-collective/schooner/) library by Quartus to return a web page when contacted by a web browser. You will learn how a basic site hosting app can handle HTTP requests and render a page using an `%html` mark.
`%feature` presents a web page from `/app/feature-ui` at `/apps/feature/feature-ui`. These paths are both configurable by the developer. `%feature` presents a web page from `/app/feature-ui` at `/apps/feature/feature-ui`. These paths are both configurable by the developer.
@ -100,7 +98,7 @@ The system only handles pokes: there are no subscriptions or Arvo calls except
[302 ~ [%login-redirect './apps/feature']] [302 ~ [%login-redirect './apps/feature']]
?~ body.request.inbound-request ?~ body.request.inbound-request
[(send [405 ~ [%stock ~]]) state] [(send [405 ~ [%stock ~]]) state]
=/ json (de-json:html q.u.body.request.inbound-request) =/ json (de:json:html q.u.body.request.inbound-request)
=/ action (dejs-action +.json) =/ action (dejs-action +.json)
(handle-action action) (handle-action action)
:: ::
@ -185,7 +183,7 @@ The most interesting part of the whole app is the `++handle-http` arm:
[302 ~ [%login-redirect './apps/feature']] [302 ~ [%login-redirect './apps/feature']]
?~ body.request.inbound-request ?~ body.request.inbound-request
[(send [405 ~ [%stock ~]]) state] [(send [405 ~ [%stock ~]]) state]
=/ json (de-json:html q.u.body.request.inbound-request) =/ json (de:json:html q.u.body.request.inbound-request)
=/ action (dejs-action +.json) =/ action (dejs-action +.json)
(handle-action action) (handle-action action)
:: ::
@ -219,7 +217,7 @@ The most interesting part of the whole app is the `++handle-http` arm:
== ==
``` ```
This arm uses the `server` library and `schooner` to produce a response of a server state and associated data. HTTP requests to `/app/feature` are checked for login authentication, while `/app/feature/public` is not. This arm uses the `server` library and `schooner` to produce a response of a server state and associated data. HTTP requests to `/apps/feature` are checked for login authentication, while `/apps/feature/public` are not.
### `POST` ### `POST`

View File

@ -1,12 +1,8 @@
+++ +++
title = "%flap JS Client" title = "Serving a JS Game"
weight = 60 weight = 193
+++ +++
# `%flap` JS Client
## Introduction
In this tutorial, we will take an off-the-shelf JavaScript game which runs in the browser and connect it to an Urbit back-end. This page assumes that you have completed some version of Hoon School and App School, whether the [live courses](/courses) or the [written docs](/guides/core/hoon-school/A-intro). Our goal is to show you one way of directly serving client code from an Urbit ship as server. In this tutorial, we will take an off-the-shelf JavaScript game which runs in the browser and connect it to an Urbit back-end. This page assumes that you have completed some version of Hoon School and App School, whether the [live courses](/courses) or the [written docs](/guides/core/hoon-school/A-intro). Our goal is to show you one way of directly serving client code from an Urbit ship as server.
_Flappy Bird_ is an "insanely irritating, difficult and frustrating game which combines a super-steep difficulty curve with bad, boring graphics and jerky movement" ([Huffington Post](https://web.archive.org/web/20140205084251/http://www.huffingtonpost.com/2014/02/03/flappy-bird-tips_n_4717406.html)). We are going to implement `%flap`, a _Flappy Bird_ leaderboard using ~paldevs `%pals` peer tracking agent. The approach given in this tutorial will apply to any game which is primarily run in the browser and has some persistent state to retain across sessions or communicate between players at discrete intervals. Direct player-v.-player games will require other techniques to implement. _Flappy Bird_ is an "insanely irritating, difficult and frustrating game which combines a super-steep difficulty curve with bad, boring graphics and jerky movement" ([Huffington Post](https://web.archive.org/web/20140205084251/http://www.huffingtonpost.com/2014/02/03/flappy-bird-tips_n_4717406.html)). We are going to implement `%flap`, a _Flappy Bird_ leaderboard using ~paldevs `%pals` peer tracking agent. The approach given in this tutorial will apply to any game which is primarily run in the browser and has some persistent state to retain across sessions or communicate between players at discrete intervals. Direct player-v.-player games will require other techniques to implement.
@ -339,7 +335,7 @@ The main app implements the logic for exposing and tracking data.
%'POST' %'POST'
?~ body.request.inbound-request ?~ body.request.inbound-request
[(send [405 ~ [%stock ~]]) this] [(send [405 ~ [%stock ~]]) this]
=/ json (de-json:html q.u.body.request.inbound-request) =/ json (de:json:html q.u.body.request.inbound-request)
=/ axn `action`(dejs-action +.json) =/ axn `action`(dejs-action +.json)
(on-poke %flap-action !>(axn)) (on-poke %flap-action !>(axn))
:: ::
@ -501,7 +497,7 @@ Now when we navigate to `localhost:8080/apps/flap`, what do we see? The game ca
### Serving Correctly ### Serving Correctly
If we investigate the Developer Tools console in our browser, we see messages to the effect that resources are unable to be located. Resource paths (for `js`, `png`, and `wav` files) tell the browser from whence the resources will come when they are loaded. We have two options here as well: hot-link the resource from its GitHub or other source, or If we investigate the Developer Tools console in our browser, we see messages to the effect that resources are unable to be located. Resource paths (for `js`, `png`, and `wav` files) tell the browser from whence the resources will come when they are loaded. We have two options here as well: hot-link the resource from its GitHub or other source or serve the resource from Urbit.
If we hot-link the resources, the corresponding lines will look like this: If we hot-link the resources, the corresponding lines will look like this:
@ -852,7 +848,7 @@ With all of the above, you should have a working `%flappy` instance at `http://l
%'POST' %'POST'
?~ body.request.inbound-request ?~ body.request.inbound-request
[(send [405 ~ [%stock ~]]) this] [(send [405 ~ [%stock ~]]) this]
=/ json (de-json:html q.u.body.request.inbound-request) =/ json (de:json:html q.u.body.request.inbound-request)
=/ axn `action`(dejs-action +.json) =/ axn `action`(dejs-action +.json)
(on-poke %flap-action !>(axn)) (on-poke %flap-action !>(axn))
:: ::
@ -1111,7 +1107,7 @@ If you examine `++on-poke` in `/app/flap.hoon`, you will see that HTTP `POST` re
%'POST' %'POST'
?~ body.request.inbound-request ?~ body.request.inbound-request
[(send [405 ~ [%stock ~]]) this] [(send [405 ~ [%stock ~]]) this]
=/ json (de-json:html q.u.body.request.inbound-request) =/ json (de:json:html q.u.body.request.inbound-request)
=/ axn `action`(dejs-action +.json) =/ axn `action`(dejs-action +.json)
(on-poke %flap-action !>(axn)) (on-poke %flap-action !>(axn))
``` ```

View File

@ -0,0 +1,189 @@
+++
title = "Building a CLI App"
weight = 28
+++
We will utilize the basic calculator app logic from the [parsing guide](/guides/additional/parsing#recursive-parsers) to produce a linked calculator agent `%rpn` supporting the following operators by the appropriate parsers:
- numbers (as `@rs` without `.` dot prefix) (`royl-rs:so`)
- `+` lus, addition (`lus`)
- `-` hep, subtraction (`hep`)
- `*` tar, multiplication (`tar`)
- `/` fas, division (`fas`)
- `.` dot, display top of stack (`dot`)
We will leave all regular Gall arms as their defaults, but of course poking, subscribing, and peeking should be supported in a full application.
## Agent Logic
**`/sur/rpn.hoon`**
We just need to define the expected operators that will show up in the stack. These are `@t` text constants.
```hoon
|%
+$ op $? [%op %add]
[%op %sub]
[%op %mul]
[%op %div]
[%op %sho]
==
+$ num @rs
+$ command ?(@rs op)
--
```
(`+$command` doesn't really feel like the right name here, but we're pattern-matching with the demo `/app/shoe.hoon`.)
**`/lib/rpn.hoon`**
These are the parsing rules that the CLI agent will use. We could include these directly in the agent file but we'll post them to a library file.
```hoon
|%
++ num royl-rs:so
++ op-add (cook |=(p=@ ?:(=('+' p) op+%add ~)) lus)
++ op-sub (cook |=(p=@ ?:(=('-' p) op+%sub ~)) hep)
++ op-mul (cook |=(p=@ ?:(=('*' p) op+%mul ~)) tar)
++ op-div (cook |=(p=@ ?:(=('/' p) op+%div ~)) fas)
++ op-sho (cook |=(p=@ ?:(=('.' p) op+%sho ~)) dot)
++ ops ;~(pose op-add op-sub op-mul op-div op-sho)
--
```
**`/app/rpn.hoon`**
```hoon
++ state-0
$: %0
stack=(list ?(@rs op:rpn))
==
```
**`++command-parser`**
We want this arm to wait until `RETURN` is pressed so we `++stag` the value with `|` `FALSE`/`%.n`.
```hoon
++ command-parser
|= =sole-id:shoe
^+ |~(nail *(like [? command:rpn]))
%+ stag |
(cook command:rpn ;~(pose num:rpnlib ops:rpnlib))
```
**`++on-command`**
This arm pushes values onto the stack, displays the stack, then checks to parse for the result of an operation.
```hoon
++ on-command
|= [=sole-id:shoe =command:rpn]
^- (quip card _this)
=/ old-stack (weld stack ~[command])
=/ new-stack (process:rpnlib old-stack)
:_ this(stack new-stack)
:~ [%shoe ~ sole+klr+~[(crip "{<old-stack>} →")]]
[%shoe ~ sole+klr+~[[[`%br ~ `%g] (crip "{<new-stack>}") ~]]]
==
```
For this we add a helper arm to `/lib/rpn.hoon` which takes each entry, makes sure it is a `@rs` atom, and carries out the operation. (This could probably be made more efficient.)
**`/lib/rpn.hoon`**
```hoon
/- rpn
:: * * *
++ process
|= stack=(list command:rpn)
^- (list command:rpn)
~| "Failure processing operation on stack {<stack>}"
?~ stack !!
?- `command:rpn`(snag 0 (flop stack))
[%op %add]
=/ augend ;;(@rs `command:rpn`(snag 1 (flop stack)))
=/ addend ;;(@rs `command:rpn`(snag 2 (flop stack)))
(flop (weld ~[(add:rs augend addend)] (slag 3 (flop stack))))
::
[%op %sub]
=/ minuend ;;(@rs `command:rpn`(snag 1 (flop stack)))
=/ subtrahend ;;(@rs `command:rpn`(snag 2 (flop stack)))
(flop (weld ~[(sub:rs minuend subtrahend)] (slag 3 (flop stack))))
::
[%op %mul]
=/ multiplicand ;;(@rs `command:rpn`(snag 1 (flop stack)))
=/ multiplier ;;(@rs `command:rpn`(snag 2 (flop stack)))
(flop (weld ~[(mul:rs multiplicand multiplier)] (slag 3 (flop stack))))
::
[%op %div]
=/ numerator ;;(@rs `command:rpn`(snag 1 (flop stack)))
=/ denominator ;;(@rs `command:rpn`(snag 2 (flop stack)))
(flop (weld ~[(div:rs numerator denominator)] (slag 3 (flop stack))))
::
[%op %sho]
~& > "{<(snag 1 (flop stack))>}"
(flop (slag 1 (flop stack)))
::
@rs
stack
==
```
### Linking
After a `%sole` agent has been `|install`ed, it should be registered for Dojo to cycle input to it using `|link`.
```hoon
|link %rpn
```
Now `Ctrl`+`X` allows you to switch to that app and evaluate expressions using it.
```hoon
gall: booted %rpn
> 50
~ →
~[.50]
> 25
~[.50] →
~[.50 .25]
> -
~[.50 .25] →
~[.-25]
> 5
~[.-25] →
~[.-25 .5]
> /
~[.-25 .5] →
~[.-0.19999999]
> 5
~[.-0.19999999] →
~[.-0.19999999 .5]
> *
~[.-0.19999999 .5] →
~[.-0.99999994]
> 1
~[.-0.99999994] →
~[.-0.99999994 .1]
> /
~[.-0.99999994 .1] →
~[.-1]
```
## Exercises
- Extend the calculator app to support modulus as `%` cen.
- Extend the calculator app so it instead operates on `@rd` values. Either use `++cook` to automatically convert the input values from a `1.23`-style input to the `.~1.23` `@rd` style or build a different input parser from the entries in `++royl:so`.
- Extend the calculator app so that it can support named variables (using `@tas`) with `=` tis. What new data structure do you need? For convenience, expose the result of the last operation as `ans` (a feature of TI graphing calculators and MATLAB, among other programs).
- The calculator app stack isn't really a proper CS stack with push and pop operations. Refactor it to use such a type.

View File

@ -0,0 +1,174 @@
+++
title = "Styled Text"
weight = 199
+++
In this tutorial, we examine how to produce `styx` styled text strings and output them to the terminal from an agent.
## `%shoe` CLI Session Manager
`%shoe` is responsible to manage attached agent sessions. It adds a few arms to the standard Gall agent, namely:
- `++command-parser` is the input parser, similar to the work we were carrying out just above. This parses every input and only permits valid keystrokes (think of Dojo real-time parsing).
- `++tab-list` provides autocompletion options. We can ignore for now.
- `++on-command` is called whenever a valid command is run. This produces the actual effects.
- `++can-connect` supports `|link` connexion to the app.
- `++on-connect` provides particular session support when a user connects. We can ignore for now.
- `++on-disconnect` provides particular session support when a user disconnects. We can ignore for now.
To get started with text parsers and CLI agents, we need to focus on `++command-parser` and `++on-command`. But first, the agent's structure and state:
The agent will adopt a two-stage process, wherein a value is put on the stack then the stack is checked for any valid operations.
### `++command-parser`
The input parser can simply accept whole words or single inputs, or parse complex expressions (as Dojo does with Hoon).
This results in a noun of `+$command-type` based on the specific application. The example `/app/shoe.hoon` agent defines:
```hoon
+$ command
$? %demo
%row
%table
==
```
and later uses this as:
```hoon
++ command-parser
|= =sole-id:shoe
^+ |~(nail *(like [? command]))
%+ stag &
(perk %demo %row %table ~)
```
where the unfamiliar parser components are:
- `++stag` adds a label, here `&` pam `TRUE`/`%.y` to indicate that the command should be run immediately when it matches. (We won't want this below so we will `++stag` a `|` `FALSE`/`%.n`.)
- `++perk` parses a fork in the type.
### `++on-command`
This arm accepts a session ID and a command resulting from `++command-parser`. It produces a regular `(quip card _this)` so you can modify agent state and produce effects here.
## `%sole` Effects
`%sole` is responsible for producing effects. If you want to yield effects to the command line from your CLI agent (which you often do), this is a great place to work.
`%sole-effect`s are head-tagged by time and produce a variety of terminal effects, from text to bells, colors, and other screen effects.
## `$styx` Styled Text String
A `klr` effect uses a `styx`, or styled text string. The relevant data structures are in `/sys/lull.hoon`:
```hoon
+$ deco ?(~ %bl %br %un) :: text decoration
+$ stye (pair (set deco) (pair tint tint)) :: decos/bg/fg
+$ styl %+ pair (unit deco) :: cascading style
(pair (unit tint) (unit tint)) ::
+$ styx (list $@(@t (pair styl styx))) :: styled text
+$ tint $@ ?(%r %g %b %c %m %y %k %w %~) :: text color
[r=@uxD g=@uxD b=@uxD] :: 24bit true color
```
- `$deco` is a text decoration, here `%bl` blinking, `%br` bright (bold), and `%un` underlined.
- `$tint` is a color, either explicitly the terminal red/green/blue/cyan etc. or a 24-bit true color value.
- `$stye` composes these into a style which will be applied to a string.
- `$styl` similarly composes styles together.
- `$styx` pairs styles with cords.
This means that composing styled text correctly can require explicitly nesting statements in rather a complicated way.
For instance, to produce a bold string with hex color `#123456`, we could produce the `sole-effect`:
```hoon
^- sole-effect:sole
:- klr
^- styx
~[[[`%br ~ `[r=0x12 g=0x34 b=0x56]] 'Hello Mars!' ~]]
```
- [~ropdeb-sormyr, "Styled output - requirements and progress" ~2016.8.2 Urbit fora post](https://github.com/urbit/fora-posts/blob/0238536650dfc284f14295d350f9acada0341480/archive/posts/~2016.8.2..21.19.29..2ab8~.md)
## Agent Logic
Here is an agent that will accept a single character and produce a line with varying random colors of that character.
**`/app/track7.hoon`**
```hoon
/+ default-agent, dbug, shoe, sole
|%
+$ versioned-state
$% state-0
==
+$ state-0 %0
+$ card card:agent:shoe
+$ command @t
--
%- agent:dbug
=| state-0
=* state -
^- agent:gall
%- (agent:shoe command)
^- (shoe:shoe command)
|_ =bowl:gall
+* this .
default ~(. (default-agent this %|) bowl)
leather ~(. (default:shoe this command) bowl)
++ on-init on-init:default
++ on-save !>(state)
++ on-load
|= old=vase
^- (quip card _this)
`this(state !<(state-0 old))
++ on-poke on-poke:default
++ on-peek on-peek:default
++ on-arvo on-arvo:default
++ on-watch on-watch:default
++ on-leave on-leave:default
++ on-agent on-agent:default
++ on-fail on-fail:default
++ command-parser
|= =sole-id:shoe
^+ |~(nail *(like [? command]))
(stag & (boss 256 (more gon qit)))
++ on-command
|= [=sole-id:shoe =command]
^- (quip card _this)
:_ this
^- (list card)
:~ :+ %shoe ~
^- shoe-effect:shoe
:- %sole
^- sole-effect:sole :- %klr
^- styx
=/ idx 0
=| fx=styx
=/ rng ~(. og eny:bowl)
|-
?: =(80 idx) fx
=^ huer rng (rads:rng 256)
=^ hueg rng (rads:rng 256)
=^ hueb rng (rads:rng 256)
%= $
idx +(idx)
fx `styx`(weld fx `styx`~[[[`%br ~ `[r=`@ux`huer g=`@ux`hueg b=`@ux`hueb]] command ~]])
== ==
++ can-connect
|= =sole-id:shoe
^- ?
?| =(~zod src.bowl)
(team:title [our src]:bowl)
==
++ on-connect on-connect:leather
++ on-disconnect on-disconnect:leather
++ tab-list tab-list:leather
--
```

View File

@ -4,7 +4,7 @@ description = "Learn to write tests with Aqua"
weight = 5 weight = 5
+++ +++
# Concepts ## Concepts
Aqua (short for "aquarium", alluding to the idea that you're running Aqua (short for "aquarium", alluding to the idea that you're running
multiple ships in a safe, artificial environment and watching them multiple ships in a safe, artificial environment and watching them
@ -14,7 +14,7 @@ within a single host.
pH is a library of functions designed to make it easy to write pH is a library of functions designed to make it easy to write
integration tests using Aqua. integration tests using Aqua.
# First test ## First test
To run your first pH test, run the following commands: To run your first pH test, run the following commands:
@ -64,7 +64,7 @@ breaches, mock http clients or servers, or anything you can imagine.
Check out `/lib/ph/io.hoon` for other available functions, and look at Check out `/lib/ph/io.hoon` for other available functions, and look at
other tests in `/ted/ph/` for inspiration. other tests in `/ted/ph/` for inspiration.
# Reference ## Reference
Aqua has the following commands: Aqua has the following commands:

View File

@ -0,0 +1,241 @@
+++
title = "Generators"
weight = 73
+++
Generator files provide a way for users to interact with code "scripts" through the Dojo prompt. There are
three basic kinds of generators:
1. Bare or naked generators, standalone computations that can accept input and carry out a single calculation.
2. `%say` generators, scripts which can utilize the full system knowledge (`now`, `our`, `eny`) and accept
optional input arguments.
3. `%ask` generators, scripts driven by interactive prompts.
(Threads have some commonalities with generators, _q.v._)
Generators are a Dojo concept, although they can also be applied to agents (such as `+dbug`). This guide will
show you how to build and invoke all kinds of generators.
## Bare Generators
A basic generator is a gate, a core with a `$` buc arm and a sample.
The Dojo will supply the sample directly to the core in the `$` buc arm.
A bare generator must be a gate but can have more complicated internal structure, as with all Hoon code. It does
not know about entropy `eny`, ship identity `our`, or the timestamp `now`.
**`/gen/add-one.hoon`**
```hoon
|= n=@ud
=<
(add-one n)
|%
++ add-one
|= a=@ud
^- @ud
(add a 1)
--
```
Invoke as `+add-one 5`.
You could in principle use a `|*` bartar wet gate as well, but other cores don't pattern-match to what Dojo expects.
## `%say` Generators
A `%say` generator can have zero, many, or optional arguments, unlike a bare generator. It can also have access to
system variables like `now`, `our`, and `eny`.
For instance, the following generator can be run with no arguments:
**`/gen/say.hoon`**
```hoon {% copy=true %}
:- %say
|= *
:- %noun
(add 40 2)
```
```hoon
> +say
42
```
A `%say` generator is structurally a head-tagged cell of a gate which returns a head-tagged cell of a mark and a value
(or a `cask`).
The head tag over the entire generator is always `%say`. The `cask` tag is most commonly `%noun`.
We use `%say` generators when we want to provide something else in Arvo, the Urbit operating system, with metadata about
the generator's output. This is useful when a generator is needed to pipe data to another program, a frequent occurrence.
To that end, `%say` generators use `mark`s to make it clear, to other Arvo computations, exactly what kind of data their
output is. A `mark` is akin to a MIME type on the Arvo level. A `mark` describes the data in some way, indicating that
it's an `%atom`, or that it's a standard such as `%json`, or even that it's an application-specific data structure like
`%talk-command`.
The gate sample follows this pattern, with undesired elements stubbed out by `*`:
```hoon
|= $: :: environment
$: now=@da :: timestamp
eny=@uvJ :: entropy
bec=beak :: clay beak
==
:: :: unnamed args
$=
$: arg=@ :: required arguments
==
~
==
:: :: named args
$=
$: named-arg=@ :: optional arguments
==
~
==
==
```
The Dojo will modify the sample by inserting `%~` (constant null) at the end of each collection, since the Dojo adapts
the input arguments into a list (either the unnamed/required argument list or the named/optional argument list).
### Zero arguments
`/gen/vats.hoon` is commonly used to check on the status of installed desks. It can be invoked with optional arguments:
```
> +vats
%base
/sys/kelvin: [%zuse 414]
base hash ends in: drceb
%cz hash ends in: drceb
app status: running
pending updates: ~
> +vats, =verb %.n
%base
/sys/kelvin: [%zuse 414]
base hash ends in: drceb
%cz hash ends in: drceb
app status: running
pending updates: ~
> +vats, =filt %suspended
```
### Optional arguments
Let's look at an example that uses all three parts.
**`/gen/dice.hoon`**
```hoon {% copy=true %}
:- %say
|= [[now=@da eny=@uvJ bec=beak] [n=@ud ~] [bet=@ud ~]]
:- %noun
[(~(rad og eny) n) bet]
```
This is a very simple dice program with an optional betting functionality. In the code, our sample specifies faces on all
of the Arvo data, meaning that we can easily access them. We also require the argument `[n=@ud ~]`, and allow the
_optional_ argument `[bet=@ud ~]`.
We can run this generator like so:
```hoon
> +dice 6, =bet 2
[4 2]
> +dice 6
[5 0]
> +dice 6
[2 0]
> +dice 6, =bet 200
[0 200]
> +dice
nest-fail
```
Notice how the `,` com works to separate arguments and how the name of the optional argument must be included.
We get a different value from the same generator between runs, something that isn't possible with a bare generator. Another
novelty is the ability to choose to not use the second argument.
- [Hoon School, “1.9 Generators”](/guides/core/hoon-school/generators)
## `%ask` Generators
We use an `%ask` generator when we want to create an interactive program that prompts for inputs as it runs, rather than
expecting arguments to be passed in at the time of initiation.
Like `%say` generators, `%ask` generators are head-tagged cells of gates, but with `%ask`.
The code below is an `%ask` generator that checks if the user inputs `"blue"` when prompted [per a classic Monty Python
scene](https://www.youtube.com/watch?v=L0vlQHxJTp0).
**`/gen/axe.hoon`**
```hoon {% copy=true mode="collapse" %}
/- sole
/+ generators
=, [sole generators]
:- %ask
|= *
^- (sole-result (cask tang))
%+ print leaf+"What is your favorite color?"
%+ prompt [%& %prompt "color: "]
|= t=tape
%+ produce %tang
?: =(t "blue")
:~ leaf+"Oh. Thank you very much."
leaf+"Right. Off you go then."
==
:~ leaf+"Aaaaagh!"
leaf+"Into the Gorge of Eternal Peril with you!"
==
```
Run the generator from the Dojo:
```hoon
> +axe
What is your favorite color?
: color:
```
Instead of simply returning something, your Dojo's prompt changed from `~sampel-palnet:dojo>` to `~sampel-palnet:dojo: color:`,
and now expects additional input. Let's give it an answer:
```hoon
: color: red
Into the Gorge of Eternal Peril with you!
Aaaaagh!
```
`%ask` generators return `sole-effect`s. For more information on these, consult the [guide on command-line agents](/guides/additional/cli).
`%ask` generators can also accept arguments, although this is uncommon.
## Generators for Agents
Generators can furthermore interact specifically with agents.
The [`+dbug` agent](/guides/additional/app-workbook/dbug) is invoked against an agent to display internal state.
Any app can implement generators to wrap raw pokes (see [`%ahoy`](/guides/additional/app-workbook/ahoy) for instance).
For instance, `:dojo|wipe` is equivalent to `:dojo +dojo/wipe`. This pokes the `%dojo` agent with the output from running the generator located at `/gen/dojo/wipe.hoon`.
The Hood/Helm tooling like `|install` are generators automatically routed by Dojo to the correct agent. `|commit`, for instance, is equivalent to `:hood +hood/commit`. `%hood` generators are special-cased because it is the system app.

View File

@ -685,7 +685,7 @@ as a reference:
[eyre-ext-ref]: /reference/arvo/eyre/external-api-ref [eyre-ext-ref]: /reference/arvo/eyre/external-api-ref
[eyre-guide]: /reference/arvo/eyre/guide [eyre-guide]: /reference/arvo/eyre/guide
[http-api-src]: https://github.com/urbit/urbit/tree/master/pkg/npm/http-api [http-api-src]: https://github.com/urbit/js-http-api
[eyre]: /reference/glossary/eyre [eyre]: /reference/glossary/eyre
[vane]: /reference/glossary/vane [vane]: /reference/glossary/vane
[arvo]: /reference/glossary/arvo [arvo]: /reference/glossary/arvo

View File

@ -10,8 +10,8 @@ Urbit represents JSON data with the `$json` structure (defined in `lull.hoon`).
JSON data on the web is encoded in text, so Urbit has two functions in `zuse.hoon` for dealing with this: JSON data on the web is encoded in text, so Urbit has two functions in `zuse.hoon` for dealing with this:
- [`+en-json:html`](/reference/hoon/zuse/2e_2-3#en-jsonhtml) - For printing `$json` to a text-encoded form. - [`+en:json:html`](/reference/hoon/zuse/2e_2-3#enjsonhtml) - For printing `$json` to a text-encoded form.
- [`+de-json:html`](/reference/hoon/zuse/2e_2-3#de-jsonhtml) - For parsing text-encoded JSON to a `$json` structure. - [`+de:json:html`](/reference/hoon/zuse/2e_2-3#dejsonhtml) - For parsing text-encoded JSON to a `$json` structure.
You typically want `$json` data converted to some other `noun` structure or vice versa, so Urbit has three collections of functions for this purpose, also in `zuse.hoon`: You typically want `$json` data converted to some other `noun` structure or vice versa, so Urbit has three collections of functions for this purpose, also in `zuse.hoon`:
@ -21,7 +21,7 @@ You typically want `$json` data converted to some other `noun` structure or vice
The relationship between these types and functions look like this: The relationship between these types and functions look like this:
![json diagram](https://media.urbit.org/docs/json-diagram.svg) ![json diagram](https://media.urbit.org/docs/json-diagram-v2.svg)
Note this diagram is a simplification - the `+dejs:format` and `+enjs:format` collections in particular are tools to be used in writing conversion functions rather than simply being used by themselves, but it demonstrates the basic relationships. Additionally, it is less common to do printing/parsing manually - this would typically be handled automatically by Eyre, though it may be necessary if you're retrieving JSON data via the web client vane Iris. Note this diagram is a simplification - the `+dejs:format` and `+enjs:format` collections in particular are tools to be used in writing conversion functions rather than simply being used by themselves, but it demonstrates the basic relationships. Additionally, it is less common to do printing/parsing manually - this would typically be handled automatically by Eyre, though it may be necessary if you're retrieving JSON data via the web client vane Iris.
@ -157,10 +157,10 @@ Now we can try calling the `+to-js` function with our data to convert it to `$js
] ]
``` ```
Let's also see how that `$json` would look as real JSON encoded in text. We can do that with `+en-json:html`: Let's also see how that `$json` would look as real JSON encoded in text. We can do that with `+en:json:html`:
``` ```
> (crip (en-json:html (to-js:user-lib usr))) > (en:json:html (to-js:user-lib usr))
'{"joined":1631440078,"username":"john456","name":["John","William","Smith"],"email":"john.smith@example.com"}' '{"joined":1631440078,"username":"john456","name":["John","William","Smith"],"email":"john.smith@example.com"}'
``` ```
@ -281,13 +281,13 @@ Let's look at an example. Here's a gate that takes in some `$json`, decodes it w
Let's try it: Let's try it:
``` ```
> +of-test (need (de-json:html '{"foo":"Hello"}')) > +of-test (need (de:json:html '{"foo":"Hello"}'))
'Hello!!!' 'Hello!!!'
> +of-test (need (de-json:html '{"bar":true}')) > +of-test (need (de:json:html '{"bar":true}'))
'Yes' 'Yes'
> +of-test (need (de-json:html '{"baz":["a","b","c"]}')) > +of-test (need (de:json:html '{"baz":["a","b","c"]}'))
'abc' 'abc'
``` ```
@ -322,13 +322,13 @@ Let's look at a practical example. Here's a generator you can save in the `%base
Let's try it: Let's try it:
``` ```
> +ou-test (need (de-json:html '{"foo":"hello","bar":true,"baz":[1,2,3,4]}')) > +ou-test (need (de:json:html '{"foo":"hello","bar":true,"baz":[1,2,3,4]}'))
['hello' %.y {1 2 3 4}] ['hello' %.y {1 2 3 4}]
> +ou-test (need (de-json:html '{"foo":"hello","bar":true}')) > +ou-test (need (de:json:html '{"foo":"hello","bar":true}'))
['hello' %.y {}] ['hello' %.y {}]
> +ou-test (need (de-json:html '{"foo":"hello"}')) > +ou-test (need (de:json:html '{"foo":"hello"}'))
[%key 'bar'] [%key 'bar']
dojo: hoon expression failed dojo: hoon expression failed
``` ```
@ -433,7 +433,7 @@ First, we'll save the code above as `user.hoon` in the `/mar` directory our of `
Let's quickly create a `$json` object to work with: Let's quickly create a `$json` object to work with:
``` ```
> =jon (need (de-json:html '{"joined":1631440078,"username":"john456","name":["John","William","Smith"],"email":"john.smith@example.com"}')) > =jon (need (de:json:html '{"joined":1631440078,"username":"john456","name":["John","William","Smith"],"email":"john.smith@example.com"}'))
> jon > jon
[ %o [ %o
p p
@ -492,7 +492,7 @@ Let's test it out by giving it our `$user` data:
Finally, let's see how that looks as JSON encoded in text: Finally, let's see how that looks as JSON encoded in text:
``` ```
> (crip (en-json:html (user-to-json usr))) > (en:json:html (user-to-json usr))
'{"joined":1631440078,"username":"john456","name":["John","William","Smith"],"email":"john.smith@example.com"}' '{"joined":1631440078,"username":"john456","name":["John","William","Smith"],"email":"john.smith@example.com"}'
``` ```

View File

@ -0,0 +1,102 @@
+++
title = "Eyre noun channels"
description = "A low-level overview of talking to Eyre's channel system in noun mode."
weight = 40
+++
So far, developers have typically used JSON to interact with Urbit ships through
Eyre's HTTP interface. As of kernel version `[%zuse 413]`, however, Eyre also
supports sending and received nouns directly. At this stage, there are limited
options for dealing with nouns in other languages, so this guide will only cover
the channel mechanics on a low-level. You may, however, be interested in the
[`@urbit/nockjs`](https://github.com/urbit/nockjs) package and the
work-in-progress [`json-bgon` PR for
`@urbit/js-http-api`](https://github.com/urbit/js-http-api/pull/4).
{% callout %}
If you are not familiar with low-level Eyre channel mechanics, please have a
read through the [Eyre guide](/reference/arvo/eyre/guide) first.
{% /callout %}
Eyre will create a noun channel if a `PUT` request to open a new channel
includes the following HTTP header:
```
content-type: application/x-urb-jam
```
...and the body contains the [`++jam`](/reference/hoon/stdlib/2p#jam) of a
`list` of `$channel-request`s with
[`@uw`](/reference/hoon/auras#table-of-auras) base64 encoding.
A `channel-request` is defined in `eyre.hoon` as:
```hoon
:: channel-request: an action requested on a channel
::
+$ channel-request
$% :: %ack: acknowledges that the client has received events up to :id
::
[%ack event-id=@ud]
:: %poke: pokes an application, validating :noun against :mark
::
[%poke request-id=@ud ship=@p app=term mark=@tas =noun]
:: %poke-json: pokes an application, translating :json to :mark
::
[%poke-json request-id=@ud ship=@p app=term mark=@tas =json]
:: %watch: subscribes to an application path
::
[%subscribe request-id=@ud ship=@p app=term =path]
:: %leave: unsubscribes from an application path
::
[%unsubscribe request-id=@ud subscription-id=@ud]
:: %delete: kills a channel
::
[%delete ~]
==
```
So, given the following (trivial) `(list channel-request)`:
```hoon
[%delete ~]~
```
...it is jammed to the following HEX:
```
0xACAE8CAD8CAC8F805
```
...then encoded in the following `@uw`-style base64 string in the request body:
```
0w2I.HEOJz.aOfw5
```
If the body of the request is not correctly encoded as described above, it will
fail with a `400` status.
If successful, you can then make a `GET` request to open an event stream for the
newly created channel. The `GET` request must include the following header:
```
x-channel-format: application/x-urb-jam
```
If the `GET` request is for an existing channel which is not already in noun
mode, it will fail with a `406` status code. You cannot change the channel mode
once the channel has been established. If the header is missing, Eyre will
assume you're asking for JSON mode, so it will also fail due to the channel mode
mismatch.
If the `GET` request is successful, you'll start receiving SSE events containing `@uw`-encoded jams of the following structure:
```hoon
[request-id=@ud channel-event]
```
See the [`$channel-event`](/reference/arvo/eyre/data-types#channel-event) entry
in the data type reference for more details.

View File

@ -274,6 +274,25 @@ One common scenario where `+cold` sees play is when writing [command line
interface (CLI) apps](/guides/additional/cli-tutorial). We usher the interface (CLI) apps](/guides/additional/cli-tutorial). We usher the
reader there to find an example where `+cold` is used. reader there to find an example where `+cold` is used.
### [`+less`](/reference/hoon/stdlib/4f/#less)
`+less` builds a `rule` to exclude matches to its first argument. It is commonly
used to filter out an undesired match.
```
> (;~(less buc next) [[1 1] " "])
[p=[p=1 q=2] q=[~ [p=' ' q=[p=[p=1 q=2] q=""]]]]
> (;~(less ace next) [[1 1] " "])
[p=[p=1 q=1] q=~]
```
Here we see that the first case refuses to parse `buc` `$` (which is not present), so
the `ace` ` ` succeeds (via `+next` which matches any character).
The second case attempts to parse the excluded character `ace` ` ` and fails on the
first character as it should.
### [`+knee`](/reference/hoon/stdlib/4f/#knee) ### [`+knee`](/reference/hoon/stdlib/4f/#knee)
Another important function in the parser builder library is `+knee`, used for building Another important function in the parser builder library is `+knee`, used for building

View File

@ -0,0 +1,216 @@
+++
title = "Remote Scry"
description = "Learn about scrying over the network"
weight = 5
+++
To [scry](/reference/glossary/scry) is to perform a *read* from Urbit's
referentially transparent namespace. In other words, it's a function from a
`path` to a `noun` (although in some cases, the resulting type may be more
constrained). Previously we only supported scrying within the same ship, but
from Kernel version `[%zuse 413]`, it is possible to scry from *other* ships.
{% callout %}
**Warning**
1. It should also be noted that, while responses are signed, encryption has not
yet been implemented.
2. The initial release of `[%zuse 413]` had a bug in the remote scry client
implementation that causes crashes and failed downloads. This issue was
fixed in [this PR](https://github.com/urbit/urbit/pull/6617), which was
[released](https://github.com/urbit/urbit/releases/tag/urbit-os-v2.141) (at
the same Kelvin) on June 1, 2023. Any ships that are still on the initial
`[%zuse 413]` Kelvin release will continue to experience this bug.
The recommended approach to dealing with this is to include a timer in clients
to fall back to using Ames for a download if a remote scry request doesn't
succeed within a certain amount of time. This is what Clay uses, and its main
advantage is that client ships on older pre-remote-scry Kelvins can still
download data from newer server ships.
{% /callout %}
## Lifecycle of a scry
When you think of scry, you probably think of [`.^`
(dotket)](/reference/hoon/rune/dot#-dotket). However, since networking is
asynchronous, this is not a suitable interface for remote scry. Instead, a ship
that wants to read from a remote part of the namespace will have to pass a
`%keen` task to its Ames, which then cooperates with Vere to produce the
desired data. In some future event when the result is available, Ames gives it
back as a `%tune` gift. From the requester's perspective, this is the entire
default lifecycle of a remote scry request.
Of course, you need to know how `%keen` and `%tune` look to be able to use
them. There are also a few exceptions to this default lifecycle. We'll go
through all of this in a moment, but first, let's look at what kind of data is
possible to scry.
## Publishing
At the moment, there are two vanes that can handle remote scry requests:
[Clay](/reference/arvo/clay/clay) and [Gall](/reference/arvo/gall/gall). Clay
uses it to distribute source code in a more efficient manner than is possible
with conventional Ames, but conceptually it only extends its [local
scries](/reference/arvo/clay/scry) over the network, with the notable
difference that you can't scry at the *current* time, since the requester
doesn't know when the request reaches the publisher. Additionally, the paths
are modified so that the vane and care are specified separately, like so:
`/c/x/1/base/sys/hoon/hoon`.
Gall is more interesting. First, let's clear up a possible misunderstanding
that could easily come up: remote scry does *not* involve calling an agent's
`+on-peek` arm. `+on-peek` scries always happen at the current time, and since
the requester can't know at which time the publisher handles the request, these
aren't possible to reliably serve.
Instead, agents *ask* Gall to `%grow` paths in the namespace on their behalf.
Gall will take care of incrementing version numbers, so that the same path
never maps to different nouns. The agent can also ask Gall to delete data,
either at a specific version number, or everything up to and including a
version number. Concretely, we've extended `$note:agent:gall` to include the
following cases:
```hoon
+$ note
$% ...
[%grow =path =page] :: publish
[%tomb =case =path] :: delete one
[%cull =case =path] :: delete up to
==
```
Here's an example sequence of cards that use these:
```hoon
[%pass /call/back/path %grow /foo atom+'lorem'] :: /foo version 0
[%pass /call/back/path %grow /foo atom+'ipsum'] :: /foo version 1
[%pass /call/back/path %grow /foo atom+'dolor'] :: /foo version 2
[%pass /call/back/path %grow /foo atom+'sit'] :: /foo version 3
[%pass /call/back/path %tomb ud+3 /foo] :: delete /foo version 3
[%pass /call/back/path %cull ud+1 /foo] :: delete /foo 0 through 1
[%pass /call/back/path %grow /foo atom+'amet'] :: /foo version 4
[%pass /call/back/path %grow /foo/bar atom+123] :: /foo/bar version 0
```
After this sequence of cards we would have the following mappings (assuming the
agent that emits them is named `%test`):
```hoon
/g/x/2/test//foo -> [%atom 'dolor']
/g/x/4/test//foo -> [%atom 'amet']
/g/x/0/test//foo/bar -> [%atom 123]
```
Let's pick apart the first one of these paths.
```hoon
/g :: g for Gall
/x :: a care of %x generally means "normal read"
/2 :: version number
/test :: the agent that published the data
/ :: ???
/foo :: the path that the data is published on
```
What's that lone `/` before the path? It signifies that this data is published
by *Gall* itself, instead of the `+on-peek` arm in the `%test` agent. As part
of the remote scry release, we will *reserve* part of the scry namespace for
Gall, effectively *preventing* any agents from directly publishing at those
paths. Though as we've seen, they can do it indirectly, by asking Gall to do it
for them using `%grow`.
As long as the extra `/` is included, Gall will serve scries with care `%x` at
both specific revision numbers and at arbitrary times. If the extra `/` is not
included, the scry has to happen at the current time, since we don't cache old
results of calling `+on-peek`.
### Additional Gall cares
Apart from supporting reads using the `%x` care, Gall now also supports three new cares:
- `%t` lists all subpaths that are bound under a path (only supported at the
current time, i.e. not remotely!).
- `%w` gives the latest revision number for a path (only supported at the
current time, i.e. not remotely!).
- `%z` gives the hash identifier of the value bound at the path (supported at
any time and at specific revisions, but not remotely).
All of these require the extra `/` to be present in the path, just as with `%x`.
## Scrying tasks
With this, we're ready to look at all the new tasks to, and gifts from, Ames:
```hoon
+$ task
$% ...
[%keen =ship =path] :: peek [ship /vane/care/case/spur]
[%yawn =ship =path] :: cancel request from arvo
[%wham =ship =path] :: cancels all scry requests from any vane
...
==
::
+$ gift
$% ...
[%tune spar roar=(unit roar)]
...
==
```
At this point, most of these should be very clear, but briefly:
- We pass `[%keen =ship =path]` to Ames to request to read from `path` on
`ship`. Example:
```hoon
[%pass /call/back/path %arvo %a %keen ~sampel /c/x/4/base/sys/hoon/hoon]
```
- We pass `[%yawn =ship =path]` to tell Ames that we're no longer interested in
a response. Example:
```hoon
[%pass /call/back/path %arvo %a %yawn ~sampel /g/x/4/test//foo]
```
- We pass `[%wham =ship =path]` to tell Ames that *no-one* on this ship is
interested in a response. Example:
```hoon
[%pass /call/back/path %arvo %a %wham ~sampel /g/x/4/test//foo]
```
- Ames gives the following to the original requester(s), either when it has a
response, or when the request gets `%wham`ed:
```hoon
[%tune [=ship =path] roar=(unit roar)]
```
The outer `unit` of `roar` will be `~` if Ames doesn't have a
response, but may have one in the future. Otherwise, it will
contain a signature and the data. The data in the
[`$roar`](/reference/arvo/ames/data-types#roar) may be `~`,
meaning that there is no value at this path and will never be
one.
## `-keen`
In addition to the above interface offered to agents, there is also support for
making scry requests from threads using `+keen` in `lib/strandio`. It accepts a
`[=ship =path]` and returns a `(unit page)`. There is also a [thread `ted/keen`
that demonstrates
this](https://github.com/urbit/urbit/blob/i/5788/remote-scry/pkg/arvo/ted/keen.hoon).
You can run it from the dojo using `-keen [ship path]`. For example, this reads
the `%noun` mark's source code out of `~zod`'s `%kids` desk, try it!
```
-keen [~zod /c/x/1/kids/mar/noun/hoon]
```
## Additional reading
- [Gall scry reference](/reference/arvo/gall/scry): Reference documentation of
Gall's vane-level and agent-level scry interface.
- [Ames API reference](/reference/arvo/ames/tasks): Reference documentation of `task`s that can be passed to Ames, including those for remote scries.

View File

@ -0,0 +1,179 @@
+++
title = "Udon (Markdown-esque)"
description = "Learn the basics of Udon"
weight = 97
+++
Udon is a domain-specific language for composing documents. Udon is very similar
to Markdown, but with some minor variation in syntax and some additional
Urbit-related affordances.
Udon files are compiled to `manx`es (Urbit's XHTML/XML representation), so can
easily be used to publish documents to the browser. Udon also allows you to
embed arbitrary [Sail](/guides/additional/sail) syntax, which itself allows
embedding arbitrary Hoon, so it can be quite powerful for dynamic content when
compiled against an appropriate subject.
This document will walk through the basics of Udon and its syntax.
## Basic example
Here's an example of an Udon file and its various allowed syntax.
````
;>
# H1
## H2
### H3
#### H4
##### H5
###### H6
This is a paragraph with _italics_, *bold* and
`inline code`. Sentences can be hard wrapped.
- unordered
- list
+ ordered
+ list
[link](https://urbit.org)
![image](https://media.urbit.org/guides/additional/dist/wut.svg)
```
fenced codeblock
(note language spec not supported)
```
horizontal rule:
---
> block quotes
may be hard-wrapped if indented
Backslash at end\
of line adds linebreak
Udon syntax may be prefixed with \*backslashes\* to escape.
Hoon atom literals like ~sampel-palnet and ~.foo will
be rendered as inline code.
;table
;tr
;td: Arbitrary
;td: Sail
==
;tr
;td: is
;td: allowed
==
==
````
## Syntax summary
- The first line of a `.udon` document *must* be a single rune: `;>`. This tells
the compiler to interpret everything following as udon.
- **Paragraphs**: Content on a single line will be made into a paragraph.
Paragraphs may be hard-wrapped, so consecutive lines of text will become a
single paragraph. The paragraph will be ended by an empty line or other block
element.
- **Headers**: lines beginning with 1-6 `#`s followed by a single space and then
some content (e.g. `## foo`) will be made into headers. The number of `#`s
dictates the header level.
- **Italics**: content wrapped in single `_`s (e.g. `_foo_`) will be made italic.
- **Bold**: content wrapped in single `*`s (e.g. `*foo*`) will be made bold.
- **Unordered lists**: lines beginning with `-` followed by a space will be made
into items in a list. List lines can be hard-wrapped, with two spaces
beginning each subsequent line to be included in the list. Lists can be nested
by indenting the `-`s a further two spaces for each level of nesting.
- **Ordered lists**: lines beginning with `+` followed by a space will be made into
ordered lists, and numbered in the order they appear. These have the same
wrapping and nesting logic as unordered lists.
- **Links**: this is standard markdown syntax: square bracks containing the display
content and then parentheses containing the URL, e.g.
`[foo](http://example.com)`. The URL may also be a relative link or an anchor
link.
- **Images**: this is also standard markdown; a link with an exclamation mark at the
beginning, e.g. `![foo](http://example.com/image.png)`. The square brackets
contain the alt-text and the the parentheses contain the image URL.
- **Inline code**: text wrapped in single backticks will be rendered verbatim in a
monospace font.
- **Fenced codeblocks**: Triple-backticks on their own line begin and end a
codeblock. All lines in between will be rendered verbatim in a monospace font.
Note that udon does not support a language specification after the opening
backticks like markdown does.
- **Horizontal rules**: Three or more hyphens (`---`) will create a horizontal rule.
- **Block quotes**: a line beginning with `>` creates a block quote. This may be
hard-wrapped, as long as the next line is indented two spaces. Block quotes
may contain anything, including other blockquotes.
- **Line breaks**: A line ending in a single backslash will have a line break
inserted at the end, so it will not flow together with the subsequent line as
is usually the case.
- **Escape characters**: You may prefix Udon syntax with a backslash to have it
treated as the literal text.
- **Hoon literals and wings**: Udon will automatically render any values with
atom aura syntax as inline code. It'll also render arms like `++foo:bar`,
`+$baz`, and `+*foo:bar:baz, as inline code.`
- **Sail**: this is hoon's native XML syntax. Udon will parse it, execute it, and
include the `+$manx`es produced in the resulting document. This means you can
embed arbitrary hoon in the document.
{% callout %}
Note that Udon is quite strict on its syntax, and may fail to parse if it's
incorrect.
{% /callout %}
## Udon Mode
An Udon file has a `.udon` extension (an `%udon` mark).
The first thing in an Udon file must be the `micgar` rune: `;>`
Micgar tells the Hoon compiler to interpret everything afterwards as Udon.
Udon-mode ends at the end of the file; there's no way to terminate micgar before
that. Udon is therefore useful for whole documents rather than embedding
snippets in other Hoon files.
The Hoon compiler will produce a `manx` as a result.
To scry out a file, compile it against the standard library, and stringify the
resulting XHTML, you can do:
```
%- crip
%- en-xml:html
!< manx
%+ slap !>(..zuse)
%- ream
.^(@t %cx /=the-desk=/the-file/udon)
```
Note you may want to provide more than just `..zuse` in the subject (like a
`bowl`), or if you're automatically building untrusted code, you may want to
provide less. It depends on your use case.
You can alternatively import and build udon files at compile time with a [`/*`
(fastar) Ford rune](/reference/hoon/rune/fas#-fastar) specifying an `%elem` mark
(which produces a `manx`), although note it compiles the Udon against an empty
subject, so Hoon in embedded Sail won't have access to standard library
functions. A mark conversion gate from `%udon` to `%elem` is another option.
## Examples
The [Docs App](https://urbit.org/applications/~pocwet/docs) includes a [a few
files written in
Udon](https://github.com/tinnus-napbus/docs-app/tree/main/bare-desk/doc) which
are useful as a reference.

View File

@ -14,3 +14,9 @@ insert_anchor_links = "right"
## [Roman Numerals](/guides/additional/workbook/roman) ## [Roman Numerals](/guides/additional/workbook/roman)
## [Solitaire Cipher](/guides/additional/workbook/solitaire) ## [Solitaire Cipher](/guides/additional/workbook/solitaire)
## [Water between Towers](/guides/additional/workbook/water-towers)
## [ABC Blocks](/guides/additional/workbook/abc-blocks)
## [Luhn Number](/guides/additional/workbook/luhn-number)

View File

@ -0,0 +1,574 @@
+++
title = "ABC Blocks"
weight = 11
+++
## Challenge: ABC Blocks
You are given a collection of blocks with two letters of the alphabet on each block. A complete alphabet is guaranteed among all sides of the blocks. You would like to check if a given word can be written with the provided set of blocks.
An example set of blocks:
```
(F E)
(A W)
(Q V)
(B M)
(X H)
(N P)
(I Z)
(G U)
(S R)
(K Y)
(T L)
(O C)
(J D)
(A N)
(O B)
(E R)
(F S)
(L Y)
(P C)
(Z M)
```
Your task for this challenge is to write a generator `abc-blocks`. It takes a cell of two arguments. The first argument is a `(list (pair @t @t))` which represents the input set of blocks. The second argument is a `@t` which represents the word that you'd like to check.
Your generator should first check if the input blocks cover all letters of the alphabet. If not, the generator should fail (possibly returning an error message). It should also check if the input word only has alphabetical characters (no spaces, numbers, or special characters). Otherwise it should fail. Then, it should check whether the word can be spelled with the blocks, either returning a `%.y` or `%.n`. It should not care about case, for the blocks or for the word.
Example usage:
```
> +abc-blocks [[['a', 'b'] ['c' 'd'] ['e' 'f'] ~] 'fad']
dojo: naked generator failure
> +abc-blocks [[['a', 'b'] ['c' 'd'] ['e' 'f'] ['g' 'h'] ['i' 'j'] ['k' 'l'] ['m' 'n'] ['o' 'p'] ['q' 'r'] ['s' 't'] ['u 'v'] ['w' 'x] ['y' z] ~] '12%-3']
dojo: naked generator failure
> +abc-blocks [[['a', 'B'] ['C' 'd'] ['e' 'F'] ['G' 'h'] ['i' 'J'] ['K' 'l'] ['m' 'N'] ['o' 'P'] ['Q' 'r'] ['s' 'T'] ['U 'v'] ['w' 'X'] ['y' Z'] ~] 'cat']
%.y
> +abc-blocks [[['a', 'B'] ['C' 'd'] ['e' 'F'] ['G' 'h'] ['i' 'J'] ['K' 'l'] ['m' 'N'] ['o' 'P'] ['Q' 'r'] ['s' 'T'] ['U 'v'] ['w' 'X'] ['y' Z'] ~] 'CAT']
%.y
> +abc-blocks [[['a', 'B'] ['C' 'd'] ['e' 'F'] ['G' 'h'] ['i' 'J'] ['K' 'l'] ['m' 'N'] ['o' 'P'] ['Q' 'r'] ['s' 'T'] ['U 'v'] ['w' 'X'] ['y' Z'] ~] 'BAT']
%.n
```
## Unit Tests
Following a principle of test-driven development, we compose a series of tests which allow us to rigorously check for expected behavior.
```hoon
/+ *test
/= abc-blocks /gen/abc-blocks
|%
:: test for failure of incomplete alphabet
::
++ test-01
=/ blocks `(list (pair @t @t))`[['a' 'b'] ['c' 'd'] ['e' 'f'] ['g' 'h'] ['i' 'j'] ['k' 'l'] ['m' 'n'] ['o' 'p'] ['q' 'q'] ['s' 't'] ['u' 'v'] ['w' 'x'] ['y' 'z'] ~]
=/ word `@t`'foo'
%- expect-fail |. (abc-blocks blocks word)
++ test-02
=/ blocks `(list (pair @t @t))`[['a' 'b'] ['c' 'd'] ['e' 'f'] ['g' 'h'] ['i' 'j'] ['k' 'l'] ['m' 'n'] ['q' 'r'] ['s' 't'] ['u' 'v'] ['w' 'x'] ['y' 'z'] ~]
=/ word `@t`'foo'
%- expect-fail |. (abc-blocks blocks word)
++ test-03
=/ blocks `(list (pair @t @t))`[['A' 'B'] ['C' 'D'] ['E' 'F'] ['G' 'H'] ['I' 'J'] ['K' 'L'] ['M' 'N'] ['O' 'P'] ['Q' 'R'] ['S' 'A'] ['U' 'V'] ['W' 'X'] ['Y' 'Z'] ~]
=/ word `@t`'foo'
%- expect-fail |. (abc-blocks blocks word)
++ test-04
=/ blocks `(list (pair @t @t))`[['A' 'B'] ['C' 'D'] ['e' 'f'] ['G' 'H'] ['I' 'J'] ['K' 'L'] ['M' 'N'] ['O' 'P'] ['Q' 'R'] ['S' 'A'] ['U' 'V'] ['W' 'X'] ['Y' 'Z'] ['A' 'B'] ['j' 'x']~]
=/ word `@t`'foo'
%- expect-fail |. (abc-blocks blocks word)
++ test-05
=/ blocks `(list (pair @t @t))`[['F' 'M'] ['W' 'S'] ['I' 'B'] ['Q' 'K'] ['Z' 'H'] ['G' 'L'] ['U' 'J'] ['V' 'A'] ['C' 'T'] ['R' 'O'] ['P' 'N'] ['E' 'Y'] ~]
=/ word `@t`'foo'
%- expect-fail |. (abc-blocks blocks word)
:: test for failure of input word
::
++ test-06
=/ blocks `(list (pair @t @t))`[['F' 'M'] ['W' 'S'] ['I' 'B'] ['D' 'X'] ['Q' 'K'] ['Z' 'H'] ['G' 'L'] ['U' 'J'] ['V' 'A'] ['C' 'T'] ['R' 'O'] ['P' 'N'] ['E' 'Y'] ~]
=/ word `@t`'foo bar'
%- expect-fail |. (abc-blocks blocks word)
++ test-07
=/ blocks `(list (pair @t @t))`[['F' 'M'] ['W' 'S'] ['I' 'B'] ['D' 'X'] ['Q' 'K'] ['Z' 'H'] ['G' 'L'] ['U' 'J'] ['V' 'A'] ['C' 'T'] ['R' 'O'] ['P' 'N'] ['E' 'Y'] ~]
=/ word `@t`'foo1bar'
%- expect-fail |. (abc-blocks blocks word)
++ test-08
=/ blocks `(list (pair @t @t))`[['F' 'M'] ['W' 'S'] ['I' 'B'] ['D' 'X'] ['Q' 'K'] ['Z' 'H'] ['G' 'L'] ['U' 'J'] ['V' 'A'] ['C' 'T'] ['R' 'O'] ['P' 'N'] ['E' 'Y'] ~]
=/ word `@t`'foo!bar'
%- expect-fail |. (abc-blocks blocks word)
:: test for success with various capitalizations and alphabets
::
++ test-09
=/ blocks `(list (pair @t @t))`[['F' 'M'] ['W' 'S'] ['I' 'B'] ['D' 'X'] ['Q' 'K'] ['Z' 'H'] ['G' 'L'] ['U' 'J'] ['V' 'A'] ['C' 'T'] ['R' 'O'] ['P' 'N'] ['E' 'Y'] ~]
=/ word `@t`'TRAP'
%+ expect-eq
!> %.y
!> (abc-blocks blocks word)
++ test-10
=/ blocks `(list (pair @t @t))`[['F' 'M'] ['W' 'S'] ['I' 'B'] ['D' 'X'] ['Q' 'K'] ['Z' 'H'] ['G' 'L'] ['U' 'J'] ['V' 'A'] ['C' 'T'] ['R' 'O'] ['P' 'N'] ['E' 'Y'] ~]
=/ word `@t`'trap'
%+ expect-eq
!> %.y
!> (abc-blocks blocks word)
++ test-11
=/ blocks `(list (pair @t @t))`[['F' 'M'] ['W' 'S'] ['I' 'B'] ['D' 'X'] ['Q' 'K'] ['Z' 'H'] ['G' 'L'] ['U' 'J'] ['V' 'A'] ['C' 'T'] ['R' 'O'] ['P' 'N'] ['E' 'Y'] ~]
=/ word `@t`'tRaP'
%+ expect-eq
!> %.y
!> (abc-blocks blocks word)
++ test-12
=/ blocks `(list (pair @t @t))`[['f' 'm'] ['w' 's'] ['i' 'b'] ['d' 'x'] ['q' 'k'] ['z' 'h'] ['g' 'l'] ['u' 'j'] ['v' 'a'] ['c' 't'] ['r' 'o'] ['p' 'n'] ['e' 'y'] ~]
=/ word `@t`'TRAP'
%+ expect-eq
!> %.y
!> (abc-blocks blocks word)
++ test-13
=/ blocks `(list (pair @t @t))`[['f' 'm'] ['w' 's'] ['i' 'b'] ['d' 'x'] ['q' 'k'] ['z' 'h'] ['g' 'l'] ['u' 'j'] ['v' 'A'] ['c' 't'] ['R' 'o'] ['p' 'n'] ['e' 'y'] ~]
=/ word `@t`'trap'
%+ expect-eq
!> %.y
!> (abc-blocks blocks word)
++ test-14
=/ blocks `(list (pair @t @t))`[['f' 'm'] ['w' 's'] ['i' 'b'] ['d' 'x'] ['q' 'k'] ['z' 'h'] ['g' 'l'] ['u' 'j'] ['v' 'A'] ['c' 't'] ['R' 'o'] ['p' 'n'] ['e' 'y'] ['x' 'y'] ['a' 'b'] ~]
=/ word `@t`'fsixqhgjvtrnyyb'
%+ expect-eq
!> %.y
!> (abc-blocks blocks word)
:: test for being unable to make a word
::
++ test-15
=/ blocks `(list (pair @t @t))`[['f' 'm'] ['w' 's'] ['i' 'b'] ['d' 'x'] ['q' 'k'] ['z' 'h'] ['g' 'l'] ['u' 'j'] ['v' 'A'] ['c' 't'] ['R' 'o'] ['p' 'n'] ['e' 'y'] ['x' 'y'] ['a' 'b'] ~]
=/ word `@t`'fsixqhgjvtrnyyyb'
%+ expect-eq
!> %.n
!> (abc-blocks blocks word)
++ test-16
=/ blocks `(list (pair @t @t))`[['f' 'm'] ['w' 's'] ['i' 'b'] ['d' 'x'] ['q' 'k'] ['z' 'h'] ['g' 'l'] ['u' 'j'] ['v' 'A'] ['c' 't'] ['R' 'o'] ['p' 'n'] ['e' 'y'] ['x' 'y'] ['a' 'b'] ~]
=/ word `@t`'fsixqhgujvtrnyyb'
%+ expect-eq
!> %.n
!> (abc-blocks blocks word)
++ test-17
=/ blocks `(list (pair @t @t))`[['f' 'm'] ['w' 's'] ['i' 'b'] ['d' 'x'] ['q' 'k'] ['z' 'h'] ['g' 'l'] ['u' 'j'] ['v' 'A'] ['c' 't'] ['R' 'o'] ['p' 'n'] ['e' 'y'] ['x' 'y'] ['a' 'b'] ~]
=/ word `@t`'AAA'
%+ expect-eq
!> %.n
!> (abc-blocks blocks word)
++ test-18
=/ blocks `(list (pair @t @t))`[['A' 'B'] ['C' 'D'] ['E' 'F'] ['G' 'H'] ['I' 'J'] ['K' 'L'] ['M' 'N'] ['O' 'P'] ['Q' 'R'] ['S' 'T'] ['U' 'V'] ['W' 'X'] ['Y' 'Z'] ~]
=/ word `@t`'AGENTT'
%+ expect-eq
!> %.n
!> (abc-blocks blocks word)
++ test-19
=/ blocks `(list (pair @t @t))`[['A' 'B'] ['C' 'D'] ['E' 'F'] ['G' 'H'] ['I' 'J'] ['K' 'L'] ['M' 'N'] ['O' 'P'] ['Q' 'R'] ['S' 'T'] ['U' 'V'] ['W' 'X'] ['Y' 'Z'] ['S' 'T'] ~]
=/ word `@t`'AGENTtT'
%+ expect-eq
!> %.n
!> (abc-blocks blocks word)
++ test-20
=/ blocks `(list (pair @t @t))`[['A' 'Z'] ['A' 'Z'] ['F' 'M'] ['W' 'S'] ['I' 'B'] ['D' 'X'] ['Q' 'K'] ['Z' 'H'] ['G' 'L'] ['U' 'J'] ['V' 'A'] ['C' 'T'] ['R' 'O'] ['P' 'N'] ['E' 'Y'] ~]
=/ word `@t`'ZAZAZ'
%+ expect-eq
!> %.n
!> (abc-blocks blocks word)
--
```
## Solutions
_These solutions were submitted by the Urbit community as part of a competition in ~2023.6. They are made available under the MIT License and CC0. We ask you to acknowledge authorship should you utilize these elsewhere._
### Solution #1
_By ~dozreg-toplud. In the process, he found and fixed a bug in the implementation of `++curr`._
```hoon
:: +abc-blocks: a solution to the HSL challenge #2
::
:: https://github.com/tamlut-modnys/template-hsl-abc-blocks
:: Takes a cell of arguments [blocks=(list (pair @t @t)) word=@t],
:: produces a flag.
:: Crashes if the alphabet is not represented in the blocks, or if there are
:: non-alphabetical characters in the blocks or in the word.
:: Produces %.y if the word can be written with the given list of blocks,
:: %.n otherwise
::
:: Solving this challenge revealed a bug in ++curr implementation! Refer to
:: the bottom of the file.
::
|^
:: Main part:
::
|= [blocks=(list (pair @t @t)) word=@t]
^- ?
=/ word-tape=tape (trip word)
:: Convert input values to lowercase
::
=. word-tape (cass word-tape)
=. blocks
%+ turn
blocks
|= [a=@t b=@t]
^- (pair @t @t)
:- (crip (cass (trip a)))
(crip (cass (trip b)))
:: Define alphabet
::
=/ alphabet=(set @t) (silt "abcdefghijklmnopqrstuvwxyz")
:: Assert: only alphabetical characters in the blocks
::
?. %+ levy
blocks
|= [a=@t b=@t]
^- ?
&((~(has in alphabet) a) (~(has in alphabet) b))
~_ leaf+"non-alphabetical character in blocks"
!!
:: Assert: only alphabetical characters in the word
::
?. %+ levy
word-tape
|= =cord
^- ?
(~(has in alphabet) cord)
~_ leaf+"non-alphabetical character in word"
!!
:: Assert: complete alphabet among the blocks
::
?. :: Iterate for block list indices i:
::
=+ i=0
|- ^- ?
:: if the alphabet set is empty, then the blocks contain all the letters
::
?: =(~ alphabet)
%.y
:: else, if we reached the end of the block list, then the opposite is true
::
?: =(i (lent blocks))
%.n
:: else, delete letters on a block from the alphabet and continue
::
=+ [a b]=(snag i blocks)
$(i +(i), alphabet (~(del in (~(del in alphabet) b)) a))
~_ leaf+"not complete alphabet in blocks"
!!
:: check if we can compose the word with the blocks
::
(check blocks word-tape)
::
:: Helping functions
:: ++check: checks if the word can be composed with the given blocks
::
++ check
|= [blocks=(list (pair @t @t)) word=tape]
^- ?
:: Self-reference
::
=* this-gate ..$
:: The word can be composed if it's empty, ...
::
?~ word %.y
:: ... or if the list of indices of blocks that contain i.word is not empty
:: and t.word can be composed with at least one list of the blocks made by
:: removing one of the blocks that contain i.word.
::
:: Logical OR on a list (%.n if the list is empty)
::
%+ lien
:: (list of lists of blocks made by removing one block that contains
:: i.word for each such block)
::
%+ turn
:: (list of block indices that contain i.word)
::
(find-in-blocks i.word blocks)
:: (gate that removes a block from a list of blocks by an index)
::
(curr (curr oust blocks) 1)
:: (gate that applies ++check to a given list of blocks and t.word)
::
(curr this-gate t.word)
:: ++ find-in-blocks: returns a list of block indices that contain
:: a given letter
::
++ find-in-blocks
|= [letter=@t blocks=(list (pair @t @t))]
^- (list @)
=+ i=0
=| =(list @)
:: Iterate over elements of blocks
::
|-
?~ blocks
list
:: If a block contains the letter, append its index to the list
::
=? list |(=(letter -:i.blocks) =(letter +:i.blocks)) (snoc list i)
$(i +(i), blocks t.blocks)
:: ++curr: rewrite ++curr from stdlib because the original has a bug
:: (https://github.com/urbit/urbit/issues/6655)
::
++ curr
|* [a=$-(^ *) c=*]
|* b=_,.+<-.a
(a b c)
::
--
```
### Solution #2
_By ~bantus-follus_
```hoon
|= [blocks=(list (pair @t @t)) word=@t]
=<
=/ alphacheck (alphabet-check merged-blocks)
?. (character-check word)
~| "Input word contains invalid characters." !!
=/ spellcheck (spell-check word)
spellcheck
|%
++ alphabet "abcdefghijklmnopqrstuvwxyz"
::
:: merges all blocks into a single tape
++ merged-blocks (merge blocks)
::
:: turns all blocks into individual tapes
++ tape-blocks (turn (turn (turn (turn blocks pair-to-list) crip) trip) cass)
++ merge
|= blocks=(list (pair @t @t))
^- tape
(cass (trip (crip `(list @t)`(zing (turn blocks pair-to-list)))))
::
:: converts each pair to a (list @t)
++ pair-to-list
|= input=(pair @t @t)
^- (list @t)
[-:input +:input ~]
::
:: checks if input blocks cover all letters of the alphabet
++ alphabet-check
|= input=tape
^- ?
=/ i 0
|-
?: =(i 26)
%.y
?~ (find [(snag i alphabet)]~ input)
~| "Full alphabet not found. {<(snag i alphabet)>} not in blocks" !!
$(i +(i))
::
:: checks if input word has valid chaaracters. %.y means all characters are valid
++ character-check
|= word=@t
^- ?
=/ i 0
=/ tapeword (cass (trip word))
|-
?: =(+(i) (lent tapeword))
%.y
?~ (find [(snag i tapeword)]~ alphabet)
%.n
$(i +(i))
::
:: checks if the word can be spelled using the input blocks
++ spell-check
|= word=@t
^- ?
=/ tapeword (cass (trip word))
=/ tape-blocks tape-blocks
=/ i 0
=/ letter (snag i tapeword)
|-
?: =(+(i) (lent tapeword))
=/ blockcheck (check-blocks [tape-blocks letter])
?. check:blockcheck
%.n
%.y
=/ blockcheck (check-blocks [tape-blocks letter])
?. check:blockcheck
%.n
$(i +(i), letter (snag +(i) tapeword), tape-blocks (oust [num:blockcheck 1] tape-blocks))
:: cycles through blocks, checking for a letter
++ check-blocks
|= [tape-blocks=(list tape) letter=@t]
^- [num=@ check=?]
=/ i 0
=/ block (snag i tape-blocks)
|-
?: =(+(i) (lent tape-blocks))
?~ (find [letter]~ block)
[~ %.n]
[i %.y]
?~ (find [letter]~ block)
$(i +(i), block (snag +(i) tape-blocks))
[i %.y]
--
```
### Solution #3
_By ~dannul-bortux_
```hoon
!:
|= [inlist=(list [@t @t]) inword=@t]
^- $?(%.y %.n)
:: If, input list is empty
::
?: =(0 (lent inlist))
:: Then, throw error
::
~| 'Error - input list cannot be empty'
!!
=< (validate-input inlist (cass (trip inword)))
|%
++ validate-input
|= [blocks=(list [@t @t]) cword=tape]
=/ lblocks (to-lowercase blocks)
?: ?& (validate-alpha-only cword)
(validate-complete-alpha lblocks)
(validate-word lblocks cword)
==
%.y
%.n
++ validate-alpha-only
|= w=tape
=/ i 0
:: =/ tword (trip w)
|-
?: =(i (lent w))
%.y
?. ?& (gte `@ud`(snag i w) 97)
(lte `@ud`(snag i w) 122)
==
!!
%= $
i +(i)
==
++ validate-complete-alpha
|= blocks=(list [@t @t])
=/ alphabet "abcdefghijklmnopqrstuvwxyz"
=/ bltape (block-taper blocks)
:: ~& "bl tape is {<bltape>}"
:: =/ bltape "abcdefghijklmnopqrstuvwxyz"
=/ i 0
|-
?: =(i (lent alphabet))
:: ~& "returning yes"
%.y
?: =(~ (find (trip (snag i alphabet)) bltape))
:: ~& "returning no at letter: {<(snag i alphabet)>}"
!!
%= $
:: alphabet (remove-letters alphabet (snag i blocks))
i +(i)
==
:: %.n
:: ++ remove-letters
:: |= [in=tape let=[@t @t]]
:: ~& "removing letters"
:: in
++ block-taper
|= b=(list [@t @t])
=/ i 0
=/ bltape *tape
|-
?: =(i (lent b))
bltape
:: ~& +2:(snag i `(list [@t @t])`b)
%= $
bltape (snoc (snoc bltape +2:(snag i `(list [@t @t])`b)) +3:(snag i `(list [@t @t])`b))
:: bltape (snoc bltape 'a')
i +(i)
==
++ validate-word
|= [blocks=(list [@t @t]) cword=tape]
=/ wordcombos `(list tape)`(get-combos blocks)
:: ~& "validating word"
:: ~& wordcombos
=/ i 0
|-
?: =(i (lent wordcombos))
%.n
:: ~& (snag i wordcombos)
?: (word-compare (snag i wordcombos) cword)
%.y
%= $
i +(i)
==
:: ?: ?& (validate-alph-aonly )
:: (validate-complete-alpha )
:: (validate-word )
:: ==
:: %.y
:: %.n
++ get-combos
|= n=(list [@t @t])
=/ i 1
=/ outlist `(list tape)`(snoc `(list tape)`(snoc *(list tape) (trip +2:(snag 0 `(list [@t @t])`n))) (trip +3:(snag 0 `(list [@t @t])`n)))
:: ~& outlist
|-
?: =(i (lent n))
outlist
:: ?: =(i 0)
:: %= $
:: outlist (snoc `(list tape)`(snoc `(list tape)`outlist (trip +2:(snag 0 `(list [@t @t])`n))) (trip +3:(snag 0 `(list [@t @t])`n)))
:: i +(i)
:: ==
=/ j 0
=/ temp *(list tape)
|-
?: =(j (lent outlist))
%= ^$
outlist temp
i +(i)
==
%= $
:: temp (snoc (snoc `(list tape)`outlist (trip +2:(snag 0 `(list [@t @t])`n))) (trip +3:(snag 0 `(list [@t @t])`n)))
temp (snoc `(list tape)`(snoc `(list tape)`temp (snoc (snag j outlist) +2:(snag i `(list [@t @t])`n))) (snoc (snag j outlist) +3:(snag i `(list [@t @t])`n)))
j +(j)
==
:: %= $
:: i +(i)
:: j 3
:: ==
++ word-compare
|= [combo=tape cword=tape]
=/ i 0
:: ~& combo
:: ~& cword
|-
:: ~& combo
?: =(i (lent cword))
%.y
?: =(~ (find (trip (snag i cword)) combo))
%.n
%= $
combo (oust [+3:(find (trip (snag i cword)) combo) 1] combo)
i +(i)
==
++ to-lowercase
|= blocks=(list [@t @t])
=/ lcase *(list [@t @t])
=/ i 0
|-
?: =(i (lent blocks))
:: lcase
:: ~& lcase
lcase
=/ m (crip (cass (trip +2:(snag i blocks))))
=/ n (crip (cass (trip +3:(snag i blocks))))
%= $
lcase (snoc `(list [@t @t])`lcase [m n])
:: lcase (snoc `(list [@t @t])`lcase ['a' 'b'])
i +(i)
==
:: blocks
:: %.n
--
```

View File

@ -3,11 +3,9 @@ title = "Competitive Programming"
weight = 10 weight = 10
+++ +++
# Competitive Programming
Let's take a quick look at implementations for some common tasks in Hoon. I assume that you want to use library tools whenever possible, but you can delve into the source code itself if you want to know more. Let's take a quick look at implementations for some common tasks in Hoon. I assume that you want to use library tools whenever possible, but you can delve into the source code itself if you want to know more.
### Sorting ## Sorting
- Given a `list` of values, sort the values according to a given rule and produce a `list`. - Given a `list` of values, sort the values according to a given rule and produce a `list`.
@ -44,7 +42,7 @@ If something isn't a `list`, the easiest way to sort it is to convert it to a `l
~[1 2 3 4 5 6 7 8 9 10] ~[1 2 3 4 5 6 7 8 9 10]
``` ```
### Bitwise Operations ## Bitwise Operations
Bitwise operations are necessary when you are closely packing data into binary formats or otherwise dealing with binary data. Basically there are three scenarios: Bitwise operations are necessary when you are closely packing data into binary formats or otherwise dealing with binary data. Basically there are three scenarios:
@ -52,7 +50,7 @@ Bitwise operations are necessary when you are closely packing data into binary f
2. Working with MIME-type data. Urbit has standard library support for yielding and parsing these, but it's sometimes a bit confusing where they may be located. 2. Working with MIME-type data. Urbit has standard library support for yielding and parsing these, but it's sometimes a bit confusing where they may be located.
3. Directly processing particular kinds of data streams, like audio or video data. On Urbit, you'll be serving these or interfacing with an external service. (Remember, Urbit is more like a server than a GPU.) 3. Directly processing particular kinds of data streams, like audio or video data. On Urbit, you'll be serving these or interfacing with an external service. (Remember, Urbit is more like a server than a GPU.)
#### Binary Operations ### Binary Operations
If you are working with packed binary data, you'll typically print the atom data with a `@ux` unsigned hexadecimal aura. If you are working with packed binary data, you'll typically print the atom data with a `@ux` unsigned hexadecimal aura.
@ -153,7 +151,7 @@ Standard bitwise logical operations are available:
0b1111.1111.1111.0100 0b1111.1111.1111.0100
``` ```
#### MIME Data Operations ### MIME Data Operations
[MIME data types](https://en.wikipedia.org/wiki/MIME) allow HTTP communications to include rich content. The `++html` core in the standard library provides quite a few operations for encoding and decoding MIME-typed values. [MIME data types](https://en.wikipedia.org/wiki/MIME) allow HTTP communications to include rich content. The `++html` core in the standard library provides quite a few operations for encoding and decoding MIME-typed values.
@ -187,7 +185,7 @@ There are operations for JSON, [Base58](https://en.wikipedia.org/wiki/Binary-to-
Urbit furthermore has a notion of _jammed noun_, which is essentially a serialization (marshaling, pickling) of a noun into an atom. Urbit furthermore has a notion of _jammed noun_, which is essentially a serialization (marshaling, pickling) of a noun into an atom.
### Primes and Factors ## Primes and Factors
To calculate a prime number, the tried-and-true method is the Sieve of Eratosthenes, which is an elimination algorithm. In other words, we need to be able to calculate factors of a given positive integer. We're actually going to do an even simpler (and less efficient) method here, and leave the Sieve as an exercise to the reader. To calculate a prime number, the tried-and-true method is the Sieve of Eratosthenes, which is an elimination algorithm. In other words, we need to be able to calculate factors of a given positive integer. We're actually going to do an even simpler (and less efficient) method here, and leave the Sieve as an exercise to the reader.
@ -234,7 +232,7 @@ Now that we can find factors, it should be straightforward to find primes. In t
- How would you change this algorithm to the more efficient Sieve of Eratosthenes? - How would you change this algorithm to the more efficient Sieve of Eratosthenes?
### Pragmatic Input/Output ## Pragmatic Input/Output
While Hoon has a sophisticated text parsing library, the primitives are rather low-level and we won't assume that you want to directly implement a parser using them in a rapid-fire competitive environment. While Hoon has a sophisticated text parsing library, the primitives are rather low-level and we won't assume that you want to directly implement a parser using them in a rapid-fire competitive environment.
@ -245,11 +243,11 @@ Fortunately, there is a regular expression library you can incorporate into your
- https://github.com/lynko/re.hoon - https://github.com/lynko/re.hoon
### Functional Operators ## Functional Operators
Hoon is a functional programming language, so naturally it supports the basic collective operations which functional programming languages expect. Hoon is a functional programming language, so naturally it supports the basic collective operations which functional programming languages expect.
#### Curry ### Curry
[_Currying_](https://en.wikipedia.org/wiki/Currying) describes taking a function of multiple arguments and reducing it to a set of functions that each take only one argument. _Binding_, an allied process, is used to set the value of some of those arguments permanently. Hoon has a left-bind `++cury` and a right-bind `++curr`. [_Currying_](https://en.wikipedia.org/wiki/Currying) describes taking a function of multiple arguments and reducing it to a set of functions that each take only one argument. _Binding_, an allied process, is used to set the value of some of those arguments permanently. Hoon has a left-bind `++cury` and a right-bind `++curr`.
@ -265,7 +263,7 @@ Hoon is a functional programming language, so naturally it supports the basic co
117 117
``` ```
#### Map ### Map
The Map operation describes applying a function to each item of a set or iterable object, resulting in the same final number of items transformed. In Hoon terms, we would say slamming a gate on each member of a `list` or `set`. The standard library arms that accomplish this include [`++turn`](https://developers.urbit.org/reference/hoon/stdlib/2b#turn) for a `list`, [`++run:in`](https://developers.urbit.org/reference/hoon/stdlib/2h#repin) for a `set`, and [`++run:by`](https://developers.urbit.org/reference/hoon/stdlib/2i#runby) for a `map`. The Map operation describes applying a function to each item of a set or iterable object, resulting in the same final number of items transformed. In Hoon terms, we would say slamming a gate on each member of a `list` or `set`. The standard library arms that accomplish this include [`++turn`](https://developers.urbit.org/reference/hoon/stdlib/2b#turn) for a `list`, [`++run:in`](https://developers.urbit.org/reference/hoon/stdlib/2h#repin) for a `set`, and [`++run:by`](https://developers.urbit.org/reference/hoon/stdlib/2i#runby) for a `map`.
@ -273,7 +271,7 @@ The Map operation describes applying a function to each item of a set or iterabl
> (turn `(list @ud)`~[1 2 3 4 5] one) > (turn `(list @ud)`~[1 2 3 4 5] one)
``` ```
#### Reduce ### Reduce
The Reduce operation applies a function as a sequence of pairwise operations to each item, resulting in one summary value. The standard library arms that accomplish this are [`++roll`](https://developers.urbit.org/reference/hoon/stdlib/2b#roll) and [`++reel`](https://developers.urbit.org/reference/hoon/stdlib/2b#reel) for a `list`, [`++rep:in`](https://developers.urbit.org/reference/hoon/stdlib/2h#repin) for a `set`, and [`++rep:by`](https://developers.urbit.org/reference/hoon/stdlib/2i#repby) for a `map`. The Reduce operation applies a function as a sequence of pairwise operations to each item, resulting in one summary value. The standard library arms that accomplish this are [`++roll`](https://developers.urbit.org/reference/hoon/stdlib/2b#roll) and [`++reel`](https://developers.urbit.org/reference/hoon/stdlib/2b#reel) for a `list`, [`++rep:in`](https://developers.urbit.org/reference/hoon/stdlib/2h#repin) for a `set`, and [`++rep:by`](https://developers.urbit.org/reference/hoon/stdlib/2i#repby) for a `map`.
@ -284,7 +282,7 @@ The Reduce operation applies a function as a sequence of pairwise operations to
b=120 b=120
``` ```
#### Filter ### Filter
The Filter operation applies a true/false function to each member of a collection, resulting in some number of items equal to or fewer than the size of the original set. In Hoon, the library arms that carry this out include [`++skim`](https://developers.urbit.org/reference/hoon/stdlib/2b#skim), [`++skid`](https://developers.urbit.org/reference/hoon/stdlib/2b#skid), [`++murn`](https://developers.urbit.org/reference/hoon/stdlib/2b#murn) for a `list`, and [`++rib:by`](https://developers.urbit.org/reference/hoon/stdlib/2i#ribby) for a `map`. The Filter operation applies a true/false function to each member of a collection, resulting in some number of items equal to or fewer than the size of the original set. In Hoon, the library arms that carry this out include [`++skim`](https://developers.urbit.org/reference/hoon/stdlib/2b#skim), [`++skid`](https://developers.urbit.org/reference/hoon/stdlib/2b#skid), [`++murn`](https://developers.urbit.org/reference/hoon/stdlib/2b#murn) for a `list`, and [`++rib:by`](https://developers.urbit.org/reference/hoon/stdlib/2i#ribby) for a `map`.
@ -297,7 +295,7 @@ An interesting feature of Hoon is that it really prefers functions-that-produce-
- [Functional Programming](https://developers.urbit.org/guides/core/hoon-school/Q-func) - This module will discuss some gates-that-work-on-gates and other assorted operators that are commonly recognized as functional programming tools. - [Functional Programming](https://developers.urbit.org/guides/core/hoon-school/Q-func) - This module will discuss some gates-that-work-on-gates and other assorted operators that are commonly recognized as functional programming tools.
### Floating-Point Operations ## Floating-Point Operations
`@ud` unsigned decimal operations are, of course, postive-integer-only. For floating-point maths, you will need to work with `@rs` for 32-bit single-precision IEEE 754 floating-point atoms. These are prefixed with a single `.` which is _not_ a decimal point. `@ud` unsigned decimal operations are, of course, postive-integer-only. For floating-point maths, you will need to work with `@rs` for 32-bit single-precision IEEE 754 floating-point atoms. These are prefixed with a single `.` which is _not_ a decimal point.
@ -332,7 +330,7 @@ Equivalent mathematical operations for `@rs` values are available in the `++rs`
(I picked the above set of examples after perusing the excellent book [Antti Laaksonen (2017) _Guide to Competitive Programming: Learning and Improving Algorithms Through Contests_](https://link.springer.com/book/10.1007/978-3-319-72547-5).) (I picked the above set of examples after perusing the excellent book [Antti Laaksonen (2017) _Guide to Competitive Programming: Learning and Improving Algorithms Through Contests_](https://link.springer.com/book/10.1007/978-3-319-72547-5).)
### Debugging and Troubleshooting ## Debugging and Troubleshooting
Static typing with compile-time type checking turns out to be a secret strength of Hoon. Once you've satisfied the typechecker, your code is often surprisingly free of bugs (compared to e.g. Python). Static typing with compile-time type checking turns out to be a secret strength of Hoon. Once you've satisfied the typechecker, your code is often surprisingly free of bugs (compared to e.g. Python).

View File

@ -3,7 +3,7 @@ title = "Gleichniszahlenreihe"
weight = 30 weight = 30
+++ +++
# Challenge: The Look-and-Say Sequence ## Challenge: The Look-and-Say Sequence
_Gleichniszahlenreihe_, or the [look-and-say sequence](https://en.wikipedia.org/wiki/Look-and-say_sequence), is constructed from an aural description of a sequence of numbers. _Gleichniszahlenreihe_, or the [look-and-say sequence](https://en.wikipedia.org/wiki/Look-and-say_sequence), is constructed from an aural description of a sequence of numbers.
@ -13,7 +13,7 @@ This is a fairly complicated program. You need a few parts: the ability to tak
- Compose a `%say` generator which carries out the look-and-say sequence calculation for a given input. The input should be a number which indicates which value in the sequence is desired (e.g. 1→1, 2→11, 3→21). - Compose a `%say` generator which carries out the look-and-say sequence calculation for a given input. The input should be a number which indicates which value in the sequence is desired (e.g. 1→1, 2→11, 3→21).
## Solutions ## Solutions
_These solutions were submitted by the Urbit community as part of the Hoon School Live ~2022.2 cohort. They are made available under both the [MIT license](https://mit-license.org/) and the [CC0 license](https://creativecommons.org/share-your-work/public-domain/cc0). We ask you to acknowledge authorship should you utilize these elsewhere._ _These solutions were submitted by the Urbit community as part of the Hoon School Live ~2022.2 cohort. They are made available under both the [MIT license](https://mit-license.org/) and the [CC0 license](https://creativecommons.org/share-your-work/public-domain/cc0). We ask you to acknowledge authorship should you utilize these elsewhere._

View File

@ -0,0 +1,574 @@
+++
title = "Luhn Number"
weight = 128
+++
## Challenge: Luhn Number
The Luhn test is used by some credit card companies to distinguish valid credit card numbers from what could be a random selection of digits.
A Luhn number is a sequence of digits that passes the following test:
1. Reverse the order of the digits.
2. Take the first, third, and every odd-numbered digit in the reversed digits and sum them to form `s1`
3. Taking the second, fourth, and every even-numbered digit in the reversed digits:
1. Multiply each by two. Within each doubled digit, sum those digits (if the answer is greater than nine) to form partial sums.
2. Sum the partial sums of the even digits to form `s2`
4. If `s1 + s2` ends in zero then the original number is in the form of a valid credit card number as verified by the Luhn test.
For example, if the trial number is 49927398716:
```
Reverse the digits:
61789372994
Sum the odd digits:
6 + 7 + 9 + 7 + 9 + 4 = 42 = s1
The even digits:
1, 8, 3, 2, 9
Two times each even digit:
2, 16, 6, 4, 18
Sum the digits of each multiplication:
2, 7, 6, 4, 9
Sum the last:
2 + 7 + 6 + 4 + 9 = 28 = s2
s1 + s2 = 70 ends in zero, which means that 49927398716 passes the Luhn test
```
Your task for this challenge is as follows. First you will write a library file `lib/luhn-number` with a core containing an arm named `++validate`. `validate` will be a gate that takes as input a `tape` which is a sequence of digits, and returns either a `%.y` or `%.n` if the number is a Luhn number or not.
Example usage:
```
> =ln -build-file %/lib/luhn-number/hoon
> (validate:ln "49927398716")
%.y
> (validate:ln "1234")
%.n
```
Next you will write a generator file `gen/luhn-number` which takes as input a `tape` which consists of digits or the `*` character, such as:
```
"*1*25**574*18403"
"****"
"584"
```
It will return a `(list tape)` which contains all of the Luhn numbers that fit that format. The numbers should be in lexicographic order (smallest to largest by first digit, then second digit, and so on). You may choose to import and use your `++validate` arm, or perhaps use some other strategy.
Example usage:
```
> +luhn-number "**123"
["01123" "15123" "20123" "39123" "44123" "58123" "63123" "77123" "82123" "96123" ~]
> +luhn-number "123"
~
> +luhn-number "49927398716"
[49927398716 ~]
```
Some notes:
* We take the input as a `tape` rather than a `@ud` because a potential credit card number can have leading zeros.
* Note that in Hoon, we index starting from 0 -- so the first digit will be in the 0th index, second in 1st index, and so on.
* This website may be of use for both checking if a number is Luhn and generating a list from missing digits: https://www.dcode.fr/luhn-algorithm
* Don't worry about numbers with less than 2 digits, or improperly formatted input (with letters and spaces etc.). You can assume that the input tape will have the correct format.
## Unit Tests
Following a principle of test-driven development, we compose a series of tests which allow us to rigorously check for expected behavior.
```hoon
/+ *test
/+ ln=luhn-number
/= luhn-number /gen/luhn-number
|%
:: test valid numbers
::
++ test-01
%+ expect-eq
!> %.y
!> (validate:ln "49927398716")
++ test-02
%+ expect-eq
!> %.y
!> (validate:ln "1234567812345670")
++ test-03
%+ expect-eq
!> %.y
!> (validate:ln "4417123456789105")
++ test-04
%+ expect-eq
!> %.y
!> (validate:ln "20210917131347022")
++ test-05
%+ expect-eq
!> %.y
!> (validate:ln "1806040794512")
++ test-06
%+ expect-eq
!> %.y
!> (validate:ln "9856849794512")
++ test-07
%+ expect-eq
!> %.y
!> (validate:ln "5995841300227")
++ test-08
%+ expect-eq
!> %.y
!> (validate:ln "00")
++ test-09
%+ expect-eq
!> %.y
!> (validate:ln "34")
++ test-10
%+ expect-eq
!> %.y
!> (validate:ln "00005991")
++ test-11
%+ expect-eq
!> %.y
!> (validate:ln "02310568590238405")
:: test invalid numbers
::
++ test-12
%+ expect-eq
!> %.n
!> (validate:ln "1234")
++ test-13
%+ expect-eq
!> %.n
!> (validate:ln "92")
++ test-14
%+ expect-eq
!> %.n
!> (validate:ln "00001463")
++ test-15
%+ expect-eq
!> %.n
!> (validate:ln "754717798")
++ test-16
%+ expect-eq
!> %.n
!> (validate:ln "507274573")
++ test-17
%+ expect-eq
!> %.n
!> (validate:ln "2342352356198234238")
++ test-18
%+ expect-eq
!> %.n
!> (validate:ln "02310568590238406")
++ test-19
%+ expect-eq
!> %.n
!> (validate:ln "5019876543217144")
++ test-20
%+ expect-eq
!> %.n
!> (validate:ln "220743131719012023")
:: test number generation
::
++ test-21
%+ expect-eq
!> `(list tape)`["01123" "15123" "20123" "39123" "44123" "58123" "63123" "77123" "82123" "96123" ~]
!> (luhn-number "**123")
++ test-22
%+ expect-eq
!> `(list tape)`~
!> (luhn-number "123")
++ test-23
%+ expect-eq
!> `(list tape)`["12345690" ~]
!> (luhn-number "12345690")
++ test-24
%+ expect-eq
!> `(list tape)`["023259872" "123259871" "223259870" "323259879" "423259878" "523259877" "623259876" "723259875" "823259874" "923259873" ~]
!> (luhn-number "*2325987*")
++ test-25
%+ expect-eq
!> `(list tape)`["845927593912820" ~]
!> (luhn-number "8459275*3912820")
++ test-26
%+ expect-eq
!> `(list tape)`["00" "18" "26" "34" "42" "59" "67" "75" "83" "91" ~]
!> (luhn-number "**")
++ test-27
%+ expect-eq
!> `(list tape)`["4002" "4192" "4242" "4382" "4432" "4572" "4622" "4762" "4812" "4952" ~]
!> (luhn-number "4**2")
++ test-28
%+ expect-eq
!> `(list tape)`["10017" "10157" "10207" "10397" "10447" "10587" "10637" "10777" "10827" "10967" "11007" "11197" "11247" "11387" "11437" "11577" "11627" "11767" "11817" "11957" "12047" "12187" "12237" "12377" "12427" "12567" "12617" "12757" "12807" "12997" "13037" "13177" "13227" "13367" "13417" "13557" "13607" "13797" "13847" "13987" "14027" "14167" "14217" "14357" "14407" "14597" "14647" "14787" "14837" "14977" "15057" "15107" "15297" "15347" "15487" "15537" "15677" "15727" "15867" "15917" "16097" "16147" "16287" "16337" "16477" "16527" "16667" "16717" "16857" "16907" "17087" "17137" "17277" "17327" "17467" "17517" "17657" "17707" "17897" "17947" "18077" "18127" "18267" "18317" "18457" "18507" "18697" "18747" "18887" "18937" "19067" "19117" "19257" "19307" "19497" "19547" "19687" "19737" "19877" "19927" ~]
!> (luhn-number "1***7")
--
```
## Solutions
_These solutions were submitted by the Urbit community as part of a competition in ~2023.6. They are made available under the MIT License and CC0. We ask you to acknowledge authorship should you utilize these elsewhere._
### Solution #1
_By ~dozreg-toplud._
`lib/luhn-number.hoon`
```hoon
:: lib/luhn-number.hoon
:: Library for HSL challenge #3
::
|%
:: ++validate: gate defined in the challenge
::
++ validate
|= a=tape
^- ?
=. a (flop a)
=/ a-digits=(list @) (turn a (cury slav %ud))
=/ s1=@ (roll (skim-odd a-digits) add)
=; s2=@
=(0 (mod (add s1 s2) 10))
%+ roll
(skim-even a-digits)
:: (gate that adds digits of 2*i to the accumulator)
::
|= [i=@ acc=@]
=+ i2=(mul i 2)
:(add acc (div i2 10) (mod i2 10))
:: ++skim-odd: return elements of a list with odd indices (1-indexed)
::
++ skim-odd
|* a=(list)
^+ a
?~ a
~
?~ t.a
~[i.a]
[i.a $(a t.t.a)]
:: ++skim-even: return elements of a list with even indices (1-indexed)
::
++ skim-even
|* a=(list)
^+ a
?: |(?=(~ a) ?=(~ t.a))
~
[i.t.a $(a t.t.a)]
::
--
```
`gen/luhn-number.hoon`
```hoon
:: gen/luhn-number.hoon
:: Naked generator for HSL challenge #3
::
/+ *luhn-number
::
|= a=tape
^- (list tape)
=* this-gate ..$
=/ index-tar=(unit @) (find "*" a)
:: if no * in `a`,
::
?~ index-tar
:: then return empty list if `a` is not a Luhn number, else return list
:: with `a`
::
?. (validate a)
~
~[a]
:: else, replace first * with a digit, call this gate for each digit 0-9,
:: weld the results
::
=/ dry-snap ^~((bake snap ,[tape @ char]))
%- zing
%+ turn
"0123456789"
(cork (cury (cury dry-snap a) u.index-tar) this-gate)
```
### Solution #2
_By ~pardun-nollev._
`lib/luhn-number.hoon`
```hoon
|%
++ validate
|= input=tape
=/ input (flop input)
=(0 (mod (add (get-s1 input) (get-s2 input)) 10))
++ get-s1
|= input=tape
^- @ud
(roll (odd-digits input) add)
++ get-s2
|= input=tape
^- @ud
(roll (multiply-digits (convert-digits-to-text (double-digits input))) add)
:: take a tape
:: convert to @ud and sum
++ sum-digits
|= input=tape
^- @ud
(roll (turn input |=(a=@t (rash a dem))) add)
:: take list of tapes
:: convert to digits
:: multiply each list of digits
++ multiply-digits
|= input=(list tape)
^- (list @ud)
(turn input |=(a=tape (sum-digits a)))
:: take each number
:: convert to tape
++ convert-digits-to-text
|= input=(list @ud)
^- (list tape)
(turn input |=(a=@ud (scow %ud a)))
:: get even digits and multiply by two
++ double-digits
|= input=tape
^- (list @ud)
(turn (even-digits input) |=(a=@ud (mul a 2)))
++ get-element
|= [idx=@ud input=tape]
^- tape
?: (gte (lent input) +(idx))
`tape`~[(snag idx input)]
~
++ odd-digits
|= input=tape
^- (list @ud)
=/ output=tape ~
|-
?: =(input ~)
(turn output |=(a=@t (rash a dem)))
%= $
output (weld output (get-element 0 input))
input (oust [0 2] input)
==
++ even-digits
|= input=tape
^- (list @ud)
=/ output=tape ~
|-
?: =(input ~)
(turn output |=(a=@t (rash a dem)))
%= $
output (weld output (get-element 1 input))
input (oust [0 2] input)
==
--
```
`gen/luhn-number.hoon`
```hoon
/+ luhn-number
|= input=tape
=<
(skim `(list tape)`(get-permutations input) validate:luhn-number)
|%
++ digits "0123456789"
++ get-permutations
|= input=tape
=/ output=(list tape) ~[input]
=/ idx 0
|-
?: =(idx (lent input))
output
%= $
output (churn-numbers idx output)
idx +(idx)
==
++ churn-numbers
|= [idx=@ud input=(list tape)]
^- (list tape)
(zing (turn input |=(a=tape (generate-perms idx a))))
++ generate-perms
|= [idx=@ud input=tape]
^- (list tape)
?: =((snag idx input) '*')
(turn digits |=(a=@t (snap input idx a)))
~[input]
--
```
### Solution #3
_By ~motdeg-bintul_
`lib/luhn-number`
```hoon
:: lib/luhn-number.hoon
:: Your code goes here
::
|%
++ validate
|= a=tape
&(=((checkdits a) 0) (gth (lent a) 0))
++ checkdits
|= a=tape
=/ totalsum (add (s1 a) (s2 a))
=/ sumtape (trip `@t`(scot %ud totalsum))
=/ digits (scan sumtape (star dit))
:: ~& (odds a)
:: ~& (doubler a)
:: ~& `(list @)`(getsums (doubler a))
:: ~& (s1 a)
:: ~& (s2 a)
:: ~& totalsum
?: (lte totalsum 9)
+2:digits
(snag (sub (lent +3:digits) 1) `(list @ud)`+3:digits)
++ odds
|= a=tape
=/ reverse (flop a)
=/ count 0
|-
^- (list @ud)
|-
?: (gte count (lent reverse))
~
:- (scan (trip (snag count reverse)) dit)
$(count (add 2 count))
++ s1
|= a=tape
(roll `(list @ud)`(odds a) add)
++ evens
|= a=tape
=/ reverse (flop a)
=/ count 1
|-
^- (list @ud)
|-
?: (gte count (lent reverse))
~
:- (scan (trip (snag count reverse)) dit)
$(count (add 2 count))
++ double
|= [a=@]
(mul 2 a)
++ doubler
|= a=tape
(turn `(list @ud)`(evens a) double)
++ adddit
|= [a=(list @ud) b=@ud]
=/ list1 a
=/ list2 `(list @t)`(turn list1 (cury scot %ud))
=/ count b
=/ digits (scan (trip (snag count list2)) (star dit))
=/ d1 (snag 0 digits)
?: =((lent digits) 1)
`@ud`d1
?: (gth (lent digits) 1)
`@ud`(add d1 (snag 1 digits))
~
++ getsums
|= a=(list @ud)
=/ nums a
=/ count 0
|-
?: (lth count (lent nums))
:- (adddit nums count)
$(count (add 1 count))
?: =(count (lent nums))
~
$(count (add 1 count))
++ s2
|= a=tape
=/ nums (doubler a)
(roll `(list @)`(getsums nums) add)
--
```
`gen/luhn-number`
```hoon
:: gen/luhn-number.hoon
:: Your code goes here
::
/= ln /lib/luhn-number
|= a=tape
=<
(checkmissing a)
|%
++ missingnums
|= a=tape
=/ count 0
|-
?: =(count (lent a))
~
?: =((snag count a) '*')
:- count
$(count (add 1 count))
?: =(count (sub (lent a) 1))
~
$(count (add 1 count))
++ replaceast
|= a=tape
=/ pos `(list @)`(missingnums a)
=/ count 0
=/ newtape a
=/ num `@t`(scot %ud 0)
|-
?: =(count (sub (lent pos) 1))
`(list @t)`(snap newtape (snag count pos) num)
%= $
newtape (snap newtape (snag count pos) num)
count (add count 1)
==
++ replacedigits
|= [a=tape b=@ud]
=/ count 0
=/ dits (trip (crip (replaceast a)))
=/ newdits (a-co:co b)
=/ flopdits (flop newdits)
=/ indexcap (sub (lent flopdits) 1)
=/ pos (flop `(list @ud)`(missingnums a))
=/ newnum `tape`dits
|-
?: =(count (lent newdits))
newnum
%= $
newnum `tape`(snap newnum (snag count pos) (snag count flopdits))
count (add 1 count)
==
++ testnewnum
|= a=tape
=/ format a
=/ count 0
=/ countdit (a-co:co count)
=/ newnum `tape`~
=/ pos `(list @ud)`(missingnums format)
=/ dgtlent (lent pos)
|-
^- (list tape)
?: &(=((lent (a-co:co count)) (add 1 dgtlent)) =((validate:ln newnum) %.y))
[newnum ~]
?: =((lent (a-co:co count)) (add 1 dgtlent))
~
?: =((validate:ln newnum) %.y)
:- newnum
%= $
count (add 1 count)
newnum (replacedigits format count)
countdit (a-co:co count)
==
?: =((lent countdit) (add 1 dgtlent))
~
%= $
count (add 1 count)
newnum (replacedigits format count)
countdit (trip `@t`(scot %ud count))
==
++ checkmissing
|= a=tape
?: &(=((missingnums a) ~) =((validate:ln a) %.y))
`(list tape)`[a ~]
(testnewnum a)
--
```

View File

@ -3,7 +3,7 @@ title = "Rhonda Numbers"
weight = 48 weight = 48
+++ +++
# Challenge: Rhonda Numbers ## Challenge: Rhonda Numbers
A Rhonda number is a positive integer _n_ that satisfies the property that, for [a given base _b_](https://en.wikipedia.org/wiki/Radix), the product of the base-_b_ digits of _n_ is equal to _b_ times the sum of _n_'s prime factors. Only composite bases (non-prime bases) have Rhonda numbers. A Rhonda number is a positive integer _n_ that satisfies the property that, for [a given base _b_](https://en.wikipedia.org/wiki/Radix), the product of the base-_b_ digits of _n_ is equal to _b_ times the sum of _n_'s prime factors. Only composite bases (non-prime bases) have Rhonda numbers.

View File

@ -3,7 +3,7 @@ title = "Roman Numerals"
weight = 50 weight = 50
+++ +++
# Challenge: Printing and Parsing Roman Numerals ## Challenge: Printing and Parsing Roman Numerals
Roman numerals constitute a numeral system capable of expressing positive integers by additive values (rather than place-number notation). Additive series are produced by summing values in a series, as `iii` → 3, while subtractive values are produced by prepending certain smaller values ahead of a larger value, as `ix` → 9. Roman numerals constitute a numeral system capable of expressing positive integers by additive values (rather than place-number notation). Additive series are produced by summing values in a series, as `iii` → 3, while subtractive values are produced by prepending certain smaller values ahead of a larger value, as `ix` → 9.
@ -36,7 +36,7 @@ Roman numerals constitute a numeral system capable of expressing positive intege
**Note**: This design pattern is not optimal since analysis over a union of some types can be difficult to carry out, and it would be better to either separate the generators or use a flag. In this case, the pattern works because we are distinguishing an atom from a cell. **Note**: This design pattern is not optimal since analysis over a union of some types can be difficult to carry out, and it would be better to either separate the generators or use a flag. In this case, the pattern works because we are distinguishing an atom from a cell.
## Unit Tests ## Unit Tests
Following a principle of test-driven development, we compose a series of tests which allow us to rigorously check for expected behavior. Following a principle of test-driven development, we compose a series of tests which allow us to rigorously check for expected behavior.
@ -866,7 +866,7 @@ Following a principle of test-driven development, we compose a series of tests w
-- --
``` ```
## Solutions ## Solutions
_These solutions were submitted by the Urbit community as part of a competition in ~2022.6. They are made available under both the [MIT license](https://mit-license.org/) and the [CC0 license](https://creativecommons.org/share-your-work/public-domain/cc0). We ask you to acknowledge authorship should you utilize these elsewhere._ _These solutions were submitted by the Urbit community as part of a competition in ~2022.6. They are made available under both the [MIT license](https://mit-license.org/) and the [CC0 license](https://creativecommons.org/share-your-work/public-domain/cc0). We ask you to acknowledge authorship should you utilize these elsewhere._

View File

@ -3,9 +3,7 @@ title = "Solitaire Cipher"
weight = 60 weight = 60
+++ +++
# Solitaire Cipher ## Challenge: Solitaire Encryption Cipher
## Challenge: Solitaire Encryption Cipher
The [Solitaire or Pontifex algorithm](https://en.wikipedia.org/wiki/Solitaire_%28cipher%29) is a cryptographic algorithm designed by cryptographer [Bruce Schneier](https://www.schneier.com/academic/solitaire/) based on coordinating two decks of cards so that they can be used to communicate between two field agents. Given a standard deck of 52 playing cards and two distinguishable jokers, a message may be encrypted as a keystream, or sequence of values combined with the message to encrypt or decrypt it. The algorithm features prominently in Neal Stephenson's novel _Cryptonomicon_. The [Solitaire or Pontifex algorithm](https://en.wikipedia.org/wiki/Solitaire_%28cipher%29) is a cryptographic algorithm designed by cryptographer [Bruce Schneier](https://www.schneier.com/academic/solitaire/) based on coordinating two decks of cards so that they can be used to communicate between two field agents. Given a standard deck of 52 playing cards and two distinguishable jokers, a message may be encrypted as a keystream, or sequence of values combined with the message to encrypt or decrypt it. The algorithm features prominently in Neal Stephenson's novel _Cryptonomicon_.
@ -43,7 +41,7 @@ To generate one character:
The foregoing hyperlinks showcase worked examples of Solitaire in action. The foregoing hyperlinks showcase worked examples of Solitaire in action.
## Solutions ## Solutions
_This solution was produced by ~rabsef-bicrym. It is made available under the [GNU GPL](https://github.com/rabsef-bicrym/urbitasofia/blob/master/LICENSE). (Note that this is different from the other code snippets on this site, which are made available under the [MIT license](https://mit-license.org/)._ _This solution was produced by ~rabsef-bicrym. It is made available under the [GNU GPL](https://github.com/rabsef-bicrym/urbitasofia/blob/master/LICENSE). (Note that this is different from the other code snippets on this site, which are made available under the [MIT license](https://mit-license.org/)._

View File

@ -0,0 +1,381 @@
+++
title = "Water Towers"
weight = 230
+++
## Challenge: Water between Towers
In a two-dimensional world, we begin with a bar-chart, or rows of unit-width 'towers' of arbitrary height. Then it rains, completely filling all convex enclosures in the chart with water.
```
9 ██ 9 ██
8 ██ 8 ██
7 ██ ██ 7 ██≈≈≈≈≈≈≈≈██
6 ██ ██ ██ 6 ██≈≈██≈≈≈≈██
5 ██ ██ ██ ████ 5 ██≈≈██≈≈██≈≈████
4 ██ ██ ████████ 4 ██≈≈██≈≈████████
3 ██████ ████████ 3 ██████≈≈████████
2 ████████████████ ██ 2 ████████████████≈≈██
1 ████████████████████ 1 ████████████████████
```
Your task for this challenge is to write a generator `water-towers`. It will take as input a `(list @ud)`, with each number representing the height of a tower from left to right. It will output a `@ud` representing the units of water that can be contained within the structure.
Example usage:
```
> +water-towers [5 3 7 2 6 4 5 9 1 2 ~]
14
```
## Unit Tests
Following a principle of test-driven development, we compose a series of tests which allow us to rigorously check for expected behavior.
```hoon
/+ *test
/= water-towers /gen/water-towers
|%
++ test-01
%+ expect-eq
!> `@ud`2
!> (water-towers [1 5 3 7 2 ~])
++ test-02
%+ expect-eq
!> `@ud`14
!> (water-towers [5 3 7 2 6 4 5 9 1 2 ~])
++ test-03
%+ expect-eq
!> `@ud`35
!> (water-towers [2 6 3 5 2 8 1 4 2 2 5 3 5 7 4 1 ~])
++ test-04
%+ expect-eq
!> `@ud`0
!> (water-towers [5 5 5 5 ~])
++ test-05
%+ expect-eq
!> `@ud`0
!> (water-towers [5 6 7 8 ~])
++ test-06
%+ expect-eq
!> `@ud`0
!> (water-towers [8 7 7 6 5 4 3 2 ~])
++ test-07
%+ expect-eq
!> `@ud`0
!> (water-towers [0 1 6 7 10 7 6 1 0 ~])
++ test-08
%+ expect-eq
!> `@ud`0
!> (water-towers [100 0 0 0 0 0 0 0 ~])
++ test-09
%+ expect-eq
!> `@ud`7
!> (water-towers [100 0 0 0 0 0 0 0 1 ~])
++ test-10
%+ expect-eq
!> `@ud`50
!> (water-towers [10 0 0 0 0 0 10 ~])
++ test-11
%+ expect-eq
!> `@ud`4
!> (water-towers [8 7 8 7 8 7 8 7 8 ~])
++ test-12
%+ expect-eq
!> `@ud`40
!> (water-towers [0 1 2 3 4 5 4 3 2 1 1 2 3 4 5 4 3 2 1 1 2 3 4 5 4 3 2 1 0 ~])
--
```
## Solutions
_These solutions were submitted by the Urbit community as part of a competition in ~2023.6. They are made available under the MIT License and CC0. We ask you to acknowledge authorship should you utilize these elsewhere._
### Solution #1
_By ~dannul-bortux. A model for literate programming in Hoon._
```hoon
::
:: A gate for computing volume of water collected between towers.
::
:: Take a list (of type list @ud), with each value representing the height of
:: a tower from left to right. Outputs a @ud representing the units of water
:: that can be contained within the structure.
::
:: Our approach involves calculating the total volume of rainfall or water by
:: aggregating the water volume from each tower location. For a specific
:: tower location. water volume is determined by subtracting the “height”
:: of the tower with maximum rainfall (“total height with water”) from the
:: height of the tower alone. Tower heights are given by corresponding values
:: in the input list.
::
:: The “total height with water” at a location is determined by the height of
:: surrounding boundary towers within our structure. Each tower location will
:: have at most two boundary towers: one boundary tower on either side (left
:: and right). The left boundary tower is defined as the highest tower to the
:: left of our specified tower location. The right boundary tower is defined
:: as the highest tower to the right of our specified tower location. The
:: value of “total height with water” at a location is equal to the lesser of
:: the two boundary tower heights (the minimum of the left boundary tower
:: height vs. right boundary tower height). When less than two boundary
:: towers are present, the “total height with water” is equal to the height
:: of the tower itself because no water can be contained without boundaries.
::
|= inlist=(list @ud)
^- @ud
:: If, input list is empty
::
?: =(0 (lent inlist))
:: Then, throw error
::
~| 'Error - input list cannot be empty'
!!
=< (compute-totalvol inlist)
|%
::
:: +compute-totalvol: Gets total volume of water by summing water at each
:: individual location.
::
:: Moves left to right iterating over each location (index in list).
:: Determines waterfall at each location and aggregates all waterfall to
:: find and return total volume.
::
++ compute-totalvol
|= [n=(list @ud)]
^- @ud
:: i is face for iterating over all index/locations
::
=/ i 0
:: tot is face for aggregating volume of water
::
=/ tot 0
|-
:: If, we're at end of input list
::
?: =(i (lent n))
:: then, return total
::
tot
:: else, compute water volume at current index, add to total, and increment i
::
%= $
tot (add tot (compute-indvol i n))
i +(i)
==
::
:: +compute-indvol: Computes volume at an individual location.
::
:: Computes volume at an individual location (index in input list) by
:: subtracting tower height from “total height with water”. “Total height
:: with water” will be determined at a particular location by the height of
:: “boundary towers” for that location.
::
++ compute-indvol
|= [loc=@ud n=(list @ud)]
^- @ud
(sub (compute-waterheight loc n) (snag loc `(list @ud)`n))
::
:: +compute-waterheight: Measures the “total height with water” at a specified
:: index/location.
::
:: “Total height with water” at a particular location is measured using the
:: heights (value) at the left and right boundary towers. The lesser of these
:: two values (left height vs right height) is equal to the “total height
:: with water” at our input location.
::
:: Right boundary tower is the tallest tower to the right of the location--
:: i.e. highest value (height) with higher index. The left boundary tower is
:: the tallest tower to the left of the location i.e. highest value (height)
:: with lower index.
::
:: The “find-boundaryheight” arm iterates left to right and works for
:: measuring height of the right boundary tower. For the left boundary tower
:: we can use a mirror approach. We reverse the input list and adjust the
:: input index accordinglyto move right-to-left.
::
:: In the case where no right or left boundary tower exists, our
:: “find-boundaryheight” arm will return the tower height at our current
:: index (indicating no water present) and we correctly compute 0 water
:: volume in our compute-indvol arm.
::
++ compute-waterheight
|= [loc=@ud n=(list @ud)]
^- @ud
:: rbth is a face for our "right boundary tower height" computed using our
:: "find-boundaryheight" arm moving left to right
::
=/ rbth (find-boundaryheight loc n)
:: lbth is a face for our "right boundary tower height" computed using our
:: "find-boundaryheight" arm moving (mirrored) right to left
::
=/ lbth (find-boundaryheight (sub (lent n) +(loc)) (flop n))
:: If, right boundary tower height is less than left boundary tower height,
::
?: (lth rbth lbth)
:: then, return right boundary tower height
::
rbth
:: else, return left boundary tower height
::
lbth
::
:: +find-boundaryheight: Computes the height of the highest tower to the right
:: of the input location
::
:: Moves left to right starting at input location until the end of input
:: list. Tracks height of each tower location with a height greater than
:: height at corresponding input location.
::
++ find-boundaryheight
|= [loc=@ud n=(list @ud)]
^- @ud
:: i is face used to iterate over input list starting one past input index
::
=/ i +(loc)
:: bheight is face used to measure boundary tower heights--i.e. any tower
:: heights greater than height at input location. At start, bheight is set to
:: input location height. If no greater heights are found, input location
:: height is returned (indicating no higher boundary towers found).
::
=/ bheight (snag loc n)
|-
:: If, we are at the end of our input
::
?: (gte i (lent n))
:: then, return boundary tower height
::
bheight
:: else, if current tower height is greater than currently stored boundary
:: tower height, replace boundary tower height. Incr iteration idx.
::
%= $
bheight ?: (gth (snag i n) bheight)
(snag i n)
bheight
i +(i)
==
--
```
### Solution #2
_By ~racfer-hattes. A short and elegant solution._
```hoon
=>
|%
++ go
|= [current=@ud previous=(list @ud) next=(list @ud)]
=/ left-peak (roll previous max)
=/ right-peak (roll next max)
=/ min-peak (min left-peak right-peak)
=/ water-level
?: (lth min-peak current) 0
(sub min-peak current)
?~ next water-level
(add water-level $(current i.next, next t.next, previous [current previous]))
--
|= xs=(list @ud)
?~ xs 0
%- go [i.xs ~ t.xs]
```
### Solution #3
_By ~dozreg-toplud. Another very literate and clean solution._
```hoon
:: +water-towers: a solution to the HSL challenge #1
::
:: https://github.com/tamlut-modnys/template-hsl-water-towers
:: Takes a (list @ud) of tower heights, returns the number of the units of
:: water that can be held in the given structure.
::
|= towers=(list @ud)
^- @ud
=<
:: x, y are horizontal and vertical axes
::
=| water-counter=@ud
=/ x-last-tower=@ud (dec (lent towers))
=/ y-highest-tower=@ud (roll towers max)
:: iterate along y axis from y=0
::
=/ y=@ud 0
|-
^- @ud
:: if, y > max(towers)
::
?: (gth y y-highest-tower)
:: then, return water-counter
::
water-counter
:: else, iterate along x axis from x=1
::
=/ x=@ud 1
|-
^- @ud
:: if, x = x(last tower)
::
?: =(x x-last-tower)
:: then, go to the next y
::
^$(y +(y))
:: else, increment water-counter if the point [x y] is not occupied by a tower
:: and has towers to the left and right on the same y, after go to the next x
::
=? water-counter ?& (not-tower x y)
(has-tower-left x y)
(has-tower-right x y)
==
+(water-counter)
$(x +(x))
::
:: Core with helping functions
::
|%
:: ++not-tower: returns %.y if the coordinate [x y] is free from a tower,
:: %.n if occupied.
::
++ not-tower
|= [x=@ud y=@ud]
^- ?
(gth y (snag x towers))
:: ++has-tower-left: returns %.y if there is a tower with height >= y to
:: the left from x, %.n otherwise. Enabled computation caching to only test
:: each point once.
::
++ has-tower-left
|= [x=@ud y=@ud]
~+
^- ?
:: no towers to the left from the 0th tower
::
?: =(x 0)
%.n
:: check recursively to the left
::
?| (gte (snag (dec x) towers) y)
$(x (dec x))
==
:: ++has-tower-right: returns %.y if there is a tower with height >= y to
:: the right from x, %.n otherwise. Enabled computation caching to only test
:: each point once.
::
++ has-tower-right
|= [x=@ud y=@ud]
~+
^- ?
:: no towers to the right from the last tower
::
?: =(x (dec (lent towers)))
%.n
:: check recursively to the right
::
?| (gte (snag +(x) towers) y)
$(x +(x))
==
::
--
```

View File

@ -14,7 +14,7 @@ The best place to start when building a new agent is its type definitions in its
Let's look at each of these questions in turn, and put together our agent's Let's look at each of these questions in turn, and put together our agent's
`/sur` file, which we'll call `/sur/journal.hoon`. `/sur` file, which we'll call `/sur/journal.hoon`.
### 1. Basic types ## 1. Basic types
Our journal entries will just be plain text, so a simple `@t` will work fine to Our journal entries will just be plain text, so a simple `@t` will work fine to
store their contents. Entries will be organized by date, so we'll also need to store their contents. Entries will be organized by date, so we'll also need to
@ -34,7 +34,7 @@ The structure for a journal entry can therefore be:
+$ entry [=id =txt] +$ entry [=id =txt]
``` ```
### 2. Actions ## 2. Actions
Now that we know what a journal entry looks like, we can think about what kind Now that we know what a journal entry looks like, we can think about what kind
of actions/commands our agent will handle in its `++on-poke` arm. For our of actions/commands our agent will handle in its `++on-poke` arm. For our
@ -54,7 +54,7 @@ We can create a tagged union structure for these actions, like so:
== ==
``` ```
### 3. Updates ## 3. Updates
Updates are a little more complicated than our actions. Firstly, our front-end Updates are a little more complicated than our actions. Firstly, our front-end
needs to be able to retrieve an initial list of journal entries to display. Once needs to be able to retrieve an initial list of journal entries to display. Once
@ -113,7 +113,7 @@ milliseconds since the Unix Epoch:
== ==
``` ```
### 4. State ## 4. State
We need to store two things in our state: the journal entries and the update We need to store two things in our state: the journal entries and the update
log. We could just use a couple of `map`s like so: log. We could just use a couple of `map`s like so:

View File

@ -8,7 +8,7 @@ this section, we'll briefly look at how JSON works in Urbit, and write a library
to convert our agent's structures to and from JSON for our front-end. to convert our agent's structures to and from JSON for our front-end.
JSON data comes into Eyre as a string, and Eyre parses it with the JSON data comes into Eyre as a string, and Eyre parses it with the
[`++de-json:html`](/reference/hoon/zuse/2e_2-3#de-jsonhtml) function in [`++de:json:html`](/reference/hoon/zuse/2e_2-3#dejsonhtml) function in
[`zuse.hoon`](/reference/hoon/zuse). The [`zuse.hoon`](/reference/hoon/zuse). The
hoon type it's parsed to is `$json`, which is defined as: hoon type it's parsed to is `$json`, which is defined as:
@ -29,7 +29,7 @@ agent (unless the mark specified is already `%json`, in which case it will be
delivered directly). Outbound facts will go through the same process in delivered directly). Outbound facts will go through the same process in
reverse - converted from the agent's native mark to `$json`, then encoded in a reverse - converted from the agent's native mark to `$json`, then encoded in a
string by Eyre using string by Eyre using
[`++en-json:html`](/reference/hoon/zuse/2e_2-3#en-jsonhtml) and delivered [`++en:json:html`](/reference/hoon/zuse/2e_2-3#enjsonhtml) and delivered
to the web client. The basic flow for both inbound messages (pokes) and outbound to the web client. The basic flow for both inbound messages (pokes) and outbound
messages (facts and scry results) looks like this: messages (facts and scry results) looks like this:
@ -171,7 +171,7 @@ complex nested `$json` decoding function can be built up in this manner.
For example: For example:
``` ```
> =js %- need %- de-json:html > =js %- need %- de:json:html
''' '''
{ {
"foo": "hello", "foo": "hello",

View File

@ -10,175 +10,113 @@ React app front-end.
Node.js must be installed, and can be downloaded from their Node.js must be installed, and can be downloaded from their
[website](https://nodejs.org/en/download). With that installed, we'll have the [website](https://nodejs.org/en/download). With that installed, we'll have the
`npm` package manager available. The first thing we'll do is globally install `npm` package manager available and its utility binaries like `npx` to help
the `create-react-app` package with the following command: set up our project. The first thing we'll do is create a project using the
[`create-landscape-app`](https://www.npmjs.com/package/@urbit/create-landscape-app)
template with the following command:
```sh ```sh
npm install -g create-react-app npx @urbit/create-landscape-app
``` ✔ What should we call your application? … journal
✔ What URL do you use to access Urbit? … http://127.0.0.1:8080
Once installed, we can use it to create a new `journal-ui` directory and setup a
new React app in it with the following command:
```sh
create-react-app journal-ui
``` ```
We can then open our new directory: We can then open our new directory:
```sh ```sh {% copy=true %}
cd journal-ui cd journal/ui
``` ```
Its contents should look something like this: Its contents should look something like this:
``` ```
journal-ui ui
├── node_modules ├── index.html
├── package.json ├── package.json
├── package-lock.json ├── package-lock.json
├── public ├── postcss.config.js
├── README.md ├── tailwind.config.js
├── vite.config.js
└── src └── src
``` ```
## Install `http-api` ## Install dependencies
Inside our React app directory, let's install the `@urbit/http-api` NPM package: Inside our React app directory, let's install the NPM packages used by
our project:
```sh ```sh {% copy=true %}
npm i @urbit/http-api npm i
``` ```
We also install a handful of other packages for the UI components This command will install the Urbit interface package (i.e. `@urbit/http-api`)
(`bootstrap@5.1.3 react-bootstrap@2.2.0 react-textarea-autosize@8.3.3 and all the other packages used by our React application. When building from
date-fns@2.28.0 react-bottom-scroll-listener@5.0.0 react-day-picker@7.4.10`), scratch with `create-landscape-app`, this includes a number of useful
but that's not important to our purposes here. development libraries that enable automatic refresh on file edits (i.e. `vite`
and `@vitejs/plugin-react-refresh`) and simple page styling (i.e.
`tailwindcss`). The remainder of this tutorial will focus primarily on how the
Urbit interface package is used to communicate with a live ship from within a
React application.
## Additional tweaks ## Basic app setup
Our front-end will be served directly from the ship by the `%docket` app, where With all the basics now in place, we can begin work on the app itself. For this
a user will open it by clicking on its homescreen tile. Docket serves such simple demonstration, we'll be working just with the `src/app.jsx` file, which
front-ends with a base URL path of `/apps/[desk]/`, so in our case it will be contains the rendering logic for our React application. Before we look at the
`/apps/journal`. In order for our app to be built with correct resource paths, full front-end source for our journal app, let's first review the simpler
we must add the following line to `package.json`: default code provided by `create-landscape-app` to cover some Urbit API and
React basics.
```json ### Urbit API setup
"homepage": "/apps/journal/",
First, let's open up `src/app.jsx` and look at the import statements at the top
of this file:
```javascript
import React, { useEffect, useState } from 'react';
import Urbit from '@urbit/http-api';
import { scryCharges } from '@urbit/api';
import { AppTile } from './components/AppTile';
``` ```
Our app also needs to know the name of the ship it's being served from in order The first two of these statements are very common in Urbit React applications;
to talk with it. The `%docket` agent serves a small file for this purpose at the first imports the React library and a few of its important functions (to be
`[host]/session.js`. This file is very simple and just contains: covered in a moment) and the second imports the `Urbit` class, which will be
used subsequently to enable browser-to-ship communication.
```js Next, the code sets up the `Urbit` API object as a global variable, which
window.ship = "sampel-palnet"; allows the browser-to-ship connection to be established *exactly once* when the
page is first being loaded:
```javascript
const api = new Urbit('', '', window.desk);
api.ship = window.ship;
``` ```
`sampel-palnet` will of course be replaced by the actual name of the ship. We The first statement creates a new instance of the `Urbit` class we imported
include this script by adding the following line to the `<head>` section of from `@urbit/http-api`, and saves it to the `api` variable. The `Urbit` class
`public/index.html`: constructor takes three arguments: `url`, `code`, and `desk`, of which only `url`
```
<script src="/session.js"></script>
```
## Basic API setup
With everything now setup, we can begin work on the app itself. In this case
we'll just edit the existing `App.js` file in the `/src` directory. The first thing is to import the `Urbit` class from `@urbit/http-api`:
```js
import Urbit from "@urbit/http-api";
```
We also need to import a few other things, mostly relating to UI components (but
these aren't important for our purposes here):
```js
import React, { Component } from "react";
import "bootstrap/dist/css/bootstrap.min.css";
import "react-day-picker/lib/style.css";
import TextareaAutosize from "react-textarea-autosize";
import Button from "react-bootstrap/Button";
import Card from "react-bootstrap/Card";
import Stack from "react-bootstrap/Stack";
import Tab from "react-bootstrap/Tab";
import Tabs from "react-bootstrap/Tabs";
import ToastContainer from "react-bootstrap/ToastContainer";
import Toast from "react-bootstrap/Toast";
import Spinner from "react-bootstrap/Spinner";
import CloseButton from "react-bootstrap/CloseButton";
import Modal from "react-bootstrap/Modal";
import DayPickerInput from "react-day-picker/DayPickerInput";
import endOfDay from "date-fns/endOfDay";
import startOfDay from "date-fns/startOfDay";
import { BottomScrollListener } from "react-bottom-scroll-listener";
```
Inside the existing `App` class:
```js
class App extends Component {
```
...we'll clear out the existing demo code and start adding ours. The first thing
is to define our app's state. We'll look at most of the state entries in the
next section. For now, we'll just consider `status`.
```js
state = {
// .....
status: null,
// .....
};
```
Next, we'll setup the `Urbit` API object in `componentDidMount`. We could do
this outside the `App` class since we're adding it to `window`, but we'll do it
this way so it's all in one place:
```js
componentDidMount() {
window.urbit = new Urbit("");
window.urbit.ship = window.ship;
window.urbit.onOpen = () => this.setState({status: "con"});
window.urbit.onRetry = () => this.setState({status: "try"});
window.urbit.onError = (err) => this.setState({status: "err"});
this.init();
};
```
The first thing we do is create a new instance of the `Urbit` class we imported
from `@urbit/http-api`, and save it to `window.urbit`. The `Urbit` class
constructor takes three arguments: `url`, `desk` and `code`, of which only `url`
is mandatory. is mandatory.
- `url` is the URL of the ship we want to talk to. Since our React app will be - `url` is the URL of the ship we want to talk to. Since our React app will be
served by the ship, we can just leave it as an empty `""` string and let served by the ship, we can just leave it as an empty `''` string and let
`Urbit` use root-relative paths. `Urbit` use root-relative paths.
- `desk` is only necessary if we want to run threads through Eyre, and since - `code` is the web login code for authentication. Since the user will already
we're not going to do that, we can exclude it. have logged in, we can also leave it as an empty `''` string.
- `code` is the web login code for authentication, but since the user will - `desk` is only necessary if we want to run threads through Eyre. This example
already have logged in, we can also exclude that. doesn't submit any such requests, but the `desk` is set anyway for
demonstration purposes.
Therefore, we call the class contructor with just the empty `url` string: The second statement sets the ship name in our `Urbit` instance. Eyre requires
the ship name be specified in all requests; if we don't set it, Eyre will
reject all the messages we send. Fortunately, `create-landscape-app` handles
this detail by automatically initializing the active ship's name to the
variable `window.ship`, so we just set `api.ship` to this value.
```js While not referenced in the `create-landscape-app` default code, the `Urbit`
window.urbit = new Urbit(""); class has three additional callbacks that can be set: `onOpen`, `onRetry`, and
``` `onError`. These callbacks are fired when the state of our channel connection
changes:
Next, we need to set the ship name in our `Urbit` instance. Eyre requires the
ship name be specified in all requests, so if we don't set it, Eyre will reject
all the messages we send. We previously included `session.js` which sets
`window.ship` to the ship name, so we just set `window.urbit.ship` as that:
```js
window.urbit.ship = window.ship;
```
Next, we set three callbacks: `onOpen`, `onRetry`, and `onError`. These
callbacks are fired when the state of our channel connection changes:
- `onOpen` is called when a connection is established. - `onOpen` is called when a connection is established.
- `onRetry` is called when a channel connection has been interrupted (such as by - `onRetry` is called when a channel connection has been interrupted (such as by
@ -188,22 +126,103 @@ callbacks are fired when the state of our channel connection changes:
- `onError` is called with an `Error` message once all retries have failed, or - `onError` is called with an `Error` message once all retries have failed, or
otherwise when a fatal error occurs. otherwise when a fatal error occurs.
We'll look at how we handle these cases in the next section. For now, we'll just We'll look at how we can use these callbacks in the next section. Note that
set the `status` entry in the state to either `"con"`, `"try"`, or `"err"` as it's not mandatory to set these callbacks, but leaving connection problems
the case may be. Note that it's not mandatory to set these callbacks, but unhandled is usually a bad idea.
leaving connection problems unhandled is usually a bad idea.
The last thing we do is call: ### React app setup
```js Finally, let's take a quick look at the React rendering logic for our
this.init(); application. React rendering occurs within components, which are defined either
as classes (e.g. `class A extends Component { /* ... */ }`) or functions (e.g.
`function A() { /* ... */ }`). While recent React versions support both styles,
the latter "modern" style is preferred and used by most Urbit React
applications.
Our code defines a few components, but we'll just focus on the primary
component for this tutorial; this component is defined as a functional
component named `App`:
```javascript
export function App() {
/* ... */
}
``` ```
This function will fetch initial entries and subscribe for updates. We'll look As is common for React components, the first thing we'll define in our `App`
at it in the next section. component is its state. In React, modifying a component's state causes it to be
re-rendered, so state variables should be carefully chosen to constitute all
"display-affecting" values. In modern React, component state is defined using
the [`useState()`] hook, which returns a pair of `[stateVariable,
setStateVariableFunction]`. Since our default `create-landscape-app` code just
displays the list of apps installed on a ship, it only needs to store this list
as its state:
```javascript
const [apps, setApps] = useState();
```
With the state established, we now define the code responsible for populating
this state. The canonical way to grab data from an external service/system in
React is to use the [`useEffect()`] hook. This function takes two arguments:
(1) the callback function for loading the external data and (2) a list of all
state variables dependencies, which will cause re-invocations of the first
argument when modified. Our app just needs to load the list of apps on our ship
(called `charges`) once, so its [`useEffect()`] invocation is simple:
```javascript
useEffect(() => {
async function init() {
const charges = (await api.scry(scryCharges)).initial;
setApps(charges);
}
init();
}, []);
```
The last step is to return the HTML that will be used to render our component
in the browser. This HTML must adhere to the syntactic rules of
[JSX](https://en.wikipedia.org/wiki/JSX_(JavaScript)), which allow for greater
flexibility through extensions like embedded JavaScript (contained in curly
brace enclosures). Our component renders each app it found when scrying our
ship as a tile accompanied by its title and description:
```javascript {% mode="collapse" %}
return (
<main className="flex items-center justify-center min-h-screen">
<div className="max-w-md space-y-6 py-20">
<h1 className="text-3xl font-bold">Welcome to hut</h1>
<p>Here&apos;s your urbit&apos;s installed apps:</p>
{apps && (
<ul className="space-y-4">
{Object.entries(apps).map(([desk, app]) => (
<li key={desk} className="flex items-center space-x-3 text-sm leading-tight">
<AppTile {...app} />
<div className="flex-1 text-black">
<p>
<strong>{app.title || desk}</strong>
</p>
{app.info && <p>{app.info}</p>}
</div>
</li>
))}
</ul>
)}
</div>
</main>
);
```
With this brief primer complete, we'll take a closer look at our journal
application's front-end and how it utilizes the Urbit HTTP API in the next
section.
## Resources ## Resources
- [React Tutorial](https://react.dev/learn/tutorial-tic-tac-toe) - A tutorial
walking through the basics of writing a modern React application.
- [HTTP API Guide](/guides/additional/http-api-guide) - Reference documentation for - [HTTP API Guide](/guides/additional/http-api-guide) - Reference documentation for
`@urbit/http-api`. `@urbit/http-api`.
@ -214,3 +233,7 @@ at it in the next section.
- [`@urbit/http-api` source - [`@urbit/http-api` source
code](https://github.com/urbit/urbit/tree/master/pkg/npm/http-api) - The code](https://github.com/urbit/urbit/tree/master/pkg/npm/http-api) - The
source code for the `@urbit/http-api` NPM package. source code for the `@urbit/http-api` NPM package.
[`usestate()`]: https://react.dev/reference/react/useState
[`useeffect()`]: https://react.dev/reference/react/useEffect

View File

@ -3,58 +3,72 @@ title = "7. React app logic"
weight = 8 weight = 8
+++ +++
With the basic things setup, we can now go over the logic of our app. We'll just Now that we've reviewed the basics of setting up an Urbit React app, we can
focus on functions that are related to ship communications using the `Urbit` dive into the more complex logic that drives our [journal app's
object we previously setup, and ignore UI components and other helper functions. front-end](https://github.com/urbit/docs-examples/tree/main/journal-app/ui).
We'll focus on the app's main component `App` (defined in
[`src/app.jsx`](https://github.com/urbit/docs-examples/tree/main/journal-app/ui/src/app.jsx))
and how it leverages functions related to ship communications using the `Urbit`
object. For more information on UI components and other helper functions, see
the [resources section](#resources).
## State ## State
In the previous section we just mentioned the connection `status` field of our In the previous section, we introduced how React components use [`useState()`]
state. Here's the full state of our App: to declare state variables within components. The main `App` component in our
journal app contains a number of these statements to manage its many
constituents and sub-components:
```js {% copy=true %} ```javascript
state = { // Control/Meta State //
entries: [], // list of journal entries for display const [subEvent, setSubEvent] = useState({});
drafts: {}, // edits which haven't been submitted yet const [latestUpdate, setLatestUpdate] = useState(null);
newDraft: {}, // new entry which hasn't been submitted yet const [status, setStatus] = useState(null);
results: [], // search results const [errorCount, setErrorCount] = useState(0);
searchStart: null, // search query start date const [errors, setErrors] = useState(new Map());
searchEnd: null, // search query end date
resultStart: null, // search results start date // Journal State //
resultEnd: null, // search results end date const [entries, setEntries] = useState([]);
searchTime: null, // time of last search const [drafts, setDrafts] = useState({});
latestUpdate: null, // most recent update we've received const [newDraft, setNewDraft] = useState({});
entryToDelete: null, // deletion target for confirmation modal const [entryToDelete, setEntryToDelete] = useState(null);
status: null, // connection status (con, try, err)
errorCount: 0, // number of errors so far // Search State //
errors: new Map(), // list of error messages for display const [results, setResults] = useState([]);
}; const [searchMeta, setSearchMeta] = useState({
time: null,
start: null,
end: null,
});
``` ```
We'll see how these are used subsequently. We'll see how these are used subsequently.
## Initialize ## Initialize
The first thing our app does is call `init()`: After defining its state, the next thing our `App` component does is define a
function called `init()`, which is one of the first functions called during its
bootstrapping process:
```js ```javascript
init = () => { const init = () => {
this.getEntries().then( getEntries().then(
(result) => { (result) => {
this.handleUpdate(result); setSubEvent(result);
this.setState({ latestUpdate: result.time }); setLatestUpdate(result.time);
this.subscribe(); subscribe();
}, },
(err) => { (err) => {
this.setErrorMsg("Connection failed"); addError("Connection failed");
this.setState({ status: "err" }); setStatus("err");
} }
); );
}; };
``` ```
This function just calls `getEntries()` to retrieve the initial list of journal This function just calls `getEntries()` to retrieve the initial list of journal
entries then, if that succeeded, it calls `subscribe()` to subscribe for new entries; then, if that succeeded, it publishes this update with `setSubEvent()`
and `setLatestUpdate()` and then calls `subscribe()` to subscribe for new
updates. If the initial entry retrieval failed, we set the connection `status` updates. If the initial entry retrieval failed, we set the connection `status`
and save an error message in the `errors` map. We'll look at what we do with and save an error message in the `errors` map. We'll look at what we do with
errors later. errors later.
@ -63,13 +77,13 @@ errors later.
![entries screenshot](https://media.urbit.org/guides/core/app-school-full-stack-guide/entries.png) ![entries screenshot](https://media.urbit.org/guides/core/app-school-full-stack-guide/entries.png)
The `getEntries` function scries our `%journal` agent for up to 10 entries The `getEntries()` function scries our `%journal` agent for up to 10 entries
before the oldest we currently have. We call this initially, and then each time before the oldest we currently have. We call this initially and then each time
the user scrolls to the bottom of the list. the user scrolls to the bottom of the list.
```js ```javascript
getEntries = async () => { const getEntries = async () => {
const { entries: e } = this.state; const e = entries;
const before = e.length === 0 ? Date.now() : e[e.length - 1].id; const before = e.length === 0 ? Date.now() : e[e.length - 1].id;
const max = 10; const max = 10;
const path = `/entries/before/${before}/${max}`; const path = `/entries/before/${before}/${max}`;
@ -93,27 +107,27 @@ direct GET requests allow other marks too.
The `Urbit.scry` method returns a Promise which will contain an HTTP error The `Urbit.scry` method returns a Promise which will contain an HTTP error
message if the scry failed. We handle it with a `.then` expression back in the message if the scry failed. We handle it with a `.then` expression back in the
function that called it, either [`init()`](#initialize) or `moreEntries()`. If function that called it, either [`init()`](#initialize) or `moreEntries()`. If
the Promise is successfuly, the results are passed to the the Promise is successfully evaluated, the results are passed to the
[`handleUpdate`](#updates) function which appends the new entries to the [`setSubEvent()`](#updates) function, which appends the new entries to the
existing ones in state. existing ones via a [`useEffect()`] hook (more on this [below](#updates)).
## Subscription ## Subscription
A subscription to the `/updates` path of our `%journal` agent is opened with our A subscription to the `/updates` path of our `%journal` agent is opened with our
`subscribe()` function: `subscribe()` function:
```js ```javascript
subscribe = () => { const subscribe = () => {
try { try {
window.urbit.subscribe({ window.urbit.subscribe({
app: "journal", app: "journal",
path: "/updates", path: "/updates",
event: this.handleUpdate, event: setSubEvent,
err: () => this.setErrorMsg("Subscription rejected"), err: () => addError("Subscription rejected"),
quit: () => this.setErrorMsg("Kicked from subscription"), quit: () => addError("Kicked from subscription"),
}); });
} catch { } catch {
this.setErrorMsg("Subscription failed"); addError("Subscription failed");
} }
}; };
``` ```
@ -124,7 +138,8 @@ object:
- `app` - the target agent. - `app` - the target agent.
- `path` - the `%watch` path we're subscribing to. - `path` - the `%watch` path we're subscribing to.
- `event` - a function to handle each fact the agent sends out. We call our - `event` - a function to handle each fact the agent sends out. We call our
`handleUpdate` function, which we'll describe below. `setSubEvent()` function to set off a cascade to update the interface;
this process is described [below](#updates).
- `err` - a function to call if the subscription request is rejected (nacked). - `err` - a function to call if the subscription request is rejected (nacked).
We just display an error in this case. We just display an error in this case.
- `quit` - a function to call if we get kicked from the subscription. We also - `quit` - a function to call if we get kicked from the subscription. We also
@ -137,86 +152,130 @@ keep track of these IDs in your app's state.
## Updates ## Updates
This `handleUpdate` function handles all updates we receive. It's called The architecture for updating a React interface based on incoming facts from an
whenever an event comes in for our subscription, and it's also called with the `Urbit` subscription tends to follow a common pattern constituted of three
results of [`getEntries`](#getting-entries) and [`getUpdates`](#error-handling) major parts:
(described later).
It's a bit complex, but basically it just checks whether the JSON object is 1. A [`useState()`] call that creates an update object field as part of the
`add`, `edit`, `delete`, or `entries`, and then updates the state appropriately. main component's state:
The object it's receiving is just the `$update` structure converted to JSON by ```javascript
the mark conversion functions we wrote previously. const [subEvent, setSubEvent] = useState({});
```
2. An `Urbit.subscribe` call that passes the update object's setter function as
its `event` field:
```javascript
window.urbit.subscribe({/* ... */, event: setSubEvent});
```
3. A [`useEffect()`] invocation that triggers off of the update object, which
contains the logic for handling subscription updates:
```javascript
useEffect(() => {/* ... */}, [subEvent]);
```
```js The key piece of this architecture is the [`useEffect()`] trigger, which is
handleUpdate = (upd) => { called whenever an event comes in on the subscription wire (achieved by
const { entries, drafts, results, latestUpdate } = this.state; including the subscription object `subEvent` as a re-invocation trigger in
if (upd.time !== latestUpdate) { [`useEffect()`]'s second argument). In our application, this hook is also
if ("entries" in upd) { triggered by calls to [`getEntries()`](#getting-entries) and
this.setState({ entries: entries.concat(upd.entries) }); [`getUpdates()`](#error-handling), which will be described in greater detail
} else if ("add" in upd) { later.
const { time, add } = upd;
const eInd = this.spot(add.id, entries); The trigger code is a bit complex, but in broad brushstrokes it just checks the
const rInd = this.spot(add.id, results); header of the incoming JSON object (i.e. one of `add`, `edit`, `delete`, or
const toE = `entries`) and then updates the state appropriately. The object it's receiving
entries.length === 0 || add.id > entries[entries.length - 1].id; is just the `$update` structure converted to JSON by the mark conversion
const toR = this.inSearch(add.id, time); functions we wrote previously.
```javascript {% mode="collapse" %}
useEffect(() => {
const getDataIndex = (id, data) => {
let low = 0;
let high = data.length;
while (low < high) {
let mid = (low + high) >>> 1;
if (data[mid].id > id) low = mid + 1;
else high = mid;
}
return low;
};
const isInSearch = (id, time) => (
searchMeta.time !== null &&
time >= searchMeta.time &&
searchMeta.start.getTime() <= id &&
searchMeta.end.getTime() >= id
);
if (subEvent.time !== latestUpdate) {
if ("entries" in subEvent) {
// NOTE: `BottomScrollListener` can fire on top of `init`, which can
// cause entries to be double loaded; we trim duplicates to avoid overlap
const [existing, incoming] = [entries, subEvent.entries];
const oldestExistingId = existing.length === 0
? Date.now()
: existing[existing.length - 1].id;
let newestIncomingInd = getDataIndex(oldestExistingId, incoming);
newestIncomingInd += newestIncomingInd < incoming.length
&& incoming[newestIncomingInd].id >= oldestExistingId;
setEntries(existing.concat(incoming.slice(newestIncomingInd)));
} else if ("add" in subEvent) {
const { time, add } = subEvent;
const eInd = getDataIndex(add.id, entries);
const rInd = getDataIndex(add.id, results);
const toE = entries.length === 0 || add.id > entries[entries.length - 1].id;
const toR = isInSearch(add.id, time);
toE && entries.splice(eInd, 0, add); toE && entries.splice(eInd, 0, add);
toR && results.splice(rInd, 0, add); toR && results.splice(rInd, 0, add);
this.setState({ toE && setEntries([...entries]);
...(toE && { entries: entries }), toR && setResults([...results]);
...(toR && { results: results }), setLatestUpdate(time);
latestUpdate: time, } else if ("edit" in subEvent) {
}); const { time, edit } = subEvent;
} else if ("edit" in upd) {
const { time, edit } = upd;
const eInd = entries.findIndex((e) => e.id === edit.id); const eInd = entries.findIndex((e) => e.id === edit.id);
const rInd = results.findIndex((e) => e.id === edit.id); const rInd = results.findIndex((e) => e.id === edit.id);
const toE = eInd !== -1; const toE = eInd !== -1;
const toR = rInd !== -1 && this.inSearch(edit.id, time); const toR = rInd !== -1 && isInSearch(edit.id, time);
if (toE) entries[eInd] = edit; if (toE) entries[eInd] = edit;
if (toR) results[rInd] = edit; if (toR) results[rInd] = edit;
(toE || toR) && delete drafts[edit.id]; (toE || toR) && delete drafts[edit.id];
this.setState({ toE && setEntries([...entries]);
...(toE && { entries: entries }), toR && setResults([...results]);
...(toR && { results: results }), (toE || toR) && setDrafts({...drafts});
...((toE || toR) && { drafts: drafts }), setLatestUpdate(time);
latestUpdate: time, } else if ("del" in subEvent) {
}); const { time, del } = subEvent;
} else if ("del" in upd) {
const { time, del } = upd;
const eInd = entries.findIndex((e) => e.id === del.id); const eInd = entries.findIndex((e) => e.id === del.id);
const rInd = results.findIndex((e) => e.id === del.id); const rInd = results.findIndex((e) => e.id === del.id);
const toE = eInd !== -1; const toE = eInd !== -1;
const toR = this.inSearch(del.id, time) && rInd !== -1; const toR = isInSearch(del.id, time) && rInd !== -1;
toE && entries.splice(eInd, 1); toE && entries.splice(eInd, 1);
toR && results.splice(rInd, 1); toR && results.splice(rInd, 1);
(toE || toR) && delete drafts[del.id]; (toE || toR) && delete drafts[del.id];
this.setState({ toE && setEntries([...entries]);
...(toE && { entries: entries }), toR && setResults([...results]);
...(toR && { results: results }), (toE || toR) && setDrafts({...drafts});
...((toE || toR) && { drafts: drafts }), setLatestUpdate(time);
latestUpdate: time,
});
} }
} }
}; }, [subEvent]);
``` ```
## Add, edit, delete ## Add, edit, delete
![add screenshot](https://media.urbit.org/guides/core/app-school-full-stack-guide/add.png) ![add screenshot](https://media.urbit.org/guides/core/app-school-full-stack-guide/add.png)
When a user writes a new journal entry and hits submit, the `submitNew` function When a user writes a new journal entry and hits submit, the `createEntry()`
is called. It uses the `Urbit.poke` method to poke our `%journal` agent. function is called. It uses the `Urbit.poke` method to poke our `%journal`
agent.
```js ```javascript
submitNew = (id, txt) => { const createEntry = (id, txt) => {
window.urbit.poke({ window.urbit.poke({
app: "journal", app: "journal",
mark: "journal-action", mark: "journal-action",
json: { add: { id: id, txt: txt } }, json: { add: { id: id, txt: txt } },
onSuccess: () => this.setState({ newDraft: {} }), onSuccess: () => setDraft({}),
onError: () => this.setErrorMsg("New entry rejected"), onError: () => setError("New entry rejected"),
}); });
}; };
``` ```
@ -239,35 +298,38 @@ The `Urbit.poke` method takes five arguments:
`onSuccess` and `onError` are optional, but it's usually desirable to handle `onSuccess` and `onError` are optional, but it's usually desirable to handle
these cases. these cases.
The `delete` and `submitEdit` functions are similar to `submitNew`, but for the The `deleteEntry()` and `editEntry()` functions are similar to `createEntry()`,
`%del` and `%edit` actions rather than `%add`: but for the `%del` and `%edit` actions rather than `%add`:
![edit screenshot](https://media.urbit.org/guides/core/app-school-full-stack-guide/edit.png) ![edit screenshot](https://media.urbit.org/guides/core/app-school-full-stack-guide/edit.png)
```js ```javascript
submitEdit = (id, txt) => { const editEntry = (id, txt) => {
if (txt !== null) { if (txt === null) {
delete drafts[id];
setDrafts({...drafts});
} else {
window.urbit.poke({ window.urbit.poke({
app: "journal", app: "journal",
mark: "journal-action", mark: "journal-action",
json: { edit: { id: id, txt: txt } }, json: { edit: { id: id, txt: txt } },
onError: () => this.setErrorMsg("Edit rejected"), onError: () => setError("Edit rejected"),
}); });
} else this.cancelEdit(id); }
}; };
``` ```
![delete screenshot](https://media.urbit.org/guides/core/app-school-full-stack-guide/delete.png) ![delete screenshot](https://media.urbit.org/guides/core/app-school-full-stack-guide/delete.png)
```js ```javascript
delete = (id) => { const deleteEntry = (id) => {
window.urbit.poke({ window.urbit.poke({
app: "journal", app: "journal",
mark: "journal-action", mark: "journal-action",
json: {"del": {"id": id}}, json: { del: { id: id } },
onError: ()=>this.setErrorMsg("Deletion rejected") onError: () => setError("Deletion rejected"),
}) });
this.setState({rmModalShow: false, entryToDelete: null}) setDeleteId(null);
}; };
``` ```
@ -279,73 +341,66 @@ our agent.
![search screenshot](https://media.urbit.org/guides/core/app-school-full-stack-guide/search.png) ![search screenshot](https://media.urbit.org/guides/core/app-school-full-stack-guide/search.png)
When searching for entries between two dates, the `getSearch` function is When searching for entries between two dates, the `searchEntries()` function is
called, which uses the `Urbit.scry` method to scry for the results in a similar called, which uses the `Urbit.scry` method to scry for the results in a similar
fashion to [`getEntries`](#getting-entries), but using the fashion to [`getEntries`](#getting-entries), but using the
`/x/entries/between/[start]/[end]` endpoint. `/x/entries/between/[start]/[end]` endpoint.
```js ```javascript
getSearch = async () => { const searchEntries = async () => {
const { searchStart: ss, searchEnd: se, latestUpdate: lu } = this.state; const start = Math.max(inputStart.getTime(), 0);
if (lu !== null && ss !== null && se !== null) { const end = Math.max(inputEnd.getTime(), 0);
let start = ss.getTime(); window.urbit.scry({
let end = se.getTime(); app: "journal",
if (start < 0) start = 0; path: `/entries/between/${start}/${end}`,
if (end < 0) end = 0; }).then(
const path = `/entries/between/${start}/${end}`; (result) => {
window.urbit setInputStart(null);
.scry({ setInputEnd(null);
app: "journal", setResults(result.entries);
path: path, setSearchMeta({
}) time: result.time,
.then( start: inputStart,
(result) => { end: inputEnd
this.setState({ });
searchTime: result.time, },
searchStart: null, (err) => {
searchEnd: null, setError("Search failed");
resultStart: ss, }
resultEnd: se, );
results: result.entries,
});
},
(err) => {
this.setErrorMsg("Search failed");
}
);
} else {
lu !== null && this.setErrorMsg("Searh failed");
}
}; };
``` ```
## Error handling ## Error handling
When the channel connection is interrupted, the `Urbit` object will begin trying to reconnect. On each attempt, it sets the connection `status` to `"try"`, as we specified for the `onRetry` callback. When this is set, a "reconnecting" message is displayed at the bottom of the screen: When the channel connection is interrupted, the `Urbit` object will begin
trying to reconnect. On each attempt, it sets the connection `status` to
`"try"`, as we specified for the `onRetry` callback. When this is set, a
"reconnecting" message is displayed at the bottom of the screen:
![reconnecting screenshot](https://media.urbit.org/guides/core/app-school-full-stack-guide/reconnecting.png) ![reconnecting screenshot](https://media.urbit.org/guides/core/app-school-full-stack-guide/reconnecting.png)
If all three reconnection attempts fail, the `onError` callback is fired and we replace the "reconnecting" message with a "reconnect" button: If all three reconnection attempts fail, the `onError` callback is fired and we
replace the "reconnecting" message with a "reconnect" button:
![reconnect screenshot](https://media.urbit.org/guides/core/app-school-full-stack-guide/reconnect.png) ![reconnect screenshot](https://media.urbit.org/guides/core/app-school-full-stack-guide/reconnect.png)
When clicked, the following function is called: When clicked, the following function is called:
```js ```javascript
reconnect = () => { const reconnect = () => {
window.urbit.reset(); window.urbit.reset();
const latest = this.state.latestUpdate; if (latestUpdate === null) {
if (latest === null) { init();
this.init();
} else { } else {
this.getUpdates().then( getUpdates().then(
(result) => { (result) => {
result.logs.map((e) => this.handleUpdate(e)); result.logs.map(setSubEvent);
this.subscribe(); subscribe();
}, },
(err) => { (err) => {
this.setErrorMsg("Connection failed"); addError("Connection failed");
this.setState({ status: "err" }); setStatus("err");
} }
); );
} }
@ -363,10 +418,9 @@ Since we've reset the channel, we don't know if we've missed any updates. Rather
than having to refresh our whole state, we can use the `getUpdates()` function than having to refresh our whole state, we can use the `getUpdates()` function
to get any missing update: to get any missing update:
```js ```javascript
getUpdates = async () => { const getUpdates = async () => {
const { latestUpdate: latest } = this.state; const since = latestUpdate === null ? Date.now() : latestUpdate;
const since = latest === null ? Date.now() : latest;
const path = `/updates/since/${since}`; const path = `/updates/since/${since}`;
return window.urbit.scry({ return window.urbit.scry({
app: "journal", app: "journal",
@ -381,18 +435,21 @@ recent than `latestUpdate`, which is always set to the last logged action we
received. The `getUpdates` function returns a Promise to the `reconnect` received. The `getUpdates` function returns a Promise to the `reconnect`
function above which called it. The `reconnect` function handles it in a `.then` function above which called it. The `reconnect` function handles it in a `.then`
expression, where the success case passes each update retrieved to the expression, where the success case passes each update retrieved to the
[`handleUpdate`](#updates) function, updating our state. [`setSubEvent()`](#updates) function, updating our state.
Lastly, as well as handling channel connection errors, we also handle errors Lastly, as well as handling channel connection errors, we also handle errors
such as poke nacks or failed scries by printing error messages added to the such as poke nacks or failed scries by printing error messages added to the
`error` map by the `setErrorMsg` function. You could of course handle nacks, `error` map by the `setErrorMsg()` function. You could of course handle nacks,
kicks, scry failures, etc differently than just printing an error, it depends on kicks, scry failures, etc differently than just printing an error; it depends
the needs of your app. on the needs of your app.
![search failed screenshot](https://media.urbit.org/guides/core/app-school-full-stack-guide/search-failed.png) ![search failed screenshot](https://media.urbit.org/guides/core/app-school-full-stack-guide/search-failed.png)
## Resources ## Resources
- [React Tutorial](https://react.dev/learn/tutorial-tic-tac-toe) - A tutorial
walking through the basics of writing a modern React application.
- [HTTP API Guide](/guides/additional/http-api-guide) - Reference documentation for - [HTTP API Guide](/guides/additional/http-api-guide) - Reference documentation for
`@urbit/http-api`. `@urbit/http-api`.
@ -403,3 +460,7 @@ the needs of your app.
- [`@urbit/http-api` source - [`@urbit/http-api` source
code](https://github.com/urbit/urbit/tree/master/pkg/npm/http-api) - The code](https://github.com/urbit/urbit/tree/master/pkg/npm/http-api) - The
source code for the `@urbit/http-api` NPM package. source code for the `@urbit/http-api` NPM package.
[`usestate()`]: https://react.dev/reference/react/useState
[`useeffect()`]: https://react.dev/reference/react/useEffect

View File

@ -34,20 +34,20 @@ There's a handful of extra files we need in the root of our desk:
We only have one agent to start, so `desk.bill` is very simple: We only have one agent to start, so `desk.bill` is very simple:
``` ``` {% copy=true %}
:~ %journal :~ %journal
== ==
``` ```
Likewise, `sys.kelvin` just contains: Likewise, `sys.kelvin` just contains:
``` ``` {% copy=true %}
[%zuse 417] [%zuse 414]
``` ```
The `desk.docket-0` file is slightly more complicated: The `desk.docket-0` file is slightly more complicated:
``` ``` {% copy=true %}
:~ :~
title+'Journal' title+'Journal'
info+'Dear diary...' info+'Dear diary...'
@ -107,24 +107,21 @@ Once created, we can mount it to the unix filesystem.
In the dojo of a fake ship: In the dojo of a fake ship:
``` ``` {% copy=true %}
> |merge %journal our %webterm |new-desk %journal
>= |mount %journal
> |mount %journal
>=
``` ```
Now we can browse to it in the unix terminal: Now we can browse to it in the unix terminal:
```sh ```sh {% copy=true %}
cd ~/zod/journal cd /path/to/zod/journal
``` ```
Currently it has the same files as the `%webterm` desk, so we need to delete Currently it just contains some skeleton files, so we need to delete those:
those:
```sh ```sh {% copy=true %}
rm -r . rm -rI /path/to/zod/journal/*
``` ```
Apart from the kernel and standard library, desks need to be totally Apart from the kernel and standard library, desks need to be totally
@ -132,53 +129,56 @@ self-contained, including all mark files and libraries necessary to build them.
For example, since our app contains a number of `.hoon` files, we need the For example, since our app contains a number of `.hoon` files, we need the
`hoon.hoon` mark, and its dependencies. The easiest way to ensure our desk has `hoon.hoon` mark, and its dependencies. The easiest way to ensure our desk has
everything it needs is to copy in the "dev" versions of the `%base` and everything it needs is to copy in the "dev" versions of the `%base` and
`%garden` desks. To do this, we first clone the Urbit git repository: `%garden` desks. To do this, we first clone the Urbit and Landscape git repositories:
```sh ```sh {% copy=true %}
git clone https://github.com/urbit/urbit.git urbit-git git clone https://github.com/urbit/urbit.git urbit-git
git clone https://github.com/tloncorp/landscape.git landscape-git
``` ```
If we navigate to the `pkg` directory in the cloned repo: If we navigate to the `pkg` directory in the cloned `urbit` repo:
```sh ```sh {% copy=true %}
cd ~/urbit-git/pkg cd /path/to/urbit-git/pkg
``` ```
...we can combine the `base-dev` and `garden-dev` desks with the included ...we can combine the `base-dev` and Landscape `desk-dev` desks with the
`symbolic-merge.sh` script: included `symbolic-merge.sh` script:
```sh ```sh {% copy=true %}
./symbolic-merge.sh base-dev journal ./symbolic-merge.sh base-dev journal
./symbolic-merge.sh garden-dev journal ./symbolic-merge.sh ../../landscape-git/desk-dev journal
``` ```
Now, we copy the contents of the new `journal` folder into our empty desk: Now, we copy the contents of the new `journal` folder into our empty desk:
```sh ```sh {% copy=true %}
cp -rL journal/* ~/zod/journal/ cp -rL journal/* /path/to/zod/journal/
``` ```
Note we've used the `L` flag to resolve symbolic links, because the dev-desks Note we've used the `L` flag to resolve symbolic links.
contain symlinks to files in the actual `arvo` and `garden` folders.
We can copy across all of our own files too: We can copy across all of our own files too:
```sh ```sh {% copy=true %}
cp -r ~/ourfiles/* ~/zod/journal/ cp -r /path/to/ourfiles/* /path/to/zod/journal/
``` ```
Finally, in the dojo, we can commit the whole lot: Finally, in the dojo, we can commit the whole lot:
``` ``` {% copy=true %}
|commit %journal |commit %journal
``` ```
## Glob ## Glob
The next step is to build our front-end and upload the files to our ship. In the The next step is to build our front-end and upload the files to our ship. If
`journal-ui` folder containing our React app, we can run: you haven't yet downloaded the journal front-end source files, you can grab
them from [their repository](https://github.com/urbit/docs-examples). In the
folder containing our React app (`journal-app/ui` relative to the repository
base directory), we can run:
```sh ```sh {% copy=true %}
npm run build npm run build
``` ```
@ -186,7 +186,7 @@ This will create a `build` directory containing the compiled front-end files. To
upload it to our ship, we need to first install the `%journal` desk. In the upload it to our ship, we need to first install the `%journal` desk. In the
dojo: dojo:
``` ``` {% copy=true %}
|install our %journal |install our %journal
``` ```
@ -208,7 +208,7 @@ If we now return to the homescreen of our ship, we'll see our tile displayed, an
The last thing we need to do is publish our app, so other users can install it The last thing we need to do is publish our app, so other users can install it
from our ship. To do that, we just run the following command in the dojo: from our ship. To do that, we just run the following command in the dojo:
``` ``` {% copy=true %}
:treaty|publish %journal :treaty|publish %journal
``` ```

View File

@ -27,7 +27,7 @@ here](https://github.com/urbit/urbit/tree/master/pkg/npm/api).
Here is the reference material for each section of this walkthrough. Here is the reference material for each section of this walkthrough.
#### Types ### Types
- [App School /sur section](/guides/core/app-school/7-sur-and-marks#sur) - - [App School /sur section](/guides/core/app-school/7-sur-and-marks#sur) -
This section of App School covers writing a `/sur` structure library for This section of App School covers writing a `/sur` structure library for
@ -38,7 +38,7 @@ Here is the reference material for each section of this walkthrough.
This section of `zuse.hoon` contains all the functions for working with This section of `zuse.hoon` contains all the functions for working with
`mop`s, and is well commented. `mop`s, and is well commented.
#### Agent ### Agent
- [App School I](/guides/core/app-school/intro) - App School I covers all - [App School I](/guides/core/app-school/intro) - App School I covers all
aspects of writing Gall agents in detail. aspects of writing Gall agents in detail.
@ -52,7 +52,7 @@ Here is the reference material for each section of this walkthrough.
The `agentio` library in the `%base` desk contains a large number of useful The `agentio` library in the `%base` desk contains a large number of useful
functions which making writing Gall agents easier. functions which making writing Gall agents easier.
#### JSON ### JSON
- [The JSON Guide](/guides/additional/json-guide) - The stand-alone JSON guide - [The JSON Guide](/guides/additional/json-guide) - The stand-alone JSON guide
covers JSON encoding/decoding in great detail. covers JSON encoding/decoding in great detail.
@ -69,7 +69,7 @@ Here is the reference material for each section of this walkthrough.
- [Eyre Overview](/reference/arvo/eyre/eyre) - This section of the Eyre vane - [Eyre Overview](/reference/arvo/eyre/eyre) - This section of the Eyre vane
documentation goes over the basic features of the Eyre vane. documentation goes over the basic features of the Eyre vane.
#### Marks ### Marks
- [The Marks section of the Clay documentation](/reference/arvo/clay/marks/marks) - - [The Marks section of the Clay documentation](/reference/arvo/clay/marks/marks) -
This section of the Clay vane documentation covers mark files comprehensively. This section of the Clay vane documentation covers mark files comprehensively.
@ -80,7 +80,7 @@ Here is the reference material for each section of this walkthrough.
- [The JSON Guide](/guides/additional/json-guide) - This also covers writing mark - [The JSON Guide](/guides/additional/json-guide) - This also covers writing mark
files to convert to/from JSON. files to convert to/from JSON.
#### Eyre ### Eyre
- [The Eyre vane documentation](/reference/arvo/eyre/eyre) - This section of the vane - [The Eyre vane documentation](/reference/arvo/eyre/eyre) - This section of the vane
docs covers all aspects of Eyre. docs covers all aspects of Eyre.
@ -91,7 +91,7 @@ Here is the reference material for each section of this walkthrough.
documentation walks through using Eyre's external API at a low level (using documentation walks through using Eyre's external API at a low level (using
`curl`). `curl`).
#### React App Setup and Logic ### React App Setup and Logic
- [HTTP API Guide](/guides/additional/http-api-guide) - Reference documentation for - [HTTP API Guide](/guides/additional/http-api-guide) - Reference documentation for
`@urbit/http-api`. `@urbit/http-api`.
@ -104,7 +104,7 @@ Here is the reference material for each section of this walkthrough.
code](https://github.com/urbit/urbit/tree/master/pkg/npm/http-api) - The code](https://github.com/urbit/urbit/tree/master/pkg/npm/http-api) - The
source code for the `@urbit/http-api` NPM package. source code for the `@urbit/http-api` NPM package.
#### Desk and Glob ### Desk and Glob
- [App publishing/distribution docs](/guides/additional/software-distribution) - - [App publishing/distribution docs](/guides/additional/software-distribution) -
Documentation covering third party desk composition, publishing and Documentation covering third party desk composition, publishing and

View File

@ -15,71 +15,29 @@ define _scry endpoints_ which allow data to be requested from their states. The
endpoints can process the data in any way before returning it, but they cannot endpoints can process the data in any way before returning it, but they cannot
alter the actual state - scries can only read, not modify. alter the actual state - scries can only read, not modify.
Most of the time, scry requests are handled by Arvo, which routes the request to Gall itself defines some special vane-level endpoints [as described in its scry
the appropriate vane. When you scry a Gall agent you actually scry Gall itself. reference](/reference/arvo/gall/scry), but most scries to Gall are routed to
Gall interprets the request, runs it on the specified agent, and then returns particular agents and handled by them instead. Agent scries are what we'll
the result. Scries are performed with the focus on here.
[dotket](/reference/hoon/rune/dot#-dotket) (`.^`) rune. Here's a summary of
their format: Scries are performed with the [dotket](/reference/hoon/rune/dot#-dotket) (`.^`)
rune. Here's a summary of their format:
![scry summary diagram](https://storage.googleapis.com/media.urbit.org/docs/arvo/scry-diagram-v2.svg) ![scry summary diagram](https://storage.googleapis.com/media.urbit.org/docs/arvo/scry-diagram-v2.svg)
A note on `care`s: Cares are most carefully implemented by Clay, where they specify A note on `care`s: Cares are most carefully implemented by Clay, where they
submodules and have tightly defined behaviors. For Gall agents, most of these specify submodules and have tightly defined behaviors. For Gall agents, most of
don't have any special behavior, and are just used to indicate the general kind these don't have any special behavior, and are just used to indicate the
of data produced by the endpoint. There are a handful of exceptions to this: general kind of data produced by the endpoint, with the exception of the `%x`
`%d`, `%e`, `%u` and `%x`. care:
#### `%d`
A scry to Gall with a `%d` `care` and no `path` will produce the `desk` in which
the specified agent resides. For example:
```
> .^(desk %gd /=hark-store=)
%garden
> .^(desk %gd /=hood=)
%base
```
#### `%e`
A scry to Gall with a `%e` `care`, a `desk` rather than agent in the `desk`
field of the above diagram, and no path, will produce a set of all installed
agents on that desk and their status. For example:
```
> .^((set [=dude:gall live=?]) %ge /=garden=)
{ [dude=%hark-system-hook live=%.y]
[dude=%treaty live=%.y]
[dude=%docket live=%.y]
[dude=%settings-store live=%.y]
[dude=%hark-store live=%.y]
}
```
#### `%u`
A scry to Gall with a `%u` `care` and no `path` will check whether or not the
specified agent is installed and running:
```
> .^(? %gu /=btc-wallet=)
%.y
> .^(? %gu /=btc-provider=)
%.n
> .^(? %gu /=foobar=)
%.n
```
#### `%x` #### `%x`
A scry to Gall with a `%x` `care` will be passed to the agent for handling. Gall Gall handles `%x` specially, and expects an extra field at the end of the
handles `%x` specially, and expects an extra field at the end of the `path` that `path` that specifies the `mark` to return. Gall will take the data produced by
specifies the `mark` to return. Gall will take the data produced by the the specified endpoint and try to convert it to the given mark, crashing if the
specified endpoint and try to convert it to the given mark, crashing if the mark mark conversion fails. The extra field specifying the mark is not passed
conversion fails. The extra field specifying the mark is not passed through to through to the agent itself. Here's a couple of examples:
the agent itself. Here's a couple of examples:
``` ```
> =store -build-file /=landscape=/sur/graph-store/hoon > =store -build-file /=landscape=/sur/graph-store/hoon
@ -338,8 +296,9 @@ crash!
- Scries will fail if the scry endpoint does not exist, the requested data does - Scries will fail if the scry endpoint does not exist, the requested data does
not exist, or the data does not nest in the return type specified. not exist, or the data does not nest in the return type specified.
- Scries can only be performed on the local ship, not on remote ships. - Scries can only be performed on the local ship, not on remote ships.
- Gall scries with an agent name in the `desk` field will be passed to that - Gall scries with an agent name in the `desk` field and without an extra empty
agent's `on-peek` arm for handling. element at the beginning of the path will be passed to that agent's `on-peek`
arm for handling.
- Gall scries with a `%x` `care` take a `mark` at the end of the scry `path`, - Gall scries with a `%x` `care` take a `mark` at the end of the scry `path`,
telling Gall to convert the data returned by the scry endpoint to the mark telling Gall to convert the data returned by the scry endpoint to the mark
specified. specified.
@ -352,6 +311,7 @@ crash!
## Exercises ## Exercises
- Have a read through the [Scry Guide](/reference/arvo/concepts/scry). - Have a read through the [Scry Guide](/reference/arvo/concepts/scry).
- Have a look at Gall's [scry reference](/reference/arvo/gall/scry).
- Have a read through the [dotket rune - Have a read through the [dotket rune
documentation](/reference/hoon/rune/dot#-dotket). documentation](/reference/hoon/rune/dot#-dotket).
- Run through the [Example](#example) yourself if you've not done so already. - Run through the [Example](#example) yourself if you've not done so already.

View File

@ -115,7 +115,7 @@ Azimuth](https://developers.urbit.org/guides/core/hoon-school/C-azimuth#the-urbi
While working with Hoon, you'll often want to delete an old fake ship and While working with Hoon, you'll often want to delete an old fake ship and
recreate a fresh one. Rather than having to wait a few minutes for the fresh recreate a fresh one. Rather than having to wait a few minutes for the fresh
ship to be initialised, you can instead create a backup copy of a fake ship. ship to be initialized, you can instead create a backup copy of a fake ship.
That way you can just delete the current copy, replace it with the backup, and That way you can just delete the current copy, replace it with the backup, and
reboot in a matter of seconds. reboot in a matter of seconds.

View File

@ -356,7 +356,7 @@ This syntax is a little bit strange in the Dojo because subsequent expressions,
38 38
> perfect-number > perfect-number
38 28
``` ```
The difference is that the Dojo “pin” is permanent until deleted: The difference is that the Dojo “pin” is permanent until deleted:

View File

@ -227,12 +227,12 @@ How can we control what kind of value a gate returns? Many programming language
Remember `^-` kethep? We will use `^-` as a _fence_, a way of making sure only data matching the appropriate structure get passed on. Remember `^-` kethep? We will use `^-` as a _fence_, a way of making sure only data matching the appropriate structure get passed on.
```hoon {% copy=true %} ```hoon {% copy=true %}
:: Confirm whether a value is greater than one. :: Confirm whether a value is greater than one by return 1 (if no) or 0 (if yes).
|= a=@ud |= a=@ud
^- @ud ^- @ud
?: (gth a 1) ?: (gth a 1)
%.n 1
%.y 0
``` ```
**This is the correct way to define a gate.** Frequent annotation of type with `^-` kethep fences is _essential_ to producing good Hoon code. From this point forward in Hoon School, we will hew to this standard. **This is the correct way to define a gate.** Frequent annotation of type with `^-` kethep fences is _essential_ to producing good Hoon code. From this point forward in Hoon School, we will hew to this standard.

View File

@ -106,9 +106,9 @@ Here's a non-exhaustive list of auras, along with examples of corresponding lite
| `@sv` | signed base32 | `-0v1df64.49beg` | | `@sv` | signed base32 | `-0v1df64.49beg` |
| `@sw` | signed base64 | `--0wbnC.8haTg` | | `@sw` | signed base64 | `--0wbnC.8haTg` |
| `@sx` | signed hexadecimal | `-0x5f5.e138` | | `@sx` | signed hexadecimal | `-0x5f5.e138` |
| `@t` | UTF-8 text (cord) | `'howdy'` | | `@t` | UTF-8 text (`cord`) | `'howdy'` |
| `@ta` | ASCII text (knot) | `~.howdy` | | `@ta` | ASCII text (subset) (`knot`) | `~.howdy` |
| `@tas` | ASCII text symbol (term) | `%howdy` | | `@tas` | ASCII text symbol (subset) (`term`) | `%howdy` |
| `@u` | unsigned integer | no literal | | `@u` | unsigned integer | no literal |
| `@ub` | unsigned binary | `0b11.1000` | | `@ub` | unsigned binary | `0b11.1000` |
| `@ud` | unsigned decimal | `1.000.056` | | `@ud` | unsigned decimal | `1.000.056` |
@ -118,6 +118,8 @@ Here's a non-exhaustive list of auras, along with examples of corresponding lite
Some of these auras nest under others. For example, `@u` is for all unsigned auras. But there are other, more specific auras; `@ub` for unsigned binary numbers, `@ux` for unsigned hexadecimal numbers, etc. (For a more complete list of auras, see [Auras](/reference/hoon/auras).) Some of these auras nest under others. For example, `@u` is for all unsigned auras. But there are other, more specific auras; `@ub` for unsigned binary numbers, `@ux` for unsigned hexadecimal numbers, etc. (For a more complete list of auras, see [Auras](/reference/hoon/auras).)
`knot` and `term` values each use a URL-safe subset of ASCII, omitting characters like spaces.
### Aura Inference in Hoon ### Aura Inference in Hoon
Let's work a few more examples in the Dojo using the `?` operator. We'll focus on just the unsigned auras for now: Let's work a few more examples in the Dojo using the `?` operator. We'll focus on just the unsigned auras for now:

View File

@ -242,7 +242,7 @@ Produce a gate (generator) which accepts a `tape` value and returns a `(list @ud
The previous code simply modified a value by addition. You can generalize this to other arithmetic processes, like multiplication, but you can also grow a data structure like a list. The previous code simply modified a value by addition. You can generalize this to other arithmetic processes, like multiplication, but you can also grow a data structure like a list.
For example, given the `tape` `"hello"`, the generator should return the list `~[104 101 108 108 111]`. For example, given the `tape` `"hello"`, the generator should return the list `[104 101 108 108 111 ~]`. (A list is structurally a null-terminated tuple, or rightwards-branching cell ending in `~` or `0`.) We can equivalently write `~[104 101 108 108 111]` which is a special syntax reducing to the same thing.
Two tools that may help: Two tools that may help:
@ -779,12 +779,12 @@ and verify that our program correctly produces the sequence of numbers 1, 1, 2,
=/ q 1 =/ q 1
=/ r *(list @ud) =/ r *(list @ud)
|- ^- (list @ud) |- ^- (list @ud)
?: =(i n) r ?: =(index n) r
%= $ %= $
i +(i) index +(index)
p q p q
q (add p q) q (add p q)
r [q r] r [q r]
== ==
``` ```
@ -939,12 +939,4 @@ F_{n+1} (x, y+1) & = F_n (F_{n+1} (x, y), F_{n+1} (x, y) + y + 1) & \text{if } n
\end{array} \end{array}
{% /math %} {% /math %}
<!--
\begin{array}{lll}
F_0 (x, y) & = x+y \\
F_{n+1} (x, 0) & = x & \text{if } n \ge 0 \\
F_{n+1} (x, y+1) & = F_n (F_{n+1} (x, y), F_{n+1} (x, y) + y + 1) & \text{if } n\ge 0 \\
\end{array}
-->
- Implement the Sudan function as a gate. - Implement the Sudan function as a gate.

View File

@ -284,22 +284,31 @@ Unfortunately `/` fas runes don't work in the Dojo right now, so we need to buil
A [desk](/reference/glossary/desk) organizes a collection of files, including generators, libraries, agents, and system code, into one coherent bundle. A desk is similar to a file drive in a conventional computer, or a Git branch. Desks are supported by the Clay vane in Arvo, the Urbit OS. A [desk](/reference/glossary/desk) organizes a collection of files, including generators, libraries, agents, and system code, into one coherent bundle. A desk is similar to a file drive in a conventional computer, or a Git branch. Desks are supported by the Clay vane in Arvo, the Urbit OS.
At this point, you've likely only worked on the `%base` desk. You can see data about any particular desk using the `+vat` generator: At this point, you've likely only worked on the `%base` desk. You can see data about any particular desk using the `+vats` generator:
```hoon ```hoon
> +vat %base > +vats %base
%base %base
/sys/kelvin: [%zuse 417] /sys/kelvin: [%zuse 413]
base hash: ~ base hash ends in: hih5c
%cz hash: 0v2.r1lbp.i9jr2.hosbi.rvg16.pqe7u.i3hnp.j7k27.9jsgv.8k7rp.oi98q %cz hash ends in: hih5c
app status: running
pending updates: ~
> +vats %base, =verb %.y
%base
/sys/kelvin: [%zuse 413]
base hash: 0v2.vhcjk.rj42q.e3la7.1679q.u2qs2.35vnn.9n1jm.mj66h.kgpe5.hih5c
%cz hash: 0v2.vhcjk.rj42q.e3la7.1679q.u2qs2.35vnn.9n1jm.mj66h.kgpe5.hih5c
app status: running app status: running
force on: ~ force on: ~
force off: ~ force off: ~
publishing ship: ~ publishing ship: ~
updates: local updates: remote
source ship: ~ source ship: ~marnec-dozzod-marzod
source desk: ~ source desk: %kids
source aeon: ~ source aeon: 43
kids desk: %kids
pending updates: ~ pending updates: ~
``` ```

View File

@ -312,8 +312,6 @@ A `fish-loop` arises when using a recursive mold definition like `list`. (The r
fish-loop fish-loop
``` ```
although a promised `?#` wuthax rune should match it once implemented.
### `generator-build-fail` ### `generator-build-fail`
A `generator-build-fail` most commonly results from composing code with mismatched runes (and thus the wrong children including hanging expected-but-empty slots). A `generator-build-fail` most commonly results from composing code with mismatched runes (and thus the wrong children including hanging expected-but-empty slots).

View File

@ -32,7 +32,7 @@ The second way of making a function call involves an expression that _produces_
246 246
``` ```
The difference is subtle: the first cast has an already-created gate in the subject when we called it, while the latter involves producing a gate that doesn't exist anywhere in the subject, and then calling it. The difference is subtle: the first case has an already-created gate in the subject when we called it, while the latter involves producing a gate that doesn't exist anywhere in the subject, and then calling it.
Are calls to `++add` and `++mul` of the Hoon standard library of the first kind, or the second? Are calls to `++add` and `++mul` of the Hoon standard library of the first kind, or the second?
@ -453,7 +453,7 @@ What is that cell? Wasn't the value stored as `0xff.8833`? Well, one fundament
- What does `[~ ~]` mean when returned from a `map`? - What does `[~ ~]` mean when returned from a `map`?
`unit`s are common enough that they have their own syntax and set of operational functions. We'll look at them more in [the next module](/guides/core/hoon-school/K-doors). `unit`s are common enough that they have their own syntax and set of operational functions. We'll look at them more in [the next module](/guides/core/hoon-school/L-struct).
```hoon ```hoon
> (~(get by colors) %brown) > (~(get by colors) %brown)

View File

@ -222,7 +222,7 @@ It's important to remember to include a cast rune with each gate and trap expres
By now you've used the `|=` rune to define several gates. This rune is used to produce a _dry gate_, which has different type-checking and type-inference properties than a _wet gate_ does. We won't explain the distinction until [a later module](/guides/core/hoon-school/R-metals)—for now, just keep in mind that we're only dealing with one kind of gate (albeit the more common kind). By now you've used the `|=` rune to define several gates. This rune is used to produce a _dry gate_, which has different type-checking and type-inference properties than a _wet gate_ does. We won't explain the distinction until [a later module](/guides/core/hoon-school/R-metals)—for now, just keep in mind that we're only dealing with one kind of gate (albeit the more common kind).
The first subexpression after the `|=` defines the sample type. Any faces used in this definition have the type declared for it in this definition. Consider an addition generator `/gen/add.hoon`: The first subexpression after the `|=` defines the sample type. Any faces used in this definition have the type declared for it in this definition. Consider an addition generator `/gen/sum.hoon`:
```hoon {% copy=true %} ```hoon {% copy=true %}
|= [a=@ b=@] |= [a=@ b=@]
@ -235,10 +235,10 @@ $(a +(a), b (dec b))
We run it in the Dojo using a cell to pass the two arguments: We run it in the Dojo using a cell to pass the two arguments:
```hoon ```hoon
> +add 12 14 > +sum [12 14]
26 26
> +add 22 > +sum 22
nest-fail nest-fail
-need.[a=@ b=@] -need.[a=@ b=@]
-have.@ud -have.@ud

View File

@ -28,7 +28,7 @@ has three folders inside:
1. `bare-desk`: just the hoon files created here without any dependencies. 1. `bare-desk`: just the hoon files created here without any dependencies.
2. `full-desk`: `bare-desk` plus all dependencies. Note some files are 2. `full-desk`: `bare-desk` plus all dependencies. Note some files are
symlinked, so if you're copying them you'll need to do `cp -rL`. symlinked, so if you're copying them you'll need to do `cp -rL`.
3. `react-frontend`: the React front-end files. 3. `ui`: the React front-end files.
Let's get started. Let's get started.
@ -53,7 +53,7 @@ curl -L https://urbit.org/install/linux-aarch64/latest | tar xzk --transform='s/
#### macOS (`x86_64`) #### macOS (`x86_64`)
```shell {% copy=true %} ```shell {% copy=true %}
curl -L https://urbit.org/install/macos-x86_64/latest | tar xzk -s '/.*/urbit/' curl -L https://urbit.org/install/macos-x86_64/latest | tar xzk -s '/.*/urbit/'
``` ```
#### macOS (`aarch64`) #### macOS (`aarch64`)
@ -241,7 +241,7 @@ a hut by poking our agent with a `%post` action. Likewise, we'll be able to
subscribe to huts for groups on other ships and poke them to post messages. subscribe to huts for groups on other ships and poke them to post messages.
Remember, all Urbit ships are both clients and servers. Remember, all Urbit ships are both clients and servers.
There's three main agent arms we use for this: There are three main agent arms we use for this:
1. `on-poke`: This arm handles one-off actions/requests, such as posting a 1. `on-poke`: This arm handles one-off actions/requests, such as posting a
message to a hut. message to a hut.
@ -368,7 +368,7 @@ Gall agents live in the `/app` directory of a desk, so you can save this code in
|= old-vase=vase |= old-vase=vase
^- (quip card _this) ^- (quip card _this)
[~ this(state !<(state-0 old-vase))] [~ this(state !<(state-0 old-vase))]
:: on-poke handles "pokes", one-off requests/actions intiated either :: on-poke handles "pokes", one-off requests/actions initiated either
:: by our local ship, the front-end or other ships on the network. :: by our local ship, the front-end or other ships on the network.
:: ::
++ on-poke ++ on-poke
@ -570,7 +570,7 @@ Gall agents live in the `/app` directory of a desk, so you can save this code in
:: the subscribe succeeded or failed :: the subscribe succeeded or failed
:: ::
%watch-ack %watch-ack
:: if there's no error message it succceeded, :: if there's no error message it succeeded,
:: do nothing further :: do nothing further
:: ::
?~ p.sign `this ?~ p.sign `this
@ -784,7 +784,7 @@ Gall agents live in the `/app` directory of a desk, so you can save this code in
:: switch on the kind of event :: switch on the kind of event
:: ::
?+ -.sign (on-agent:def wire sign) ?+ -.sign (on-agent:def wire sign)
:: if it was a response to a subscriiption request... :: if it was a response to a subscription request...
%watch-ack %watch-ack
:: if there's no error message the subscription succeeded, :: if there's no error message the subscription succeeded,
:: no nothing :: no nothing
@ -1117,7 +1117,7 @@ in `hut/mar/hut/do.hoon` and `hut/mar/hut/did.hoon` respectively.
:: ::
++ grow ++ grow
|% |%
:: this mark is primarily used inbound from the :: this mark is primarily used inbound from the
:: front-end, so we only need a simple %noun :: front-end, so we only need a simple %noun
:: conversion method here :: conversion method here
:: ::
@ -1252,7 +1252,7 @@ in `hut/mar/hut/do.hoon` and `hut/mar/hut/did.hoon` respectively.
['joined' (en-joined joined.u)] ['joined' (en-joined joined.u)]
== ==
== ==
:: this function creates an array of the the members of a the :: this function creates an array of the members of a the
:: huts for a squad :: huts for a squad
:: ::
++ en-joined ++ en-joined
@ -1333,248 +1333,256 @@ in `hut/mar/hut/do.hoon` and `hut/mar/hut/did.hoon` respectively.
Our back-end is complete, so we can now work on our React front-end. We'll just Our back-end is complete, so we can now work on our React front-end. We'll just
look at the basic setup process here, but you can get the full React app by look at the basic setup process here, but you can get the full React app by
cloning [this repo on Github](https://github.com/urbit/docs-examples) and run cloning [this repo on Github](https://github.com/urbit/docs-examples) and run
`npm i` in `chat-app/react-frontend`. Additional commentary on the code is in `npm i` in `chat-app/ui`. Additional commentary on the code is in the
the [additional commentary](#additional-commentary) section below. [additional commentary](#additional-commentary) section below.
#### Basic setup process #### Basic setup process
When creating it from scratch, we can first run `create-react-app` like usual: When creating it from scratch, first make sure you have Node.js installed on
your computer (you can download it from their
[website](https://nodejs.org/en/download)) and then run `create-landscape-app`:
```shell {% copy=true %} ```shell
npx create-react-app hut-ui npx @urbit/create-landscape-app
cd hut-ui ✔ What should we call your application? … hut
✔ What URL do you use to access Urbit? … http://127.0.0.1:8080
``` ```
To make talking to our ship easy, we'll install the `@urbit/http-api` module: This will generate a React project in the `hut/ui` directory with all the
basic necessities for Urbit front-end development. Next, run the following
commands to install the project's dependencies:
``` ```shell
npm i @urbit/http-api cd hut/ui
npm i
``` ```
`http-api` handles most of the tricky parts of communicating with our ship for We can now open `src/app.jsx`, wipe its contents, and start writing our own
us, and has a simple set of methods for doing things like pokes, subscriptions, app. The first thing is to import the `Urbit` class from `@urbit/http-api`:
receiving updates, etc.
The next thing we need to do is edit `package.json`. We'll change the name of
the app, and we'll also add an additional `"homepage"` entry. Front-ends are
serve at `/apps/<name>`, so we need to set that as the root for when we build
it:
```json
"name": "hut",
"homepage": "/apps/hut/",
```
Next, we need to edit `public/index.html` and add a script import to the
`<head>` section. `http-api` needs to know the name of our ship in order to talk
to it, so our ship serves a simple script at `/session.js` that just does
`window.ship = "sampel-palnet";`.
```html
<script src="/session.js"></script>
```
We can now open `src/App.js`, wipe its contents, and start writing our own app.
The first thing is to import the `Urbit` class from `@urbit/http-api`:
```javascript ```javascript
import React, {Component} from "react"; import React, {useEffect, useState} from "react";
import Urbit from "@urbit/http-api"; import Urbit from "@urbit/http-api";
// .....
``` ```
In our App class, we'll create a new `Urbit` instance and tell it our ship name. We'll create an `App` component that will create a new `Urbit` instance on load
We'll also add some connection state callbacks. Our app is simple and will just to monitor our front-end's connection with our ship. Our app is simple and will
display the connection status in the top-right corner. just display the connection status in the top-left corner:
```javascript ```javascript
constructor(props) { export function App() {
super(props); const [status, setStatus] = useState("try");
window.urbit = new Urbit("");
window.urbit.ship = window.ship; useEffect(() => {
// ...... window.urbit = new Urbit("");
window.urbit.onOpen = () => this.setState({conn: "ok"}); window.urbit.ship = window.ship;
window.urbit.onRetry = () => this.setState({conn: "try"});
window.urbit.onError = () => this.setState({conn: "err"}); window.urbit.onOpen = () => setStatus("con");
// ...... window.urbit.onRetry = () => setStatus("try");
}; window.urbit.onError = () => setStatus("err");
const subscription = window.urbit.subscribe({
app: "hut",
path: "/all",
event: (e) => console.log(e),
});
return () => window.urbit.unsubscribe(subscription);
}, []);
return (<h1>{status}</h1>);
}
``` ```
```javascript After we've finished writing our React app, we can build it and view the
constructor(props) { resulting files in the `dist` directory:
super(props);
window.urbit = new Urbit("");
window.urbit.ship = window.ship;
// ......
window.urbit.onOpen = () => this.setState({conn: "ok"});
window.urbit.onRetry = () => this.setState({conn: "try"});
window.urbit.onError = () => this.setState({conn: "err"});
// ......
};
```
After we've finished writing our React app, we can build it: ```shell
```shell {% copy=true %}
npm run build npm run build
ls dist
``` ```
#### Additional commentary #### Additional commentary
There are a fair few functions our front-end uses, so we'll just look at a There are a fair few functions in the
handful. The first is `doPoke`, which (as the name suggests) sends a poke to a [complete front-end source for `%hut`](https://github.com/urbit/docs-examples);
ship. It takes the poke in JSON form. It then calls the `poke` method of our we'll just look at a handful to cover the basics. The first is the `appPoke`
`Urbit` object to perform the poke. in `src/lib.js`, which (as the name suggests) sends a poke to a ship. It takes
the poke in JSON form and calls the `poke` method of our `Urbit` object to
perform the poke:
```javascript ```javascript
doPoke = jon => { export function appPoke(jon) {
window.urbit.poke({ return api.poke({
app: "hut", app: "hut",
mark: "hut-do", mark: "hut-do",
json: jon, json: jon,
}) });
}; }
``` ```
Here's an example of a `%join`-type `act` in JSON form: An example of sending a `poke` with a `%join`-type `act` in JSON form can be
found in the `src/components/SelectGid.jsx` source file:
```javascript ```javascript
joinGid = () => { const handleJoin = () => {
const joinSelect = this.state.joinSelect if (joinSelect !== "def") {
if (joinSelect === "def") return; const [host, name] = joinSelect.split("/");
const [host, name] = joinSelect.split("/"); appPoke({
this.doPoke( "join": {
{"join": { "gid" : {"host": host, "name": name},
"gid" : {"host": host, "name": name}, "who" : OUR
"who" : this.our }
}} });
); }
this.setState({joinSelect: "def"})
}; };
``` ```
Our front-end will subscribe to updates for all groups our `%hut` agent is Our front-end will subscribe to updates for all groups our `%hut` agent is
currently tracking. To do so, it calls the `subscribe` method of the `Urbit` currently tracking. To do so, it calls the `subscribe` method of the `Urbit`
object with the `path` to subscribe to and an `event` callback to handle each object (aliased to `api` in our example) with the `path` to subscribe to and an
update it receives. Our agent publishes all updates on the local-only `/all` `event` callback to handle each update it receives. Our agent publishes all
path. updates on the local-only `/all` path. Here's the source in the `src/app.jsx`
file:
```javascript ```javascript
subscribe = () => { const subscription = api.subscribe({
window.urbit.subscribe({ app: "hut",
app: "hut", path: "/all",
path: "/all", event: setSubEvent,
event: this.handleUpdate });
});
};
``` ```
Here's the `handleUpdate` function we gave as a callback. The update will be one Notice that the above call to `subscribe` passes the `setSubEvent` function.
of our `hut-upd` types in JSON form, so we just switch on the type and handle it This is part of a common pattern for Urbit React applications wherein a state
as appropriate. variable is used to track new events and cause component re-rendering. The
broad outline for this workflow is as follows:
1. Create a component subscription event variable with:
```javascript
const [subEvent, setSubEvent] = useState();
```
2. Call the `subscribe` function, passing `setSubEvent` as the `event` keyword
argument:
```javascript
urbit.subscribe({ /* ... */, event: setSubEvent });
```
3. Create a subscription handler function that updates when new events are
available with:
```javascript
useEffect(() => {/* handler goes here */}, [subEvent]);
```
The source for the final `useEffect` portion of this workflow (found in the
`src/app.jsx` file) can be found below:
```javascript {% mode="collapse" %} ```javascript {% mode="collapse" %}
handleUpdate = upd => { useEffect(() => {
const {huts, msgJar, joined, currentGid, currentHut} = this.state; const updateFuns = {
if ("initAll" in upd) { "initAll": (update) => {
upd.initAll.huts.forEach(obj => update.huts.forEach(obj =>
huts.set(this.gidToStr(obj.gid), new Set(obj.names)) huts.set(gidToStr(obj.gid), new Set(obj.names))
); );
this.setState({
huts: huts, setHuts(new Map(huts));
msgJar: new Map( setChatContents(new Map(
upd.initAll.msgJar.map(obj => [this.hutToStr(obj.hut), obj.msgs]) update.msgJar.map(o => [hutToStr(o.hut), o.msgs])
), ));
joined: new Map( setChatMembers(new Map(
upd.initAll.joined.map(obj => update.joined.map(o => [gidToStr(o.gid), new Set(o.ppl)])
[this.gidToStr(obj.gid), new Set(obj.ppl)] ));
) }, "init": (update) => {
) setChatContents(new Map(update.msgJar.reduce(
}) (a, n) => a.set(hutToStr(n.hut), n.msgs)
} else if ("init" in upd) { , chatContents)));
upd.init.msgJar.forEach(obj => setHuts(new Map(huts.set(
msgJar.set(this.hutToStr(obj.hut), obj.msgs) gidToStr(update.huts[0].gid),
); new Set(update.huts[0].names)
this.setState({ )));
msgJar: msgJar, setChatMembers(new Map(chatMembers.set(
huts: huts.set( gidToStr(update.joined[0].gid),
this.gidToStr(upd.init.huts[0].gid), new Set(update.joined[0].ppl)
new Set(upd.init.huts[0].names) )));
), }, "new": (update) => {
joined: joined.set( const gidStr = gidToStr(update.hut.gid);
this.gidToStr(upd.init.joined[0].gid), const hutStr = hutToStr(update.hut);
new Set(upd.init.joined[0].ppl) if (huts.has(gidStr)) {
) huts.get(gidStr).add(update.hut.name);
}) } else {
} else if ("new" in upd) { huts.set(gidStr, new Set(update.hut.name));
const gidStr = this.gidToStr(upd.new.hut.gid);
const hutStr = this.hutToStr(upd.new.hut);
(huts.has(gidStr))
? huts.get(gidStr).add(upd.new.hut.name)
: huts.set(gidStr, new Set(upd.new.hut.name));
this.setState({
huts: huts,
msgJar: msgJar.set(hutStr, upd.new.msgs)
})
} else if ("post" in upd) {
const hutStr = this.hutToStr(upd.post.hut);
(msgJar.has(hutStr))
? msgJar.get(hutStr).push(upd.post.msg)
: msgJar.set(hutStr, [upd.post.msg]);
this.setState(
{msgJar: msgJar},
() => {
(hutStr === this.state.currentHut)
&& this.scrollToBottom();
} }
)
} else if ("join" in upd) { setHuts(new Map(huts));
const gidStr = this.gidToStr(upd.join.gid); setChatMembers(new Map(chatMembers.set(hutStr, update.msgs)));
(joined.has(gidStr)) }, "post": (update) => {
? joined.get(gidStr).add(upd.join.who) const newHut = hutToStr(update.hut);
: joined.set(gidStr, new Set([upd.join.who])); if (chatContents.has(newHut)) {
this.setState({joined: joined}) chatContents.set(newHut, [...chatContents.get(newHut), update.msg]);
} else if ("quit" in upd) { } else {
const gidStr = this.gidToStr(upd.quit.gid); chatContents.set(newHut, [update.msg]);
if ("~" + window.ship === upd.quit.who) { }
(huts.has(gidStr)) &&
huts.get(gidStr).forEach(name => setChatContents(new Map(chatContents));
msgJar.delete(gidStr + "/" + name) }, "join": (update) => {
const gidStr = gidToStr(update.gid);
if (chatMembers.has(gidStr)) {
chatMembers.get(gidStr).add(update.who)
} else {
chatMembers.set(gidStr, new Set([update.who]));
}
setChatMembers(new Map(chatMembers));
setJoinSelect("def");
}, "quit": (update) => {
const gidStr = gidToStr(update.gid);
if (update.who === OUR) {
huts.delete(gidStr);
chatMembers.delete(gidStr);
if(huts.has(gidStr)) {
huts.get(gidStr).forEach(name =>
chatContents.delete(gidStr + "/" + name)
);
}
setHuts(new Map(huts));
setChatMembers(new Map(chatMembers));
setChatContents(new Map(chatContents));
setCurrGid((currGid === gidStr) ? null : currGid);
setCurrHut((currHut === null)
? null
: (`${currHut.split("/")[0]}/${currHut.split("/")[1]}` === gidStr)
? null
: currHut
); );
huts.delete(gidStr); setViewSelect("def");
joined.delete(gidStr); setHutInput((currGid === gidStr) ? "" : hutInput);
this.setState({ } else {
msgJar: msgJar, if (chatMembers.has(gidStr)) {
huts: huts, chatMembers.get(gidStr).delete(update.who);
joined: joined, }
currentGid: (currentGid === gidStr)
? null : currentGid, setChatMembers(new Map(chatMembers));
currentHut: (currentHut === null) ? null : }
( }, "del": (update) => {
currentHut.split("/")[0] + "/" + currentHut.split("/")[1] const gidStr = gidToStr(update.hut.gid);
=== gidStr const hutStr = hutToStr(update.hut);
) if (huts.has(gidStr)) {
? null : currentHut, huts.get(gidStr).delete(update.hut.name);
make: (currentGid === gidStr) ? "" : this.state.make }
}) chatContents.delete(hutStr);
} else {
(joined.has(gidStr)) && setHuts(new Map(huts));
joined.get(gidStr).delete(upd.quit.who); setChatContents(new Map(chatContents));
this.setState({joined: joined}) setCurrHut((currHut === hutStr) ? null : currHut);
} },
} else if ("del" in upd) { };
const gidStr = this.gidToStr(upd.del.hut.gid);
const hutStr = this.hutToStr(upd.del.hut); const eventTypes = Object.keys(subEvent);
(huts.has(gidStr)) && if (eventTypes.length > 0) {
huts.get(gidStr).delete(upd.del.hut.name); const eventType = eventTypes[0];
msgJar.delete(hutStr); updateFuns[eventType](subEvent[eventType]);
this.setState({
huts: huts,
msgJar: msgJar,
currentHut: (currentHut === hutStr) ? null : currentHut
})
} }
}; }, [subEvent]);
``` ```
## Desk config ## Desk config
@ -1587,7 +1595,7 @@ this by adding a `sys.kelvin` file to the root of our `hut` directory:
```shell {% copy=true %} ```shell {% copy=true %}
cd hut cd hut
echo "[%zuse 417]" > sys.kelvin echo "[%zuse 414]" > sys.kelvin
``` ```
We also need to specify which agents to start when our desk is installed. We do We also need to specify which agents to start when our desk is installed. We do
@ -1627,10 +1635,10 @@ the moment.
## Put it together ## Put it together
Our app is now complete, so let's try it out. In the Dojo of our comet, Our app is now complete, so let's try it out. In the Dojo of our comet,
we'll create a new desk by forking from an existing one: we'll create a new desk with the `|new-desk` generator:
``` {% copy=true %} ``` {% copy=true %}
|merge %hut our %webterm |new-desk %hut
``` ```
Next, we'll mount the desk so we can access it from the host OS: Next, we'll mount the desk so we can access it from the host OS:
@ -1639,12 +1647,12 @@ Next, we'll mount the desk so we can access it from the host OS:
|mount %hut |mount %hut
``` ```
Currently its contents are the same as the `%webterm` desk, so we'll need to It'll have a handful of skeleton files in it, but we can just delete those and
delete those files and copy in our own instead. In the normal shell, do the add our own instead. In the normal shell, do the
following: following:
```shell {% copy=true %} ```shell {% copy=true %}
rm -r dev-comet/hut/* rm -rI dev-comet/hut/*
cp -r hut/* dev-comet/hut/ cp -r hut/* dev-comet/hut/
``` ```
@ -1660,7 +1668,7 @@ to `localhost:8080` (or just `localhost` on a Mac). Login with the comet's web
code, which you can get by running `+code` in the Dojo. Next, go to code, which you can get by running `+code` in the Dojo. Next, go to
`localhost:8080/docket/upload` (or `localhost/docket/upload` on a Mac) and `localhost:8080/docket/upload` (or `localhost/docket/upload` on a Mac) and
it'll bring up the Docket Globulator tool. Select the `hut` desk from the it'll bring up the Docket Globulator tool. Select the `hut` desk from the
drop-down menu, then navigate to `hut-ui/build` and select the whole folder. drop-down menu, then navigate to `hut/ui/dist` and select the whole folder.
Finally, hit `glob!` and it'll upload our React app. Finally, hit `glob!` and it'll upload our React app.
If we return to `localhost:8080` (or `localhost` on a Mac), we should see a If we return to `localhost:8080` (or `localhost` on a Mac), we should see a

View File

@ -139,7 +139,7 @@ For the actions/requests our app will accept, we'll need the following:
6. Leave a squad. 6. Leave a squad.
7. Make a private squad public. 7. Make a private squad public.
8. Make a public squad private. 8. Make a public squad private.
9. Change the title of a squad . 9. Change the title of a squad.
These actions will only be allowed to be taken by the host ship. These actions will only be allowed to be taken by the host ship.
@ -256,7 +256,7 @@ host and receive updates such as access control list changes or members joining
and leaving. Likewise, we'll be able to subscribe to squads on other ships and and leaving. Likewise, we'll be able to subscribe to squads on other ships and
receive their updates. Remember, all Urbit ships are both clients and servers. receive their updates. Remember, all Urbit ships are both clients and servers.
There's three main agent arms we use for this: There are three main agent arms we use for this:
1. `on-poke`: This arm handles one-off actions/requests (our `act` structure). 1. `on-poke`: This arm handles one-off actions/requests (our `act` structure).
It will also handle requests from the front-end, which we'll create in the It will also handle requests from the front-end, which we'll create in the
@ -1103,7 +1103,7 @@ Gall agents live in the `/app` directory of a desk, so you can save this code in
== ==
:: ::
:: if it's a kick alert, it may or may not be intentional, :: if it's a kick alert, it may or may not be intentional,
:: so we just automaticall try to resubscribe :: so we just automatically try to resubscribe
:: ::
%kick %kick
?. (~(has by squads) gid) `this ?. (~(has by squads) gid) `this
@ -1208,7 +1208,7 @@ Gall agents live in the `/app` directory of a desk, so you can save this code in
:: check whether it's US, and do nothing if so :: check whether it's US, and do nothing if so
:: ::
?: =(our.bol ship.upd) `this ?: =(our.bol ship.upd) `this
:: otherwise, update the member list and and let local :: otherwise, update the member list and let local
:: subscribers know :: subscribers know
:: ::
:- ~[(fact:io cage.sign ~[/local/all])] :- ~[(fact:io cage.sign ~[/local/all])]
@ -1399,7 +1399,7 @@ mark in `squad/mar/squad/did.hoon`.
:: first we import our /sur/squad.hoon type defs and expose them directly :: first we import our /sur/squad.hoon type defs and expose them directly
:: ::
/- *squad /- *squad
:: the mark door takes an $act action in in the outbound case :: the mark door takes an $act action in the outbound case
:: ::
|_ a=act |_ a=act
:: the grow arm converts from an $act to other things :: the grow arm converts from an $act to other things
@ -1656,7 +1656,7 @@ Save the code below in `squad/app/squad/index.hoon`.
?:(success.page "success" "failed") ?:(success.page "success" "failed")
success.page success.page
== ==
:: This component lets a group host change whther a squad is private :: This component lets a group host change whether a squad is private
:: or public. It's a form that POSTs the new state to either the /squad/private :: or public. It's a form that POSTs the new state to either the /squad/private
:: or /squad/public URL path, and our Gall agent processes the request. :: or /squad/public URL path, and our Gall agent processes the request.
:: ::
@ -1916,7 +1916,7 @@ this by adding a `sys.kelvin` file to the root of our `squad` directory:
```shell {% copy=true %} ```shell {% copy=true %}
cd squad cd squad
echo "[%zuse 417]" > sys.kelvin echo "[%zuse 414]" > sys.kelvin
``` ```
We also need to specify which agents to start when our desk is installed. We do We also need to specify which agents to start when our desk is installed. We do
@ -1967,10 +1967,10 @@ squad
``` ```
Let's now try it out. In the Dojo of our comet, Let's now try it out. In the Dojo of our comet,
we'll create a new desk by forking from an existing one: we'll create a new desk with the `|new-desk` generator:
``` {% copy=true %} ``` {% copy=true %}
|merge %squad our %webterm |new-desk %squad
``` ```
Next, we'll mount the desk so we can access it from the host OS: Next, we'll mount the desk so we can access it from the host OS:
@ -1979,9 +1979,8 @@ Next, we'll mount the desk so we can access it from the host OS:
|mount %squad |mount %squad
``` ```
Currently its contents are the same as the `%webterm` desk, so we'll need to Currently it just contains some skeleton files, but we can just delete those
delete those files and copy in our own instead. In the normal shell, do the and add our own instead. In the normal shell, do the following:
following:
```shell {% copy=true %} ```shell {% copy=true %}
rm -r dev-comet/squad/* rm -r dev-comet/squad/*

View File

@ -116,7 +116,7 @@ across that our app will depend on:
mkdir -p tally/{app,sur,mar,lib} mkdir -p tally/{app,sur,mar,lib}
cp -r dev-comet/squad/mar/{bill*,hoon*,json*,kelvin*,mime*,noun*,ship*,txt*,docket-0*} tally/mar/ cp -r dev-comet/squad/mar/{bill*,hoon*,json*,kelvin*,mime*,noun*,ship*,txt*,docket-0*} tally/mar/
cp -r dev-comet/squad/lib/{agentio*,dbug*,default-agent*,skeleton*,docket*} tally/lib/ cp -r dev-comet/squad/lib/{agentio*,dbug*,default-agent*,skeleton*,docket*} tally/lib/
cp -r dev-comet/squad/sur/{docket*, squad*} tally/sur/ cp -r dev-comet/squad/sur/{docket*,squad*} tally/sur/
cp -r dev-comet/base/sur/ring.hoon tally/sur/ cp -r dev-comet/base/sur/ring.hoon tally/sur/
cp -r dev-comet/garden/lib/mip.hoon tally/lib/ cp -r dev-comet/garden/lib/mip.hoon tally/lib/
``` ```
@ -146,10 +146,10 @@ be sent or received in *pokes* (one-off messages):
- `%withdraw`: delete a poll. - `%withdraw`: delete a poll.
We'll also define an `update` structure, which wil be the kinds of events that We'll also define an `update` structure, which will be the kinds of events that
subscribers may be notified about: subscribers may be notified about:
- `%init`: given the intial polls and their state to a new subscriber. - `%init`: given the initial polls and their state to a new subscriber.
- `%vote`: someone has voted on a poll. - `%vote`: someone has voted on a poll.
- `%new`: someone has created a new poll. - `%new`: someone has created a new poll.
- `%withdraw`: someone has withdrawn an existing poll. - `%withdraw`: someone has withdrawn an existing poll.
@ -317,12 +317,12 @@ groups we're a member of. It additionally handles group updates from the
`%squad` agent. In the former case, the events we'll receive will contain `%squad` agent. In the former case, the events we'll receive will contain
`update`s, which we'll process in a similar manner to the `action`s in `update`s, which we'll process in a similar manner to the `action`s in
`on-poke`. All incoming votes will be validated here also. In the latter case, `on-poke`. All incoming votes will be validated here also. In the latter case,
we'll receive `%squad` `upd` updates, such as members joining or leave groups, we'll receive `%squad` `upd` updates, such as members joining or leaving groups,
and we'll handle them as appropriate. and we'll handle them as appropriate.
We differentiate between these two cases by testing the `wire`, which is a We differentiate between these two cases by testing the `wire`, which is a
message tag we set when we initially subscribed. For `%squad` updates it will be message tag we set when we initially subscribed. For `%squad` updates it will be
`/squad`, and for the rest it'll be the group ID we've subscribe to. `/squad`, and for the rest it'll be the group ID we've subscribed to.
#### The code #### The code
@ -428,7 +428,7 @@ Gall agents live in the `/app` directory of a desk, so save this code in
%1 `this(state old) %1 `this(state old)
%0 `this(state [%1 by-group.old voted.old withdrawn.old %subs]) %0 `this(state [%1 by-group.old voted.old withdrawn.old %subs])
== ==
:: on-poke handles "pokes", which are one-off requests/actions intiated :: on-poke handles "pokes", which are one-off requests/actions initiated
:: locally or by remote ships. If it's a %tally-action we call :: locally or by remote ships. If it's a %tally-action we call
:: a handle-action function, and if it's a %handle-http-request from the :: a handle-action function, and if it's a %handle-http-request from the
:: front-end we call the handle-http function :: front-end we call the handle-http function
@ -454,7 +454,7 @@ Gall agents live in the `/app` directory of a desk, so save this code in
?. authenticated.req ?. authenticated.req
:_ state(section %subs) :_ state(section %subs)
(give-http:hc rid [307 ['Location' '/~/login?redirect='] ~] ~) (give-http:hc rid [307 ['Location' '/~/login?redirect='] ~] ~)
:: otherwise, switch on the method, prodicing a 405 response for :: otherwise, switch on the method, producing a 405 response for
:: unhandled methods :: unhandled methods
:: ::
?+ method.request.req ?+ method.request.req
@ -616,7 +616,7 @@ Gall agents live in the `/app` directory of a desk, so save this code in
:: calculate the expiry data :: calculate the expiry data
:: ::
=/ expiry=@da (add now.bol (yule days.act 0 0 0 ~)) =/ expiry=@da (add now.bol (yule days.act 0 0 0 ~))
:: retreive all polls from state for this gid :: retrieve all polls from state for this gid
:: ::
=/ polls=(map pid [=poll =votes]) =/ polls=(map pid [=poll =votes])
(fall (~(get by by-group) gid.act) *(map pid [=poll =votes])) (fall (~(get by by-group) gid.act) *(map pid [=poll =votes]))
@ -647,7 +647,7 @@ Gall agents live in the `/app` directory of a desk, so save this code in
:: vote on a poll :: vote on a poll
:: ::
%vote %vote
:: retreive the target poll from state :: retrieve the target poll from state
:: ::
=/ [=poll =votes] (~(got bi by-group) gid.act pid.act) =/ [=poll =votes] (~(got bi by-group) gid.act pid.act)
:: make sure it hasn't expired :: make sure it hasn't expired
@ -737,7 +737,7 @@ Gall agents live in the `/app` directory of a desk, so save this code in
?> ?| =(our.bol src.bol) ?> ?| =(our.bol src.bol)
&(=(src.bol creator.poll) (gte expiry.poll now.bol)) &(=(src.bol creator.poll) (gte expiry.poll now.bol))
== ==
:: update state and notify subscribers of the withdrawl :: update state and notify subscribers of the withdrawal
:: ::
:_ %= state :_ %= state
by-group (~(del bi by-group) gid.act pid.act) by-group (~(del bi by-group) gid.act pid.act)
@ -777,7 +777,7 @@ Gall agents live in the `/app` directory of a desk, so save this code in
(fall (~(get by by-group) gid) *(map pid [=poll =votes])) (fall (~(get by by-group) gid) *(map pid [=poll =votes]))
== ==
:: on-agent handles either responses to requests we've :: on-agent handles either responses to requests we've
:: initated or subscription updates from people or agents :: initiated or subscription updates from people or agents
:: to which we've previously subscribed :: to which we've previously subscribed
:: ::
++ on-agent ++ on-agent
@ -1329,7 +1329,7 @@ Save the code below in `tally/app/tally/index.hoon`.
== ==
== ==
== ==
:: we retreive a list of all squads and sort them :: we retrieve a list of all squads and sort them
:: alphabetically by title :: alphabetically by title
:: ::
=/ all-squads=(list (pair gid squad)) =/ all-squads=(list (pair gid squad))
@ -1902,7 +1902,7 @@ this by adding a `sys.kelvin` file to the root of our `tally` directory:
```shell {% copy=true %} ```shell {% copy=true %}
cd tally cd tally
echo "[%zuse 417]" > sys.kelvin echo "[%zuse 414]" > sys.kelvin
``` ```
We also need to specify which agents to start when our desk is installed. We do We also need to specify which agents to start when our desk is installed. We do
@ -1933,10 +1933,10 @@ following:
## Put it together ## Put it together
Our app is now complete, so let's try it out. In the Dojo of our comet, we'll Our app is now complete, so let's try it out. In the Dojo of our comet, we'll
create a new desk (filesystem repo) by forking from an existing one: create a new desk with the `|new-desk` generator:
```{% copy=true %} ```{% copy=true %}
|merge %tally our %webterm |new-desk %tally
``` ```
Next, we'll mount the desk so we can access it from the host OS: Next, we'll mount the desk so we can access it from the host OS:
@ -1945,13 +1945,12 @@ Next, we'll mount the desk so we can access it from the host OS:
|mount %tally |mount %tally
``` ```
Currently its contents are the same as the `%webterm` desk, so we'll need to Currently it just contains some skeleton files, but we can delete those and add
delete those files and copy in our own instead. In the normal shell, do the our own instead. In the normal shell, do the following:
following:
```shell {% copy=true %} ```shell {% copy=true %}
rm -r dev-comet/tally/* rm -r dev-comet/tally/*
cp -r tally/* dev-comet/tally/* cp -r tally/* dev-comet/tally/
``` ```
Back in the Dojo again, we can now commit those files and install the app: Back in the Dojo again, we can now commit those files and install the app:

View File

@ -1,6 +1,6 @@
+++ +++
title = "Hoon School Live Spring '23" title = "Assembly Hackathon 2023"
image = "https://storage.googleapis.com/media.urbit.org/site/featured/hoon-school-march-23.png" image = "https://storage.googleapis.com/media.urbit.org/developers/hackathon/hackathon_logo.svg"
url="https://developers.urbit.org/courses/hsl" url="/hackathon"
description = "The next session of Hoon School Live begins in March. Sign up to learn the fundamentals of programming on Urbit." description = "This years Assembly Hackathon has the highest prize pool on Urbit ever. Are you prepared to rise to the challenge, showcase your skills, and claim victory?"
+++ +++

View File

@ -20,7 +20,7 @@ about 3000 lines of Hoon.
to build Urbit in. to build Urbit in.
* [Hoon School](/guides/core/hoon-school/): A collection of tutorials * [Hoon School](/guides/core/hoon-school/): A collection of tutorials
designed to teach you the Hoon language. designed to teach you the Hoon language.
* [Guides](/guides/additional/hoon/): Guides to specific Hoon tasks, * [Guides](/guides/additional/): Guides to specific Hoon tasks,
including testing, command-line interface apps, and parsing. including testing, command-line interface apps, and parsing.
* [Reference](/reference/hoon/): Reference material primarily * [Reference](/reference/hoon/): Reference material primarily
intended for Hoon developers with some experience. intended for Hoon developers with some experience.

View File

@ -11,13 +11,13 @@ cryptography on Urbit. We first summarize the two categories of keys and how
they are utilized by each ship type, then cover how different parts of Urbit are they are utilized by each ship type, then cover how different parts of Urbit are
involved in cryptography. involved in cryptography.
### Types of keys ## Types of keys
The two categories of keys are your Azimuth/Ethereum keys and your networking The two categories of keys are your Azimuth/Ethereum keys and your networking
keys. In both cases, these are public/private key pairs utilized for public key keys. In both cases, these are public/private key pairs utilized for public key
cryptography. cryptography.
#### Azimuth keys ### Azimuth keys
Your Urbit ID exists as an ERC-721 non-fungible token on the Ethereum Your Urbit ID exists as an ERC-721 non-fungible token on the Ethereum
blockchain, and as such is contained in a wallet whose private key you possess. blockchain, and as such is contained in a wallet whose private key you possess.
@ -44,7 +44,7 @@ private keys there is no way to retrieve them somehow from your ship.
For more information on the usage of these keys and the associated proxies, see For more information on the usage of these keys and the associated proxies, see
the [Azimuth documentation](/reference/azimuth/azimuth). the [Azimuth documentation](/reference/azimuth/azimuth).
#### Networking keys ### Networking keys
All communications in Urbit over the [Ames](/reference/glossary/ames) network All communications in Urbit over the [Ames](/reference/glossary/ames) network
are end-to-end encrypted, and thus your ship stores its own public/private pair are end-to-end encrypted, and thus your ship stores its own public/private pair
@ -83,7 +83,7 @@ other method. This is merely a technical limitation imposed by the design of the
system, not an intentional handicapping of comet abilities. A workaround to this system, not an intentional handicapping of comet abilities. A workaround to this
limitation is slated to be implemented as of May 2021. limitation is slated to be implemented as of May 2021.
### System components ## System components
[Ames](/reference/arvo/ames/ames) is Arvo's networking vane. All packets sent by [Ames](/reference/arvo/ames/ames) is Arvo's networking vane. All packets sent by
Ames are encrypted utilizing a cryptosuite found in `zuse`. The only exception Ames are encrypted utilizing a cryptosuite found in `zuse`. The only exception
@ -113,7 +113,7 @@ infrastructure utilized by Urbit. `azimuth-tracker` obtains networking public
keys for planets, stars, and galaxies from this store, which are then stored in keys for planets, stars, and galaxies from this store, which are then stored in
Jael and utilized by Ames for end-to-end encrypted communication. Jael and utilized by Ames for end-to-end encrypted communication.
### Additional documentation ## Additional documentation
The following pages contained more detailed information about the cryptography The following pages contained more detailed information about the cryptography
utilized by each of the system components. utilized by each of the system components.

View File

@ -85,7 +85,7 @@ There's a different process for globs to be distributed over HTTP from a webserv
To begin, you'll need to spin up a ship (typically a fake ship) and `|mount` a desk for which to add the files. In order for Clay to add the files, the desk must contain `mark` files in its `/mar` directory for all file extensions your folder contains. The `%garden` desk is a good bet because it includes `mark` files for `.js`, `.html`, `.png`, `.svg`, `.woff2` and a couple of others. If there's no desk with a mark for a particular file type you want included in your glob, you may need to add a new mark file. A very rudimentary mark file like the `png.hoon` mark will suffice. To begin, you'll need to spin up a ship (typically a fake ship) and `|mount` a desk for which to add the files. In order for Clay to add the files, the desk must contain `mark` files in its `/mar` directory for all file extensions your folder contains. The `%garden` desk is a good bet because it includes `mark` files for `.js`, `.html`, `.png`, `.svg`, `.woff2` and a couple of others. If there's no desk with a mark for a particular file type you want included in your glob, you may need to add a new mark file. A very rudimentary mark file like the `png.hoon` mark will suffice.
With the desk mounted, add the folder to be globbed to the root of the desk in Unix. It's imporant it's in the root because the `%make-glob` thread will only strip the first level of the folder heirarchy. With the desk mounted, add the folder to be globbed to the root of the desk in Unix. It's imporant it's in the root because the `%make-glob` thread will only strip the first level of the folder heirarchy. Additionally ensure that all file names in the folder you're globbing are lowercase, otherwise this next step will not work correctly.
Next, `|commit` the files to the desk, then run `-garden!make-glob %the-desk /folder-name`, where `%the-desk` is the desk containing the folder to be globbed and `/folder-name` is its name. Next, `|commit` the files to the desk, then run `-garden!make-glob %the-desk /folder-name`, where `%the-desk` is the desk containing the folder to be globbed and `/folder-name` is its name.

View File

@ -65,6 +65,15 @@ Application-level message, as a `%pass`.
- `path` - Internal route on the receiving ship. - `path` - Internal route on the receiving ship.
- `payload` - Semantic message contents. - `payload` - Semantic message contents.
## `$spar`
```hoon
+$ spar [=ship =path]
```
Instead of a fully qualifying scry path, Ames infers rift and life based on the
ship.
## `$bone` ## `$bone`
```hoon ```hoon
@ -167,6 +176,7 @@ All Ames knows about a peer.
$: messages=(list [=duct =plea]) $: messages=(list [=duct =plea])
packets=(set =blob) packets=(set =blob)
heeds=(set duct) heeds=(set duct)
keens=(jug path duct)
== ==
``` ```
@ -175,6 +185,7 @@ What to do when Ames learns a peer's life and keys.
- `messages` - [$plea](#plea)s local vanes have asked Ames to send. - `messages` - [$plea](#plea)s local vanes have asked Ames to send.
- `packets` - Packets we've tried to send. - `packets` - Packets we've tried to send.
- `heeds` - Local tracking requests; passed through into [$peer-state](#peer-state). - `heeds` - Local tracking requests; passed through into [$peer-state](#peer-state).
- `keens` - Subscribers to remote scry paths.
## `$peer-state` ## `$peer-state`
@ -182,6 +193,7 @@ What to do when Ames learns a peer's life and keys.
+$ peer-state +$ peer-state
$: $: =symmetric-key $: $: =symmetric-key
=life =life
=rift
=public-key =public-key
sponsor=ship sponsor=ship
== ==
@ -192,6 +204,9 @@ What to do when Ames learns a peer's life and keys.
rcv=(map bone message-sink-state) rcv=(map bone message-sink-state)
nax=(set [=bone =message-num]) nax=(set [=bone =message-num])
heeds=(set duct) heeds=(set duct)
closing=(set bone)
corked=(set bone)
keens=(map path keen-state)
== ==
``` ```
@ -204,6 +219,137 @@ State for a peer with known life and keys.
- `rcv` - Per-`bone` message sinks to assemble messages from fragments. - `rcv` - Per-`bone` message sinks to assemble messages from fragments.
- `nax` - Unprocessed nacks (negative acknowledgments). - `nax` - Unprocessed nacks (negative acknowledgments).
- `heeds` - Listeners for `%clog` notifications. - `heeds` - Listeners for `%clog` notifications.
- `closing`: Bones closed on the sender side.
- `corked`: Bones closed on both sender and receiver.
- `keens`: Remote scry state.
## `$keen-state`
```hoon
+$ keen-state
$: wan=((mop @ud want) lte) :: request packets, sent
nex=(list want) :: request packets, unsent
hav=(list have) :: response packets, backward
num-fragments=@ud
num-received=@ud
next-wake=(unit @da)
listeners=(set duct)
metrics=pump-metrics
==
```
Remote scry state for a peer.
- `wan`: Request packets, sent.
- `nex`: Request packets, unsent.
- `hav`: Response packets, backwards.
- `num-fragments`: Total fragment count.
- `num-received`: Fragments received.
- `next-wake`: Retry timing.
- `listeners`: Ducts waiting for a response.
- `metrics`: Stats.
## `$want`
```hoon
+$ want
$: fra=@ud
=hoot
packet-state
==
```
Remote scry request fragment.
## `$have`
```hoon
+$ have
$: fra=@ud
meow
==
```
Remote scry response fragment.
## `$meow`
```hoon
+$ meow
$: sig=@ux
num=@ud
dat=@ux
==
```
Remote scry response fragment data.
- `sig`: signature.
- `num`: number of fragments.
- `dat`: contents.
## `$peep`
```hoon
+$ peep
$: =path
num=@ud
==
```
Remote scry fragment request.
## `$wail`
```hoon
+$ wail
$% [%0 peep]
==
```
Tagged remote scry request fragment.
## `$roar`
```hoon
+$ roar
(tale:pki:jael (pair path (unit (cask))))
```
Remote scry response message.
A `tale:pki:jael` is a:
```hoon
++ tale :: urbit-signed *
|$ [typ] :: payload mold
$: dat=typ :: data
syg=(map ship (pair life oath)) :: signatures
== ::
```
Therefore, a `$roar` looks like:
```
> *roar:ames
[dat=[p=/ q=~] syg=~]
```
In `dat`, for the `(pair path (unit (cask)))`, the `path` is the remote scry
path and the `(unit (cask))` contains the value, or is null if there's no value
at this path and will never be one (equivalent to the `[~ ~]` case of a local
scry).
## `$purr`
```hoon
+$ purr
$: peep
meow
==
```
Response packet payload.
## `$qos` ## `$qos`

View File

@ -123,3 +123,21 @@ A scry with a `%x` `care` and a `path` of `/snd-bones/[ship]/[bone]`, where `[sh
] ]
] ]
``` ```
## /snubbed
A scry with a `%x` `care` and a `path` of `/snubbed` will return Ames' current
ship whitelist/blacklist. The type is a `[form=?(%allow %deny) ships=(list
ship)]`, where `form` says whether it's a whitelist or blacklist and `ships`
are the ships on that list.
#### Example
```
> |ames-snub %deny ~wet ~sampel
>=
> .^([form=?(%allow %deny) ships=(list ship)] %ax /=//=/snubbed)
[form=%deny ships=[i=~sampel t=[i=~wet t=~]]]
```

View File

@ -10,8 +10,8 @@ Some `task`s appear to have more than one arm associated to them, e.g. there are
four `+on-hear` arms. We denote this where it occurs, but always refer to the four `+on-hear` arms. We denote this where it occurs, but always refer to the
`+on-hear:event-core` arm. `+on-hear:event-core` arm.
Ames `task`s can be naturally divided into two categories: messaging tasks and Ames `task`s can be naturally divided into three categories: messaging tasks,
system/lifecycle tasks. system/lifecycle tasks, and remote scry tasks.
## Messaging Tasks ## Messaging Tasks
@ -33,6 +33,8 @@ There are multiple `+on-hear` arms in `ames.hoon`. Here we refer to `+on-hear:ev
`%hear` can trigger a number of possible returns. It can trigger the release of zero or more additional packets via `%send` `gift`s. It may also trigger a `%boon` or `%plea` `gift` (collectively referred to as a `%memo` within Ames) to a local vane in the case of a completed message. `%hear` can trigger a number of possible returns. It can trigger the release of zero or more additional packets via `%send` `gift`s. It may also trigger a `%boon` or `%plea` `gift` (collectively referred to as a `%memo` within Ames) to a local vane in the case of a completed message.
---
### `%heed` ### `%heed`
```hoon ```hoon
@ -51,6 +53,8 @@ If the `ship` is indeed being unresponsive, as measured by backed up `%boon`s,
Ames will `give` a `%clog` `gift` to the requesting vane containing the Ames will `give` a `%clog` `gift` to the requesting vane containing the
unresponsive peer's urbit address. unresponsive peer's urbit address.
---
### `%jilt` ### `%jilt`
```hoon ```hoon
@ -68,6 +72,8 @@ The `ship` field specifies the peer we want to stop tracking.
This `task` returns no `gift`s. This `task` returns no `gift`s.
---
### `%plea` ### `%plea`
```hoon ```hoon
@ -93,6 +99,8 @@ A `%plea` `task` takes in the `ship` the `plea` is addressed to, and a [$plea](/
This `task` returns no `gift`s. This `task` returns no `gift`s.
---
## System Tasks ## System Tasks
### `%born` ### `%born`
@ -111,6 +119,8 @@ The `duct` along which `%born` comes is Ames' only duct to Unix, so `%send`
`gift`s (which are instructions for Unix to send a packet) are also returned in `gift`s (which are instructions for Unix to send a packet) are also returned in
response to `%born`. response to `%born`.
---
### `%init` ### `%init`
```hoon ```hoon
@ -134,6 +144,8 @@ contained by Jael.
`%init` sends two moves that subscribe to `%turf` and `%private-keys` in Jael. `%init` sends two moves that subscribe to `%turf` and `%private-keys` in Jael.
---
### `%sift` ### `%sift`
```hoon ```hoon
@ -148,6 +160,41 @@ The `ships` field specifies the ships for which debug output is desired.
This `task` returns no `gift`s. This `task` returns no `gift`s.
---
### `%snub`
```hoon
[%snub form=?(%allow %deny) ships=(list ship)]
```
This `task` blacklists/whitelists ships in Ames.
The `form` field specifies whether the given ships should be blacklisted or whitelisted. The `ships` field are the ships to blacklist/whitelist.
The Ames `snub` settings can only have one form at a time: an `%allow` list or
`%deny` list. If an `%allow` form is set, packets from **all ships not on the
list will be blocked**. If a `%deny` form is set, packets from **any ship on
the list will be blocked, and all others allowed**.
{% callout %}
Note: a `%snub` `task` overrides the existing snub list and form entirely,
it does not merely add/remove ships from the existing list.
If you just want to add/remove a ship from an existing blacklist/whitelist,
you'll need to first [scry out the existing snub
settings](/reference/arvo/ames/scry#snubbed), make your changes, and send the
whole modified list and form in a new `%snub` `task`.
{% /callout %}
#### Returns
This `task` returns no `gift`s.
---
### `%spew` ### `%spew`
```hoon ```hoon
@ -164,6 +211,8 @@ Sets verbosity toggles on debug output. This `task` is used internally when the
This `task` returns no `gift`s. This `task` returns no `gift`s.
---
### `%stir` ### `%stir`
```hoon ```hoon
@ -178,6 +227,8 @@ The `arg` field is unused.
This `task` returns no `gift`s. This `task` returns no `gift`s.
---
### `%vega` ### `%vega`
```hoon ```hoon
@ -190,3 +241,67 @@ anything in response to this.
#### Returns #### Returns
This `task` returns no `gift`s. This `task` returns no `gift`s.
---
## Remote scry tasks
### `%keen`
```hoon
[%keen =ship =path]
```
A `%keen` `task` asks Ames to perform a remote scry, retrieving the value of
`path` on the given `ship`. The `path` has the general format of
`/[vane-letter]/[care]/[revision]/[rest-of-path]`. For a regular read into
Gall, it's `/g/x/[revision]/[agent]//[rest-of-path]`. Note the empty element in
between the agent and the rest of the path.
#### Returns
A `%tune` gift. A `%tune` gift looks like:
```hoon
[%tune spar roar=(unit roar)]
```
It represents a *result*. The `roar` field is null if Ames doesn't have a
response, but may have one in the future. The
[`$roar`](/reference/arvo/ames/data-types#roar) contains a signature and the
data. The data in the `$roar` will be null if there is no value at the path in
question and will never be. These two cases are equivalent to `~` and `[~ ~]` of
a local scry.
---
### `%yawn`
```hoon
[%keen =ship =path]
```
A `%yawn` task asks Ames to cancel an existing remote scry request to the given
`path` on the given `ship`.
#### Returns
This `task` returns no `gift`s.
---
### `%wham`
```hoon
[%wham =ship =path]
```
A `%wham` task asks Ames to cancel all existing remote scry requests from all
vanes on all ducts for the given `path` on the given `ship`.
#### Returns
A `%tune` gift with a null `data` is given to all listeners. See the
[`%keen`](#keen) entry for more details of the `%tune` gift.
---

View File

@ -43,7 +43,7 @@ DVCSes, and more.
Clay has two other unique properties that we'll cover later on: Clay has two other unique properties that we'll cover later on:
it supports typed data and is referentially transparent. it supports typed data and is referentially transparent.
### Revision Control ## Revision Control
Every urbit has one or more desks, which are independently Every urbit has one or more desks, which are independently
revision-controlled branches. Each desk contains its own `mark` revision-controlled branches. Each desk contains its own `mark`
@ -113,7 +113,7 @@ from revision 5 of the `%base` desk on `~sampel-sipnym`, we refer to
`/~sampel-sipnym/base/5/try/readme/md`. Clay's namespace is thus `/~sampel-sipnym/base/5/try/readme/md`. Clay's namespace is thus
global and referentially transparent. global and referentially transparent.
### A Typed Filesystem ## A Typed Filesystem
Since Clay is a general filesystem for storing data of arbitrary Since Clay is a general filesystem for storing data of arbitrary
types, in order to revision control correctly it needs to be types, in order to revision control correctly it needs to be
@ -169,7 +169,7 @@ As far as we are aware, Clay is the first generalized,
type-aware revision control system. We'll go into the workings type-aware revision control system. We'll go into the workings
of this system in some detail. of this system in some detail.
### Marks ## Marks
Central to a typed filesystem is the idea of file types. In Clay, we Central to a typed filesystem is the idea of file types. In Clay, we
call these `mark`s. See the [Marks](/reference/arvo/clay/marks/marks) call these `mark`s. See the [Marks](/reference/arvo/clay/marks/marks)

View File

@ -20,14 +20,14 @@ zuse. Second is the write, query, and subscription logic. Finally, there
is the logic for communicating requests to, and receiving requests from, is the logic for communicating requests to, and receiving requests from,
foreign ships. foreign ships.
### User documentation ## User documentation
[Filesystem](https://urbit.org/using/os/filesystem) [Filesystem](https://urbit.org/using/os/filesystem)
How to interact with the Clay filesystem via Dojo. This includes basics such as How to interact with the Clay filesystem via Dojo. This includes basics such as
mounting to Unix, changing directory, merging, and listing files. mounting to Unix, changing directory, merging, and listing files.
### Developer Documentation ## Developer Documentation
[Architecture](/reference/arvo/clay/architecture) [Architecture](/reference/arvo/clay/architecture)

File diff suppressed because it is too large Load Diff

View File

@ -5,9 +5,9 @@ weight = 4
These are the files used in the [Writing Marks](/reference/arvo/clay/marks/writing-marks) guide. These are the files used in the [Writing Marks](/reference/arvo/clay/marks/writing-marks) guide.
## `/lib/csv/hoon` ## `/lib/csv-utils/hoon`
```hoon ```hoon {% mode="collapse" copy="true" %}
|% |%
++ validate :: All rows same length? ++ validate :: All rows same length?
|= csv=(list (list @t)) |= csv=(list (list @t))
@ -177,8 +177,8 @@ These are the files used in the [Writing Marks](/reference/arvo/clay/marks/writi
## `/mar/csv/hoon` ## `/mar/csv/hoon`
```hoon ```hoon {% mode="collapse" copy="true" %}
/+ *csv /+ *csv-utils
|_ csv=(list (list @t)) |_ csv=(list (list @t))
++ grab ++ grab
|% |%
@ -232,7 +232,7 @@ These are the files used in the [Writing Marks](/reference/arvo/clay/marks/writi
## `/mar/csv-diff/hoon` ## `/mar/csv-diff/hoon`
```hoon ```hoon {% copy="true" %}
|_ dif=(urge:clay (list @t)) |_ dif=(urge:clay (list @t))
++ grab ++ grab
|% |%

View File

@ -5,7 +5,7 @@ weight = 2
Here we'll walk through a practical example of writing a `mark` file. Here we'll walk through a practical example of writing a `mark` file.
We'll create a `mark` for CSV (comma separated values) files, a simple format for storing tabular data in a text file. Note that there's already a `csv.hoon` `mark` and library, but we'll create new ones for demonstrative purposes. It shouldn't be an issue to overwrite the existing ones on a fakezod. We'll create a `mark` for CSV (comma separated values) files, a simple format for storing tabular data in a text file.
CSV files separate fields with commas and rows with line breaks. They look something like: CSV files separate fields with commas and rows with line breaks. They look something like:
@ -69,7 +69,7 @@ The `%mime` `mark` is used by Clay to store and convert `$mime` data. It's an im
So with the nature of the `%mime` `mark` hopefully now clear, the reason we want conversion methods to and from `%mime` in our `%csv` `mark` is so we can import CSV files from Unix and vice versa. So with the nature of the `%mime` `mark` hopefully now clear, the reason we want conversion methods to and from `%mime` in our `%csv` `mark` is so we can import CSV files from Unix and vice versa.
Since a CSV file on Unix will just be a long string with ASCII or UTF-8 encoding, we can treat `q.q` in the `$mime` as a `cord`, and thus write a parser to convert it to a `(list (list @t))`. For this purpose, here's a library: `csv.hoon`, which you can view in full on the [Examples](/reference/arvo/clay/marks/examples#libcsvhoon) page. Since a CSV file on Unix will just be a long string with ASCII or UTF-8 encoding, we can treat `q.q` in the `$mime` as a `cord`, and thus write a parser to convert it to a `(list (list @t))`. For this purpose, here's a library: `csv-utils.hoon`, which you can view in full on the [Examples](/reference/arvo/clay/marks/examples#libcsv-utilshoon) page.
The library contains four functions: The library contains four functions:
@ -83,27 +83,27 @@ The decoding and encoding arms use parsing functions from the Hoon standard libr
Let's try the library in the dojo. After we've added it to `/lib` and run `|commit`, we can build the file: Let's try the library in the dojo. After we've added it to `/lib` and run `|commit`, we can build the file:
``` ```
> =csv -build-file %/lib/csv/hoon > =csv -build-file %/lib/csv-utils/hoon
``` ```
...try decode a CSV-format `@t`: ...try decode a CSV-format `@t`:
``` ```
> (de-csv:csv 'foo,bar,baz\0ablah,blah,blah\0a1,2,3') > (de-csv:csv-utils 'foo,bar,baz\0ablah,blah,blah\0a1,2,3')
~[<|foo bar baz|> <|blah blah blah|> <|1 2 3|>] ~[<|foo bar baz|> <|blah blah blah|> <|1 2 3|>]
``` ```
...and try encode a `(list (list @t))` as a CSV-format `@t`: ...and try encode a `(list (list @t))` as a CSV-format `@t`:
``` ```
> (en-csv:csv [['foo' 'bar' 'baz' ~] ['blah' 'blah' 'blah' ~] ['1' '2' '3' ~] ~]) > (en-csv:csv-utils [['foo' 'bar' 'baz' ~] ['blah' 'blah' 'blah' ~] ['1' '2' '3' ~] ~])
'foo,bar,baz\0ablah,blah,blah\0a1,2,3\0a' 'foo,bar,baz\0ablah,blah,blah\0a1,2,3\0a'
``` ```
With that working, we can add an import for our library to our `%csv` `mark` defintion and add a `+mime` arm to both our `+grab` and `+grow` arms: With that working, we can add an import for our library to our `%csv` `mark` defintion and add a `+mime` arm to both our `+grab` and `+grow` arms:
```hoon ```hoon
/+ *csv /+ *csv-utils
|_ csv=(list (list @t)) |_ csv=(list (list @t))
++ grab ++ grab
|% |%
@ -184,12 +184,12 @@ For demonstrative purposes, we can just poach the algorithms used in the `+grad`
Our diff format will be a `(urge:clay (list @t))`, and we'll use some `differ` functions from `zuse.hoon` like `+loss`, `+lusk` and `+lurk` to produce diffs and apply patches. Our diff format will be a `(urge:clay (list @t))`, and we'll use some `differ` functions from `zuse.hoon` like `+loss`, `+lusk` and `+lurk` to produce diffs and apply patches.
The [csv.hoon library](/reference/arvo/clay/marks/examples#libcsvhoon) we imported also contains a `+csv-join` function which we'll use in the `+join` arm, just to save space here. The [csv.hoon library](/reference/arvo/clay/marks/examples#libcsv-utilshoon) we imported also contains a `+csv-join` function which we'll use in the `+join` arm, just to save space here.
Here's the new `%csv` `mark` defintion: Here's the new `%csv` `mark` defintion:
```hoon ```hoon
/+ *csv /+ *csv-utils
|_ csv=(list (list @t)) |_ csv=(list (list @t))
++ grab ++ grab
|% |%

View File

@ -49,7 +49,7 @@ Example:
### `/rang` - Get `rang` ### `/rang` - Get `rang`
A buc scry with a path of `/rang` will return the full A buc scry with a path of `/rang` will return the full
[`rang`](/reference/arvo/clay/data-types#rangclay) from Clay's state. [`rang`](/reference/arvo/clay/data-types#rang) from Clay's state.
Example: Example:
@ -79,10 +79,50 @@ Example:
--- ---
### `/cult/[desk]` - Subscribers
A buc scry with a path of `/cult/[desk]` will return the current subscriptions
for the specified `desk`.
The type returned is:
```hoon
(set [@p rave:clay])
```
See the [`$rave:clay`](/reference/arvo/clay/data-types#rave) data
type entry for more details of the `$rave` structure.
Example:
```
> .^((set [@p rave:clay]) %cx /=//=/cult/kids)
{ [~zod [%next mood=[care=%z case=[%da p=~2023.7.27..13.41.30..0536] path=/]]]
[~zod [%next mood=[care=%z case=[%da p=~2023.7.27..13.41.30..0536] path=/desk/docket-0]]]
}
```
---
### `/flow` - Build cache
A buc scry with a path of `/flow` will return the global build cache.
The type returned is a
[`$flow:clay`](/reference/arvo/clay/data-types#flow).
Example:
```
> ~(wyt by .^(flow:clay %cx /=//=/flow))
960
```
---
### `/domes` - All domes ### `/domes` - All domes
A buc scry with a path of `/domes` will return a A buc scry with a path of `/domes` will return a
[`cone:clay`](/reference/arvo/clay/data-types#coneclay) containing the `dome`s [`cone`](/reference/arvo/clay/data-types#cone) containing the `dome`s
and associated metadata for all desks, foreign and local. and associated metadata for all desks, foreign and local.
Example: Example:
@ -109,7 +149,7 @@ Example:
A buc scry with a path of `/tire` will return the `rock:tire:clay` for all A buc scry with a path of `/tire` will return the `rock:tire:clay` for all
domestic desks, which is a `(map desk [=zest wic=(set weft)])`. The domestic desks, which is a `(map desk [=zest wic=(set weft)])`. The
[`zest`](/reference/arvo/clay/data-types#zestclay) specifies whether apps on the [`zest`](/reference/arvo/clay/data-types#zest) specifies whether apps on the
desk are running or suspended. The `wic` set contains the `weft`s (kernel desk are running or suspended. The `wic` set contains the `weft`s (kernel
versions) of any queued updates. versions) of any queued updates.
@ -158,7 +198,7 @@ Example:
## `%b` - Dyn. mark core ## `%b` - Dyn. mark core
A scry with a `care` of `%b` will produce a `dais:clay` processed `mark` core A scry with a `care` of `%b` will produce a `dais` processed `mark` core
for the specified `mark`. The `path` in the scry is a `mark`. for the specified `mark`. The `path` in the scry is a `mark`.
Example: Example:
@ -169,7 +209,7 @@ Example:
## %c - Dyn. mark convert ## %c - Dyn. mark convert
A scry with a `care` of `%c` will produce a `tube:clay` dynamically typed `mark` A scry with a `care` of `%c` will produce a `tube` dynamically typed `mark`
conversion gate. The `path` specifies two `mark`s - _from_ and _to_, like conversion gate. The `path` specifies two `mark`s - _from_ and _to_, like
`/txt/noun`. `/txt/noun`.
@ -188,10 +228,14 @@ Example:
A scry with a `care` of `%d` will return a `(set desk)` of the `desk`s that A scry with a `care` of `%d` will return a `(set desk)` of the `desk`s that
exist on your ship. exist on your ship.
Note this scry should be performed with an empty `desk` field (`%$`) in
the `beak` (e.g. `/=//=`). If it's not empty, it'll work but Clay will
complain in the terminal.
Example: Example:
``` ```
> .^((set desk) %cd %) > .^((set desk) %cd /=//=)
{%bitcoin %base %landscape %webterm %garden %kids} {%bitcoin %base %landscape %webterm %garden %kids}
``` ```
@ -199,7 +243,7 @@ Example:
## `%e` - Static mark core ## `%e` - Static mark core
A scry with a `care` of `%e` will return a statically typed `nave:clay` `mark` A scry with a `care` of `%e` will return a statically typed `nave` `mark`
core. The `path` in the scry specifies the `mark`. The type returned is a core. The `path` in the scry specifies the `mark`. The type returned is a
`(nave:clay [type] [diff])`, where `[type]` is the type the `mark` takes and `(nave:clay [type] [diff])`, where `[type]` is the type the `mark` takes and
`[diff]` is the type taken by the `mark` specified in `+form:grad`. `[diff]` is the type taken by the `mark` specified in `+form:grad`.
@ -229,7 +273,7 @@ A scry with a `care` of `%f` will return a static `mark` conversion gate. The
A scry with a `care` of `%p` will return the permissions of the file or A scry with a `care` of `%p` will return the permissions of the file or
directory in question. The type returned is a [`[dict:clay directory in question. The type returned is a [`[dict:clay
dict:clay]`](/reference/arvo/clay/data-types#dictclay) where the head is read dict:clay]`](/reference/arvo/clay/data-types#dict) where the head is read
permissions and the tail is write permissions. permissions and the tail is write permissions.
If the specified file or directory has no permissions set, it will default to If the specified file or directory has no permissions set, it will default to
@ -285,14 +329,14 @@ you use. We'll look at each in turn.
### `%yaki` - Commit ### `%yaki` - Commit
This will return the [yaki:clay](/reference/arvo/clay/data-types#yakiclay) of This will return the [yaki:clay](/reference/arvo/clay/data-types#yaki) of
the specified commit. It takes a the specified commit. It takes a
[tako:clay](/reference/arvo/clay/data-types#takoclay). [tako:clay](/reference/arvo/clay/data-types#tako).
Example: Example:
Here we scry the [dome:clay](/reference/arvo/clay/data-types#domeclay) for `%`, Here we scry the [dome:clay](/reference/arvo/clay/data-types#dome) for `%`,
get the latest `tako:clay` and the do a `%s` scry for the `yaki:clay` in get the latest `tako` and the do a `%s` scry for the `yaki` in
question. question.
``` ```
@ -316,13 +360,13 @@ question.
### `%blob` - File blob ### `%blob` - File blob
This will return the [page:clay](/reference/arvo/clay/data-types#pageclay) of This will return the [page:clay](/reference/arvo/clay/data-types#page) of
some file. It takes a [lobe:clay](/reference/arvo/clay/data-types#lobeclay). some file. It takes a [lobe:clay](/reference/arvo/clay/data-types#lobe).
Example: Example:
Here we grab the `lobe:clay` of `/gen/hood/hi/hoon` with a `%y` scry, then use Here we grab the `lobe` of `/gen/hood/hi/hoon` with a `%y` scry, then use
it to do a `%s` scry for the `blob:clay` of the file. it to do a `%s` scry for the `blob` of the file.
``` ```
> =/ =arch .^(arch %cy %/gen/hood/hi/hoon) > =/ =arch .^(arch %cy %/gen/hood/hi/hoon)
@ -340,13 +384,13 @@ it to do a `%s` scry for the `blob:clay` of the file.
### `%hash` - Commit hash ### `%hash` - Commit hash
This will return the `@uvI` (256-bit) content hash of the specified commit. It This will return the `@uvI` (256-bit) content hash of the specified commit. It
takes a [`tako:clay`](/reference/arvo/clay/data-types#takoclay). takes a [`tako`](/reference/arvo/clay/data-types#tako).
Example: Example:
Here we grab the [`dome:clay`](/reference/arvo/clay/data-types#domeclay) for `%` Here we grab the [`dome`](/reference/arvo/clay/data-types#dome) for `%`
with a `%v` scry, get the latest with a `%v` scry, get the latest
[`tako:clay`](/reference/arvo/clay/data-types#takoclay) and then do a `%s` [`tako`](/reference/arvo/clay/data-types#tako) and then do a `%s`
`%hash` scry for it. `%hash` scry for it.
``` ```
@ -360,11 +404,11 @@ with a `%v` scry, get the latest
### `%cage` - File as cage ### `%cage` - File as cage
This will return a `cage` of the data of some file. It takes a `lobe:clay`. This will return a `cage` of the data of some file. It takes a `lobe`.
Example: Example:
Here we grab the `lobe:clay` of `/gen/hood/hi/hoon` with a `%y` scry, then use it to do a `%s` scry for the `cage` of the data. Here we grab the `lobe` of `/gen/hood/hi/hoon` with a `%y` scry, then use it to do a `%s` scry for the `cage` of the data.
``` ```
> =/ =arch .^(arch %cy %/gen/hood/hi/hoon) > =/ =arch .^(arch %cy %/gen/hood/hi/hoon)
@ -394,10 +438,10 @@ documentaton at a later date.
This will return the most recent revision number of a `desk` that has been fully This will return the most recent revision number of a `desk` that has been fully
downloaded. The type it returns is a downloaded. The type it returns is a
[`cass:clay`](/reference/arvo/clay/data-types#cassclay). The `case` in the [`cass`](/reference/arvo/clay/data-types#cass). The `case` in the
`beak` must be a revision number rather than a date. You can just provide a case `beak` must be a revision number rather than a date. You can just provide a case
of `1` since it returns the latest regardless. If we have nothing for the of `1` since it returns the latest regardless. If we have nothing for the
specified `desk`, this will just return the bunt of a `cass:clay` like specified `desk`, this will just return the bunt of a `cass` like
`cass=[ud=0 da=~2000.1.1]`. `cass=[ud=0 da=~2000.1.1]`.
Example: Example:
@ -498,7 +542,7 @@ Examples:
## `%v` - Desk state ## `%v` - Desk state
A scry with a care of `%v` will return the entire state of a `desk` as a A scry with a care of `%v` will return the entire state of a `desk` as a
[`dome:clay`](/reference/arvo/clay/data-types#domeclay). [`dome`](/reference/arvo/clay/data-types#dome).
Example: Example:
@ -516,7 +560,7 @@ Note: If you try printing this it will take forever and probably OOM your ship.
A scry with a `care` of `%w` will return the revision number and date of a given A scry with a `care` of `%w` will return the revision number and date of a given
`case`. The type returned is a `case`. The type returned is a
[`cass:clay`](/reference/arvo/clay/data-types#cassclay) like `[ud=@ud da=@da]` [`cass`](/reference/arvo/clay/data-types#cass) like `[ud=@ud da=@da]`
where `ud` is the revision number and `da` is the date. where `ud` is the revision number and `da` is the date.
Example: Example:
@ -530,19 +574,20 @@ Example:
## `%x` - Read file ## `%x` - Read file
A scry with a `care` of `%x` will return the raw data of a file as an `@` or A scry with a `care` of `%x` will return the data of a file or crash if it's a
crash if it's a directory. directory. The type returned will be whatever is defined by the `mark` of the
file.
Examples: Examples:
``` ```
> .^(@ %cx %/gen/hood/hi/hoon) > .^(@t %cx %/gen/hood/hi/hoon)
3.548.750.706.400.251.607.252.023.288.575.526.190.856.734.474.077.821.289.791.377.301.707.878.697.553.411.219.689.905.949.957.893.633.811.025.757.107.990.477.902.858.170.125.439.223.250.551.937.540.468.638.902.955.378.837.954.792.031.592.462.617.422.136.386.332.469.076.584.061.249.923.938.374.214.925.312.954.606.277.212.923.859.309.330.556.730.410.200.952.056.760.727.611.447.500.996.168.035.027.753.417.869.213.425.113.257.514.474.700.810.203.348.784.547.006.707.150.406.298.809.062.567.217.447.347.357.039.994.339.342.906 ':: Helm: send message to an urbit\0a::\0a:::: /hoon/hi/hood/gen\0a ::\0a/? 310\0a:- %say\0a|=([^ [who=ship mez=$@(~ [a=tape ~])] ~] helm-send-hi+[who ?~(mez ~ `a.mez)])\0a'
``` ```
``` ```
> .^(@t %cx %/gen/hood/hi/hoon) > .^(waft:clay %cx %/sys/kelvin)
':: Helm: send message to an urbit\0a::\0a:::: /hoon/hi/hood/gen\0a ::\0a/? 310\0a:- %say\0a|=([^ [who=ship mez=$@(~ [a=tape ~])] ~] helm-send-hi+[who ?~(mez ~ `a.mez)])\0a' [lal=%zuse num=413]
``` ```
``` ```
@ -557,7 +602,7 @@ Crash!
A scry with a `care` of `%y` will return the `arch` of a file or directory. A scry with a `care` of `%y` will return the `arch` of a file or directory.
An `arch` is a `[fil=(unit lobe:clay) dir=(map @ta ~)]`. The `fil` will contain An `arch` is a `[fil=(unit lobe:clay) dir=(map @ta ~)]`. The `fil` will contain
the [`lobe:clay`](/reference/arvo/clay/data-types#lobeclay) hash if it's a file, the [`lobe`](/reference/arvo/clay/data-types#lobe) hash if it's a file,
otherwise it will be null. The `dir` will contain a map of the files and otherwise it will be null. The `dir` will contain a map of the files and
directories it contains, otherwise it will be null. directories it contains, otherwise it will be null.

View File

@ -23,7 +23,10 @@ Clay from a kernel development perspective.
A `%warp` `task` is for reading and subscribing to files and directories. A `%warp` `task` is for reading and subscribing to files and directories.
The `wer` field is the target ship. The `(unit rave)` of the [riff](/reference/arvo/clay/data-types#riff-clay-request-desist) is null to cancel an existing subscription, otherwise the [rave](/reference/arvo/clay/data-types#rave-clay-general-subscription-request) is tagged with one of: The `wer` field is the target ship. The `(unit rave)` of the
[riff](/reference/arvo/clay/data-types#riff) is null to cancel an existing
subscription, otherwise the [rave](/reference/arvo/clay/data-types#rave) is
tagged with one of:
- `%sing` - Read a single file or directory. - `%sing` - Read a single file or directory.
- `%next` - Subscribe for the next change to a file or directory. - `%next` - Subscribe for the next change to a file or directory.
@ -48,7 +51,12 @@ A `%wris` `gift` looks like:
[%writ p=riot] :: response [%writ p=riot] :: response
``` ```
The `unit` of the [riot](/reference/arvo/clay/data-types#riot-clay-response) will be null if the target file cannot be found or if a subscription has ended (depending on context). Otherwise it will have a [rant](/reference/arvo/clay/data-types#rant-clay-response-data) with a `cage` containing the data you requested. Its contents will vary depending on the kind of request and `care`. The `unit` of the [riot](/reference/arvo/clay/data-types#riot) will be null
if the target file cannot be found or if a subscription has ended (depending on
context). Otherwise it will have a
[rant](/reference/arvo/clay/data-types#rant) with a `cage` containing the
data you requested. Its contents will vary depending on the kind of request and
`care`.
Now we'll look at each of the `rave` request types in turn. Now we'll look at each of the `rave` request types in turn.
@ -60,9 +68,16 @@ Now we'll look at each of the `rave` request types in turn.
This `rave` is for reading a single file or directory immediately. This `rave` is for reading a single file or directory immediately.
The `care` of the [mood](/reference/arvo/clay/data-types#mood-clay-single-subscription-request) will determine what you can read and what type of data will be returned. See the [care](/reference/arvo/clay/data-types#care-clay-clay-submode) documentation and [scry](/reference/arvo/clay/scry) documentation for details on the various `care`s. The `care` of the [mood](/reference/arvo/clay/data-types#mood) will
determine what you can read and what type of data will be returned. See the
[care](/reference/arvo/clay/data-types#care) documentation and
[scry](/reference/arvo/clay/scry) documentation for details on the various
`care`s.
The [case](/reference/arvo/clay/data-types#case-specifying-a-commit) specifies the `desk` revision and you can use whichever kind you prefer. The `path` will usually be a path to a file or directory like `/gen/hood/hi/hoon` but may be something else depending on the `care`. The [case](/reference/arvo/clay/data-types#case) specifies the `desk`
revision and you can use whichever kind you prefer. The `path` will usually be
a path to a file or directory like `/gen/hood/hi/hoon` but may be something
else depending on the `care`.
#### Example #### Example
@ -76,7 +91,9 @@ The [case](/reference/arvo/clay/data-types#case-specifying-a-commit) specifies t
[%next =mood] :: await next version [%next =mood] :: await next version
``` ```
This subscribes to the next version of the specified file. See [here](/reference/arvo/clay/data-types#mood-clay-single-subscription-request) for details of the `mood` structure. This subscribes to the next version of the specified file. See
[here](/reference/arvo/clay/data-types#mood) for details of the `mood`
structure.
If you subscribe to the current `case` of the `desk`, Clay will not respond until the file changes. If you subscribe to a previous `case` of the `desk` and the file has changed in between then and now, it will immediately return the first change it comes across in that range. For example, if you're currently at `case` `100`, subscribe to case `50` and the file in question has been modified at both `60` and `80`, clay will immediately return the version of the file at `case` `60`. If you subscribe to the current `case` of the `desk`, Clay will not respond until the file changes. If you subscribe to a previous `case` of the `desk` and the file has changed in between then and now, it will immediately return the first change it comes across in that range. For example, if you're currently at `case` `100`, subscribe to case `50` and the file in question has been modified at both `60` and `80`, clay will immediately return the version of the file at `case` `60`.
@ -126,9 +143,17 @@ If the `track` is `%.y` it will just return a `%writ` like:
...that merely informs you of a change. If you want the actual data you'll have to request it separately. ...that merely informs you of a change. If you want the actual data you'll have to request it separately.
If the `track` is `%.n`, the `cage` of the `%writ` will contain a [nako](/reference/arvo/clay/data-types#nako-subscription-response-data) with the relevant data for all changes to a desk between what you have and the `case` requested. It is very large and fairly complicated. The `nako` structure is defined in the `clay.hoon` source file itself rather than in `lull.hoon` or elsewhere since you're unlikely to work with it yourself. If the `track` is `%.n`, the `cage` of the `%writ` will contain a
[nako](/reference/arvo/clay/data-types#nako) with the relevant data for all
changes to a desk between what you have and the `case` requested. It is very
large and fairly complicated. The `nako` structure is defined in the
`clay.hoon` source file itself rather than in `lull.hoon` or elsewhere since
you're unlikely to work with it yourself.
The `from` and `to` fields of the [moat](/reference/arvo/clay/data-types#moat-clay-range-subscription-request) specify the range of `case`s for which to subscribe. The range is _inclusive_. It can be specified by date or by revision number, whichever you prefer. The `from` and `to` fields of the
[moat](/reference/arvo/clay/data-types#moat) specify the range of `case`s
for which to subscribe. The range is _inclusive_. It can be specified by date
or by revision number, whichever you prefer.
The `path` in the `moat` is a path to a file or directory. If it's `~` it refers to the root of the `desk` in question. This lets you say "only inform me of changes to the `desk` if the specified file or directory exists". If it doesn't exist, Clay will not send you anything. The `path` in the `moat` is a path to a file or directory. If it's `~` it refers to the root of the `desk` in question. This lets you say "only inform me of changes to the `desk` if the specified file or directory exists". If it doesn't exist, Clay will not send you anything.
@ -164,7 +189,19 @@ To cancel a subscription, you just send a `%warp` with a null `(unit rave)` in t
To write or modify a file, we send Clay a `%info` `task`. To write or modify a file, we send Clay a `%info` `task`.
The `%|` tag in the [nori](/reference/arvo/clay/data-types#nori-clay-repository-action) is not currently supported and will crash with a `%labelling-not-implemented` if used, so you can focus on the `%&` part. The [soba](/reference/arvo/clay/data-types#soba-clay-delta) in the `nori` is just a list of changes so you can make more than one change in one request. Its `path` is just the path to a file like `/gen/hood/hi/hoon` and the [miso](/reference/arvo/clay/data-types#miso-clay-ankh-delta) is one of these types of requests: If the head of the [nori](/reference/arvo/clay/data-types#nori) `dit` is
`%|`, it's a request to add a label to a commit, and the `nori` looks like `[%|
p=@tas q=(unit aeon)]` where `p` is the label and `q` is the
[`aeon`](/reference/arvo/clay/data-types#aeon) (commit reference). If `q`
is null, the label is applied to the latest commit in the desk.
If the head of the `nori` is `%&`, it's a request to add, delete or modify one
or more files in the given desk, and looks like `[%& p=soba]`. The
[soba](/reference/arvo/clay/data-types#soba) in the `nori` is just a list
of changes so you can make more than one change in one request. Its `path` is
just the path to a file like `/gen/hood/hi/hoon` and the
[miso](/reference/arvo/clay/data-types#miso) is one of these types of
requests:
- `%del` - Delete a file. - `%del` - Delete a file.
- `%ins` - Insert file. This will also replace an existing file. - `%ins` - Insert file. This will also replace an existing file.
@ -195,7 +232,7 @@ Here are examples of using each of these as well as making multiple changes in o
``` ```
Force on/off apps on a desk. A Force on/off apps on a desk. A
[`rein:clay`](/reference/arvo/clay/data-types#rein) is a `map` from Gall agent [`rein`](/reference/arvo/clay/data-types#rein) is a `map` from Gall agent
name to `?`, where `%.y` is *on* and `%.n` is *off*. By default, a live desk name to `?`, where `%.y` is *on* and `%.n` is *off*. By default, a live desk
will run the agents defined in its `desk.bill` manifest, so this is used to will run the agents defined in its `desk.bill` manifest, so this is used to
either stop agents in its manifest or start agents which aren't in its manifest. either stop agents in its manifest or start agents which aren't in its manifest.
@ -228,7 +265,7 @@ A `rock:tire` is a:
+$ rock (map desk [=zest wic=(set weft)]) +$ rock (map desk [=zest wic=(set weft)])
``` ```
The [`zest:clay`](/reference/arvo/clay/data-types#zestclay) says whether the The [`zest`](/reference/arvo/clay/data-types#zest) says whether the
desk is running (`%live`), suspended (`%dead`), or suspended pending a desk is running (`%live`), suspended (`%dead`), or suspended pending a
kernel-compatible update (`%held`). The `wic` set contains the `weft`s (kernel kernel-compatible update (`%held`). The `wic` set contains the `weft`s (kernel
versions) of any queued updates. versions) of any queued updates.
@ -264,7 +301,7 @@ Try to apply a queued kernel update.
``` ```
A `%zest` `task` suspends or unsuspends a desk. the A `%zest` `task` suspends or unsuspends a desk. the
[`zest:clay`](/reference/arvo/clay/data-types#zestclay) in `liv` is one of: [`zest`](/reference/arvo/clay/data-types#zest) in `liv` is one of:
- `%live`: running. - `%live`: running.
- `%dead`: suspended. - `%dead`: suspended.
@ -279,7 +316,7 @@ A `%zest` `task` suspends or unsuspends a desk. the
``` ```
Tombstoning is the deletion of data for old desk revisions. Clay has a single Tombstoning is the deletion of data for old desk revisions. Clay has a single
`%tomb` `task`, but its [`clue:clay`](/reference/arvo/clay/data-types#clueclay) `%tomb` `task`, but its [`clue`](/reference/arvo/clay/data-types#clue)
has a number of different possible actions: has a number of different possible actions:
```hoon ```hoon
@ -302,7 +339,7 @@ We'll look at each of these in turn.
``` ```
A `%tomb` `task` with a `%lobe` `clue` will tombstone the `page` matching the A `%tomb` `task` with a `%lobe` `clue` will tombstone the `page` matching the
given [`lobe:clay`](/reference/arvo/clay/data-types#lobeclay). If the `page` in given [`lobe`](/reference/arvo/clay/data-types#lobe). If the `page` in
question is used in the current revision of any desks, it will fail. Otherwise, question is used in the current revision of any desks, it will fail. Otherwise,
it will be tombstoned globally. it will be tombstoned globally.
@ -327,7 +364,7 @@ by current desk revisions, globally. This should be used with caution.
A `%tomb` `task` with a `%pick` `clue` will perform garbage collection, A `%tomb` `task` with a `%pick` `clue` will perform garbage collection,
tombstoning any data that should be tombstoned according to current tombstoning tombstoning any data that should be tombstoned according to current tombstoning
policy ([`norm`](/reference/arvo/clay/data-types#normclay)s). policy ([`norm`](/reference/arvo/clay/data-types#norm)s).
--- ---
@ -339,7 +376,7 @@ policy ([`norm`](/reference/arvo/clay/data-types#normclay)s).
A `%tomb` `task` with a `%norm` `clue` will set the default tombstoning policy A `%tomb` `task` with a `%norm` `clue` will set the default tombstoning policy
for the given `desk` and `ship`. A for the given `desk` and `ship`. A
[`norm:clay`](/referende/arvo/clay/data-types#normclay) is an `(axal ?)`. An [`norm`](/referende/arvo/clay/data-types#norm) is an `(axal ?)`. An
`axal` is like a recursive `arch`, and is defined in `arvo.hoon`. The `?` says `axal` is like a recursive `arch`, and is defined in `arvo.hoon`. The `?` says
whether to *keep* the given file or directory. You may want to look at the `+of` whether to *keep* the given file or directory. You may want to look at the `+of`
axal engine in `arvo.hoon` for constructing and manipulating the `norm`. axal engine in `arvo.hoon` for constructing and manipulating the `norm`.
@ -358,7 +395,7 @@ make your changes.
A `%tomb` `task` with a `%worn` `clue` is like A `%tomb` `task` with a `%worn` `clue` is like
[`%norm`](#norm---default-policy), except it only applies to a specific commit [`%norm`](#norm---default-policy), except it only applies to a specific commit
for a ship/desk. The [`tako:clay`](/reference/arvo/clay/data-types#takoclay) for a ship/desk. The [`tako`](/reference/arvo/clay/data-types#tako)
denotes the commit to apply the policy. denotes the commit to apply the policy.
--- ---
@ -371,10 +408,10 @@ denotes the commit to apply the policy.
A `%tomb` `task` with a `%seek` `clue` will attempt to retrieve missing, A `%tomb` `task` with a `%seek` `clue` will attempt to retrieve missing,
tombstoned data and integrate it into Clay's object store. The tombstoned data and integrate it into Clay's object store. The
[`cash:clay`](/reference/arvo/clay/data-types#cashclay) is a reference to a [`cash`](/reference/arvo/clay/data-types#cash) is a reference to a
commit on the given ship/desk as either a commit on the given ship/desk as either a
[`tako:clay`](/reference/arvo/clay/data-types#takoclay) or a [`tako`](/reference/arvo/clay/data-types#tako) or a
[`case:clay`](/reference/arvo/clay/data-types#caseclay). [`case`](/reference/arvo/clay/data-types#case).
--- ---
@ -421,7 +458,7 @@ The type it returns is a `%hill` `gift`, which looks like:
A `%mont` `task` mounts the specified `beam` to the specified `term` mount point. A `%mont` `task` mounts the specified `beam` to the specified `term` mount point.
A `beam:clay` is the following structure: A `beam` is the following structure:
```hoon ```hoon
+$ beam [[p=ship q=desk r=case] s=path] :: global name +$ beam [[p=ship q=desk r=case] s=path] :: global name
@ -447,7 +484,7 @@ Clay does not return a `gift` in response to a `%mont` `%task`.
A `%ogre` `task` unmounts the specified mount. A `%ogre` `task` unmounts the specified mount.
It's defined in `lull.hoon` as taking `$@(desk beam)` but in fact it will only unmount the target when specified as a `term` mount name. Passing it a `desk` will incidentally work if the mount is named the same as the `desk` but otherwise it won't work. Passing it a `beam:clay` will simply not work. It's defined in `lull.hoon` as taking `$@(desk beam)` but in fact it will only unmount the target when specified as a `term` mount name. Passing it a `desk` will incidentally work if the mount is named the same as the `desk` but otherwise it won't work. Passing it a `beam` will simply not work.
#### Returns #### Returns
@ -532,9 +569,9 @@ If permissions are not set for a particular file, they will be inherited from th
A group is called a `crew` and is just a `set` of ships with a `@ta` name. A group is called a `crew` and is just a `set` of ships with a `@ta` name.
The permissions for each file or directory are a pair of `dict:clay` where the head is read permissions and the tail is write permissions. The permissions for each file or directory are a pair of `dict` where the head is read permissions and the tail is write permissions.
A `dict:clay` is this structure: A `dict` is this structure:
```hoon ```hoon
+$ dict [src=path rul=real] :: effective permission +$ dict [src=path rul=real] :: effective permission
@ -703,7 +740,7 @@ To read files on a foreign `desk`, you just send Clay a `%warp` `task` (as you w
Clay only allows a subset of `care`s to be used remotely. They are: Clay only allows a subset of `care`s to be used remotely. They are:
- `%u` - Check for existence of file. - `%u` - Check for existence of file.
- `%v` - Get entire `dome:clay` state of a desk. - `%v` - Get entire `dome` state of a desk.
- `%w` - Get revision number. - `%w` - Get revision number.
- `%x` - Get data of file. - `%x` - Get data of file.
- `%y` - Get `arch` of file or directory. - `%y` - Get `arch` of file or directory.
@ -711,6 +748,11 @@ Clay only allows a subset of `care`s to be used remotely. They are:
Any other `care` will crash with a `%clay-bad-foreign-request-care` error. Any other `care` will crash with a `%clay-bad-foreign-request-care` error.
In addition, Clay only allows `%sing` and `%many` requests -- not `%next` or
`%mult`. One way to get the next revision is to first `%sing` the current
revision number using `%w` and case `da+now`, then `%sing` again with the next
revision number using case `ud+<next-revision-number>`.
The foreign ship will respond only if correct permissions have been set. See the [Permissions](#permissions) section for details. The foreign ship will respond only if correct permissions have been set. See the [Permissions](#permissions) section for details.
Note that if you're reading a whole `desk` or directory, all subfolders and files must also permit reading. If even a single file does not permit you reading it, the foreign ship will not respond to the request. Note that if you're reading a whole `desk` or directory, all subfolders and files must also permit reading. If even a single file does not permit you reading it, the foreign ship will not respond to the request.

View File

@ -6,7 +6,7 @@ weight = 3
## Reading and Subscribing ## Reading and Subscribing
When reading from Clay, there are three types of requests. A When reading from Clay, there are three types of requests. A
`%sing` request asks for data at single revsion. A `%next` `%sing` request asks for data at a single revision. A `%next`
request asks to be notified the next time there's a change to request asks to be notified the next time there's a change to
given file. A `%many` request asks to be notified on every given file. A `%many` request asks to be notified on every
change in a `desk` for a range of changes. change in a `desk` for a range of changes.

View File

@ -3,17 +3,19 @@ title = "Scries"
weight = 40 weight = 40
+++ +++
### What is a scry? This document mostly covers local scries. Remote scries have recently been introduced, and are documented in a [separate guide](/guides/additional/remote-scry).
## What is a scry?
A scry is a read-only request to Arvo's global namespace. A scry is a read-only request to Arvo's global namespace.
Vanes and agents define _scry endpoints_ which allow one to request data from their respective states. The endpoints can process the data in any way before returning it, but they cannot alter the actual state - scries can _only_ read, not modify. Vanes and agents define _scry endpoints_ which allow one to request data from their respective states. The endpoints can process the data in any way before returning it, but they cannot alter the actual state - scries can _only_ read, not modify.
### Why scry? ## Why scry?
The subject available in something like a Gall agent or thread contains a great many functions and structures from the standard library as well as `zuse` and `lull`, but it does not include any of the actual data stored elsewhere in the ship. All it has is its own state, a `bowl` and any `card`s it has been passed. Ordinarily, in order to access such data, one would need to `%poke` or `%watch` other agents, or `%pass` `task`s to vanes, then wait for a response. Arvo's scry system is the one exception to this; it allows direct retrieval of data from other vanes or agents in situ, from any context, without any of the normal messaging rigmarole. The subject available in something like a Gall agent or thread contains a great many functions and structures from the standard library as well as `zuse` and `lull`, but it does not include any of the actual data stored elsewhere in the ship. All it has is its own state, a `bowl` and any `card`s it has been passed. Ordinarily, in order to access such data, one would need to `%poke` or `%watch` other agents, or `%pass` `task`s to vanes, then wait for a response. Arvo's scry system is the one exception to this; it allows direct retrieval of data from other vanes or agents in situ, from any context, without any of the normal messaging rigmarole.
### How do I scry? ## How do I scry?
Scries are performed exclusively with the dotket rune: `.^` Scries are performed exclusively with the dotket rune: `.^`
@ -25,11 +27,11 @@ One further note on `care`s (which can sometimes be confusing): While `care`s ar
Most other vanes also make use of `care`s in their scry endpoints. While such vanes don't have corresponding submodules with strictly defined behaviour like Clay, the `care`s still confer the general nature of the endpoint. The most widely used `care` is `%x`, which implies reading data in a general sense. Gall has special handling of `%x` scries as described in the [Gall agents](#gall-agents) section below, but otherwise `care`s have no special behaviour for non-Clay vanes (though they must still be included if the endpoint specifies it). Most other vanes also make use of `care`s in their scry endpoints. While such vanes don't have corresponding submodules with strictly defined behaviour like Clay, the `care`s still confer the general nature of the endpoint. The most widely used `care` is `%x`, which implies reading data in a general sense. Gall has special handling of `%x` scries as described in the [Gall agents](#gall-agents) section below, but otherwise `care`s have no special behaviour for non-Clay vanes (though they must still be included if the endpoint specifies it).
### What can I scry? ## What can I scry?
There are two places where scry endpoints are defined: There are two places where scry endpoints are defined:
#### Vanes ### Vanes
Each of Arvo's nine vanes (kernel modules) include a `+scry` arm which defines Each of Arvo's nine vanes (kernel modules) include a `+scry` arm which defines
that vane's scry endpoints. The number of endpoints and extent of data available that vane's scry endpoints. The number of endpoints and extent of data available
@ -43,13 +45,22 @@ endpoints you'd not typically use in your applications).
To explore what scry endpoints are available for vanes, you can refer to the Scry Reference section of each vane in the [Arvo](/reference/arvo/overview) section of the documents. To explore what scry endpoints are available for vanes, you can refer to the Scry Reference section of each vane in the [Arvo](/reference/arvo/overview) section of the documents.
#### Gall agents ### Gall agents
Gall has a single scry endpoint of its own to check for the existence of an agent, but otherwise all Gall scries are passed through to one of the agents it manages. The target agent to scry is specified in place of the `desk` as described in the diagram above. Each Gall agent includes a `+on-peek` arm that defines its own scry endpoints. For example, `%graph-store` has a number of scry endpoints to access the data it stores, such as chat messages and the like. Gall has a single scry endpoint of its own to check for the existence of an agent, but otherwise all Gall scries are passed through to one of the agents it manages. The target agent to scry is specified in place of the `desk` as described in the diagram above. Each Gall agent includes a `+on-peek` arm that defines its own scry endpoints. For example, `%graph-store` has a number of scry endpoints to access the data it stores, such as chat messages and the like.
Gall agents can expose scry endpoints with any `care`, but most commonly they'll take a `%x` `care`. Gall handles `%x` scries specially - it expects an extra field at the end of the `path` that specifies a `mark`. Gall will attempt to perform a `mark` conversion from the `mark` returned by the scry endpoint to the `mark` specified. Note the trailing `mark` in the `path` will not be passed through to the agent itself. Gall agents can expose scry endpoints with any `care`, but most commonly they'll take a `%x` `care`. Gall handles `%x` scries specially - it expects an extra field at the end of the `path` that specifies a `mark`. Gall will attempt to perform a `mark` conversion from the `mark` returned by the scry endpoint to the `mark` specified. Note the trailing `mark` in the `path` will not be passed through to the agent itself.
### What is an endpoint? {% callout %}
**Note:** you should not perform agent scries from within the
`++on-load` arm of your agent. All Gall agents are suspended during
kernel upgrade, and then reloaded one-by-one. If the agent you scry
wasn't reloaded before yours, the scry will fail.
{% /callout %}
## What is an endpoint?
"Endpoint" refers to a specific scry path in a vane or agent. They will sometimes informally be noted in documentation or source comments like `/x/foo/bar/baz` or maybe just `/foo/bar/baz`. The first part of the former example is the `care`, then the rest is the `path` portion as noted in the diagram earlier. "Endpoint" refers to a specific scry path in a vane or agent. They will sometimes informally be noted in documentation or source comments like `/x/foo/bar/baz` or maybe just `/foo/bar/baz`. The first part of the former example is the `care`, then the rest is the `path` portion as noted in the diagram earlier.
@ -69,11 +80,11 @@ The case in the beginning says it takes a `%x` `care` and has a `path` of `/keys
.^(json %gx /(scot %p our)/graph-store/(scot %da now)/keys/json) .^(json %gx /(scot %p our)/graph-store/(scot %da now)/keys/json)
``` ```
### Web scries ## Web scries
The webserver vane Eyre has a system which allows clients like web browsers to perform scries over HTTP. For details, refer to the [Scry section of Eyre's External API Reference](/reference/arvo/eyre/external-api-ref#scry). The webserver vane Eyre has a system which allows clients like web browsers to perform scries over HTTP. For details, refer to the [Scry section of Eyre's External API Reference](/reference/arvo/eyre/external-api-ref#scry).
### Further reading ## Further reading
[dotket](/reference/hoon/rune/dot#-dotket) - Documentation of the `.^` rune which performs scries. [dotket](/reference/hoon/rune/dot#-dotket) - Documentation of the `.^` rune which performs scries.

View File

@ -7,180 +7,253 @@ Here are the data types used by Dill, as defined in `/sys/lull.hoon`.
## `$blew` ## `$blew`
Terminal dimension.
```hoon ```hoon
+$ blew [p=@ud q=@ud] +$ blew [p=@ud q=@ud]
``` ```
Terminal dimension; `p` is columns, `q` is rows. This structure is passed to Dill by the runtime in a [%blew](/reference/arvo/dill/tasks#blew) `task` whenever the dimensions of the terminal changes. `p` is columns, `q` is rows. This structure is passed to Dill by the
runtime in a [%blew](/reference/arvo/dill/tasks#blew) `task` whenever
the dimensions of the terminal changes.
---
## `$belt` ## `$belt`
```hoon
+$ belt
$? bolt
$% [%mod mod=?(%ctl %met %hyp) key=bolt]
[%txt p=(list @c)]
[%ctl p=@c]
[%met p=@c]
== ==
```
Terminal client input. Terminal client input.
A `$belt` is passed to Dill in a [%belt](/reference/arvo/dill/tasks#belt) `task` by the runtime whenever there is input, such as a user typing in the console. This is only used between the terminal client and Dill, a [$dill-belt](#dill-belt) is used between Dill and Arvo. ```hoon
+$ belt :: client input
$? bolt :: simple input
[%mod mod=?(%ctl %met %hyp) key=bolt] :: w/ modifier
[%txt p=(list @c)] :: utf32 text
::TODO consider moving %hey, %rez, %yow here ::
== ::
```
A `$belt` is passed to Dill in a
[%belt](/reference/arvo/dill/tasks#belt) `task` by the runtime whenever
there is input, such as a user typing in the console. This is only used
between the terminal client and Dill, a [$dill-belt](#dill-belt) is used
between Dill and Arvo.
May either be a [$bolt](#bolt) or one of: May either be a [$bolt](#bolt) or one of:
- `%mod` - Modifier (Ctrl, Meta or Hyper) plus [key]. - `%mod` - Modifier (Ctrl, Meta or Hyper) plus a key (see
- `%txt` - A series of characters [`$bolt`](#bolt).
- `%ctl` - Ctrl+[key], deprecated in favour of `%mod`. - `%txt` - A series of UTF-32 characters.
- `%met` - Meta+[key], deprecated in favour of `%mod`.
--
## `$bolt` ## `$bolt`
```hoon
+$ bolt
$@ @c
$% [%aro p=?(%d %l %r %u)]
[%bac ~]
[%del ~]
[%hit r=@ud c=@ud]
[%ret ~]
==
```
Simple input. Simple input.
Either a single simple character or one of: ```hoon
+$ bolt :: simple input
$@ @c :: simple keystroke
$% [%aro p=?(%d %l %r %u)] :: arrow key
[%bac ~] :: true backspace
[%del ~] :: true delete
[%hit x=@ud y=@ud] :: mouse click
[%ret ~] :: return
== ::
```
Either a single UTF-32 character or one of:
- `%aro` - Arrow keys. - `%aro` - Arrow keys.
- `%bac` - Backspace key. - `%bac` - Backspace key.
- `%del` - Delete key. - `%del` - Delete key.
- `%hit` - Mouse click - `r` is row and `c` is column. Note these are zero-indexed, with `[0 0]` being the _bottom left_ corner. - `%hit` - Mouse click - `r` is row and `c` is column. Note these are
zero-indexed, with `[0 0]` being the _bottom left_ corner.
- `%ret` - Return (Enter) key. - `%ret` - Return (Enter) key.
---
## `$blit` ## `$blit`
```hoon
+$ blit
$% [%bel ~]
[%clr ~]
[%hop p=@ud]
[%klr p=stub]
[%lin p=(list @c)]
[%mor ~]
[%sag p=path q=*]
[%sav p=path q=@]
[%url p=@t]
==
```
Terminal client output. Terminal client output.
A `$blit` is given to the terminal client by Dill in a `%blit` `gift` when it wants to print some text, clear the screen, go _ding_ or what have you. ```hoon
+$ blit :: client output
$% [%bel ~] :: make a noise
[%clr ~] :: clear the screen
[%hop p=$@(@ud [x=@ud y=@ud])] :: set cursor col/pos
[%klr p=stub] :: put styled
[%mor p=(list blit)] :: multiple blits
[%nel ~] :: newline
[%put p=(list @c)] :: put text at cursor
[%sag p=path q=*] :: save to jamfile
[%sav p=path q=@] :: save to file
[%url p=@t] :: activate url
[%wyp ~] :: wipe cursor line
== ::
```
This is only used between Dill and the terminal client, a [$dill-blit](#dill-blit) is used instead between Arvo and Dill. A `$blit` is given to the terminal client by Dill in a `%blit` `gift`
when it wants to print some text, clear the screen, go _ding_ or what
have you.
This is directly used between Dill and the terminal client, while a
[$dill-blit](#dill-blit) is used between Arvo and Dill. A `$dill-blit`
includes the `$blit` union as a subset.
A `$blit` is one of: A `$blit` is one of:
- `%bel` - Ring the terminal bell. - `%bel` - Ring the terminal bell.
- `%clr` - Clear the screen. - `%clr` - Clear the screen.
- `%hop` - Set cursor position, `p` specifies its position on the prompt line. - `%hop` - Set cursor position. If `p` is an atom, it specifies the
horizontal position on the prompt line. If `p` is a cell, it
represents a 2D location where `x` is columns and `y` is
rows.
- `%klr` - Set styled line, the `$stub` specifies the text and style. - `%klr` - Set styled line, the `$stub` specifies the text and style.
- `%lin` - Set current line, `p` contains the text. - `%mor` - multiple `$blit`s.
- `%mor` - Newline. - `%nel` - a newline.
- `%sag` - Save to jamfile, typically in `/[pier]/.urb/put/`. `p` is `/[path]/[filename]/[extension]`. For example, `/foo/bar` will save it in `/[pier]/.urb/put/foo.bar`, `/a/b/c/foo/bar` will save it in `/[pier]/.urb/put/a/b/c/foo.bar`, and `/foo` will save it in `/[pier]/.urb/put.foo`. `q` is the `noun` to `jam` and save in the file. - `%put` - put text (as a list of UTF-32 characters) at the current
- `%sav` - Save to file. Same behaviour as `%sag` except `q` is an `atom` rather than a `noun` and therefore doesn't need to be `jam`med. The `atom` is written to disk as if it were the bytestring in the tail of an `$octs`. That is, `%sav`ing the `cord` `'abcdef'`, whose `@ux` value is `0x6665.6463.6261`, results in a unix file whose hex dump renders as `61 62 63 64 65 66`. cursor position.
- `%sag` - Save to jamfile, typically in `/[pier]/.urb/put/`. `p` is
`/[path]/[filename]/[extension]`. For example, `/foo/bar` will save it
in `/[pier]/.urb/put/foo.bar`, `/a/b/c/foo/bar` will save it in
`/[pier]/.urb/put/a/b/c/foo.bar`, and `/foo` will save it in
`/[pier]/.urb/put.foo`. `q` is the `noun` to `jam` and save in the
file.
- `%sav` - Save to file. Same behaviour as `%sag` except `q` is an
`atom` rather than a `noun` and therefore doesn't need to be `jam`med.
The `atom` is written to disk as if it were the bytestring in the tail
of an `$octs`. That is, `%sav`ing the `cord` `'abcdef'`, whose `@ux`
value is `0x6665.6463.6261`, results in a unix file whose hex dump
renders as `61 62 63 64 65 66`.
- `%url` - Activate URL, `p` is the URL. - `%url` - Activate URL, `p` is the URL.
- `%wyp` - clear the cursor line.
---
## `$dill-belt` ## `$dill-belt`
```hoon
+$ dill-belt
$% [%aro p=?(%d %l %r %u)]
[%bac ~]
[%cru p=@tas q=(list tank)]
[%ctl p=@]
[%del ~]
[%hey ~]
[%met p=@]
[%ret ~]
[%rez p=@ud q=@ud]
[%txt p=(list @c)]
[%yow p=gill:gall]
==
```
Terminal input for Arvo. Terminal input for Arvo.
While [$belt](#belt) is used between the terminal client and Dill, `$dill-belt` is used between Dill and Arvo. ```hoon
+$ dill-belt :: arvo input
$% belt :: client input
[%cru p=@tas q=(list tank)] :: errmsg (deprecated)
[%hey ~] :: refresh
[%rez p=@ud q=@ud] :: resize, cols, rows
[%yow p=gill:gall] :: connect to app
== ::
```
a `$dill-belt` is one of: A [$belt](#belt) is used between the terminal client and Dill, while a
`$dill-belt` is used between Dill and Arvo. A `$dill-belt` includes the
`$belt` union as a subset.
- `%aro` - Arrow keys. a `$dill-belt` is either [`$belt`](#belt) or one of:
- `%bac` - Backspace key.
- `%cru` - Echo error, `p` is an error `@tas` and `q` is a traceback. - `%cru` - Echo error, `p` is some error tag and `q` is a stack trace.
- `%ctl` - Ctrl+[key].
- `%del` - Delete key.
- `%hey` - Refresh. - `%hey` - Refresh.
- `%met` - Meta+[key].
- `%ret` - Return key (Enter).
- `%rez` - Terminal resized, `p` is columns and `q` is rows. - `%rez` - Terminal resized, `p` is columns and `q` is rows.
- `%txt` - Text input.
- `%yow` - Connect to app. - `%yow` - Connect to app.
---
## `$dill-blit` ## `$dill-blit`
```hoon
+$ dill-blit
$% [%bel ~]
[%clr ~]
[%hop p=@ud]
[%klr p=stub]
[%mor p=(list dill-blit)]
[%pom p=stub]
[%pro p=(list @c)]
[%qit ~]
[%out p=(list @c)]
[%sag p=path q=*]
[%sav p=path q=@]
[%url p=@t]
==
```
Terminal output from Arvo. Terminal output from Arvo.
While [$blit](#blit) is used between Dill and the terminal client, `$dill-blit` is used between Arvo and Dill. ```hoon
+$ dill-blit :: arvo output
$% blit :: client output
[%qit ~] :: close console
== ::
```
A `$dill-blit` is one of: While [$blit](#blit) is used between Dill and the terminal client,
`$dill-blit` is used between Arvo and Dill. A `$blit` is a subset of a
`$dill-blit`.
A `$dill-blit` is either a [`$blit`](#blit) or a:
- `%bel` - Terminal bell.
- `%clr` - Clear screen.
- `%hop` - Set cursor position, `p` is its horizontal position on the prompt line.
- `%klr` - Styled text, the `$stub` specifies both the text and style.
- `%mor` - Multiple `$dill-blit`.
- `%pom` - Styled prompt, the `$stub` specifies both the text and style.
- `%pro` - Set prompt, `p` is the text for the prompt.
- `%qit` - Close console. - `%qit` - Close console.
- `%out` - Print text.
- `%sag` - Save `noun` to jamfile. See [$blit](#blit) section for further details. ---
- `%sav` - Save `atom` to file. See [$blit](#blit) section for further details.
- `%url` - Activate URL.
## `$flog` ## `$flog`
```hoon
+$ flog
$% [%crop p=@ud]
[%crud p=@tas q=(list tank)]
[%heft ~]
[%meld ~]
[%pack ~]
[%text p=tape]
[%verb ~]
==
```
Wrapped Dill `task`s. Wrapped Dill `task`s.
These are a subset of Dill's `task`s which can be wrapped in a `%flog` `task`. See the [API Reference](/reference/arvo/dill/tasks) document for details of each of these `task`s. ```hoon
+$ flog :: sent to %dill
$% [%crop p=@ud] :: trim kernel state
$>(%crud told) ::
[%heft ~] ::
[%meld ~] :: unify memory
[%pack ~] :: compact memory
$>(%text told) ::
[%verb ~] :: verbose mode
== ::
```
These are a subset of Dill's `task`s which can be wrapped in a `%flog`
`task`. See the [API Reference](/reference/arvo/dill/tasks) document for
details of each of these `task`s.
---
## `$poke`
Dill to userspace.
```hoon
+$ poke :: dill to userspace
$: ses=@tas :: target session
dill-belt :: input
== ::
```
A [`$dill-belt`](#dill-belt) (client input) for a particular session.
---
## `$session-task`
A subset of [Dill's `task`s](/reference/arvo/dill/tasks#session-tasks)
for interacting with a particular session.
```hoon
+$ session-task :: session request
$% [%belt p=belt] :: terminal input
[%blew p=blew] :: terminal config
[%flee ~] :: unwatch session
[%hail ~] :: terminal refresh
[%open p=dude:gall q=(list gill:gall)] :: setup session
[%shut ~] :: close session
[%view ~] :: watch session blits
== ::
```
This type is used in the [`%shot`](/reference/arvo/dill/tasks#shot)
wrapper `task`.
See the [Session Tasks](/reference/arvo/dill/tasks#session-tasks) entry
in the API reference for more details of these `task`s.
---
## `$told`
A subset of [Dill's `task`s](/reference/arvo/dill/tasks#session-tasks)
for basic text printing.
```hoon
+$ told :: system output
$% [%crud p=@tas q=tang] :: error
[%talk p=(list tank)] :: tanks (in order)
[%text p=tape] :: tape
== ::
```
See the [Told Tasks](/reference/arvo/dill/tasks#told-tasks) entry
in the API reference for more details of these `task`s.
---

View File

@ -5,35 +5,72 @@ weight = 1
The terminal driver vane. The terminal driver vane.
Keyboard events and the like from Unix are received by Dill as [%belt](/reference/arvo/dill/tasks#belt) `task`s, and Dill sends `%blit` `gift`s containing [$blit](/reference/arvo/dill/data-types#blit)s back to the runtime to be displayed in the Unix terminal. The manner of interacting with Dill differs depending on whether you're in userspace or kernelspace, as we'll explore below. Keyboard events and the like from Unix are received by Dill as
[%belt](/reference/arvo/dill/tasks#belt) `task`s, and Dill sends `%blit`
`gift`s containing [$blit](/reference/arvo/dill/data-types#blit)s back to the
runtime to be displayed in the Unix terminal. The manner of interacting with
Dill differs depending on whether you're in userspace or kernelspace, as we'll
explore below.
## Kernelspace ## Kernelspace
For technical reasons, Dill performs a handful of system tasks related to booting a ship and some memory operations. Aside from those, other Vanes mostly just pass Dill [tasks](/reference/arvo/dill/tasks) to print error messages and the like to the terminal. For technical reasons, Dill performs a handful of system tasks related to
booting a ship and some memory operations. Aside from those, other Vanes mostly
just pass Dill [tasks](/reference/arvo/dill/tasks) to print error messages and
the like to the terminal.
## Userspace ## Userspace
Unlike in kernelspace, userspace applications are unlikely to `%pass` Dill `task`s directly. Instead, Dill looks at things in terms of sessions. A session is a pipeline between a client and a handler, where: Unlike in kernelspace, userspace applications are unlikely to `%pass` Dill
`task`s directly. Instead, Dill looks at things in terms of sessions. A session
is a pipeline between a client and a handler, where:
- The client is an external input source and output sink; a terminal with with dimensions and so forth. - The client is an external input source and output sink; a terminal with
- The handler is an application in Urbit that interprets input, maybe does something with it, maybe produces output to be displayed in the client, etc. dimensions and so forth.
- The handler is an application in Urbit that interprets input, maybe does
something with it, maybe produces output to be displayed in the client,
etc. The handler may itself handle and multiplex terminal interfaces for
other applications, as is the case with the `%hood` module `%drum`, or it
may be a stand-alone application.
There are plans for Dill to support multiple independent sessions (i.e. independent terminal clients displaying and processing different information at the same time), but at this stage Dill only supports a single session. The client for this default session is just the Unix terminal via the Urbit runtime. The handler for the default session is the `%hood` app, whose `drum` module does the heavy lifting. Currently, Dill supports multiple *sessions*, but Vere (the runtime) only
supports a single Unix terminal *client* for the default session (`%$`). This
means any non-default sessions will need to be linked through the default
session handler `%drum` (a module of the `%hood` app) if they are to work in
the Unix terminal. Alternatively, a client could be built that talks to Dill
via the HTTP server Eyre in a similar way to the `%webterm` app, and
interacts with sessions entirely separately from the Unix terminal and its
`%drum` handler.
`drum` is the CLI app manager. By default you'll have a couple of CLI applications running—`%dojo` and `%chat-cli`—which you can switch between with `ctrl+x`. You may also have additional CLI apps which you have attached with the `|link` command. It's `drum` that keeps track of which one is active, which one input should be routed to, which one should be displayed, what each prompt should look like, and so forth. Dill itself is oblivious to the distinction between these CLI apps. It only sees the default session with `drum`, so it just passes all input to `drum` and display whatever `drum` gives it. `%drum` is Arvo's CLI app manager. By default you'll have one CLI application
running: the `%dojo`. You may also have additional CLI apps which you have
started or attached with the `|link` command. It's `%drum` that keeps track of
which one is active, which one input should be routed to, which one should be
displayed, what each prompt should look like, and so forth. Dill itself is
oblivious to the distinction between these CLI apps. It only sees the session
with `%drum`, so it just passes all input to `%drum` and display whatever
`%drum` gives it.
While `drum` talks with Dill in `$dill-belt`s and `$dill-blit`s, it talks to CLI apps with `$sole-action`s and `$sole-event`s, which are defined in the `sole` library. For more information on the `sole` library and the related `shoe` library, and for information on how to build CLI apps, you can refer to the [CLI app tutorial](/guides/additional/cli-tutorial). While `%drum` talks with Dill in `$dill-belt`s and `$dill-blit`s, it talks to
CLI apps with `$sole-action`s and `$sole-event`s, which are defined in the
`sole` library. For more information on the `sole` library and the related
`shoe` library, and for information on how to build CLI apps, you can refer to
the [CLI app tutorial](/guides/additional/cli-tutorial).
To give a basic idea of how keyboard events flow through these systems and produce terminal output, here's a diagram showing the messages in pseudo-Hoon: To give a basic idea of how keyboard events flow through these systems and
produce terminal output, here's a diagram showing the messages in pseudo-Hoon:
![Dill userspace diagram](https://media.urbit.org/docs/arvo/dill/dill-userspace.svg) ![Dill userspace diagram](https://media.urbit.org/docs/arvo/dill/dill-userspace.svg)
You can use a [move trace](/reference/arvo/tutorials/move-trace) to get a hands-on feel for this data flow. You can use a [move trace](/reference/arvo/tutorials/move-trace) to get a
hands-on feel for this data flow.
## Sections ## Sections
[API Reference](/reference/arvo/dill/tasks) - The `task`s Dill takes and the `gift`s it returns. [API Reference](/reference/arvo/dill/tasks) - The `task`s Dill takes and the
`gift`s it returns.
[Scry Reference](/reference/arvo/dill/scry) - The scry endpoints of Dill. [Scry Reference](/reference/arvo/dill/scry) - The scry endpoints of Dill.
[Data Types](/reference/arvo/dill/data-types) - Reference documentation of the data types used by Dill. [Data Types](/reference/arvo/dill/data-types) - Reference documentation of the
data types used by Dill.

View File

@ -3,42 +3,39 @@ title = "Scry Reference"
weight = 3 weight = 3
+++ +++
Here are the scry endpoints of Dill. They take a `%x` `care` and require the `desk` in the path prefix be empty, so the general format is `.^([type] %dx /=//=/[some-path])`. Here are the scry endpoints of Dill. They require the `desk` in the path prefix
be empty, so the general format is `.^([type] %d[care] /=//=/[some-path])`.
Dill only has a couple of scry endpoints, both of which begin with `/session//`. The empty `//` would be where the target session would be specified, but at this stage Dill can only return a result for the default session, so it's always empty. ## `%x` - `/sessions`
## /sessions//line Get all sessions.
A scry with a `care` of `%x` and a `path` of `/sessions//line` returns the current text of the prompt line of the default session. The type returned is a [$blit](/reference/arvo/dill/data-types#blit). A scy with a `care` of `%x` and a `path` of `/sessions` returns a `(set @tas)`
of the current sessions. The `%$` session is the default session.
#### Example #### Example
``` ```
> .^(blit:dill %dx /=//=/sessions//line) > .^((set @tas) %dy /=//=/sessions)
[ %lin {%$}
p
~[
~-~~
~-z
~-o
~-d
~-~3a.
~-d
~-o
~-j
~-o
...truncated for brevity...
]
]
``` ```
## /sessions//cursor ---
A scry with a `care` of `%x` and a `path` of `/sessions//cursor` returns the current horizontal position of the cursor in the default session. The type returned is a `@ud`. ## `%u` - `/sessions/[ses]`
Does session exist?
A scry with a `care` of `%x` and a `path` of `/sessions/[ses]` where `[ses]` is
a session name returns a `?` of whether `[ses]` exists.
#### Example #### Example
``` ```
> .^(@ud %dx /=//=/sessions//cursor) > .^(? %du /=//=/sessions/$)
44 %.y
> .^(? %du /=//=/sessions/foo)
%.n
``` ```
---

View File

@ -3,339 +3,478 @@ title = "API Reference"
weight = 2 weight = 2
+++ +++
In this document we describe the public interface for Dill. Namely, we describe each `task` that Dill can be `pass`ed, and which `gift`(s) Dill can `give` in return. In this document we describe the public interface for Dill. Namely, we
describe each `task` that Dill can be `pass`ed, and which `gift`(s) Dill
can `give` in return.
## `%belt` Dill's `task`s are organized into three categories: [Session
`task`s](#session-tasks) for interacting with a particular session,
[Told `task`s](#told-tasks) for basic terminal printing, and
[system/miscellaneous `task`s](#system-misc-tasks) which are a combination of
general system `task`s and Dill `task`s which don't fit in the previous
two categories.
```hoon ## System & Misc. Tasks
[%belt p=belt]
```
Terminal input. These are the Dill `task`s not otherwise categorized as [session
tasks](#session-tasks) or [told tasks](#told-tasks). Most of them are
interfaces for Vere or Arvo and do thing like toggle verbose mode,
defragment state, etc. The most notable Dill-specific `task` is
[`%shot`](#shot), which is used as a wrapper for the [session
tasks](#session-tasks) to specify the session. The [`%logs`](#logs)
`task` is also useful, letting you subscribe to all system output.
The runtime sends a `%belt` `task` to Dill whenever there is input in the terminal, such as a keystroke. ### `%boot`
The [$belt](/reference/arvo/dill/data-types#belt) in `p` contains the input such as which key was pressed.
Dill will convert the `$belt` into a [$dill-belt](/reference/arvo/dill/data-types#dill-belt) and `%poke` the session handler (typically `drum`) with it.
This `task` would not typically be used from userspace.
#### Returns
Dill returns no `gift` in response to a `%belt` `task`.
## `%blew`
```hoon
[%blew p=blew]
```
Terminal resized.
The runtime passes Dill a `%blew` `task` whenever the terminal is resized.
The [$blew](/reference/arvo/dill/data-types#blew) specifies the new dimensions.
Dill will convert the `$blew` into a `%rez` [$dill-belt](/reference/arvo/dill/data-types#dill-belt) and `%poke`s the session handler (typically `drum`) with it.
This `task` would not typically be used from userspace.
#### Returns
Dill returns no `gift` in response to a `%blew` `task`.
## `%boot`
```hoon ```hoon
[%boot lit=? p=*] [%boot lit=? p=*]
``` ```
This `task` is used only once, when Arvo first enters the [adult stage](/reference/arvo/overview#structural-interface-core). Dill is technically the first vane to be activated, via the `%boot` `task`, which then sends Jael (considered the "true" first vane) the `%dawn` or `%fake` `task` wrapped in the `%boot` `task`. Jael then goes on to call `%init` `task`s for other vanes (including Dill). This `task` is used only once, when Arvo first enters the [adult
stage](/reference/arvo/overview#structural-interface-core). Dill is
technically the first vane to be activated, via the `%boot` `task`,
which then sends Jael (considered the "true" first vane) the `%dawn` or
`%fake` `task` wrapped in the `%boot` `task`. Jael then goes on to call
`%init` `task`s for other vanes (including Dill).
`lit` specifies whether to boot in lite mode. `p` is either a [%dawn](/reference/arvo/jael/data-types#dawn) or [%fake](/reference/arvo/jael/tasks#fake) `task:jael`. `%dawn` is for an ordinary boot and `%fake` is for booting a fake ship. `lit` specifies whether to boot in lite mode. `p` is either a
[%dawn](/reference/arvo/jael/tasks#dawn) or
[%fake](/reference/arvo/jael/tasks#fake) `task:jael`. `%dawn` is for an
ordinary boot and `%fake` is for booting a fake ship.
This `task` would not be used from userspace. This `task` would not be used from userspace.
#### Returns #### Returns
Dill returns no `gift` in response to a `%boot` `task`, but it will `%pass` the wrapped `%dawn` or `%fake` `task` to Jael. Dill returns no `gift` in response to a `%boot` `task`, but it will
`%pass` the wrapped `%dawn` or `%fake` `task` to Jael.
## `%crop` ---
### `%crop`
Trim kernel state.
```hoon ```hoon
[%crop p=@ud] [%crop p=@ud]
``` ```
Trim kernel state. This `task` is the same as the `%trim` vane `task`. Like `%trim`, Dill
does nothing with it.
This `task` is the same as the [%trim](#trim) `task`. Like `%trim`, Dill does nothing with it.
#### Returns #### Returns
Dill returns no `gift` in response to a `%crop` `task`. Dill returns no `gift` in response to a `%crop` `task`.
## `%crud` ---
```hoon ### `%flog`
[%crud p=@tas q=(list tank)]
```
Print error.
Dill prints the given error to the terminal. The verbosity for the particular `@tas` error tag specified in `p` is determined by the `level` set by a [%knob](#knob) `task` - either `%hush`, `%soft` or `%loud`. The default is `%loud`, where it will print the full `(list tank)` specified in `q`. See [%knob](#knob) for details of verbosity levels.
#### Returns
Dill does not return a `gift` in response to a `%crud` `task`.
## `%flee`
```hoon
[%flee session=~]
```
Unwatch a session to which you've previously subscribed with [%view](#view).
The ability to specify a session is not yet implemented in Dill, so `session` is always `~`, the default session.
#### Returns
Dill does not return a `gift` in response to a `%flee` `task`.
## `%flog`
```hoon ```hoon
[%flog p=flog] [%flog p=flog]
``` ```
A `%flog` `task` is a wrapper over a `task` sent by another vane. Dill removes the wrapper and sends the bare `task` to itself over the default `duct`. A `%flog` `task` is a wrapper over a `task` sent by another vane. Dill
removes the wrapper and sends the bare `task` to itself over the default
`duct`.
A `%flog` `task` takes a [$flog](/reference/arvo/dill/data-types#flog) as its argument. A `$flog` is a subset of Dill's `task`s. A `%flog` `task` takes a [$flog](/reference/arvo/dill/data-types#flog)
as its argument. A `$flog` is a subset of Dill's `task`s.
#### Returns #### Returns
Dill does not return a `gift` in response to a `%flog` `task`. Dill does not return a `gift` in response to a `%flog` `task`.
## `%flow` ---
```hoon ### `%heft`
[%flow p=@tas q=(list gill:gall)]
```
Terminal config. Produce memory report.
This `task` is not used.
## `%hail`
```hoon
[%hail ~]
```
Refresh.
Dill converts a `%hail` `task` into a `%hey` [$dill-belt](/reference/arvo/dill/data-types#dill-belt) and `%poke`s the session handler (typically `drum`) with it to handle the refresh.
This `task` would not be used from userspace.
#### Returns
Dill returns no `gift` in response to a `%hail` `task`.
## `%heft`
```hoon ```hoon
[%heft ~] [%heft ~]
``` ```
Produce memory report. When Dill receives a `%heft` `task` it passes Arvo a `%whey` `waif` to
obtain a memory report and prints it to the terminal.
When Dill receives a `%heft` `task` it passes Arvo a `%whey` `waif` to obtain a memory report and prints it to the terminal.
#### Returns #### Returns
Dill does not return a `gift` in response to a `%heft` `task`. Dill does not return a `gift` in response to a `%heft` `task`.
## `%hook` ---
### `%logs`
Watch system output.
```hoon ```hoon
[%hook ~] [%logs p=(unit ~)]
``` ```
This terminal hung up. A non-null `p` subscribes to system output, and a null `p` unsubscribes.
While subscribed, you'll receive each piece of output in a `%logs` gift
This task is not used. as it occurs.
## `%harm`
```hoon
[%harm ~]
```
All terminals hung up.
This `task` is not used.
## `%init`
```hoon
[%init ~]
```
This `task` is called only once, when Arvo first enters the [adult stage](/reference/arvo/overview#structural-interface-core). It performs initial setup for Dill, such as setting the width of the console.
Note that this is not actually the first `task` passed to Dill - see [%boot](#%boot).
This `task` would not be used from userspace.
#### Returns #### Returns
Dill does not return a `gift` in response to a `%init` `task`. Dill does not immediately return anything, but will give you a `%logs`
gifts each time system output occurs. A `%logs` gift looks like:
## `%meld` ```hoon
[%logs =told]
```
``` A `$told` is either a [`%crud`](#crud), [`%talk`](#talk) or
[%meld ~] [`%text`](#text) task. See the
``` [`$told`](/reference/arvo/dill/data-types#dill-belt) entry in the data
types reference for more details.
---
### `%meld`
Deduplicate persistent state. Deduplicate persistent state.
```hoon
[%meld ~]
```
Dill asks the runtime to perform the memory deduplication. Dill asks the runtime to perform the memory deduplication.
#### Returns #### Returns
Dill does not return a `gift` in response to a `%meld` `task`. Dill does not return a `gift` in response to a `%meld` `task`.
## `%noop` ---
```hoon ### `%pack`
[%noop ~]
```
No operation. Defragment persistent state.
A `%noop` `task` does nothing, as the name implies.
#### Returns
Dill does not return a `gift` in response to a `%noop` `task`.
## `%pack`
```hoon ```hoon
[%pack ~] [%pack ~]
``` ```
Defragment persistent state.
Dill asks the runtime to perform the defragmentation. Dill asks the runtime to perform the defragmentation.
#### Returns #### Returns
Dill does not return a `gift` in response to a `%meld` `task`. Dill does not return a `gift` in response to a `%meld` `task`.
## `%talk` ---
### `%seat`
Install desk.
```hoon ```hoon
[%talk p=tank] [%seat =desk]
``` ```
This `task` is not used. This indirectly pokes `%hood` with a `%kiln-install` `mark` to install
the specified `desk`. You should just poke `%hood` directly rather than
## `%text` using this.
```hoon
[%text p=tape]
```
Print `tape` to terminal.
Upon receiving a `%text` `task`, Dill will convert the `tape` given in `p` to a `(list @c)` and give it to the runtime in a `%blit` `gift` including a `%lin` [$blit](/reference/arvo/dill/data-types#blit).
#### Returns #### Returns
Dill does not return a `gift` in response to a `%text` `task`, but it does give a `%blit` `gift` to the runtime which looks like: Dill does not return a `gift` in response to a `%seat` `task`.
---
### `%shot`
Task for session.
```hoon ```hoon
[%blit p=(list blit)] [%shot ses=@tas task=session-task]
``` ```
## `%view` A `$session-task` is one of these `task`s: [`%belt`](#belt),
[`%blew`](#blew), [`%flee`](#flee), [`%hail`](#hail), [`%open`](#open),
[`%shut`](#shut), [`%view`](#view). See the
[`$session-task`](/reference/arvo/dill/data-types#session-task) entry in
the data types reference.
```hoon ---
[%view session=~]
```
Watch session. ### `%verb`
A `%view` `task` subscribes for a copy of all `%blit` `gift`s which Dill `%give`s for the default session. This `task` is used by the `%herm` app so it can convert the `$blit`s to JSON and render them in the web terminal. Toggle Arvo verbose mode.
The ability to specify a session is not yet implemented in Dill, so `session` is always `~`, the default session.
#### Returns
Dill will `%give` a copy of all `%blit`s for the default session. A `%blit` `gift` is:
```hoon
[%blit p=(list blit)]
```
## `%trim`
```hoon
[%trim p=@ud]
```
`%trim` is a common vane `task` used to reduce memory usage. It does nothing for Dill because Dill only keeps a minimal necessary state.
#### Returns
Dill does not return a `gift` in response to a `%trim` `task`.
## `%vega`
```hoon
[%vega ~]
```
This is a common vane `task` used to inform the vane that the kernel has been
upgraded. Dill does not do anything in response to this.
This `task` would not be used from userspace.
#### Returns
Dill returns no `gift` in response to a `%vega` `task`.
## `%verb`
```hoon ```hoon
[%verb ~] [%verb ~]
``` ```
This `task` toggles verbose mode for all of Arvo, which is located here since This `task` toggles verbose mode for all of Arvo, which is located here
Dill is the vane that prints errors. To be precise, `%verb` toggles the laconic since Dill is the vane that prints errors. To be precise, `%verb`
bit `lac` in the [Arvo state](/reference/arvo/overview#the-state) by passing a `%verb` `waif` to Arvo. toggles the laconic bit `lac` in the [Arvo
state](/reference/arvo/overview#the-state) by passing a `%verb` `waif`
to Arvo.
#### Returns #### Returns
Dill does not return a `gift` in response to a `%verb` `task`. Dill does not return a `gift` in response to a `%verb` `task`.
## `%knob` ---
## Session Tasks
These `task`s are for interacting with a particular session. These all
would normally be wrapped in a [`%shot`](#shot) `task` to specify the
session, rather than sent directly.
This subset of Dill's `task`s are defined in the [`$session-task`
type](/reference/arvo/dill/data-types#session-task).
### `%belt`
Terminal input.
```hoon ```hoon
[%knob tag=term level=?(%hush %soft %loud)] [%belt p=belt]
``` ```
`%knob` sets the verbosity level for each error tag. The error `tag` can be any `@tas`. The given `tag` and `level` will be added to Dill's `veb` which maps tags to levels. Subsequent [%crud](#crud) `task`s will then print with the specified verbosity. The [$belt](/reference/arvo/dill/data-types#belt) in `p` contains the
input such as which key was pressed. Dill will convert the `$belt` into
a [$dill-belt](/reference/arvo/dill/data-types#dill-belt) and `%poke` it
into the session handler agent for the session in question.
The `level`s behave like so: This `task` should be wrapped in a [`%shot`](#shot) `task` to specify
the session. Without the `%shot` wrapper, it will use the default
- `%hush` - Completely silent, print nothing. session (`%$`).
- `%soft` - Just print `crud: %error-tag event failed`, ignore any `tang` given in the `%crud`.
- `%loud` - Print the `%soft` message as well as the full `tang` given in the `%crud` `task`.
#### Returns #### Returns
Dill does not return a `gift` in response to a `%knob` `task`. Dill returns no `gift` in response to a `%belt` `task`.
---
### `%blew`
Terminal resized.
```hoon
[%blew p=blew]
```
The [$blew](/reference/arvo/dill/data-types#blew) specifies the new
dimensions.
Dill will convert the `$blew` into a `%rez`
[$dill-belt](/reference/arvo/dill/data-types#dill-belt) and `%poke`s the
session handler (typically `drum`) with it.
This `task` would not typically be used from userspace. Instead, it
would come in from the runtime for the default session (`%$`) when the
actual terminal were resized. If in an odd scenario it were used from
userspace, it would need to be wrapped in a [`%shot`](#shot) `task` to
specify a session other than `%$`.
#### Returns
Dill returns no `gift` in response to a `%blew` `task`.
---
### `%flee`
Unwatch a session to which you've previously subscribed with
[%view](#view).
```hoon
[%flee ~]
```
This `task` must be wrapped in a [`%shot`](#shot) `task` in order to
specify the session. Without that, it will default to the default
session (`%$`). The subscription to end will be determined implicitly by
the `duct`.
#### Returns
Dill does not return a `gift` in response to a `%flee` `task`.
---
### `%hail`
Refresh.
```hoon
[%hail ~]
```
Dill converts a `%hail` `task` into a `%hey`
[$dill-belt](/reference/arvo/dill/data-types#dill-belt) and `%poke`s the
session handler (typically `%drum`) with it.
This `task` would not typically be used from userspace. If in an odd
scenario it were used from userspace, it would need to be wrapped in a
[`%shot`](#shot) `task` to specify a session other than `%$`.
#### Returns
Dill returns no `gift` in response to a `%hail` `task`.
---
### `%open`
Setup session.
```hoon
[%open p=dude:gall q=(list gill:gall)]
```
This `task` is always wrapped in a [`%shot`](#shot) `task`, and creates
the new session specified in that wrapper. If it's not wrapped in a
`%shot` task, it will default to the default session (`%$`) and fail
because it already exists. This `task` is designed to be used by a
userspace session handler like `%drum` that multiplexes terminal
interfaces for multiple userspace applications, but could also be used
by a stand-alone application that talks to Dill directly.
The Gall agent specified in `p` is the session handler or stand-alone
application. The `q` field contains a list of `gill:gall`, which are
pairs of `[ship term]`, representing an app on a ship. The `gill`s in
`q` are the list of apps being managed by the session handler `q` that
should be notified of being connected to this session. If `p` were a
stand-alone application, `q` could be empty, or else just contain that
one app.
Dill will poke every agent listed in `q` (local or remote) with a
[`%yow` `$dill-belt`](/reference/arvo/dill/data-types#dill-belt), to let
it know it's been connected. It will also `%watch` the agent `p` for
[`$dill-blit`](/reference/arvo/dill/data-types#dill-blit)s in `%fact`s
with a `%dill-blit` `mark` on the `path` `/dill/[ses]` where `ses` is
the session specified in the [`%shot`](#shot) wrapper.
Additionally, the source of the `%open` request (as determined by the
`duct`, typically the agent in `p`) will begin receiving terminal output
gifts for the session in question. Essentially, it behaves as though you
also passed it a [`%view`](#view) task.
#### Returns
Dill does not give a `gift` in direct response to an `%open` `task`. It
will, however, start giving terminal output `%blit` `gift`s to the
requester as the occur for the session. A `%blit` `gift` looks like:
```hoon
[%blit p=(list blit)]
```
See the [`$blit`](/reference/arvo/dill/data-types#blit) entry in the
data type reference for details of what it can contain.
This subscription for `$blit`s can be stopped with a [`%flee`](#flee)
`task` at any time.
---
### `%shut`
Close session.
```hoon
[%shut ~]
```
This `task` needs to be wrapped in [`%shot`](#shot) `task` to specify
the session to close. Otherwise, it will default to the default session
(`%$`).
The session handler will be passed a `%leave`. Subscribers for `$blit`s
will not be notified, they'll just stop receiving `$blit`s.
#### Returns
Dill does not give a `gift` in response to a `%shut` `task`. It will,
however, pass a `%leave` to the session handler agent.
---
### `%view`
Watch session.
```hoon
[%view ~]
```
A `%view` `task` subscribes for a copy of all `%blit` `gift`s which Dill
`%give`s for the session in question. This `task` must be wrapped in a
[`%shot`](#shot) `task` which specifies the session if you want to
subscribe to anything other than the default session (`%$`).
#### Returns
Dill will begin giving a copy of all `%blit`s for the session specified
in the [`%shot`](#shot) wrapper, or the default session (`%$`) if a
`%shot` wrapper is not used. A `%blit` `gift` is:
```hoon
[%blit p=(list blit)]
```
See the [`$blit`](/reference/arvo/dill/data-types#blit) entry in the
data type reference for more details.
The subscription can be ended with a [`%flee`](#flee) `task`.
---
## Told Tasks
This subset of Dill `task`s are for printing things. They are defined in
the [`$told` type](/reference/arvo/dill/data-types#told).
### `%crud`
Print error.
```hoon
[%crud p=@tas q=tang]
```
Dill prints the given error to the terminal.
#### Returns
Dill does not return a `gift` in response to a `%crud` `task`.
---
### `%talk`
Print `tang` to terminal.
```hoon
[%talk p=(list tank)]
```
The `tank`s in `p` will be printed to the terminal, first to last.
#### Returns
Dill does not return a `gift` in response to a `%talk` `task`.
---
### `%text`
Print `tape` to terminal.
```hoon
[%text p=tape]
```
The `tape` in `p` will be printed to the terminal.
#### Returns
Dill does not return a `gift` in response to a `%text` `task`.
---

View File

@ -11,6 +11,24 @@ This document describes the data types used by Eyre as defined in `/sys/lull.hoo
## Eyre ## Eyre
### `$cache-entry`
```hoon
+$ cache-entry
$: auth=?
$= body
$% [%payload =simple-payload:http]
== ==
```
- `auth`: Whether the request must include a valid session cookie or otherwise
be authenticated. If this is false, the entry will be publicly accessible.
- `body`: The HTTP response to give. This contains a `[%payload
=simple-payload:http]`. See the [`$simple-payload:http`](#simple-payloadhttp)
for more details of the data.
---
### `$origin` ### `$origin`
```hoon ```hoon
@ -19,6 +37,8 @@ This document describes the data types used by Eyre as defined in `/sys/lull.hoo
A single CORS origin as used in an HTTP Origin header and the [$cors-registry](#cors-registry). A single CORS origin as used in an HTTP Origin header and the [$cors-registry](#cors-registry).
---
### `$cors-registry` ### `$cors-registry`
```hoon ```hoon
@ -31,6 +51,8 @@ A single CORS origin as used in an HTTP Origin header and the [$cors-registry](#
CORS origins categorised by approval status. The `requests` `set` contains all [$origin](#origin)s Eyre has received in the headers of HTTP requests that have not been explicitly approved or rejected. The `approved` and `rejected` `set`s are those that have been explicitly approved or rejected. CORS origins categorised by approval status. The `requests` `set` contains all [$origin](#origin)s Eyre has received in the headers of HTTP requests that have not been explicitly approved or rejected. The `approved` and `rejected` `set`s are those that have been explicitly approved or rejected.
---
### `$outstanding-connection` ### `$outstanding-connection`
```hoon ```hoon
@ -44,6 +66,8 @@ CORS origins categorised by approval status. The `requests` `set` contains all [
An HTTP connection that is currently open. The [$action](#action) is how it's being handled (e.g. by a Gall app, the channel system, etc). The [$inbound-request](#inbound-request) is the original request which opened the connection. The `response-header` contains the status code and headers. The `bytes-sent` is the total bytes sent so far in response. An HTTP connection that is currently open. The [$action](#action) is how it's being handled (e.g. by a Gall app, the channel system, etc). The [$inbound-request](#inbound-request) is the original request which opened the connection. The `response-header` contains the status code and headers. The `bytes-sent` is the total bytes sent so far in response.
---
### `$authentication-state` ### `$authentication-state`
```hoon ```hoon
@ -52,6 +76,8 @@ An HTTP connection that is currently open. The [$action](#action) is how it's be
This represents the authentication state of all sessions. It maps session cookies (without the `urbauth-{SHIP}=` prefix) to [$session](#session)s. This represents the authentication state of all sessions. It maps session cookies (without the `urbauth-{SHIP}=` prefix) to [$session](#session)s.
---
### `$session` ### `$session`
```hoon ```hoon
@ -63,6 +89,8 @@ This represents the authentication state of all sessions. It maps session cookie
This represents server-side data about a session. The `expiry-time` is when the `session` expires and `channels` is the `set` of [$channel](#channel) names opened by the session. This represents server-side data about a session. The `expiry-time` is when the `session` expires and `channels` is the `set` of [$channel](#channel) names opened by the session.
---
### `$channel-state` ### `$channel-state`
```hoon ```hoon
@ -74,6 +102,8 @@ This represents server-side data about a session. The `expiry-time` is when the
The state used by the channel system. The `session` is a `map` between channel names and [$channel](#channel)s and the `duct-to-key` `map`s `duct`s to `channel` names. The state used by the channel system. The `session` is a `map` between channel names and [$channel](#channel)s and the `duct-to-key` `map`s `duct`s to `channel` names.
---
### `$timer` ### `$timer`
```hoon ```hoon
@ -85,6 +115,8 @@ The state used by the channel system. The `session` is a `map` between channel n
A reference to a timer so it can be cancelled or updated. The `date` is when it will fire and the `duct` is what set the timer. A reference to a timer so it can be cancelled or updated. The `date` is when it will fire and the `duct` is what set the timer.
---
### `$channel-event` ### `$channel-event`
```hoon ```hoon
@ -98,21 +130,42 @@ A reference to a timer so it can be cancelled or updated. The `date` is when it
An unacknowledged event in the channel system. An unacknowledged event in the channel system.
---
### `$channel` ### `$channel`
```hoon ```hoon
+$ channel +$ channel
$: state=(each timer duct) $: mode=?(%json %jam)
next-id=@ud state=(each timer duct)
last-ack=@da next-id=@ud
events=(qeu [id=@ud request-id=@ud =channel-event]) last-ack=@da
unacked=(map @ud @ud) events=(qeu [id=@ud request-id=@ud =channel-event])
subscriptions=(map @ud [ship=@p app=term =path duc=duct]) unacked=(map @ud @ud)
heartbeat=(unit timer) subscriptions=(map @ud [ship=@p app=term =path duc=duct])
== heartbeat=(unit timer)
==
``` ```
This is the state of a particular channel in the channel system. The `state` is either the expiration time or the duct currently listening. The `next-id` is the next event ID to be used in the event stream. The `last-ack` is the date of the last client ack and is used for clog calculations in combination with `unacked`. The `events` queue contains all unacked events - `id` is the server-set event ID, `request-id` is the client-set request ID and the [$channel-event](#channel-event) is the event itself. The `unacked` `map` contains the unacked event count per `request-id` and is used for clog calculations. The `subscriptions` `map` contains gall subscriptions by `request-id`. The `heartbeat` is the SSE heartbeat [$timer](#timer). This is the state of a particular channel in the channel system.
- `mode` says whether the channel sends/received JSON or
[nouns](/guides/additional/noun-channels).
- `state` is either the expiration time or the duct currently listening.
- `next-id` is the next event ID to be used in the event stream.
- `last-ack` is the date of the last client ack and is used for clog
calculations in combination with `unacked`.
- `events` queue contains all unacked events:
- `id` is the server-set event ID.
- `request-id` is the client-set request ID.
- [$channel-event](#channel-event) is the event itself.
- `unacked` `map` contains the unacked event count per `request-id` and is used
for clog calculations.
- `subscriptions` `map` contains gall subscriptions by `request-id`.
- `heartbeat` is the SSE heartbeat [$timer](#timer).
---
### `$binding` ### `$binding`
@ -125,22 +178,27 @@ This is the state of a particular channel in the channel system. The `state` is
A `binding` is a rule to match a URL `path` and optional `site` domain which can then be tied to an [$action](#action). A `path` of `/foo` will also match `/foo/bar`, `/foo/bar/baz`, etc. If the `site` is `~` it will be determined implicitly. A binding must be unique. A `binding` is a rule to match a URL `path` and optional `site` domain which can then be tied to an [$action](#action). A `path` of `/foo` will also match `/foo/bar`, `/foo/bar/baz`, etc. If the `site` is `~` it will be determined implicitly. A binding must be unique.
---
### `$action` ### `$action`
```hoon ```hoon
+$ action +$ action
$% [%gen =generator] :: dispatch to a generator $% [%gen =generator]
[%app app=term] :: dispatch to an application [%app app=term]
[%authentication ~] :: internal authentication page [%authentication ~]
[%logout ~] :: internal logout page [%logout ~]
[%channel ~] :: gall channel system [%channel ~]
[%scry ~] :: gall scry endpoint [%scry ~]
[%four-oh-four ~] :: respond with the default file not found page [%name ~]
== [%four-oh-four ~]
==
``` ```
The action to take when a [$binding](#binding) matches an incoming HTTP request. The action to take when a [$binding](#binding) matches an incoming HTTP request.
---
### `$generator` ### `$generator`
```hoon ```hoon
@ -153,6 +211,8 @@ The action to take when a [$binding](#binding) matches an incoming HTTP request.
This refers to a generator on a local ship that can handle requests. Note that serving generators via Eyre is not fully implmented and should not be used. This refers to a generator on a local ship that can handle requests. Note that serving generators via Eyre is not fully implmented and should not be used.
---
### `$http-config` ### `$http-config`
```hoon ```hoon
@ -166,6 +226,8 @@ This refers to a generator on a local ship that can handle requests. Note that s
The configuration of the runtime HTTP server itself. The `secure` field contains the PEM-encoded RSA private key and certificate or certificate chain when using HTTPS, and otherwise is `~` when using plain HTTP. The `proxy` field is not currently used. The `log` field turns on HTTP(S) access logs but is not currently implemented. The `redirect` field turns on 301 redirects to upgrade HTTP to HTTPS if the `key` and `cert` are set in `secure`. The configuration of the runtime HTTP server itself. The `secure` field contains the PEM-encoded RSA private key and certificate or certificate chain when using HTTPS, and otherwise is `~` when using plain HTTP. The `proxy` field is not currently used. The `log` field turns on HTTP(S) access logs but is not currently implemented. The `redirect` field turns on 301 redirects to upgrade HTTP to HTTPS if the `key` and `cert` are set in `secure`.
---
### `$http-rule` ### `$http-rule`
```hoon ```hoon
@ -177,6 +239,8 @@ The configuration of the runtime HTTP server itself. The `secure` field contains
This is for updating the server configuration. In the case of `%cert`, a `cert` of `~` clears the HTTPS cert & key, otherwise `cert` contains the PEM-encoded RSA private key and certificate or certificate chain. In the case of `%turf`, a `%put` `action` sets a domain name and a `%del` `action` removes it. The [$turf](#turf) contains the domain name. This is for updating the server configuration. In the case of `%cert`, a `cert` of `~` clears the HTTPS cert & key, otherwise `cert` contains the PEM-encoded RSA private key and certificate or certificate chain. In the case of `%turf`, a `%put` `action` sets a domain name and a `%del` `action` removes it. The [$turf](#turf) contains the domain name.
---
### `$address` ### `$address`
```hoon ```hoon
@ -188,6 +252,8 @@ This is for updating the server configuration. In the case of `%cert`, a `cert`
A client IP address. A client IP address.
---
### `$inbound-request` ### `$inbound-request`
```hoon ```hoon
@ -201,6 +267,8 @@ A client IP address.
An inbound HTTP request and metadata. The `authenticated` field says whether the request was made with a valid session cookie. The `secure` field says whether it was made with HTTPS. The [$address](#address) is the IP address from which the request originated, except if it came from localhost and included a `Forwarded` header, in which case it's the address specified in that header. The [$request:http](#requesthttp) contains the HTTP request itself. An inbound HTTP request and metadata. The `authenticated` field says whether the request was made with a valid session cookie. The `secure` field says whether it was made with HTTPS. The [$address](#address) is the IP address from which the request originated, except if it came from localhost and included a `Forwarded` header, in which case it's the address specified in that header. The [$request:http](#requesthttp) contains the HTTP request itself.
---
## HTTP ## HTTP
### `$header-list:http` ### `$header-list:http`
@ -211,6 +279,8 @@ An inbound HTTP request and metadata. The `authenticated` field says whether the
An ordered list of HTTP headers. The `key` is the header name e.g `'Content-Type'` and the `value` is the value e.g. `text/html`. An ordered list of HTTP headers. The `key` is the header name e.g `'Content-Type'` and the `value` is the value e.g. `text/html`.
---
### `$method:http` ### `$method:http`
```hoon ```hoon
@ -228,6 +298,8 @@ An ordered list of HTTP headers. The `key` is the header name e.g `'Content-Type
An HTTP method. An HTTP method.
---
### `$request:http` ### `$request:http`
```hoon ```hoon
@ -241,6 +313,8 @@ An HTTP method.
A single HTTP request. The [$method:http](#methodhttp) is the HTTP method, the `url` is the unescaped URL, the [$header-list:http](#header-listhttp) contains the HTTP headers of the request and the `body` is the actual data. An `octs` is just `[p=@ud q=@]` where `p` is the byte-length of `q`, the data. A single HTTP request. The [$method:http](#methodhttp) is the HTTP method, the `url` is the unescaped URL, the [$header-list:http](#header-listhttp) contains the HTTP headers of the request and the `body` is the actual data. An `octs` is just `[p=@ud q=@]` where `p` is the byte-length of `q`, the data.
---
### `$response-header:http` ### `$response-header:http`
```hoon ```hoon
@ -252,6 +326,8 @@ A single HTTP request. The [$method:http](#methodhttp) is the HTTP method, the `
The status code and [$header-list:http](#header-listhttp) of an HTTP response. The status code and [$header-list:http](#header-listhttp) of an HTTP response.
---
### `$http-event:http` ### `$http-event:http`
```hoon ```hoon
@ -275,6 +351,8 @@ Urbit treats Earth's HTTP servers as pipes, where Urbit sends or receives one or
Calculation of control headers such as `'Content-Length'` or `'Transfer-Encoding'` should be performed at a higher level; this structure is merely for what gets sent to or received from Earth. Calculation of control headers such as `'Content-Length'` or `'Transfer-Encoding'` should be performed at a higher level; this structure is merely for what gets sent to or received from Earth.
---
### `$simple-payload:http` ### `$simple-payload:http`
```hoon ```hoon
@ -286,3 +364,5 @@ Calculation of control headers such as `'Content-Length'` or `'Transfer-Encoding
``` ```
A simple, one-event response used for generators. The [$reponse-header:http](#response-headerhttp) contains the status code and HTTP headers. The `octs` in the `data` contains the body of the response and is a `[p=@ud q=@]` where `p` is the byte-length of `q`, the data. A simple, one-event response used for generators. The [$reponse-header:http](#response-headerhttp) contains the status code and HTTP headers. The `octs` in the `data` contains the body of the response and is a `[p=@ud q=@]` where `p` is the byte-length of `q`, the data.
---

View File

@ -3,9 +3,12 @@ title = "Scry Reference"
weight = 4 weight = 4
+++ +++
Here are all of Eyre's scry endpoints. There's not too many and they mostly deal with either CORS settings or aspects of the state of connections. Here are all of Eyre's scry endpoints. There's not too many and they mostly
deal with either CORS settings or aspects of the state of connections.
The first few have a `care` of `x` and are a scry like `.^({TYPE} %ex /=//=/{SOME-PATH})` (note the empty `desk`). The rest have no `care` and the tag replaces the `desk` like `.^({TYPE} %e /={SOMETHING}=)`. The first few have a `care` of `x` and are a scry like `.^([type] %ex
/=//=/[some-path])` (note the empty `desk`). The rest have no `care` and the
tag replaces the `desk` like `.^([type] %e /=[something]=)`.
All examples are run from the dojo. All examples are run from the dojo.
@ -13,7 +16,7 @@ All examples are run from the dojo.
An `x` scry with a `path` of `/cors` will return Eyre's CORS origin registry. The type returned is a [cors-registry](/reference/arvo/eyre/data-types#cors-registry) which contains the `set`s of approved, rejected and requested origins. An `x` scry with a `path` of `/cors` will return Eyre's CORS origin registry. The type returned is a [cors-registry](/reference/arvo/eyre/data-types#cors-registry) which contains the `set`s of approved, rejected and requested origins.
### Example {% #example-1 %} #### Example {% #example-1 %}
``` ```
> .^(cors-registry:eyre %ex /=//=/cors) > .^(cors-registry:eyre %ex /=//=/cors)
@ -23,35 +26,48 @@ An `x` scry with a `path` of `/cors` will return Eyre's CORS origin registry. Th
] ]
``` ```
---
## `/cors/requests` ## `/cors/requests`
An `x` scry with a `path` of `/cors/requests` will return the `set` of pending origin requests. These are origins that were in an `Origin: ...` HTTP header but weren't in the existing approved or rejected sets. The type returned is a `(set origin:eyre)`. An `x` scry with a `path` of `/cors/requests` will return the `set` of pending
origin requests. These are origins that were in an `Origin: ...` HTTP header
but weren't in the existing approved or rejected sets. The type returned is a
`(set origin:eyre)`.
### Example {% #example-2 %} #### Example {% #example-2 %}
``` ```
> .^(requests=(set origin:eyre) %ex /=//=/cors/requests) > .^(requests=(set origin:eyre) %ex /=//=/cors/requests)
requests={~~http~3a.~2f.~2f.baz~.example} requests={~~http~3a.~2f.~2f.baz~.example}
``` ```
---
## `/cors/approved` ## `/cors/approved`
An `x` scry with a `path` of `/cors/approved` will return the `set` of approved CORS origins. The type returned is a `(set origin:eyre)`. An `x` scry with a `path` of `/cors/approved` will return the `set` of approved CORS origins. The type returned is a `(set origin:eyre)`.
### Example {% #example-3 %} #### Example {% #example-3 %}
``` ```
> .^(approved=(set origin:eyre) %ex /=//=/cors/approved) > .^(approved=(set origin:eyre) %ex /=//=/cors/approved)
approved={~~http~3a.~2f.~2f.foo~.example} approved={~~http~3a.~2f.~2f.foo~.example}
``` ```
## `/cors/approved/{ORIGIN}` ---
An `x` scry whose `path` is `/cors/approved/{ORIGIN}` tests whether the given origin URL is in the `approved` set of the CORS registry. The type returned is a simple `?`. ## `/cors/approved/[origin]`
The origin URL is a `@t`, but since `@t` may not be valid in a path, it must be encoded in a `@ta` using `+scot` like `(scot %t 'foo')` rather than just `'foo'`. An `x` scry whose `path` is `/cors/approved/[origin]` tests whether the given
origin URL is in the `approved` set of the CORS registry. The type returned is
a simple `?`.
### Examples {% #examples-1 %} The origin URL is a `@t`, but since `@t` may not be valid in a path, it must be
encoded in a `@ta` using `+scot` like `(scot %t 'foo')` rather than just
`'foo'`.
#### Examples {% #examples-1 %}
``` ```
> .^(? %ex /=//=/cors/approved/(scot %t 'http://foo.example')) > .^(? %ex /=//=/cors/approved/(scot %t 'http://foo.example'))
@ -63,24 +79,32 @@ The origin URL is a `@t`, but since `@t` may not be valid in a path, it must be
%.n %.n
``` ```
---
## `/cors/rejected` ## `/cors/rejected`
An `x` scry with a `path` of `/cors/rejected` will return the `set` of rejected CORS origins. The type returned is a `(set origin:eyre)`. An `x` scry with a `path` of `/cors/rejected` will return the `set` of rejected
CORS origins. The type returned is a `(set origin:eyre)`.
### Example {% #example-4 %} #### Example {% #example-4 %}
``` ```
> .^(rejected=(set origin:eyre) %ex /=//=/cors/rejected) > .^(rejected=(set origin:eyre) %ex /=//=/cors/rejected)
rejected={~~http~3a.~2f.~2f.bar~.example} rejected={~~http~3a.~2f.~2f.bar~.example}
``` ```
## `/cors/rejected/{ORIGIN}` ---
An `x` scry whose `path` is `/cors/rejected/{ORIGIN}` tests whether the given origin URL is in the `rejected` set of the CORS registry. The type returned is a simple `?`. ## `/cors/rejected/[origin]`
The origin URL must be a cord-encoded `@t` rather than just the plain `@t`, so you'll have to do something like `(scot %t 'foo')` rather than just `'foo'`. An `x` scry whose `path` is `/cors/rejected/[origin]` tests whether the given
origin URL is in the `rejected` set of the CORS registry. The type returned is
a simple `?`.
### Examples {% #examples-2 %} The origin URL must be a cord-encoded `@t` rather than just the plain `@t`, so
you'll have to do something like `(scot %t 'foo')` rather than just `'foo'`.
#### Examples {% #examples-2 %}
``` ```
> .^(? %ex /=//=/cors/rejected/(scot %t 'http://bar.example')) > .^(? %ex /=//=/cors/rejected/(scot %t 'http://bar.example'))
@ -92,13 +116,18 @@ The origin URL must be a cord-encoded `@t` rather than just the plain `@t`, so y
%.n %.n
``` ```
---
## `/authenticated/cookie` ## `/authenticated/cookie`
An `x` scry whose `path` is `/authenticated/cookie/{COOKIE}` tests whether the given cookie is currently valid. The type returned is a `?`. An `x` scry whose `path` is `/authenticated/cookie/[cookie]` tests whether the
given cookie is currently valid. The type returned is a `?`.
The cookie must be the full cookie including the `urbauth-{SHIP}=` part. The cookie must be a cord-encoded `@t` rather than just a plain `@t`, so you'll have to do something like `(scot %t 'foo')` rather than just `'foo'`. The cookie must be the full cookie including the `urbauth-{SHIP}=` part. The
cookie must be a knot-encoded `@t` rather than just a plain `@t`, so you'll
have to do something like `(scot %t 'foo')` rather than just `'foo'`.
### Examples {% #examples-3 %} #### Examples {% #examples-3 %}
``` ```
> .^(? %ex /=//=/authenticated/cookie/(scot %t 'urbauth-~zod=0vvndn8.bfsjj.j3614.k40ha.8fomi')) > .^(? %ex /=//=/authenticated/cookie/(scot %t 'urbauth-~zod=0vvndn8.bfsjj.j3614.k40ha.8fomi'))
@ -110,11 +139,27 @@ The cookie must be the full cookie including the `urbauth-{SHIP}=` part. The coo
%.n %.n
``` ```
---
## `/cache/[aeon]/[url]`
An `%x` `/cache` scry will return the cached value for the given `[url]` at the
given `[aeon]` if it exists.
The `[url]` must be a knot-encoded `@t` rather than just a plain `@t`, so
you'll have to do something like `(scot %t 'foo')` rather than just `'foo'`.
---
## `%bindings` ## `%bindings`
A scry with `bindings` in place of the `desk` in the `beak` will return Eyre's URL path bindings. The type returned is a `(list [binding:eyre duct action:eyre])` (see the [$binding](/reference/arvo/eyre/data-types#binding) & [$action](/reference/arvo/eyre/data-types#action) sections of the Data Types document for details). A scry with `bindings` in place of the `desk` in the `beak` will return Eyre's
URL path bindings. The type returned is a `(list [binding:eyre duct
action:eyre])` (see the [$binding](/reference/arvo/eyre/data-types#binding) &
[$action](/reference/arvo/eyre/data-types#action) sections of the Data Types
document for details).
### Example {% #example-5 %} #### Example {% #example-5 %}
``` ```
> .^((list [binding:eyre duct action:eyre]) %e /=bindings=) > .^((list [binding:eyre duct action:eyre]) %e /=bindings=)
@ -133,20 +178,29 @@ A scry with `bindings` in place of the `desk` in the `beak` will return Eyre's U
## `%connections` ## `%connections`
A scry with `bindings` in place of the `desk` in the `beak` will return all open HTTP connections that aren't fully complete. The type returned is a `(map duct outstanding-connection:eyre)` (see the [$outstanding-connection](/reference/arvo/eyre/data-types#outstanding-connection) section of the Data Types document for details). A scry with `connections` in place of the `desk` in the `beak` will return all
open HTTP connections that aren't fully complete. The type returned is a `(map
duct outstanding-connection:eyre)` (see the
[$outstanding-connection](/reference/arvo/eyre/data-types#outstanding-connection)
section of the Data Types document for details).
### Example {% #example-6 %} #### Example {% #example-6 %}
``` ```
> .^((map duct outstanding-connection:eyre) %e /=connections=) > .^((map duct outstanding-connection:eyre) %e /=connections=)
{} {}
``` ```
---
## `%authentication-state` ## `%authentication-state`
A scry with `authentication-state` in place of the `desk` in the `beak` will return authentication details of all current sessions. The type returned is a [$authentication-state](/reference/arvo/eyre/data-types#authentication-state). The `p` field is the cookie sans the `urbauth-{SHIP}=` part. A scry with `authentication-state` in place of the `desk` in the `beak` will
return authentication details of all current sessions. The type returned is a
[$authentication-state](/reference/arvo/eyre/data-types#authentication-state).
The `p` field is the cookie sans the `urbauth-[ship]=` part.
### Example {% #example-7 %} #### Example {% #example-7 %}
``` ```
> .^(authentication-state:eyre %e /=authentication-state=) > .^(authentication-state:eyre %e /=authentication-state=)
@ -157,11 +211,15 @@ A scry with `authentication-state` in place of the `desk` in the `beak` will ret
} }
``` ```
---
## `%channel-state` ## `%channel-state`
A scry with `channel-state` in place of the `desk` in the `beak` will return details of the state of each channel. The type returned is a [channel-state](/reference/arvo/eyre/data-types#channel-state). A scry with `channel-state` in place of the `desk` in the `beak` will return
details of the state of each channel. The type returned is a
[channel-state](/reference/arvo/eyre/data-types#channel-state).
### Example {% #example-8 %} #### Example {% #example-8 %}
``` ```
> .^(channel-state:eyre %e /=channel-state=) > .^(channel-state:eyre %e /=channel-state=)
@ -182,13 +240,18 @@ A scry with `channel-state` in place of the `desk` in the `beak` will return det
] ]
``` ```
---
## `%host` ## `%host`
A scry with `host` in place of the `desk` in the `beak` will return host details of the ship. The type returned is a `hart:eyre`. A scry with `host` in place of the `desk` in the `beak` will return host
details of the ship. The type returned is a `hart:eyre`.
### Example {% #example-9 %} #### Example {% #example-9 %}
``` ```
> .^(hart:eyre %e /=host=) > .^(hart:eyre %e /=host=)
[p=%.n q=[~ 8.080] r=[%.y p=<|localhost|>]] [p=%.n q=[~ 8.080] r=[%.y p=<|localhost|>]]
``` ```
---

View File

@ -23,6 +23,8 @@ The `insecure` field is the HTTP port and `secure` is the optional HTTPS port.
Eyre returns no `gift` in response to a `%live` `task`. Eyre returns no `gift` in response to a `%live` `task`.
---
## `%rule` ## `%rule`
```hoon ```hoon
@ -37,6 +39,8 @@ The [$http-rule](/reference/arvo/eyre/data-types#http-rule) is either tagged wit
Eyre returns no `gift` in response to a `%rule` `task`. Eyre returns no `gift` in response to a `%rule` `task`.
---
## `%request` ## `%request`
```hoon ```hoon
@ -51,6 +55,8 @@ The `secure` field says whether it's over HTTPS. The `address` is the IP address
Eyre may `pass` a `%response` `gift` on the appropriate `duct` depending on the contents of the `%request`, state of the connection, and other factors. Eyre may `pass` a `%response` `gift` on the appropriate `duct` depending on the contents of the `%request`, state of the connection, and other factors.
---
## `%request-local` ## `%request-local`
```hoon ```hoon
@ -63,6 +69,8 @@ This `task` is how Eyre receives an inbound HTTP request over the local loopback
Eyre may `pass` a `%response` `gift` on the appropriate `duct` depending on the contents of the `%request`, state of the connection, and other factors. Eyre may `pass` a `%response` `gift` on the appropriate `duct` depending on the contents of the `%request`, state of the connection, and other factors.
---
## `%cancel-request` ## `%cancel-request`
```hoon ```hoon
@ -77,6 +85,8 @@ This `task` takes no arguments.
Eyre may `pass` a `%response` `gift` on the appropriate `duct` depending on the state of the connection and other factors. Eyre may `pass` a `%response` `gift` on the appropriate `duct` depending on the state of the connection and other factors.
---
## `%connect` ## `%connect`
```hoon ```hoon
@ -111,6 +121,8 @@ The `accepted` field says whether the binding succeeded and the `binding` is the
See the [Agents: Direct HTTP](/reference/arvo/eyre/guide#agents-direct-http) section of the [Guide](/reference/arvo/eyre/guide) document for an example. See the [Agents: Direct HTTP](/reference/arvo/eyre/guide#agents-direct-http) section of the [Guide](/reference/arvo/eyre/guide) document for an example.
---
## `%serve` ## `%serve`
```hoon ```hoon
@ -147,6 +159,8 @@ Eyre will return a `%bound` `gift` as described at the end of the [%connect](#co
See the [Generators](/reference/arvo/eyre/guide#generators) section of the [Guide](/reference/arvo/eyre/guide) document for an example. See the [Generators](/reference/arvo/eyre/guide#generators) section of the [Guide](/reference/arvo/eyre/guide) document for an example.
---
## `%disconnect` ## `%disconnect`
```hoon ```hoon
@ -161,6 +175,8 @@ The [$binding](/reference/arvo/eyre/data-types#binding) is the URL path and doma
Eyre returns no `gift` in response to a `%disconnect` `task`. Eyre returns no `gift` in response to a `%disconnect` `task`.
---
## `%code-changed` ## `%code-changed`
```hoon ```hoon
@ -210,3 +226,47 @@ Eyre returns no `gift` in response to a `%reject-origin` `task`.
#### Example #### Example
See the [Managing CORS Origins](/reference/arvo/eyre/guide#managing-cors-origins) section of the [Guide](/reference/arvo/eyre/guide) document for an example. See the [Managing CORS Origins](/reference/arvo/eyre/guide#managing-cors-origins) section of the [Guide](/reference/arvo/eyre/guide) document for an example.
---
## `%set-response`
```hoon
[%set-response url=@t entry=(unit cache-entry)]
```
This `task` tells Eyre to set a cache entry for a URL path. Adding entries to
Eyre's cache will make them much faster to load, and more capable of handling
many connections.
The `url` field is the URL path you want to bind with the cache entry. Note this
will just be the URL path as a cord like `'/foo/bar/baz'`, it does not include
the host, etc.
The `entry` field is a
[`$cache-entry`](/reference/arvo/eyre/data-types#cache-entry) in a `unit`. If
the unit is null, the specified `url` will be unbound and the cache entry
removed. If non-null, the given `entry` will be added to the cache (or updated
if the binding already exists).
Each time the entry for a URL path is changed, its revision number will be
incremented.
See the [`$cache-entry`](/reference/arvo/eyre/data-types#cache-entry) entry in
Eyre's type reference for more details of the entry itself.
#### Returns
Eyre gives a `%grow` `gift` in response to a `%set-response` `task`. A `%grow`
`gift` looks like:
```hoon
[%grow =path]
```
The `path` will be of the format `/cache/[revision]/[url]`, for example
`/cache/12/~~~2f.foo~2f.bar`. The revision number is incremented each time the
entry is updated, including if it's removed, and is in `@ud` format. The url
element uses `%t` [`++scot`](/reference/hoon/stdlib/4m#scot) encoding, so will
need to be decoded with `%t` [`++slav`](/reference/hoon/stdlib/4m#slav).
---

View File

@ -4,3 +4,20 @@ weight = 70
sort_by = "weight" sort_by = "weight"
insert_anchor_links = "right" insert_anchor_links = "right"
+++ +++
## [Overview](/reference/arvo/gall/gall)
An overview of Eyre and its capabilities.
## [API Reference](/reference/arvo/gall/gall-api)
Reference documentation of both Gall's vane API and the API available to
agents.
## [Scry Reference](/reference/arvo/gall/scry)
Reference documentation of Gall scry endpoints.
## [Data Types](/reference/arvo/gall/data-types)
Reference documentation of Gall's types as defined in `lull.hoon`.

View File

@ -5,59 +5,79 @@ weight = 5
This document describes the data types for Gall defined in `lull.hoon`. This document describes the data types for Gall defined in `lull.hoon`.
## `bitt` ## `bitt`
Incoming subscriptions.
```hoon ```hoon
+$ bitt (map duct (pair ship path)) +$ bitt (map duct (pair ship path))
``` ```
Incoming subscriptions. This is the structure Gall uses to keep track of incoming subscriptions
for a Gall agent. The `sup` field of a [`bowl`](#bowl) contains the
`bitt` for our agent.
This is the structure Gall uses to keep track of subscribers to our Gall agent. ---
The `sup` field of a [`bowl`](#bowl) contains the `bitt` for our agent.
## `boat` ## `boat`
```hoon
+$ boat
%+ map [=wire =ship =term]
[acked=? =path]
```
Outgoing subscriptions. Outgoing subscriptions.
This is the structure Gall uses to keep track of subscriptions our agent has ```hoon
initiated. The `wex` field of a [`bowl`](#bowl) contails the `boat` for that +$ boat (map [=wire =ship =term] [acked=? =path])
agent. ```
The `wire` field is the `wire` which [`sign:agent`](#signagent)s will come in on. This is the structure Gall uses to keep track of subscriptions our agent
The `ship` and `term` fields are the ship and the name of the agent to which our has initiated. The `wex` field of a [`bowl`](#bowl) contails the `boat`
agent has subscribed. for that agent.
The `acked` field is `%.y` if they have acknowledged our subscription request, The `wire` field is the `wire` which [`sign:agent`](#signagent)s will
and `%.n` if they have not. The `path` field is the `path` on the other agent to come in on. The `ship` and `term` fields are the ship and the name of
which our agent has subscribed. the agent to which our agent has subscribed.
The `acked` field is `%.y` if they have acknowledged our subscription
request with a `%watch-ack`, and `%.n` if they have not. The `path`
field is the `path` on the other agent to which our agent has
subscribed.
---
## `boar`
Subscription nonces.
```hoon
+$ boar (map [=wire =ship =term] nonce=@)
```
Gall uses this to keep track of nonces for subscriptions.
---
## `bowl` ## `bowl`
Additional agent state.
```hoon ```hoon
+$ bowl :: standard app state +$ bowl :: standard app state
$: $: our=ship :: host $: $: our=ship :: host
src=ship :: guest src=ship :: guest
dap=term :: agent dap=term :: agent
== :: == ::
$: wex=boat :: outgoing subs $: wex=boat :: outgoing subs
sup=bitt :: incoming subs sup=bitt :: incoming subs
== :: $= sky :: scry bindings
$: act=@ud :: change number %+ map path ::
eny=@uvJ :: entropy ((mop @ud (pair @da (each page @uvI))) lte) ::
now=@da :: current time == ::
byk=beak :: load source $: act=@ud :: change number
== == :: eny=@uvJ :: entropy
now=@da :: current time
byk=beak :: load source
== == ::
``` ```
Additional agent state.
A `bowl` is given to the agent core each time an event comes in. The fields are A `bowl` is given to the agent core each time an event comes in. The fields are
as follows: as follows:
@ -68,6 +88,9 @@ as follows:
See the [`boat`](#boat) section for details of the type. See the [`boat`](#boat) section for details of the type.
- `sup`: Incoming subscriptions. That is, subscriptions others have made to our - `sup`: Incoming subscriptions. That is, subscriptions others have made to our
agent. See the [`bitt`](#bitt) section for details of the type. agent. See the [`bitt`](#bitt) section for details of the type.
- `sky`: Remote scry bindings. A map from binding paths to a
[`mop`](/reference/hoon/zuse/2m#mop) (ordered map) of files by revision
number. Tombstoned files have an `@uvI` hash rather than `page`.
- `act`: The total number of [`move`](/reference/arvo/overview#move)s our agent has - `act`: The total number of [`move`](/reference/arvo/overview#move)s our agent has
processed so far. processed so far.
- `eny`: 512 bits of entropy. - `eny`: 512 bits of entropy.
@ -76,24 +99,49 @@ as follows:
`case` will be `[%da @da]` where the `@da` is the when the agent was loaded. A `case` will be `[%da @da]` where the `@da` is the when the agent was loaded. A
`beak` is a triple of `[ship desk case]`. `beak` is a triple of `[ship desk case]`.
---
## `dude` ## `dude`
Agent name.
```hoon ```hoon
+$ dude term +$ dude term
``` ```
Agent name. ---
## `gill` ## `gill`
A general contact.
```hoon ```hoon
+$ gill (pair ship term) +$ gill (pair ship term)
``` ```
A general contact: A pair of the ship and agent name. A pair of the ship and agent name.
---
## `load`
Loadout.
```hoon
+$ load (list [=dude =beak =agent])
```
The [`dude`](#dude) is the agent name, the `beak` is the ship/desk/case
in which it resides, and the [`agent`](#agent) is the built agent
itself. Clay passes this to Gall when it builds or modifies the state of
running agents.
---
## `scar` ## `scar`
Opaque duct - used internally.
```hoon ```hoon
+$ scar +$ scar
$: p=@ud $: p=@ud
@ -102,40 +150,30 @@ A general contact: A pair of the ship and agent name.
== ==
``` ```
Opaque duct - used internally. ---
## `suss` ## `suss`
Configuration report.
```hoon ```hoon
+$ suss (trel dude @tas @da) +$ suss (trel dude @tas @da)
``` ```
Configuration report. ---
## `well` ## `well`
Desk and agent.
```hoon ```hoon
+$ well (pair desk term) +$ well (pair desk term)
``` ```
Desk and agent.
## `neat`
```hoon
+$ neat
$% [%arvo =note-arvo]
[%agent [=ship name=term] =deal]
[%pyre =tang]
==
```
Like a [`note:agent`](#noteagent), except the `%agent` case has a [`deal`](#deal) instead
of just a [`task:agent`](#taskagent). This is used for messages that come in over the
network and would not be used manually.
## `deal` ## `deal`
An agent task or raw poke.
```hoon ```hoon
+$ deal +$ deal
$% [%raw-poke =mark =noun] $% [%raw-poke =mark =noun]
@ -143,12 +181,17 @@ network and would not be used manually.
== ==
``` ```
Like a [`task:agent`](#taskagent) but with the additional case of a raw poke. The additional `%raw-poke` is for pokes which haven't yet been converted
This is used for messages that come in over the network and would not be used to an ordinary `%poke` by molding the `noun` with the specified `mark`
manually. core. This structure is passed around on the kernel level, it would not
be used in userspace.
---
## `unto` ## `unto`
An agent gift or a raw fact.
```hoon ```hoon
+$ unto +$ unto
$% [%raw-fact =mark =noun] $% [%raw-fact =mark =noun]
@ -156,9 +199,24 @@ manually.
== ==
``` ```
Like a [`sign:agent`](#signagent) but with the additional case of a raw fact. The additional `%raw-fact` is for facts which haven't yet been converted
This is used for messages that come in over the network and would not be used to an ordinary `%fact` by molding the `noun` it with the specified
manually. `mark` core. This structure is passed around on the kernel level, it
would not be used in userspace.
---
## `verb`
Verbosity flags.
```hoon
+$ verb ?(%odd)
```
Flags to set Gall verbosity. Currently only `%odd` for unusual errors.
---
## `agent` ## `agent`
@ -169,10 +227,10 @@ manually.
``` ```
Container for Gall agent types. The most significant arm is Container for Gall agent types. The most significant arm is
[`form:agent`](#formagent), which specifies the structure of the agent itself. [`form:agent`](#formagent), which specifies the structure of the agent
There are also some additional structures defined here, mostly defining the itself. There are also some additional structures defined here, mostly
kinds of messages agents can send. The different arms of the core in `agent` defining the kinds of messages agents can send. The different arms of
are considered separately below. the core in `agent` are considered separately below.
### `step:agent` ### `step:agent`
@ -180,8 +238,12 @@ are considered separately below.
+$ step (quip card form) +$ step (quip card form)
``` ```
A cell of [`card:agent`](#cardagent)s to be sent and a new agent state. This is the A cell of [`card:agent`](#cardagent)s to be sent and a new agent state.
type returned by most arms of an agent. A `(quip a b)` is the same as `[(list a) b]`, it's just a more convenient way to specify it. This is the type returned by most arms of an agent. A `(quip a b)` is
the same as `[(list a) b]`, it's just a more convenient way to specify
it.
---
### `card:agent` ### `card:agent`
@ -189,9 +251,9 @@ type returned by most arms of an agent. A `(quip a b)` is the same as `[(list a)
+$ card (wind note gift) +$ card (wind note gift)
``` ```
An effect - typically a message to be sent to another agent or vane. A list of An effect - typically a message to be sent to another agent or vane. A
these are returned by most agent arms along with a new state in a list of these are returned by most agent arms along with a new state in
[`step:agent`](#stepagent). A `wind` is the following: a [`step:agent`](#stepagent). A `wind` is the following:
```hoon ```hoon
++ wind ++ wind
@ -207,9 +269,11 @@ Gall will not allow a `%slip`, so in practice a `card` will be one of:
- `[%pass path note]` - `[%pass path note]`
- `[%give gift]` - `[%give gift]`
For `%pass`, `p` specifies the `wire` on which a response should be returned. For `%pass`, `p` specifies the `wire` on which a response should be
See [`note:agent`](#noteagent) and [`gift:agent`](#giftagent) below for details of their returned. See [`note:agent`](#noteagent) and [`gift:agent`](#giftagent)
types. below for details of their types.
---
### `note:agent` ### `note:agent`
@ -218,17 +282,23 @@ types.
$% [%agent [=ship name=term] =task] $% [%agent [=ship name=term] =task]
[%arvo note-arvo] [%arvo note-arvo]
[%pyre =tang] [%pyre =tang]
::
[%grow =spur =page]
[%tomb =case =spur]
[%cull =case =spur]
== ==
``` ```
The type for messages initiated by our agent. This is opposed to The type for messages initiated by our agent. This is opposed to
[`gift:agent`](#giftagent), which is the type for responding to other agents or [`gift:agent`](#giftagent), which is the type for responding to other
vanes, or for sending out updates to subscribers. The three cases are: agents or vanes, or for sending out updates to subscribers. The three
cases are:
- `%agent`: Poke another agent, subscribe to another agent, or cancel a - `%agent`: Poke another agent, subscribe to another agent, or cancel a
subscription to another agent. The `ship` and `name` fields are the ship and subscription to another agent. The `ship` and `name` fields are the
agent to which the `task` should be sent. The `task` is the request itself, ship and agent to which the `task` should be sent. The `task` is the
see [`task:agent`](#taskagent) below for its possible types. request itself, see [`task:agent`](#taskagent) below for its possible
types.
- `%arvo`: Pass a `task` to a vane. The type of a `note-arvo` is: - `%arvo`: Pass a `task` to a vane. The type of a `note-arvo` is:
```hoon ```hoon
+$ note-arvo +$ note-arvo
@ -246,16 +316,23 @@ vanes, or for sending out updates to subscribers. The three cases are:
[@tas %meta vase] [@tas %meta vase]
== ==
``` ```
You can refer to the `/sys/lull.hoon` source code for all the possible vane You can refer to the `/sys/lull.hoon` source code for all the possible
tasks, or see each vane's API Reference section in the [Arvo vane tasks, or see each vane's API Reference section in the [Arvo
documentation](/reference/arvo/overview) documentation](/reference/arvo/overview)
- `%pyre`: This is for aborting side-effects initiated during agent - `%pyre`: This is for aborting side-effects initiated during agent
installation. The `tang` is an error message. installation. The `tang` is an error message.
- `%grow`/`%tomb`/`%cull`: These are used for publishing and managing
data available for remote scries. For more information, see the
[remote scries guide](/guides/additional/remote-scry).
A `note:agent` is always wrapped in a `%pass` [`card:agent`](#cardagent). A `note:agent` is always wrapped in a `%pass` [`card:agent`](#cardagent).
---
### `task:agent` ### `task:agent`
The types of messages initiated by our agent and sent to another agent.
```hoon ```hoon
+$ task +$ task
$% [%watch =path] $% [%watch =path]
@ -266,10 +343,9 @@ A `note:agent` is always wrapped in a `%pass` [`card:agent`](#cardagent).
== ==
``` ```
The types of messages initiated by our agent and sent to another agent. This is This is in contrast to [`gift:agent`](#giftagent)s, which are responses
in contrast to [`gift:agent`](#giftagent)s, which are responses to incoming to incoming messages from agents or updates to agents already
messages from agents or updates to agents already subscribed. The five kinds subscribed. The five kinds of `task:agent` are:
are:
- `%watch`: Subscribe to `path` on the target ship and agent. - `%watch`: Subscribe to `path` on the target ship and agent.
- `%watch-as`: Same as `%watch`, except you ask the target's Gall to convert - `%watch-as`: Same as `%watch`, except you ask the target's Gall to convert
@ -285,8 +361,13 @@ are:
A `task:agent` is always wrapped in a `%pass` [`card:agent`](#cardagent). A `task:agent` is always wrapped in a `%pass` [`card:agent`](#cardagent).
---
### `gift:agent` ### `gift:agent`
The types of messages our agent can either send in response to messages
from other agents, or send to subscribed agents.
```hoon ```hoon
+$ gift +$ gift
$% [%fact paths=(list path) =cage] $% [%fact paths=(list path) =cage]
@ -296,37 +377,46 @@ A `task:agent` is always wrapped in a `%pass` [`card:agent`](#cardagent).
== ==
``` ```
The types of messages our agent can either send in response to messages from This is in contrast to [`task:agent`](#taskagent)s, which are messages
other agents, or send to subscribed agents. This is in contrast to to other agents our agent initiates rather than sends in response. The
[`task:agent`](#taskagent)s, which are messages to other agents our agent four kinds of `gift:agent` are:
initiates rather than sends in response. The four kinds are:
- `%fact`: An update to existing subscribers. The `paths` field specifies which - `%fact`: An update to existing subscribers. The `paths` field
subscription paths the update should go out to. The `cage` is the data, and is specifies which subscription paths the update should go out to. The
a `[mark vase]`. `cage` is the data, and is a `[mark vase]`.
- `%kick`: Kick subscriber, ending their subscription. The `paths` field - `%kick`: Kick subscriber, ending their subscription. The `paths` field
specifies which paths the subscriber should be kicked from, and the `ship` specifies which paths the subscriber should be kicked from, and the
field specifies the ship to kick. If the `ship` field is null, all subscribers `ship` field specifies the ship to kick. If the `ship` field is null,
on the specified paths are kicked. Gall will automatically remove the all subscribers on the specified paths are kicked. Gall will
subscription from our agent's [`bitt`](#bitt) (inbound subscription `map`), automatically remove the subscription from our agent's [`bitt`](#bitt)
and subscriber will no longer receive updates on the `path`s in question. (inbound subscription `map`), and subscriber will no longer receive
- `%watch-ack`: Acknowledge a subscription request. If `p` is null, it's an ack updates on the `path`s in question.
(positive acknowledgement), and if `p` is non-null, it's a nack (negative - `%watch-ack`: Acknowledge a subscription request. If `p` is null, it's
acknowledgement). Simply crashing will caused Gall to nack a subscription an ack (positive acknowledgement), and if `p` is non-null, it's a nack
request, and not crashing but not explicitly producing a `%watch-ack` `gift` (negative acknowledgement). Simply crashing will caused Gall to nack a
will cause Gall to ack a subscription request. Therefore, you'd typically only subscription request, and not crashing but not explicitly producing a
explicitly produce a `%watch-ack` `gift` if you wanted to nack a subscription `%watch-ack` `gift` will cause Gall to ack a subscription request.
request with a custom error in the `tang`. Therefore, you'd typically only explicitly produce a `%watch-ack`
- `%poke-ack`: Acknowledge a poke. If `p` is null, it's an ack, and if `p` is `gift` if you wanted to nack a subscription request with a custom
non-null, it's a nack. Simply crashing will cause Gall to nack a poke, and not error in the `tang`.
crashing but not explicitly producing a `%poke-ack` `gift` will cause Gall to - `%poke-ack`: Acknowledge a poke. If `p` is null, it's an ack, and if
ack a poke. Therefore, you'd typically only explicitly produce a `%poke-ack` `p` is non-null, it's a nack. Simply crashing will cause Gall to nack
`gift` if you wanted to nack a poke with a custom error in the `tang`. a poke, and not crashing but not explicitly producing a `%poke-ack`
`gift` will cause Gall to ack a poke. Therefore, you'd typically only
explicitly produce a `%poke-ack` `gift` if you wanted to nack a poke
with a custom error in the `tang`.
A `gift:agent` is always wrapped in a `%give` [`card:agent`](#cardagent). A `gift:agent` is always wrapped in a `%give`
[`card:agent`](#cardagent).
---
### `sign:agent` ### `sign:agent`
A `sign` is like a [`gift:agent`](#giftagent) but it's something that
comes _in_ to our agent from another agent rather than something we send
out.
```hoon ```hoon
+$ sign +$ sign
$% [%poke-ack p=(unit tang)] $% [%poke-ack p=(unit tang)]
@ -336,31 +426,34 @@ A `gift:agent` is always wrapped in a `%give` [`card:agent`](#cardagent).
== ==
``` ```
A `sign` is like a [`gift:agent`](#giftagent) but it's something that comes The possible types are:
_in_ to our agent from another agent rather than something we send out. The
possible types are:
- `%poke-ack`: Another agent has acked (positively acknowledged) or nacked - `%poke-ack`: Another agent has acked (positively acknowledged) or
(negatively acknowledged) a `%poke` [`task:agent`](#taskagent) we previously nacked (negatively acknowledged) a `%poke` [`task:agent`](#taskagent)
sent. It's an ack if `p` is null and a nack if `p` is non-null. The `tang` we previously sent. It's an ack if `p` is null and a nack if `p` is
contains an error or traceback if it's a nack. non-null. The `tang` contains an error or traceback if it's a nack.
- `%watch-ack`: Another agent has acked or nacked a `%watch` - `%watch-ack`: Another agent has acked or nacked a `%watch`
[`task:agent`](#taskagent) (subscription request) we previously sent. It's an [`task:agent`](#taskagent) (subscription request) we previously sent.
ack if `p` is null and a nack if `p` is non-null. The `tang` contains an error It's an ack if `p` is null and a nack if `p` is non-null. The `tang`
or traceback if it's a nack. If it's a nack, Gall will automatically remove contains an error or traceback if it's a nack. If it's a nack, Gall
the subscription from our agent's [`boat`](#boat) (outbound subscription will automatically remove the subscription from our agent's
map). [`boat`](#boat) (outbound subscription map).
- `%fact`: An update from another agent to which we've previously subscribed - `%fact`: An update from another agent to which we've previously
with a `%watch` [`task:agent`](#taskagent) (subscription request). The `cage` subscribed with a `%watch` [`task:agent`](#taskagent) (subscription
contains the data, and is a `[mark vase]`. request). The `cage` contains the data, and is a `[mark vase]`.
- `%kick`: Our subscription to another agent has been ended, and we'll no longer - `%kick`: Our subscription to another agent has been ended, and we'll
receive updates. A `%kick` may be intentional, but it may also happen due to no longer receive updates. A `%kick` may be intentional, but it may
certain network conditions or other factors. As a result, it's best to try and also happen due to certain network conditions or other factors. As a
resubscribe with another `%watch` [`task:agent`](#taskagent), and if they result, it's best to try and resubscribe with another `%watch`
nack the `%watch`, we can conclude it was intentional and give up. [`task:agent`](#taskagent), and if they nack the `%watch`, we can
conclude it was intentional and give up.
---
### `form:agent` ### `form:agent`
This defines the structure of the agent itself.
```hoon ```hoon
++ form ++ form
$_ ^| $_ ^|
@ -406,7 +499,8 @@ possible types are:
-- --
``` ```
This defines the structure of the agent itself. The agent is a door with a [`bowl`](#bowl) as its sample and exactly ten arms. Below we'll describe each arm briefly. The agent is a door with a [`bowl`](#bowl) as its sample and exactly ten
arms. Below we'll describe each arm briefly.
#### `on-init` #### `on-init`
@ -434,16 +528,16 @@ This arm is called when another agent subscribes to our agent.
- Accepts: `path` - Accepts: `path`
- Produces: [`step:agent`](#stepagent) - Produces: [`step:agent`](#stepagent)
This arm is called when another agent unsubscribes from a subscription path on This arm is called when another agent unsubscribes from a
our agent. subscription path on our agent.
#### `on-peek` #### `on-peek`
- Accepts: `path` - Accepts: `path`
- Produces: `(unit (unit cage))` - Produces: `(unit (unit cage))`
This arm is called when a [scry](/reference/arvo/concepts/scry) is performed on our This arm is called when a [scry](/reference/arvo/concepts/scry) is
agent. performed on our agent.
#### `on-agent` #### `on-agent`
@ -458,7 +552,8 @@ This arm is called when another agent give our agent a
- Accepts: `[wire sign-arvo]` - Accepts: `[wire sign-arvo]`
- Produces: [`step:agent`](#stepagent) - Produces: [`step:agent`](#stepagent)
This arm is called when a vane gives our agent a `gift`. A `sign-arvo` is: This arm is called when a vane gives our agent a `gift`. A `sign-arvo`
is:
```hoon ```hoon
+$ sign-arvo +$ sign-arvo
@ -481,13 +576,15 @@ This arm is called when a vane gives our agent a `gift`. A `sign-arvo` is:
== ==
``` ```
You can refer to the `/sys/lull.hoon` source code, or the API Reference of each You can refer to the `/sys/lull.hoon` source code, or the API Reference
vane in the [Arvo documentation](/reference/arvo/overview). of each vane in the [Arvo documentation](/reference/arvo/overview).
#### `on-fail` #### `on-fail`
- Accepts: `[term tang]` - Accepts: `[term tang]`
- Produces: [`step:agent`](#stepagent) - Produces: [`step:agent`](#stepagent)
This arm is called if certain errors occur in Gall, such as if our agent tries This arm is called if certain errors occur in Gall, such as if our agent
to create a duplicate subscription. tries to create a duplicate subscription.
---

File diff suppressed because it is too large Load Diff

View File

@ -59,10 +59,11 @@ definable in a regular recursive data type).
## Specification ## Specification
An agent is defined as a [core](/reference/glossary/core/) with a set of [arms](/reference/glossary/arm/) to handle various An agent is defined as a [core](/reference/glossary/core/) with a set of
events. These handlers usually produce a list of effects and the next [arms](/reference/glossary/arm/) to handle various events. These
state of the agent. The interface definition can be found in handlers usually produce a list of effects and the next state of the
`sys/lull.hoon`, which at the time of writing is: agent. The interface definition can be found in `sys/lull.hoon`, which
at the time of writing is:
```hoon ```hoon
++ agent ++ agent
@ -71,8 +72,13 @@ state of the agent. The interface definition can be found in
+$ step (quip card form) +$ step (quip card form)
+$ card (wind note gift) +$ card (wind note gift)
+$ note +$ note
$% [%arvo =note-arvo] $% [%agent [=ship name=term] =task]
[%agent [=ship name=term] =task] [%arvo note-arvo]
[%pyre =tang]
::
[%grow =spur =page]
[%tomb =case =spur]
[%cull =case =spur]
== ==
+$ task +$ task
$% [%watch =path] $% [%watch =path]
@ -82,8 +88,8 @@ state of the agent. The interface definition can be found in
[%poke-as =mark =cage] [%poke-as =mark =cage]
== ==
+$ gift +$ gift
$% [%fact path=(unit path) =cage] $% [%fact paths=(list path) =cage]
[%kick path=(unit path) ship=(unit ship)] [%kick paths=(list path) ship=(unit ship)]
[%watch-ack p=(unit tang)] [%watch-ack p=(unit tang)]
[%poke-ack p=(unit tang)] [%poke-ack p=(unit tang)]
== ==
@ -134,6 +140,7 @@ state of the agent. The interface definition can be found in
|~ [term tang] |~ [term tang]
*(quip card _^|(..on-init)) *(quip card _^|(..on-init))
-- --
--
``` ```
Here's a skeleton example of an implementation: Here's a skeleton example of an implementation:

View File

@ -0,0 +1,252 @@
+++
title = "Scry Reference"
weight = 4
+++
Gall's scry interface is mainly used to scry into the individual agents it's
running. The vane itself does have its own interface, however. Both [agent
scries](#agent-scries) and [vane scries](#vane-scries) are documented below.
Note that for all agent scries and most vane scries, `q.beak`, where there'd
usually be a `desk`, will be the agent name instead, like:
```
.^(some-type %gx /=agent-name-here=/some/path/noun)
```
{% callout %}
**Important:** Vane scries are differentiated from agent scries by an extra
empty (`%$`) element at the beginning of the `spur` (the path after the
`beak`), like: `/=agent-name/$` or `/=agent-name=//some/more/fields`. Without
that empty element, Gall will try route the scry to an agent instead.
{% /callout %}
## Agent scries
In order to hit the `+on-peek` arm of a Gall agent, you need to:
1. Put the agent in `q.beak` of the scry path (where the `desk` usually goes), like `/=some-agent=`.
2. Make sure the beginning of the `spur` is *not* an empty `%$` element, as
that will route the scry to the [vane endpoints](#vane-scries) instead. An
agent scry must be `/=some-agent=/some/path` not `/=some-agent=//some/path`.
Any `care` can be used (dependent on what the agent accepts, of course). The
most common is `%x`.
{% callout %}
Note that `%x` cares alone must include an extra `mark` field at the end of the
`spur`. This mark field lets Gall perform any necessary mark conversions before
returning your data. For plain unmarked data, you can just use the `%noun`
mark. As an example, if an agent specifies an endpoint `/x/some/path` and just
returns ordinary data, you'd do `.^(some-type %gx
/=some-agent=/some/path/noun)`. If the endpoint returns a `%json` mark or
whatever (and that's what you want), you'd put that at the end instead.
{% /callout %}
---
## Vane scries
Gall itself provides the special vane-level endpoints listed below. They are
organized by the `care`. In order to hit the vane-level endpoints, the
beginning of the the `spur` (e.g. the `path` after the `beak`) *must* be a `%$`
empty element. For example:
```hoon
.^(desk %gd /=acme=/$)
.^((set [=dude:gall live=?]) %ge /=base=/$)
.^((list path) %gt /=acme=//foo)
```
Note you can use `$` to make the last element empty since it won't allow a
trailing `/`. Note how in the third example, the empty element is at the
*beginning* of the `spur` and *after* the `beak`. If you fail to include this
empty element, Gall will try route the scry to an agent for handling instead.
### `%d`: get desk of app
A scry with a `%d` care and an agent in `q.beak` will
give you the desk that agent is on.
#### Produces
A `desk`
#### Example
```
> .^(desk %gd /=acme=/$)
%base
```
---
### `%e`: running apps
A scry with an `%e` care will give you the agents on the desk given in
`q.beak`.
#### Produces
A `(set [=dude live=?])`, where `live` is true if running and false if not.
#### Examples
```
> .^((set [=dude:gall live=?]) %ge /=base=/$)
{ [dude=%acme live=%.y]
[dude=%hood live=%.y]
[dude=%lens live=%.y]
[dude=%dbug live=%.y]
[dude=%azimuth live=%.y]
[dude=%ping live=%.y]
[dude=%dojo live=%.y]
[dude=%eth-watcher live=%.y]
[dude=%spider live=%.y]
[dude=%herm live=%.y]
}
```
---
### `%f`: nonces of apps
A scry with a care of `%f` and anything in `q.beak` will produce the
subscription nonces of all apps. You are unlikely to use this, it's
mostly for kernel debugging.
#### Produces
A `(map dude @)` where the `@` is the nonce.
#### Examples
```
> .^((map dude:gall @) %gf /=//=/$)
[ n=[p=%treaty q=2]
l
[ n=[p=%metadata-hook q=1]
l={[p=%contacts q=1] [p=%notify q=2] [p=%groups q=1] [p=%dm-hook q=1] [p=%spider q=1]}
r
{ [p=%docket q=9]
[p=%bait q=1]
[p=%hood q=15]
[p=%hark-graph-hook q=2]
[p=%s3-store q=1]
[p=%hark-system-hook q=1]
......(truncated for brevity)......
```
---
### `%n`: get nonce of subscription
A scry with a care of `%n`, an agent in `q.beak` and a path of
`//[ship]/[agent]/[wire]` will produce the nonce for that subscription.
You are unlikely to use this, it's mostly for kernel debugging.
#### Produces
A `@`.
---
### `%t`: remote scry subpaths
A scry with a `%t` care, an agent in `q.beak` and a path of `//some/path`
will give you the list of remote scry subpaths bound under the given
path.
See the [remote scry guide](/guides/additional/remote-scry) for more
details.
#### Produces
A `(list path)`
#### Examples
```
> .^((list path) %gt /=acme=//foo)
~
```
---
### `%u`: check if installed
A scry with a `%u` care will check whether the given agent is installed and
running.
#### Produces
A `?`
#### Examples
```
> .^(? %gu /=acme=/$)
%.y
```
```
> .^(? %gu /=doesnt-exist=/$)
%.n
```
---
### `%w`: latest revision of path
A scry with a `%w` care and an agent in `q.beak` will get the latest revision
number of the bound remote scry path given in the `spur`.
See the [remote scry guide](/guides/additional/remote-scry) for more
details.
#### Produces
A `cass:clay`, specifically the `%ud` kind.
---
### `%x`: remote scry file
A scry with a `%x` care and an agent in `q.beak` will get the value
bound at the remote scry path given in the `spur`. The revision of the
file must be given in the `beak` portion of the scry path. The general
format is therefore:
```hoon
[%gx /[ship]/[agent]/[file revision]//[file path]
```
See the [remote scry guide](/guides/additional/remote-scry) for more
details.
#### Produces
The type returned is an `(each page @uvI)`. If the file has been
tombstoned, it'll be the `@uvI` hash of the file. Otherwise, it will be
the data as a `page` (a pair of `mark` and `noun`).
---
### `%z`: hash of value at path
A scry with a `%z` care and an agent in `q.beak` will get the hash identifier
of the value bound at the remote scry path given in the `spur`.
See the [remote scry guide](/guides/additional/remote-scry) for more
details.
#### Produces
A `@uvI`.
---

View File

@ -1,86 +0,0 @@
+++
title = "Threads Reference"
weight = 50
+++
## Start thread
Poke `spider` with mark `%spider-start` and a vase containing `start-args`:
```hoon
+$ start-args
[parent=(unit tid) use=(unit tid) =beak file=term =vase]
```
Where:
- `parent` - optional `tid` of parent thread if the thread is a child. If specified, the child thread will be killed with the parent thread ends.
- `use` - `tid` (thread ID) to give the new thread. Can be generated with something like `(scot %ta (cat 3 'my-agent_' (scot %uv (sham eny))))`. However you do it, make sure it's unique.
- `beak` - A `$beak` is a triple of `[p=ship q=desk r=case]`. `p` is always our ship, `q` is the desk which contains the thread we want to run. `r` is a `case`, which specifies a desk revision and is a tagged union of:
```hoon
+$ case
$% [%da p=@da] :: date
[%tas p=@tas] :: label
[%ud p=@ud] :: number
==
```
You'll almost always just want the current revision, so you can specify the `case` as `da+now.bowl`. If the thread is on the same desk as the agent you can also just use `byk.bowl(r da+now)` for the `beak`.
- `file` - name of the thread file in `/ted`. For example, if the thread you want to start is `/ted/foo/hoon` you'd specify `%foo`.
- `vase` - `vase` to be given to the thread when it's started. Can be whatever or just `!>(~)` if it doesn't need any args.
#### Example
```hoon
[%pass /some-path %agent [our.bowl %spider] %poke %spider-start !>([~ `tid byk.bowl %foo !>(~)])]
```
## Stop thread
Poke `spider` with mark `%spider-stop` and a vase containing `[tid ?]`, where:
- `tid` - the `tid` of the thread you want to stop
- `?` - whether thread should end nicely. If `%.y` it'll end with mark `%thread-done` and the bunt value of a vase. If `%.n` it'll end with mark `%thread-fail` and a `[term tang]` where `term` is `%cancelled` and `tang` is `~`.
#### Example
```hoon
[%pass /some-path %agent [our.bowl %spider] %poke %spider-stop !>([tid %.y)]
```
## Subscribe for result
Spider will send the result on `/thread-result/[tid]` so you can subscribe there for the result. You should subscribe before starting the thread.
The result will have a mark of either `%thread-fail` or `%thread-done`.
- `%thread-fail` - has a vase containing a `[term tang]` where `term` is an error message and `tang` is a traceback.
- `%thread-done` - has a vase of the result of the thread.
#### Example
```hoon
[%pass /some-path %agent [our.bowl %spider] %watch /thread-result/[tid]]
```
## Subscribe to thread
You can subscribe to a thread on `/thread/[tid]/path`. Note this is for facts sent off by the thread while it's running, not the final result. The path depends on the particular thread.
#### Example
```hoon
[%pass /some-path %agent [our.bowl %spider] %watch /thread/[tid]/thread-path]
```
## Poke thread
To poke a thread you poke spider with a mark of `%spider-input` and a vase of `[tid cage]`.
- `tid` is the tid of the thread you want to poke
- `cage` is whatever mark and vase you want to poke it with
#### Example
```hoon
[%pass /some-path %agent [our.bowl %spider] %poke %spider-input !>([tid %foo !>('foooo')])]
```

View File

@ -27,6 +27,27 @@ A `%code` scry gets the current web login code. It takes your ship name as its `
~lidlut-tabwed-pillex-ridrup ~lidlut-tabwed-pillex-ridrup
``` ```
## `%fake`
A `%fake` scry checks whether the current ship is fake (a development ship
booted with the `-F` option). The type returned is a `?`.
#### Example
On a fake `~zod`:
```
> .^(? %j /=fake=)
%.y
```
On a real planet:
```
> .^(? %j /=fake=)
%.n
```
## `%life` ## `%life`
A `%life` scry gets the current `life` (key revision number) of a ship if known, otherwise it crashes. It takes a ship as its `path` and the type returned is a `@ud`. A `%life` scry gets the current `life` (key revision number) of a ship if known, otherwise it crashes. It takes a ship as its `path` and the type returned is a `@ud`.

View File

@ -3,222 +3,451 @@ title = "API Reference"
weight = 2 weight = 2
+++ +++
This document details all the `task`s you may wish to send Jael, as well as the `gift`s you'll receive in response. This document details all the tasks you may wish to send Jael, as well
as the gifts you'll receive in response.
You may also wish to reference the [Data Types](/reference/arvo/jael/data-types) document for details of the types referenced here, and the [Examples](/reference/arvo/jael/examples) document for practical examples of using these `task`s. You may also wish to reference the [Data
Types](/reference/arvo/jael/data-types) document for details of the
types referenced here, and the [Examples](/reference/arvo/jael/examples)
document for practical examples of using these tasks.
## `%dawn` ## Tasks
### `%dawn`
Boot from keys.
```hoon ```hoon
[%dawn dawn-event] [%dawn dawn-event]
``` ```
Boot from keys. This task is called once per ship during the vane initialization phase
immediately following the beginning of the [adult
stage](/reference/arvo/overview#structural-interface-core). This task
is `%pass`ed to Jael by Dill, as Dill is the first vane to be loaded for
technical reasons, though we consider Jael to be the true "first" vane.
This task is only used for ships that will join the Ames network -
fake ships (i.e. made with `./urbit -F zod`) use the [%fake](#fake)
task instead.
This `task` is called once per ship during the vane initialization phase immediately following the beginning of the [adult stage](/reference/arvo/overview#structural-interface-core). This `task` is `%pass`ed to Jael by Dill, as Dill is the first vane to be loaded for technical reasons, though we consider Jael to be the true "first" vane. This `task` is only used for ships that will join the Ames network - fake ships (i.e. made with `./urbit -F zod`) use the [%fake](#fake) `task` instead. `%dawn` is used to perform a sequence of initialization tasks related to
saving information about Azimuth and the Ames network and booting other
`%dawn` is used to perform a sequence of initialization tasks related to saving information about Azimuth and the Ames network and booting other vanes for the first time. Upon receipt of a `%dawn` `task`, Jael will: vanes for the first time. Upon receipt of a `%dawn` task, Jael will:
- record the Ethereum block the public key is registered to, - record the Ethereum block the public key is registered to,
- record the URL of the Ethereum node used, - record the URL of the Ethereum node used,
- save the signature of the parent planet (if the ship is a moon), - save the signature of the parent planet (if the ship is a moon),
- load the initial public and private keys for the ship, - load the initial public and private keys for the ship,
- set the DNS suffix(es) used by the network (currently just `urbit.org`), - set the DNS suffix(es) used by the network (currently just
`urbit.org`),
- save the public keys of all galaxies, - save the public keys of all galaxies,
- set Jael to subscribe to `%azimuth-tracker`, - set Jael to subscribe to `%azimuth-tracker`,
- `%slip` a `%init` `task` to Ames, Clay, Gall, Dill, and Eyre, and `%give` an `%init` - `%slip` a `%init` task to Ames, Clay, Gall, Dill, and Eyre, and
`gift` to Arvo, which then informs Unix that the initialization process has concluded. `%give` an `%init` gift to Arvo, which then informs Unix that the
initialization process has concluded.
This `task` takes a [$dawn-event](/reference/arvo/jael/data-types#dawn-event) as its argument. This task takes a
[$dawn-event](/reference/arvo/jael/data-types#dawn-event) as its
argument.
You would not use this `task` manually. You would not use this task manually.
#### Returns #### Returns
Jael `%give`s an `%init` `gift` to Unix. This occurs after the Dill `%slip` init. Jael `%give`s an `%init` gift to Unix. This occurs after the Dill
`%slip` init.
## `%fake` ---
### `%fake`
Boot fake ship.
```hoon ```hoon
[%fake =ship] [%fake =ship]
``` ```
Boot fake ship. This task is used instead of [%dawn](#dawn) when creating a fake ship
via the `-F` flag when calling the Urbit binary. It performs a subset of
the actions that `%dawn` performs, modified to accommodate the fake
ship.
This `task` is used instead of [%dawn](#dawn) when creating a fake ship via the `-F` flag when calling the Urbit binary. It performs a subset of the actions that `%dawn` performs, modified to accommodate the fake ship. `%fake` endows the ship with a private key and a public key
deterministically derived from the ship's `@p`. It sets `fak.own.pki` to
`%fake` endows the ship with a private key and a public key deterministically derived from the ship's `@p`. It sets `fak.own.pki` to `%.y`, which is the bit that determines whether or not a ship is fake. Other parts of the Jael state, such as the sponsorship chain and galaxy public keys are left at their bunted values. `%.y`, which is the bit that determines whether or not a ship is fake.
Other parts of the Jael state, such as the sponsorship chain and galaxy
public keys are left at their bunted values.
The `ship` field specifies the `@p` of the fake ship being created. The `ship` field specifies the `@p` of the fake ship being created.
You would not use this `task` manually. You would not use this task manually.
#### Returns #### Returns
Jael `%give`s a `%init` `gift` to Unix. Jael `%give`s a `%init` gift to Unix.
## `%listen` ---
### `%listen`
Set Ethereum source.
```hoon ```hoon
[%listen whos=(set ship) =source] [%listen whos=(set ship) =source]
``` ```
Set Ethereum source. Sets the source that the public keys for a set of `ship`s should be
obtained from. This can either be a Gall app that communicates with an
Ethereum node such as `%azimuth-tracker`, as in the case of galaxies,
stars, and planets, or a ship, as in the case of moons.
Sets the source that the public keys for a set of `ship`s should be obtained from. This can either be a Gall app that communicates with an Ethereum node such as `%azimuth-tracker`, as in the case of galaxies, stars, and planets, or a ship, as in the case of moons. `whos` is the set of ships whose key data source is to be monitored. The
[$source](/reference/arvo/jael/data-types#source) is either a ship or
the name of a Gall app to use as a source. A `%listen` task with empty
`whos` will set the default source. When the `source` is a ship, Jael
will obtain public keys for ships in `(set ship)` from the given ship.
By default, the `source` for a moon will be the planet that spawned that
moon.
`whos` is the set of ships whose key data source is to be monitored. The [$source](/reference/arvo/jael/data-types#source) is either a ship or the name of a Gall app to use as a source. A `%listen` `task` with empty `whos` will set the default source. When the `source` is a ship, Jael will obtain public keys for ships in `(set ship)` from the given ship. By default, the `source` for a moon will be the planet that spawned that moon. You are unlikely to use this task manually.
You are unlikely to use this `task` manually.
#### Returns #### Returns
Jael will not return any `gift`s in response to a `%listen` `task`. Jael will not return any gifts in response to a `%listen` task.
## `%meet` ---
### `%meet`
This task is deprecated and does not perform any actions.
```hoon ```hoon
[%meet =ship =life =pass] [%meet =ship =life =pass]
``` ```
This `task` is deprecated and does not perform any actions. ---
## `%moon` ### `%moon`
Register moon keys or otherwise administer a moon.
```hoon ```hoon
[%moon =ship =udiff:point] [%moon =ship =udiff:point]
``` ```
Register moon keys or otherwise administer a moon. This is what is sent to Jael by `%hood` behind the scenes when you run
`|moon`, `|moon-breach` or `|moon-cycle-keys`. The `ship` field is the
This is what is sent to Jael by `%hood` behind the scenes when you run `|moon`, `|moon-breach` or `|moon-cycle-keys`. The `ship` field is the moon's `@p`. The [$udiff:point](/reference/arvo/jael/data-types#udiffpoint) will contain the bunt of an [$id:block](/reference/arvo/jael/data-types#idblock) (since moons aren't registered in Azimuth) and one of the `udiff` actions depending on what you want to do. moon's `@p`. The
[$udiff:point](/reference/arvo/jael/data-types#udiffpoint) will contain
the bunt of an [$id:block](/reference/arvo/jael/data-types#idblock)
(since moons aren't registered in Azimuth) and one of the `udiff`
actions depending on what you want to do.
#### Returns #### Returns
Jael does not return any `gift`s in response to a `%moon` `task`. Jael does not return any gifts in response to a `%moon` task.
## `%nuke` ---
### `%nuke`
Cancel subscription to public or private key updates.
```hoon ```hoon
[%nuke whos=(set ship)] [%nuke whos=(set ship)]
``` ```
Cancel subscription to public or private key updates. If you've subscribed to public or private key updates from Jael with a
[%private-keys](#private-keys) or [%public-keys](#public-keys) task,
you can unsubscribe and stop receiving updates with a `%nuke` task.
The `(set ship)` is the `set` of `ship`s which you want to stop
tracking. Jael organises subscriptions based on `duct`s, and will
determine which subscription to cancel implicitly based on the `duct`
the `%nuke` task came from. This means a `%nuke` task only works
from the same thread or agent and on the same `path` as the original
subscription request.
If you've subscribed to public or private key updates from Jael with a [%private-keys](#private-keys) or [%public-keys](#public-keys) `task`, you can unsubscribe and stop receiving updates with a `%nuke` `task`. The `(set ship)` is the `set` of `ship`s which you want to stop tracking. Jael organises subscriptions based on `duct`s, and will determine which subscription to cancel implicitly based on the `duct` the `%nuke` `task` came from. This means a `%nuke` `task` only works from the same thread or agent and on the same `path` as the original subscription request. To cancel a subscription to the ship's private keys you must leave
`whos` empty like `[%nuke ~]`.
To cancel a subscription to the ship's private keys you must leave `whos` empty like `[%nuke ~]`.
#### Returns #### Returns
Jael does not return a `gift` in response to a `%nuke` `task`. Jael does not return a gift in response to a `%nuke` task.
#### Examples #### Examples
See the [%public-keys and %nuke](/reference/arvo/jael/examples#public-keys-and-nuke) section of the Examples document for an example of using `%nuke` to cancel a `%public-keys` subscription. See the thread in the [%private-keys](/reference/arvo/jael/examples#private-keys) example for cancelling a `%private-keys` subscription. See the [%public-keys and
%nuke](/reference/arvo/jael/examples#public-keys-and-nuke) section of
the Examples document for an example of using `%nuke` to cancel a
`%public-keys` subscription. See the thread in the
[%private-keys](/reference/arvo/jael/examples#private-keys) example for
cancelling a `%private-keys` subscription.
## `%private-keys` ---
### `%private-keys`
Subscribe to private key updates.
```hoon ```hoon
[%private-keys ~] [%private-keys ~]
``` ```
Subscribe to private key updates. Subscribe to be notified of private key updates for the local ship. The
subscription will continue until Jael receives a [%nuke](#nuke) task
Subscribe to be notified of private key updates for the local ship. The subscription will continue until Jael receives a [%nuke](#nuke) `task` to cancel it. to cancel it.
#### Returns #### Returns
Jael responds to a `%private-keys` `task` with a `%private-keys` `gift` which looks like: Jael responds to a `%private-keys` task with a [`%private-keys`
gift](#private-keys-1).
```hoon Jael will immediately respond with a `%private-keys` gift. Then,
[%private-keys =life vein=(map life ring)] whenever the ship's private keys are changed, it'll send a new and
``` updated `%private-keys` gift.
The `life` is the current life of the ship and the `vein` `map` contains the private key for each life up to (and including) the current one. Upon first subscribing, Jael will immediately respond with a `%private-keys` `gift`. Then, whenever the ship's private keys are changed, it'll send a new and updated `%private-keys` `gift`.
#### Example #### Example
See the [%private-keys](/reference/arvo/jael/examples#private-keys) section of the Examples document for a practical example. See the [%private-keys](/reference/arvo/jael/examples#private-keys)
section of the Examples document for a practical example.
## `%public-keys` ---
### `%public-keys`
Subscribe to public key (and related) updates from Jael.
```hoon ```hoon
[%public-keys ships=(set ship)] [%public-keys ships=(set ship)]
``` ```
Subscribe to public key (and related) updates from Jael. An agent or thread can subscribe to be notified of public key updates,
sponsorship changes and continuity breaches for the `set` of `ship`s
An agent or thread can subscribe to be notified of public key updates, sponsorship changes and continuity breaches for the `set` of `ship`s specified in the `ships` field. The subscription will continue until Jael receives a [%nuke](#nuke) `task` to cancel it. specified in the `ships` field. The subscription will continue until
Jael receives a [%nuke](#nuke) task to cancel it.
#### Returns #### Returns
Jael responds to a `%public-keys` `task` with `%public-keys` `gift`s which look like: Jael responds to a `%public-keys` task with [`%public-keys`
gift](#public-keys-1).
```hoon Upon subscription, Jael will immeditely respond with a `%public-keys`
[%public-keys =public-keys-result] gift containing a `%full` `public-keys-result` with the public key for
``` each `life` up until the current one for each `ship` specified in the
original task. After than, Jael will send a `%public-keys` gift with
The [$public-keys-result](/reference/arvo/jael/data-types#public-keys-result) contains whatever changes have occurred. either a `%diff` or `%breach`
[`$public-keys-result`](/reference/arvo/jael/data-types#public-keys-result)
Upon subscription, Jael will immeditely respond with a `%public-keys` `gift` containing a `%full` `public-keys-result` with the public key for each `life` up until the current one for each `ship` specified in the original `task`. After than, Jael will send a `%public-keys` `gift` with either a `%diff` or `%breach` `public-keys-result` each time a change occurs for any of the `ship`s to which you're subscribed. each time a change occurs for any of the `ship`s to which you're
subscribed.
#### Example #### Example
See the [%public-keys and %nuke](/reference/arvo/jael/examples#public-keys-and-nuke) section of the Examples document for a practical example. See the [%public-keys and
%nuke](/reference/arvo/jael/examples#public-keys-and-nuke) section of
the Examples document for a practical example.
## `%rekey` ---
### `%rekey`
Update private keys.
```hoon ```hoon
[%rekey =life =ring] [%rekey =life =ring]
``` ```
Update private keys. This is what is sent to Jael by `%hood` when you run `|rekey`, as you
must after setting new Azimuth keys or running `|cycle-moon-keys` on a
This is what is sent to Jael by `%hood` when you run `|rekey`, as you must after setting new Azimuth keys or running `|cycle-moon-keys` on a moon's parent. It will update your `life` (key revision number) and private keys. The `life` field is the new `life` (typically an increment of the current `life`) and the `ring` is a private key `@`. moon's parent. It will update your `life` (key revision number) and
private keys. The `life` field is the new `life` (typically an increment
of the current `life`) and the `ring` is a private key `@`.
#### Returns #### Returns
Jael does not return any `gift` in response to a `%rekey` `task`. Jael does not return any gift in response to a `%rekey` task.
## `%turf` ---
### `%resend`
Resend private keys.
```hoon
[%resend ~]
```
This task asks Jael to resend our private keys to subscribers who have
subscribed with a [`%private-keys` task](#private-keys).
#### Returns
Jael doesn't return any gifts in response to a `%rekey` task, but
`%private-keys` subscribers will receive a [`%private-keys`
gift](#private-keys-1).
---
### `%ruin`
Pretend breach.
```hoon
[%ruin ships=(set ship)]
```
This simulates a breach locally for the given `set` of `ship`s. Jael
will blast out a `%breach` [`%public-keys` gift](#public-keys-1) to all
subscribers. Ames will delete all message state for the ships in
question in response to the `%breach` gift.
{% callout %}
**WARNING**
This will break communications with the given ships, and is not
reversible until they actually breach. **Use with extreme caution.**
Note it's better to use the [`%snub` Ames
task](/reference/arvo/ames/tasks#snub) if you want to block packets from
ships.
{% /callout %}
#### Returns
Jael doesn't return any gifts in response to a `%ruin` task.
---
### `%turf`
View domains.
```hoon ```hoon
[%turf ~] [%turf ~]
``` ```
View domains. The domains returned by a `%turf` task are used as the base for
individual galaxy domain names (e.g. from `urbit.org` you get
The domains returned by a `%turf` `task` are used as the base for individual galaxy domain names (e.g. from `urbit.org` you get `zod.urbit.org`, `bus.urbit.org`, etc). Jael gets these from Azimuth, then Ames gets them from Jael and passes them to the runtime, which will perform the DNS lookups and give Ames back the galaxy IP addresses. A `%turf` task takes no additional arguments. You're unlikely to use this manually - if you want the current `turf`s you'd likely want to do a [turf scry](/reference/arvo/jael/scry#turf) instead. `zod.urbit.org`, `bus.urbit.org`, etc). Jael gets these from Azimuth,
then Ames gets them from Jael and passes them to the runtime, which will
perform the DNS lookups and give Ames back the galaxy IP addresses. A
`%turf` task takes no additional arguments. You're unlikely to use this
manually - if you want the current `turf`s you'd likely want to do a
[turf scry](/reference/arvo/jael/scry#turf) instead.
#### Returns #### Returns
Jael will respond to a `%turf` `task` with a `%turf` `gift`, which looks like: Jael will respond to a `%turf` task with a [`%turf` gift](#turf-1).
```hoon
[%turf turf=(list turf)]
```
The `turf` in the `(list turf)` is a domain as a `(list @t)`, TLD-first. The current default is `[['org' 'urbit' ~] ~]`.
#### Example #### Example
See the [%turf section of the Examples document](/reference/arvo/jael/examples#turf) for a practical example. See the [%turf section of the Examples
document](/reference/arvo/jael/examples#turf) for a practical example.
## `%step` ---
### `%step`
Reset web login code.
```hoon ```hoon
[%step ~] [%step ~]
``` ```
Reset web login code. Jael maintains a `step` value that represents the web login code
revision number, and uses it to derive the code itself. It begins at `0`
Jael maintains a `step` value that represents the web login code revision number, and uses it to derive the code itself. It begins at `0` and is incremented each time the code is changed. When Jael updates the web login code, it sends Eyre a `%code-changed` `task:eyre` so that Eyre can throw away all of its current cookies and sessions. A `%step` task takes no additional argument. and is incremented each time the code is changed. When Jael updates the
web login code, it sends Eyre a `%code-changed` `task:eyre` so that Eyre
can throw away all of its current cookies and sessions. A `%step` task
takes no additional argument.
#### Returns #### Returns
Jael does not return a `gift` in response to a `%step` `task`. Jael does not return a gift in response to a `%step` task.
#### Example #### Example
See the [%step](/reference/arvo/jael/examples#step) section of the Examples document for a practical example. See the [%step](/reference/arvo/jael/examples#step) section of the
Examples document for a practical example.
---
## Gifts
### `%done`
Ames message (n)ack.
```hoon
[%done error=(unit error:ames)]
```
This is given in response to a `%plea` from Ames. You would not use this
from userspace.
---
### `%boon`
Ames response.
```hoon
[%boon payload=*]
```
This is given in response to a request from Ames. You would not use this
from userspace.
---
### `%private-keys`
Private keys.
```hoon
[%private-keys =life vein=(map life ring)]
```
This is given to those who have subscribed with a [`%private-keys`
task](#private-keys) whenever our keys change.
The `life` is our current key revision number, and the `vein` contains a
map from current and previous `life`s to private keys as `ring`s.
---
### `%public-keys`
Ethereum changes.
```hoon
[%public-keys =public-keys-result]
```
Public key information, diffs, and breach notifications. This is given
to those who have subscribed with a [`%public-keys` task](#public-keys).
See the
[`$public-keys-result`](/reference/arvo/dill/data-types#public-keys-result)
entry in the data types reference for details of the data this gift
contains.
---
### `%turf`
Domains.
```hoon
[%turf turf=(list turf)]
```
This is given in response to a [`%turf` task](#turf), and contains the
list of domains used for galaxies (the `urbit.org` part in
`zod.urbit.org`).
A `$turf` is a `(list @t)` of domain components, TLD-first. For example,
`urbit.org` would be `~['org' 'urbit']`.
---

View File

@ -12,13 +12,15 @@ not yet proper libraries for other languages that can make use of it. Therefore,
these documents will only touch on Khan's internal interface. these documents will only touch on Khan's internal interface.
Khan's internal interface lets you run threads via Khan rather than having to Khan's internal interface lets you run threads via Khan rather than having to
poke [Spider](/reference/arvo/threads/reference) and subscribe for the result. This interface is simpler and more poke [Spider](/reference/arvo/threads/reference) and subscribe for the result.
ergonomic than Spider's, so is usually preferable. This interface is simpler and more ergonomic than Spider's, so is usually
preferable.
There are currently three `task`s to run a thread: `%fard`, `%fyrd`, and `%lard`. There are two `task`s for running threads from userspace:
Only `%fard` is currently documented in [`%fard`](/reference/arvo/khan/tasks#fard) and
the [API Reference](/reference/arvo/khan/tasks) section, and a practical example [`%lard`](/reference/arvo/khan/tasks#lard). The former is for running a thread
is given in the [Example](/reference/arvo/khan/example) section. from file and the latter is for running an "in-line" thread, where you pass
Khan the thread directly.
## Sections ## Sections

View File

@ -3,31 +3,101 @@ title = "API Reference"
weight = 2 weight = 2
+++ +++
Khan's external interface is still experimental, so there's only one `task` that These are the `task`s Khan can be passed and the `gift`s it can give.
is currently useful:
## `%fard` ## Tasks
Here are the `task`s you can pass Khan. You'd either use
[`%fard`](#fard) to run a thread from a file or [`%lard`](#lard) to run
an in-line thread.
### `%fard`
Run a thread from within Arvo.
```hoon ```hoon
[%fard p=(fyrd cage)] [%fard p=(fyrd cage)]
``` ```
Run a thread from within Arvo
`p` contains the thread location, name, and start arguments. See the `p` contains the thread location, name, and start arguments. See the
[`fyrd`](/reference/arvo/khan/types#fyrd) data type reference entry for details. [`fyrd`](/reference/arvo/khan/types#fyrd) data type reference entry for details.
#### Returns #### Returns
When the thread finishes, either by succeeding or failing, Khan will return an When the thread finishes, either by succeeding or failing, Khan will return an
`%arow` `gift`, which looks like : [`%arow`](#arow) gift.
---
### `%fyrd`
External thread.
```hoon
[%fyrd p=(fyrd cast)]
```
This is passed to Khan by the runtime when a thread is run externally.
You would not use this from userspace.
---
### `%lard`
In-line thread.
```hoon
[%lard =bear =shed]
```
The [`bear`](/reference/arvo/khan/types#bear) is either a `desk` or
`beak`. The [`shed`](/reference/arvo/khan/types#shed) is the thread
itself. Since Spider doesn't need to read out the thread from Clay, the
`bear` doesn't do much apart from be included in the thread name that
Spider generates. Khan will have Spider run the given thread, and
eventually give an [`%arow`](#arow) gift back with the result.
#### Returns
When the thread eventually finishes (or if it fails), Khan with give an
[`%arow`](#arow) gift back with the result.
---
## Gifts
These are the two `gift`s Khan can give. In userspace, you'd only
receive an [`%arow`](#arow).
### `%arow`
In-arvo result.
```hoon ```hoon
[%arow p=(avow cage)] [%arow p=(avow cage)]
``` ```
`p` either contains the result in a `cage`, or an error and stack trace if it This gift contains the result of a finished thread if successful, or an
failed. See the [`avow`](/reference/arvo/khan/types#avow) data type reference error and stack trace if it failed. It's given for threads run from
entry for details. within Arvo. See the [`avow`](/reference/arvo/khan/types#avow) entry in
the types reference for more details.
---
### `%avow`
External result.
```hoon
[%avow p=(avow page)]
```
This gift contains the result of running a thread externally. You would
not receive this in userspace.
A `page` is a pair of `mark` and `noun`. See the
[`avow`](/reference/arvo/khan/types#avow) entry in the types reference
for more details of that mold builder.
--- ---

View File

@ -276,7 +276,7 @@ every step in the path the request takes onto the chain until we get to the
terminal cause of the computation. Then we use this causal stack to route terminal cause of the computation. Then we use this causal stack to route
results back to the caller. results back to the caller.
The Arvo causal stack is called a `duct`. This is represented simply as a list of paths, where each path represents a step in the causal chain. The first element in the path is the first letter of whichever vane handled that step in the computation, or the empty span for Unix. The Arvo causal stack is called a `duct`. This is represented simply as a list of paths, where each path represents a step in the causal chain. The first element in the path is the first letter of whichever vane handled that step in the computation, or the empty path element for Unix.
Here's a `duct` that was recently observed in the wild upon entering `-time ~s1` Here's a `duct` that was recently observed in the wild upon entering `-time ~s1`
into the dojo and pressing Enter, which sets a timer for one second that will into the dojo and pressing Enter, which sets a timer for one second that will
@ -301,7 +301,7 @@ time app. This app returns a `@d` which denotes the current time, which falls do
which drops it through to the terminal. Terminal drops this down to Dill, which which drops it through to the terminal. Terminal drops this down to Dill, which
converts it into an effect that Unix will recognize as a request to print the converts it into an effect that Unix will recognize as a request to print the
current time to the screen. When Dill produces this, the last path in the `duct` has an current time to the screen. When Dill produces this, the last path in the `duct` has an
initial element of the empty span, so this is routed to Unix, which applies the effects. initial empty element, so this is routed to Unix, which applies the effects.
This is a call stack, with a crucial feature: the stack is a first-class citizen. You can respond over a `duct` zero, one, or many times. You can save `duct`s for later use. There are definitely parallels to Scheme-style continuations, but simpler and with more structure. This is a call stack, with a crucial feature: the stack is a first-class citizen. You can respond over a `duct` zero, one, or many times. You can save `duct`s for later use. There are definitely parallels to Scheme-style continuations, but simpler and with more structure.

View File

@ -9,9 +9,17 @@ insert_anchor_links = "right"
Overview of threads. Overview of threads.
## [Threads Guide](/guides/additional/threads/) ## [HTTP API](/reference/arvo/threads/http-api)
Tutorial and explanation of the basics of writing threads. Documenation of Spider's HTTP API.
## [Reference](/reference/arvo/threads/reference)
Documentation of Spider's API.
## [Strandio](/reference/arvo/threads/strandio)
Reference documentation of the Strandio helper library.
## [Gall](/reference/arvo/threads/gall/) ## [Gall](/reference/arvo/threads/gall/)

View File

@ -15,12 +15,12 @@ The `desk` is the desk in which the thread resides. The `inputMark` is the `mark
When Spider receives an HTTP request, the following steps happen: When Spider receives an HTTP request, the following steps happen:
1. It converts the raw body of the message to `json` using `de-json:html` 1. It converts the raw body of the message to `json` using `de:json:html`
2. It creates a `tube:clay` (`mark` conversion gate) from `json` to whatever input `mark` you've specified and does the conversion. 2. It creates a `tube:clay` (`mark` conversion gate) from `json` to whatever input `mark` you've specified and does the conversion.
3. It runs the specified thread and provides a `vase` of `(unit inputMark)` as the argument. 3. It runs the specified thread and provides a `vase` of `(unit inputMark)` as the argument.
4. The thread does its thing and finally produces its result as a `vase` of `outputMark`. 4. The thread does its thing and finally produces its result as a `vase` of `outputMark`.
5. Spider creates another `tube:clay` from the output `mark` to `json` and converts it. 5. Spider creates another `tube:clay` from the output `mark` to `json` and converts it.
6. It converts the `json` back into raw data suitable for the HTTP response body using `en-json:html`. 6. It converts the `json` back into raw data suitable for the HTTP response body using `en:json:html`.
7. Finally, it composes the HTTP response and passes it back to Eyre which passes it on to the client. 7. Finally, it composes the HTTP response and passes it back to Eyre which passes it on to the client.
Thus, it's important to understand that the original HTTP request and final HTTP response must contain JSON data, and therefore the input & output `mark`s you specify must each have a `mark` file in `/mar` that includes a conversion method for `json -> inputMark` and `outputMark -> json` respectively. Thus, it's important to understand that the original HTTP request and final HTTP response must contain JSON data, and therefore the input & output `mark`s you specify must each have a `mark` file in `/mar` that includes a conversion method for `json -> inputMark` and `outputMark -> json` respectively.

View File

@ -79,3 +79,7 @@ These docs walk through the basics of interacting with threads from gall agents.
## [Reference](/reference/arvo/threads/reference) ## [Reference](/reference/arvo/threads/reference)
Basic reference information. For usage of particular `strandio` functions just refer directly to `/lib/strandio/hoon` since they're largely self-explanatory. Basic reference information. For usage of particular `strandio` functions just refer directly to `/lib/strandio/hoon` since they're largely self-explanatory.
## [Strandio](/reference/arvo/threads/strandio)
Reference documentation of the Strandio helper library.

File diff suppressed because it is too large Load Diff

View File

@ -127,10 +127,11 @@ Here is the line of code in `arvo.hoon`, found in the [section
First we note that this line is executed only if the laconic bit is set to true, First we note that this line is executed only if the laconic bit is set to true,
as we did when we input `|verb`. Here, `ovo` is the input `ovum`. Knowing that an `ovum` is a `[p=wire q=curd]`, as we did when we input `|verb`. Here, `ovo` is the input `ovum`. Knowing that an `ovum` is a `[p=wire q=curd]`,
we can then say that this is a `%unix` `move` tagged with `%belt` whose cause is a `wire` given by `//term/1`, we can then say that this is a `%unix` `move` tagged with `%belt` whose cause is a `wire` given by `//term/1`,
where the empty span `//` represents Unix and `term/1` represents the terminal where the empty path element `//` represents Unix and `term/1`
in Unix. Here we have a `wire` instead of a `duct` (i.e. a list of `wire`s) represents the terminal in Unix. Here we have a `wire` instead of a
since Unix I/O events are always the beginning and end of the Arvo event loop, `duct` (i.e. a list of `wire`s) since Unix I/O events are always the
thus only a single `wire` is ever required at this initial stage. beginning and end of the Arvo event loop, thus only a single `wire` is
ever required at this initial stage.
The `""` here is a metadatum that keeps track of how many steps deep in the The `""` here is a metadatum that keeps track of how many steps deep in the
causal chain the event is. An event causal chain the event is. An event

View File

@ -100,7 +100,7 @@ Corresponds to the layer 2 `%spawn` action.
``` ```
Transfer `_point` to `_target`, clearing all permissions data and keys if Transfer `_point` to `_target`, clearing all permissions data and keys if
`_reset` is true. `_reset` set to makes this transaction a `_reset` is true. `_reset` set to true makes this transaction a
[breach](https://urbit.org/using/id/guide-to-resets), and thus this action increments the [breach](https://urbit.org/using/id/guide-to-resets), and thus this action increments the
[`continuityNumber`](/reference/azimuth/azimuth-eth#points) of `_point`, and usually [`continuityNumber`](/reference/azimuth/azimuth-eth#points) of `_point`, and usually
the `keyRevisionNumber` as well (see [Life and the `keyRevisionNumber` as well (see [Life and

View File

@ -35,9 +35,9 @@ The Gall agents involved with Azimuth are summarized as follows:
The transaction processing library is [`/lib/naive.hoon`](#naive). The transaction processing library is [`/lib/naive.hoon`](#naive).
### Gall agents ## Gall agents
#### `%azimuth` {% #azimuth %} ### `%azimuth` {% #azimuth %}
`%azimuth`, located at `/app/azimuth.hoon`, is a Gall agent and thread handler `%azimuth`, located at `/app/azimuth.hoon`, is a Gall agent and thread handler
responsible for finding Azimuth transactions gathered by `%eth-watcher`, responsible for finding Azimuth transactions gathered by `%eth-watcher`,
@ -79,12 +79,12 @@ Scries can be inferred from the `+on-peek` arm:
== ==
``` ```
#### `%azimuth-rpc` {% #azimuth-rpc %} ### `%azimuth-rpc` {% #azimuth-rpc %}
`%azimuth-rpc`, located at `app/azimuth-rpc.hoon`, is a JSON RPC-API for getting `%azimuth-rpc`, located at `app/azimuth-rpc.hoon`, is a JSON RPC-API for getting
`point` and `dns` data from the Azimuth PKI state kept by `%azimuth`. `point` and `dns` data from the Azimuth PKI state kept by `%azimuth`.
#### `%eth-watcher` {% #eth-watcher %} ### `%eth-watcher` {% #eth-watcher %}
`%eth-watcher`, located at `/app/eth-watcher.hoon`, is responsible for listening `%eth-watcher`, located at `/app/eth-watcher.hoon`, is responsible for listening
to an Ethereum node and collecting event logs from it. It is general-purpose and to an Ethereum node and collecting event logs from it. It is general-purpose and
@ -94,7 +94,7 @@ not particular to Azimuth. It sends collected transactions to `+on-agent` in
[![Eth-watcher](https://media.urbit.org/docs/layer2/roller-agents.png)](https://media.urbit.org/docs/layer2/roller-agents.png) [![Eth-watcher](https://media.urbit.org/docs/layer2/roller-agents.png)](https://media.urbit.org/docs/layer2/roller-agents.png)
#### `%roller` {% #roller %} ### `%roller` {% #roller %}
`%roller`, stored at `/app/roller.hoon`, is a Gall agent responsible for `%roller`, stored at `/app/roller.hoon`, is a Gall agent responsible for
collecting and submitting batches of layer 2 transactions to the Ethereum collecting and submitting batches of layer 2 transactions to the Ethereum
@ -142,7 +142,7 @@ This app is not responsible for communicating with Bridge via HTTP. Instead, tha
handled by `%roller-rpc`. The scries are also communicated to Bridge via handled by `%roller-rpc`. The scries are also communicated to Bridge via
`%roller-rpc`. `%roller-rpc`.
#### `%roller-rpc` ### `%roller-rpc`
`%roller-rpc`, stored at `/app/roller-rpc.hoon`, is a very simple Gall app responsible for receiving HTTP RPC-API `%roller-rpc`, stored at `/app/roller-rpc.hoon`, is a very simple Gall app responsible for receiving HTTP RPC-API
calls, typically sent from other Urbit ID users via Bridge. It then translates calls, typically sent from other Urbit ID users via Bridge. It then translates
@ -152,7 +152,7 @@ does not keep any state - its only purpose is to act as an intermediary between
Bridge and `%roller`. See [here](/reference/azimuth/l2/layer2-api) for more Bridge and `%roller`. See [here](/reference/azimuth/l2/layer2-api) for more
information on the JSON RPC-API. information on the JSON RPC-API.
### `naive.hoon` {% #naive %} ## `naive.hoon` {% #naive %}
`/lib/naive.hoon` consists of a gate whose sample is a `verifier`, `chain-id=@ud`, `/lib/naive.hoon` consists of a gate whose sample is a `verifier`, `chain-id=@ud`,
`state`, and `input`, which outputs a cell of `[effects state]`. This is the `state`, and `input`, which outputs a cell of `[effects state]`. This is the

View File

@ -23,7 +23,7 @@ HD wallet [below](#hardware-hd-wallet).
Urbit HD wallets are composed of the following items, which are each assigned to Urbit HD wallets are composed of the following items, which are each assigned to
their own individual Ethereum key-pairs. their own individual Ethereum key-pairs.
### Master Ticket ## Master Ticket
Think of your master ticket like a very high-value password. The master ticket Think of your master ticket like a very high-value password. The master ticket
is the secret code from which all of your other keys are derived. Technically, is the secret code from which all of your other keys are derived. Technically,
@ -31,14 +31,14 @@ your master ticket is a cryptographic seed. You should never share it with anyon
store it very securely. This ticket can derive all of your other keys: your store it very securely. This ticket can derive all of your other keys: your
ownership key and all of the related proxies. ownership key and all of the related proxies.
### Ownership Address ## Ownership Address
An ownership address has all rights over the assets deeded to it. These rights An ownership address has all rights over the assets deeded to it. These rights
are on-chain actions described and implemented in are on-chain actions described and implemented in
[Ecliptic](/reference/glossary/ecliptic), Azimuth's suite of governing [Ecliptic](/reference/glossary/ecliptic), Azimuth's suite of governing
smart-contracts. smart-contracts.
### Proxies ## Proxies
Each permanent Urbit ID can designate one or more Each permanent Urbit ID can designate one or more
[proxies](https://urbit.org/using/id/proxies), which are Ethereum addresses capable of a [proxies](https://urbit.org/using/id/proxies), which are Ethereum addresses capable of a
@ -46,7 +46,7 @@ limited subset of Urbit ID transactions, such as spawning planets or rotating
keys. The HD wallet automatically generates additional addresses utilized as keys. The HD wallet automatically generates additional addresses utilized as
proxies according to what is appropriate for your Urbit ID. proxies according to what is appropriate for your Urbit ID.
### HD wallet generation ## HD wallet generation
Your Urbit HD wallet is generated from a `@q` seed called `T`, which looks Your Urbit HD wallet is generated from a `@q` seed called `T`, which looks
something like `~sampel-ticket-bucbel-sipnem`. This is the string known as your something like `~sampel-ticket-bucbel-sipnem`. This is the string known as your
@ -68,7 +68,7 @@ which will be known as your ownership address. Bridge then automatically uses
your ownership address to assign the other proxies to the other wallets your ownership address to assign the other proxies to the other wallets
generated. generated.
### ERC-721 ## ERC-721
Most Ethereum tokens use the ERC-20 standard for smart contracts. Urbit Most Ethereum tokens use the ERC-20 standard for smart contracts. Urbit
identities are, however, essentially different from most Ethereum tokens, due to identities are, however, essentially different from most Ethereum tokens, due to

View File

@ -17,14 +17,14 @@ while otherwise remaining on layer 1, but it is not possible to transfer only
the management proxy to layer 2; it may only happen as a side-effect of the management proxy to layer 2; it may only happen as a side-effect of
transferring ownership to layer 2. transferring ownership to layer 2.
### Moving a pre-existing ship to L2 ## Moving a pre-existing ship to L2
In order to move your ship from layer 1 to layer 2, transfer ownership of your In order to move your ship from layer 1 to layer 2, transfer ownership of your
ship to the address `0x1111111111111111111111111111111111111111`. The easiest ship to the address `0x1111111111111111111111111111111111111111`. The easiest
way to accomplish this is using [Bridge](/reference/glossary/bridge). The Azimuth way to accomplish this is using [Bridge](/reference/glossary/bridge). The Azimuth
smart contracts interpret any ship at this address as being on layer 2. smart contracts interpret any ship at this address as being on layer 2.
### Dominion ## Dominion
Layer 2 Azimuth data for a given ship includes which layer that ship is on. We Layer 2 Azimuth data for a given ship includes which layer that ship is on. We
call this the ship's _dominion_. There are three dominions: `%l1`, `l2`, and call this the ship's _dominion_. There are three dominions: `%l1`, `l2`, and
@ -32,11 +32,11 @@ call this the ship's _dominion_. There are three dominions: `%l1`, `l2`, and
of the three dominions, and galaxies may exist in dominion `%l1` or `%spawn`. We of the three dominions, and galaxies may exist in dominion `%l1` or `%spawn`. We
detail what this means in each case in the following. detail what this means in each case in the following.
### Planets ## Planets
*Permitted dominions:* `%l1`, `%l2`. *Permitted dominions:* `%l1`, `%l2`.
#### `%l1` planets ### `%l1` planets
*Permitted layer 2 actions:* *Permitted layer 2 actions:*
- owner: `%escape`, `%cancel-escape` - owner: `%escape`, `%cancel-escape`
@ -54,7 +54,7 @@ sponsorship actions.
Layer 1 planets may also move to dominion `%l2` by depositing their ownership to Layer 1 planets may also move to dominion `%l2` by depositing their ownership to
the layer 2 deposit address. the layer 2 deposit address.
#### `%l2` planets ### `%l2` planets
*Permitted layer 2 actions:* *Permitted layer 2 actions:*
- owner: `%transfer-point`, `%configure-keys`, - owner: `%transfer-point`, `%configure-keys`,
@ -71,11 +71,11 @@ will always be on layer 2.
A layer 2 planet is no longer capable of performing any layer 1 actions, and A layer 2 planet is no longer capable of performing any layer 1 actions, and
cannot move to layer 1. cannot move to layer 1.
### Stars ## Stars
*Permitted dominions:* `%l1`, `%spawn`, `%l2`. *Permitted dominions:* `%l1`, `%spawn`, `%l2`.
#### `%l1` stars ### `%l1` stars
*Permitted layer 2 actions:* *Permitted layer 2 actions:*
- owner: `%escape`, `%cancel-escape`, `%adopt`, - owner: `%escape`, `%cancel-escape`, `%adopt`,
@ -94,7 +94,7 @@ A `%l1` dominion star may move to dominion `%spawn` by depositing its spawn prox
layer 2 deposit address, or may move to dominion `%l2` by depositing its ownership layer 2 deposit address, or may move to dominion `%l2` by depositing its ownership
to the layer 2 deposit address. Both actions are irreversible. to the layer 2 deposit address. Both actions are irreversible.
#### `%spawn` stars ### `%spawn` stars
*Permitted layer 2 actions:* *Permitted layer 2 actions:*
- owner: `%escape`, `%cancel-escape`, `%adopt`, - owner: `%escape`, `%cancel-escape`, `%adopt`,
@ -116,7 +116,7 @@ A star moving from `%l1` to `%spawn` has no effect on sponsorship status of any
of its sponsored planets. Moving to `%spawn` from `%l1` is currently of its sponsored planets. Moving to `%spawn` from `%l1` is currently
irreversible - the only further change to dominion permitted is moving to `%l2`. irreversible - the only further change to dominion permitted is moving to `%l2`.
#### `%l2` stars ### `%l2` stars
*Permitted layer 2 actions:* *Permitted layer 2 actions:*
- owner: `%transfer-point`, `%spawn`, `%configure-keys`, `%escape`, - owner: `%transfer-point`, `%spawn`, `%configure-keys`, `%escape`,
@ -134,11 +134,11 @@ spawned by a `%spawn` dominion galaxy.
A star in dominion `%l2` cannot perform any layer 1 actions. A star in dominion `%l2` cannot perform any layer 1 actions.
### Galaxies ## Galaxies
*Permitted dominions:* `%l1`, `%spawn`. *Permitted dominions:* `%l1`, `%spawn`.
#### `%l1` galaxies ### `%l1` galaxies
*Permitted layer 2 actions:* *Permitted layer 2 actions:*
- owner: `%adopt`, `%reject`, `%detach` - owner: `%adopt`, `%reject`, `%detach`
@ -162,7 +162,7 @@ Layer 2 has no interactions with this contract - all stars released in this
manner are `%l1` dominion stars. Moving to the `%spawn` dominion has no effect manner are `%l1` dominion stars. Moving to the `%spawn` dominion has no effect
on sponsorship status. on sponsorship status.
#### `%spawn` galaxies ### `%spawn` galaxies
*Permitted layer 2 actions:* *Permitted layer 2 actions:*
- owner: `%adopt`, `%reject`, `%detach`, `%spawn`, - owner: `%adopt`, `%reject`, `%detach`, `%spawn`,

View File

@ -32,7 +32,7 @@ There are three main steps involved with setting up a roller:
- starting and configuring `%roller`, - starting and configuring `%roller`,
- aiming your front-end at the roller - aiming your front-end at the roller
### 1. Make sure `%azimuth` state is up to date ## 1. Make sure `%azimuth` state is up to date
If you are using an ordinary live ship on the network as the roller, you should If you are using an ordinary live ship on the network as the roller, you should
already have the latest `%azimuth` state and this step should not be necessary already have the latest `%azimuth` state and this step should not be necessary
@ -52,7 +52,7 @@ found under the Setting page for the node on infura.io listed under `ENDPOINTS`.
If you do not perform this step, you'll later see an error "roller not ready" If you do not perform this step, you'll later see an error "roller not ready"
when the first roller batch is about to be submitted. when the first roller batch is about to be submitted.
### 2. Starting and configuring `%roller` {% #step2 %} ## 2. Starting and configuring `%roller` {% #step2 %}
This step must be performed whether you're using a fakezod or a live ship. This step must be performed whether you're using a fakezod or a live ship.
@ -125,7 +125,7 @@ work with a front-end if you want to use it on livenet.
We cover the additional settings for `%roller` at the end. We cover the additional settings for `%roller` at the end.
### 3. Aiming Bridge at the roller ## 3. Aiming Bridge at the roller
The last step is to set up the web interface by which users can submit The last step is to set up the web interface by which users can submit
transactions to be batched by the roller, which we refer to as the front-end. We transactions to be batched by the roller, which we refer to as the front-end. We
@ -153,7 +153,7 @@ REACT_APP_ROLLER_HOST=https://myroller.sampel-pal.net/v1/roller npm run pilot-ma
This will launch a server running Bridge that utilizes the mainnet roller you This will launch a server running Bridge that utilizes the mainnet roller you
set up at `https://myroller.sampel-pal.net/v1/roller`. set up at `https://myroller.sampel-pal.net/v1/roller`.
### Additional `%roller` commmands ## Additional `%roller` commmands
`%roller` has a few other settings and commands for managing things like the `%roller` has a few other settings and commands for managing things like the
rate at which transactions are submitted and manually submitting batches. These rate at which transactions are submitted and manually submitting batches. These

View File

@ -1,5 +1,5 @@
+++ +++
title = "Kelvin versioning" title = "Kernel"
[extra] [extra]
category = "arvo" category = "arvo"

View File

@ -0,0 +1,32 @@
+++
title = "Scry"
[extra]
category = "hoon-nock"
[glossaryEntry."Remote scry"]
name = "remote scry"
symbol = ""
usage = "hoon-nock"
desc = "A remote scry is a read-only request to the namespace of a remote ship."
+++
A **remote scry** is a read-only request to the namespace of a remote
[vane](/reference/glossary/vane) or [agent](/reference/glossary/agent). These
differ from ordinary [local scries](/reference/glossary/scry) and are not
performed with the `.^` [rune](/reference/glossary/rune).
Remote scries reduce event log bloat on the publishing ship, allow the
publisher's runtime to cache data, and especially improve performance when
publishing the same data for many ships to retrieve.
At the the time of writing, Gall allows agents to bind data to remote scry
paths and perform remote scries with `task`s to
[Ames](/reference/glossary/ames). Additionally, Clay uses remote scries
internally to sync remote desks.
#### Further Reading
- [Remote scry guide](/guides/additional/remote-scry): developer documentation
of how remote scries work and how to use them.

View File

@ -51,7 +51,7 @@ changed two parts: `%core` and `%face`. We added polymorphism to
If cores never changed, we wouldn't need polymorphism. Of If cores never changed, we wouldn't need polymorphism. Of
course, nouns are immutable and never change, but we use them as course, nouns are immutable and never change, but we use them as
but we use them as templates to construct new nouns around. templates to construct new nouns around.
Suppose we take a core, a cell `[battery payload]`, and replace Suppose we take a core, a cell `[battery payload]`, and replace
the payload with a different noun. Then, we invoke an arm from the payload with a different noun. Then, we invoke an arm from

View File

@ -9,7 +9,7 @@ This is used for type checking as well as pretty printing.
You can learn more about auras in [Hoon school](/guides/core/hoon-school/B-syntax#nouns). You can learn more about auras in [Hoon school](/guides/core/hoon-school/B-syntax#nouns).
### Table of Auras ## Table of Auras
``` ```
Aura Meaning Example Literal Syntax Aura Meaning Example Literal Syntax
@ -48,7 +48,7 @@ Aura Meaning Example Literal Syntax
@ux unsigned hexadecimal 0x5f5.e138 @ux unsigned hexadecimal 0x5f5.e138
``` ```
### Bitwidth ## Bitwidth
Capital letters at the end of auras indicate the bitwidth in binary powers of Capital letters at the end of auras indicate the bitwidth in binary powers of
two, starting from A. two, starting from A.
@ -61,7 +61,7 @@ two, starting from A.
@uvJ unsigned, 512-bit integer (frequently used for entropy) @uvJ unsigned, 512-bit integer (frequently used for entropy)
``` ```
### Nesting ## Nesting
A given aura nests under any aura whose name is a substring or extension of the A given aura nests under any aura whose name is a substring or extension of the
given aura: given aura:
@ -94,7 +94,7 @@ This is implicitly done by the irregular form of `^-`.
7.303.014 7.303.014
``` ```
### Bunting ## Bunting
The bunt value for all auras is 0 except for `@da`. The bunt value for all auras is 0 except for `@da`.

View File

@ -3,6 +3,17 @@ title = "Irregular forms"
weight = 20 weight = 20
+++ +++
While Hoon has a large amount of sugar syntax, some forms that may look irregular are
actually regular wing syntax or another language feature, such as `,`.
When in doubt, you can use the [`!,` zapcom](/reference/hoon/rune/zap#-zapcom) rune to
determine the AST to which Hoon parses an expression.
```
> !,(*hoon c.b.a)
[%wing p=~[%c %b %a]]
```
## Quick Lookup of Irregular Forms ## Quick Lookup of Irregular Forms
| Form | Regular Form | | Form | Regular Form |
@ -366,6 +377,82 @@ See [%sand](/reference/hoon/rune/constants#warm) for other irregular definitions
"~[1 2 3]" "~[1 2 3]"
``` ```
### `,` com
`,` can serve in several capacities in Hoon programs:
1. As sugar for the `^:` ketcol or `$;` bucmic runes, toggling structure and value mode.
(Toggling out of structure mode is uncommon.)
```
> !,(*hoon ,[@t @t])
[ %ktcl
p=[%bccl p=[i=[%base p=[%atom p=~.t]] t=[i=[%base p=[%atom p=~.t]] t=~]]]
]
> !,(*hoon |=(a=,[@t @t] b))
[ %brts
p
[ %bcts
p=term=%a
q
[ %bcmc
p=[%cltr p=[i=[%base p=[%atom p=~.t]] t=[i=[%base p=[%atom p=~.t]] t=~]]]
]
]
q=[%cnts p=~[[%.y p=2] %a] q=~]
]
> !,(*hoon ,,[@t @t])
[ %ktcl
p
[ %bcmc
p=[%cltr p=[i=[%base p=[%atom p=~.t]] t=[i=[%base p=[%atom p=~.t]] t=~]]]
]
]
```
(`$;` bucmic, or manual value mode, allows the use of value mode syntax to
construct a mold. Concretely, it lets you build a mold out of `hoon` instead
of out of `spec`. It is not commonly used.)
From value mode to structure mode:
```hoon
[%ktcl p=spec]
```
From structure mode to value mode:
```hoon
[%bcmc p=hoon]
```
2. As wing syntax for stripping a face.
For instance, a line similar to the following is present in many Gall agents
receiving HTTP requests via Eyre:
```
=/ ,request-line:server (parse-request-line:server url.request.inbound-request)
```
This `,` lets you avoid using an outer face when handling the result.
```
> =/ ,@ud 1
-
1
> !,(*hoon =/(,@ud 1 -))
[ %tsfs
p=[%spec spec=[%bcmc p=[%base p=[%atom p=~.ud]]] skin=[%base base=%noun]]
q=[%sand p=%ud q=1]
r=[%cnts p=~[[%.y p=2]] q=~]
]
```
3. As a separator, e.g. between pairs in an inline `%=` centis expression, `$(i +(i), j (dec j))`.
## Commentary ## Commentary
In our in-house examples throughout our documentation, we use irregular forms instead of regular for the sake of verbosity. But remember with irregular forms: everything is just runes! Like magic. In general, irregular forms (usually) read better, but of course regular forms provide more information about what you're doing by showing you the full rune. Of course, it's up to you, the Hoon programmer, as to whether or not you want to use these. In our in-house examples throughout our documentation, we use irregular forms instead of regular for the sake of verbosity. But remember with irregular forms: everything is just runes! Like magic. In general, irregular forms (usually) read better, but of course regular forms provide more information about what you're doing by showing you the full rune. Of course, it's up to you, the Hoon programmer, as to whether or not you want to use these.

View File

@ -4,13 +4,13 @@ weight = 1
+++ +++
A limb is an attribute of subject. A limb is an attribute of subject.
### Produces ## Produces
There are two kinds of limbs: arms and legs. An **arm** is a computation of some core. A **leg** is a piece of data in the subject. There are two kinds of limbs: arms and legs. An **arm** is a computation of some core. A **leg** is a piece of data in the subject.
If a limb expression resolves to a leg, the leg is produced. If a limb expression resolves to an arm -- in particular, by way of an arm name -- then the arm is computed with its parent core as the subject. The result of that computation is produced. If a limb expression resolves to a leg, the leg is produced. If a limb expression resolves to an arm -- in particular, by way of an arm name -- then the arm is computed with its parent core as the subject. The result of that computation is produced.
### Syntax ## Syntax
Irregular: `+15` is slot `15`. The value at address `15` of the subject is produced. Irregular: `+15` is slot `15`. The value at address `15` of the subject is produced.
@ -26,7 +26,7 @@ Irregular: `^^^abc` is the name `abc`, but which will skip the first three name
Irregular: `+<-` is "take the tail, then take the head of that, then the head of that." `+` and `>` mean "tail" while `-` and `<` mean "head." This limb syntax starts on `+` or `-` and alternates with `>` or `<` for readability. Irregular: `+<-` is "take the tail, then take the head of that, then the head of that." `+` and `>` mean "tail" while `-` and `<` mean "head." This limb syntax starts on `+` or `-` and alternates with `>` or `<` for readability.
### Traverse ## Traverse
Name resolution happens by way of a search through the subject. The search traverse takes a name `q` and a **skip count** `p`. Name resolution happens by way of a search through the subject. The search traverse takes a name `q` and a **skip count** `p`.
@ -47,7 +47,7 @@ If the skip count `p` is nonzero, we pretend our first `p`
matches are actually mismatches. This lets the programmer "look matches are actually mismatches. This lets the programmer "look
through" an overriding label. through" an overriding label.
### Examples ## Examples
The Dojo prompt gives you a subject with a decent namespace. The Dojo prompt gives you a subject with a decent namespace.
Try: Try:

View File

@ -6,15 +6,15 @@ weight = 2
A wing is a limb search path into the subject. A wing is a limb search path into the subject.
### Produces ## Produces
A wing is a list of limbs (including a trivial list of one limb). The limbs are resolved in succession. The result of the last limb resolution is the value produced by the wing expression. A wing is a list of limbs (including a trivial list of one limb). The limbs are resolved in succession. The result of the last limb resolution is the value produced by the wing expression.
### Syntax ## Syntax
Irregular: `a.b.c`. Read this as '`a` in `b` in `c`'. Finds limb `a` within limb `b` within limb `c` of the subject. Irregular: `a.b.c`. Read this as '`a` in `b` in `c`'. Finds limb `a` within limb `b` within limb `c` of the subject.
### Discussion ## Discussion
Intuitively, Hoon wings are written in the opposite order Intuitively, Hoon wings are written in the opposite order
from attribute dot-paths in most languages. Hoon `a.b.c` is Java's from attribute dot-paths in most languages. Hoon `a.b.c` is Java's
@ -29,7 +29,7 @@ The mysterious idiom `..b` produces the leg `b` if `b`
is a leg; the core exporting `b` if `b` is an arm. Since `.` is a leg; the core exporting `b` if `b` is an arm. Since `.`
is the same limb as `+`, `..b` is the same wing as `+1.foo`. is the same limb as `+`, `..b` is the same wing as `+1.foo`.
### Examples ## Examples
``` ```
~zod:dojo> =a [fod=3 bat=[baz=1 moo=2]] ~zod:dojo> =a [fod=3 bat=[baz=1 moo=2]]
@ -40,3 +40,26 @@ is the same limb as `+`, `..b` is the same wing as `+1.foo`.
~zod:dojo> moo.bat.a ~zod:dojo> moo.bat.a
2 2
``` ```
## Wing Resolution
There are two common syntaxes used to resolve a wing path into the
current subject: `.` dot and `:` col.
- `.` dot syntax, as `c.b.a`, resolves the wing path into the subject
at the right hand using Nock 0 (or possibly Nock 9 or Nock 10
depending on the expression).
```hoon
> !,(*hoon c.b.a)
[%wing p=~[%c %b %a]]
```
- The `:` col operator expands to a `=>` tisgar to resolve the wing path
against its right-hand side as the subject. This can be a Nock 7
or possibly optimized by the compiler to a Nock 0.
```hoon
> !,(*hoon c:b:a)
[%tsgl p=[%wing p=~[%c]] q=[%tsgl p=[%wing p=~[%b]] q=[%wing p=~[%a]]]]
```

View File

@ -11,7 +11,7 @@ language that set it apart from others. If you're looking to learn Hoon, check
out our tutorial series called [Hoon School](/guides/core/hoon-school/) out our tutorial series called [Hoon School](/guides/core/hoon-school/)
below. below.
### What can Hoon do that other languages can't? ## What can Hoon do that other languages can't?
The short answer is: implement a purely functional operating system. The short answer is: implement a purely functional operating system.
Try to do this in a principled way in Haskell, and the problems you'll Try to do this in a principled way in Haskell, and the problems you'll
@ -25,7 +25,7 @@ functional languages are:
- Typesafe metaprogramming, and - Typesafe metaprogramming, and
- Hot code reload and online data migration. - Hot code reload and online data migration.
### What is Hoon good at? ## What is Hoon good at?
Hoon is mostly good at compiling and running other Hoon code. Urbit Hoon is mostly good at compiling and running other Hoon code. Urbit
consists of many layers of bootstrapping. Several of these layers lean consists of many layers of bootstrapping. Several of these layers lean
@ -34,7 +34,7 @@ build system, the Dojo shell, and the Arvo kernel itself. Even Urbit's
chat application lets you run Hoon expressions and share the results chat application lets you run Hoon expressions and share the results
with your friends. with your friends.
### Why did we write the OS in Hoon? ## Why did we write the OS in Hoon?
The chain of reasoning goes something like this: The chain of reasoning goes something like this:
@ -78,13 +78,13 @@ Urbit's solution to these design constraints. Some Lisps come close to
meeting these criteria — and Nock is very Lisp-like — but no practical meeting these criteria — and Nock is very Lisp-like — but no practical
Lisp dialects are nearly as pure or axiomatic as Nock. Lisp dialects are nearly as pure or axiomatic as Nock.
### What is special about Hoon? ## What is special about Hoon?
It's a purely functional systems language. Calling it a functional It's a purely functional systems language. Calling it a functional
analog of C is not too far off in several ways. Almost all code analog of C is not too far off in several ways. Almost all code
throughout Urbit's kernelspace and userspace is written in Hoon. throughout Urbit's kernelspace and userspace is written in Hoon.
### What properties does Hoon have? What type of language is it? ## What properties does Hoon have? What type of language is it?
Hoon is a statically typed, purely functional, strictly evaluated Hoon is a statically typed, purely functional, strictly evaluated
programming language. programming language.
@ -215,7 +215,7 @@ Hoon and Nock have several unusual properties:
calmness of working with such inert building blocks is addictive, as calmness of working with such inert building blocks is addictive, as
many Hoon programmers will attest. many Hoon programmers will attest.
### Why is Hoon the way it is? ## Why is Hoon the way it is?
Minimalism, mostly. Minimalism, mostly.

Some files were not shown because too many files have changed in this diff Show More