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` | |
| `@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:

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:
```hoon
:: if processing succeded, send positive ack packet and exit
:: if processing succeeded, send positive ack packet and exit
++ send-ack
|= =bone
^+ event-core
@ -121,7 +121,7 @@ Gall uses two nested cores to manage agents: `++mo` handles Arvo-level moves, w
```hoon
::
++  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-abet: finalize, reversing moves
++  mo-abet  [(flop moves) gall-payload]

View File

@ -4,29 +4,25 @@ date = "2023-05-01"
weight = 90
next_cohort = "August 2023"
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
School](/guides/core/app-school) curriculum jointly.
The Urbit Foundation offers live, cohort-based classes to cover the [App
School](/guides/core/app-school) curriculum.
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.
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.
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
will be a good fit for you.
By completing App School as a cohort student, you should be able to design and
implement basic user apps, produce minimal working example applications, and
contribute at the level of basic Urbit userspace bounties.
By completing App School, you will have the basic skillset to design and implement apps, pick up Urbit userspace bounties, and participate in Hackathons.
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).

View File

@ -1,27 +1,24 @@
+++
title = "Core School Live"
title = "Core Academy"
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"
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.
Most core developers have been trained up on-the-job inside of Tlon, but as
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
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,
and guide in-depth projects with minimal oversight (as distinct from
collaboration).
The first cohort for Core School Live will take place in late summer 2023.
- [Complete this form](https://forms.gle/gdDWFLiDV1Te65nH8) to get on our
mailing list about the next opportunity.
- [Complete this form](https://airtable.com/shr6V87yXZxJEZqyu) to sign up for Core Academy before August 6th.
Until then, feel free to work through the [Arvo docs](/reference/arvo).

View File

@ -1,7 +1,7 @@
+++
title = "Hoon School Live"
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"
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
lessons.
The next cohort will begin on March 29, 2023. The cohort after that is
scheduled to begin in June 2023.
The cohort will start in September until October on Thursdays at 1pm EST | 6pm CEST.
- [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
docs](/guides/core/hoon-school).
Until then, feel free to work through the [Hoon School docs](/guides/core/hoon-school).
### What will you learn?

View File

@ -5,11 +5,14 @@ weight = 3
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"
weight = 10
title = "Ship Monitoring"
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).
`%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"
weight = 40
title = "Debugging Wrapper"
weight = 42
+++

View File

@ -1,10 +1,8 @@
+++
title = "%feature Page Hosting"
weight = 160
title = "Host a Website"
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` 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']]
?~ body.request.inbound-request
[(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)
(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']]
?~ body.request.inbound-request
[(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)
(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`

View File

@ -1,12 +1,8 @@
+++
title = "%flap JS Client"
weight = 60
title = "Serving a JS Game"
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.
_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'
?~ body.request.inbound-request
[(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)
(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
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:
@ -852,7 +848,7 @@ With all of the above, you should have a working `%flappy` instance at `http://l
%'POST'
?~ body.request.inbound-request
[(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)
(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'
?~ body.request.inbound-request
[(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)
(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
+++
# Concepts
## Concepts
Aqua (short for "aquarium", alluding to the idea that you're running
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
integration tests using Aqua.
# First test
## First test
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
other tests in `/ted/ph/` for inspiration.
# Reference
## Reference
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-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
[vane]: /reference/glossary/vane
[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:
- [`+en-json:html`](/reference/hoon/zuse/2e_2-3#en-jsonhtml) - 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.
- [`+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#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`:
@ -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:
![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.
@ -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"}'
```
@ -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:
```
> +of-test (need (de-json:html '{"foo":"Hello"}'))
> +of-test (need (de:json:html '{"foo":"Hello"}'))
'Hello!!!'
> +of-test (need (de-json:html '{"bar":true}'))
> +of-test (need (de:json:html '{"bar":true}'))
'Yes'
> +of-test (need (de-json:html '{"baz":["a","b","c"]}'))
> +of-test (need (de:json:html '{"baz":["a","b","c"]}'))
'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:
```
> +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}]
> +ou-test (need (de-json:html '{"foo":"hello","bar":true}'))
> +ou-test (need (de:json:html '{"foo":"hello","bar":true}'))
['hello' %.y {}]
> +ou-test (need (de-json:html '{"foo":"hello"}'))
> +ou-test (need (de:json:html '{"foo":"hello"}'))
[%key 'bar']
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:
```
> =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
[ %o
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:
```
> (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"}'
```

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
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)
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)
## [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
+++
# 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.
### Sorting
## Sorting
- 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]
```
### 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:
@ -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.
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.
@ -153,7 +151,7 @@ Standard bitwise logical operations are available:
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.
@ -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.
### 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.
@ -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?
### 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.
@ -245,11 +243,11 @@ Fortunately, there is a regular expression library you can incorporate into your
- 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.
#### 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`.
@ -265,7 +263,7 @@ Hoon is a functional programming language, so naturally it supports the basic co
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`.
@ -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)
```
#### 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`.
@ -284,7 +282,7 @@ The Reduce operation applies a function as a sequence of pairwise operations to
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`.
@ -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.
### 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.
@ -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).)
### 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).

View File

@ -3,7 +3,7 @@ title = "Gleichniszahlenreihe"
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.
@ -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).
## 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._

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
+++
# 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.

View File

@ -3,7 +3,7 @@ title = "Roman Numerals"
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.
@ -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.
## 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.
@ -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._

View File

@ -3,9 +3,7 @@ title = "Solitaire Cipher"
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_.
@ -43,7 +41,7 @@ To generate one character:
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/)._

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
`/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
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]
```
### 2. Actions
## 2. Actions
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
@ -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
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
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.
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
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
reverse - converted from the agent's native mark to `$json`, then encoded in a
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
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:
```
> =js %- need %- de-json:html
> =js %- need %- de:json:html
'''
{
"foo": "hello",

View File

@ -10,175 +10,113 @@ React app front-end.
Node.js must be installed, and can be downloaded from their
[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
the `create-react-app` package with the following command:
`npm` package manager available and its utility binaries like `npx` to help
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
npm install -g create-react-app
```
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
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
```
We can then open our new directory:
```sh
cd journal-ui
```sh {% copy=true %}
cd journal/ui
```
Its contents should look something like this:
```
journal-ui
├── node_modules
ui
├── index.html
├── package.json
├── package-lock.json
├── public
├── README.md
├── postcss.config.js
├── tailwind.config.js
├── vite.config.js
└── 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
npm i @urbit/http-api
```sh {% copy=true %}
npm i
```
We also install a handful of other packages for the UI components
(`bootstrap@5.1.3 react-bootstrap@2.2.0 react-textarea-autosize@8.3.3
date-fns@2.28.0 react-bottom-scroll-listener@5.0.0 react-day-picker@7.4.10`),
but that's not important to our purposes here.
This command will install the Urbit interface package (i.e. `@urbit/http-api`)
and all the other packages used by our React application. When building from
scratch with `create-landscape-app`, this includes a number of useful
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
a user will open it by clicking on its homescreen tile. Docket serves such
front-ends with a base URL path of `/apps/[desk]/`, so in our case it will be
`/apps/journal`. In order for our app to be built with correct resource paths,
we must add the following line to `package.json`:
With all the basics now in place, we can begin work on the app itself. For this
simple demonstration, we'll be working just with the `src/app.jsx` file, which
contains the rendering logic for our React application. Before we look at the
full front-end source for our journal app, let's first review the simpler
default code provided by `create-landscape-app` to cover some Urbit API and
React basics.
```json
"homepage": "/apps/journal/",
### Urbit API setup
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
to talk with it. The `%docket` agent serves a small file for this purpose at
`[host]/session.js`. This file is very simple and just contains:
The first two of these statements are very common in Urbit React applications;
the first imports the React library and a few of its important functions (to be
covered in a moment) and the second imports the `Urbit` class, which will be
used subsequently to enable browser-to-ship communication.
```js
window.ship = "sampel-palnet";
Next, the code sets up the `Urbit` API object as a global variable, which
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
include this script by adding the following line to the `<head>` section of
`public/index.html`:
```
<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`
The first statement creates a new instance of the `Urbit` class we imported
from `@urbit/http-api`, and saves it to the `api` variable. The `Urbit` class
constructor takes three arguments: `url`, `code`, and `desk`, of which only `url`
is mandatory.
- `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.
- `desk` is only necessary if we want to run threads through Eyre, and since
we're not going to do that, we can exclude it.
- `code` is the web login code for authentication, but since the user will
already have logged in, we can also exclude that.
- `code` is the web login code for authentication. Since the user will already
have logged in, we can also leave it as an empty `''` string.
- `desk` is only necessary if we want to run threads through Eyre. This example
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
window.urbit = new Urbit("");
```
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:
While not referenced in the `create-landscape-app` default code, the `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:
- `onOpen` is called when a connection is established.
- `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
otherwise when a fatal error occurs.
We'll look at how we handle these cases in the next section. For now, we'll just
set the `status` entry in the state to either `"con"`, `"try"`, or `"err"` as
the case may be. Note that it's not mandatory to set these callbacks, but
leaving connection problems unhandled is usually a bad idea.
We'll look at how we can use these callbacks in the next section. Note that
it's not mandatory to set these callbacks, but leaving connection problems
unhandled is usually a bad idea.
The last thing we do is call:
### React app setup
```js
this.init();
Finally, let's take a quick look at the React rendering logic for our
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
at it in the next section.
As is common for React components, the first thing we'll define in our `App`
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
- [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
`@urbit/http-api`.
@ -214,3 +233,7 @@ at it in the next section.
- [`@urbit/http-api` source
code](https://github.com/urbit/urbit/tree/master/pkg/npm/http-api) - The
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
+++
With the basic things setup, we can now go over the logic of our app. We'll just
focus on functions that are related to ship communications using the `Urbit`
object we previously setup, and ignore UI components and other helper functions.
Now that we've reviewed the basics of setting up an Urbit React app, we can
dive into the more complex logic that drives our [journal app's
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
In the previous section we just mentioned the connection `status` field of our
state. Here's the full state of our App:
In the previous section, we introduced how React components use [`useState()`]
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 %}
state = {
entries: [], // list of journal entries for display
drafts: {}, // edits which haven't been submitted yet
newDraft: {}, // new entry which hasn't been submitted yet
results: [], // search results
searchStart: null, // search query start date
searchEnd: null, // search query end date
resultStart: null, // search results start date
resultEnd: null, // search results end date
searchTime: null, // time of last search
latestUpdate: null, // most recent update we've received
entryToDelete: null, // deletion target for confirmation modal
status: null, // connection status (con, try, err)
errorCount: 0, // number of errors so far
errors: new Map(), // list of error messages for display
};
```javascript
// Control/Meta State //
const [subEvent, setSubEvent] = useState({});
const [latestUpdate, setLatestUpdate] = useState(null);
const [status, setStatus] = useState(null);
const [errorCount, setErrorCount] = useState(0);
const [errors, setErrors] = useState(new Map());
// Journal State //
const [entries, setEntries] = useState([]);
const [drafts, setDrafts] = useState({});
const [newDraft, setNewDraft] = useState({});
const [entryToDelete, setEntryToDelete] = useState(null);
// Search State //
const [results, setResults] = useState([]);
const [searchMeta, setSearchMeta] = useState({
time: null,
start: null,
end: null,
});
```
We'll see how these are used subsequently.
## 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
init = () => {
this.getEntries().then(
```javascript
const init = () => {
getEntries().then(
(result) => {
this.handleUpdate(result);
this.setState({ latestUpdate: result.time });
this.subscribe();
setSubEvent(result);
setLatestUpdate(result.time);
subscribe();
},
(err) => {
this.setErrorMsg("Connection failed");
this.setState({ status: "err" });
addError("Connection failed");
setStatus("err");
}
);
};
```
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`
and save an error message in the `errors` map. We'll look at what we do with
errors later.
@ -63,13 +77,13 @@ errors later.
![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
before the oldest we currently have. We call this initially, and then each time
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
the user scrolls to the bottom of the list.
```js
getEntries = async () => {
const { entries: e } = this.state;
```javascript
const getEntries = async () => {
const e = entries;
const before = e.length === 0 ? Date.now() : e[e.length - 1].id;
const max = 10;
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
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
the Promise is successfuly, the results are passed to the
[`handleUpdate`](#updates) function which appends the new entries to the
existing ones in state.
the Promise is successfully evaluated, the results are passed to the
[`setSubEvent()`](#updates) function, which appends the new entries to the
existing ones via a [`useEffect()`] hook (more on this [below](#updates)).
## Subscription
A subscription to the `/updates` path of our `%journal` agent is opened with our
`subscribe()` function:
```js
subscribe = () => {
```javascript
const subscribe = () => {
try {
window.urbit.subscribe({
app: "journal",
path: "/updates",
event: this.handleUpdate,
err: () => this.setErrorMsg("Subscription rejected"),
quit: () => this.setErrorMsg("Kicked from subscription"),
event: setSubEvent,
err: () => addError("Subscription rejected"),
quit: () => addError("Kicked from subscription"),
});
} catch {
this.setErrorMsg("Subscription failed");
addError("Subscription failed");
}
};
```
@ -124,7 +138,8 @@ object:
- `app` - the target agent.
- `path` - the `%watch` path we're subscribing to.
- `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).
We just display an error in this case.
- `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
This `handleUpdate` function handles all updates we receive. It's called
whenever an event comes in for our subscription, and it's also called with the
results of [`getEntries`](#getting-entries) and [`getUpdates`](#error-handling)
(described later).
The architecture for updating a React interface based on incoming facts from an
`Urbit` subscription tends to follow a common pattern constituted of three
major parts:
It's a bit complex, but basically it just checks whether the JSON object is
`add`, `edit`, `delete`, or `entries`, and then updates the state appropriately.
The object it's receiving is just the `$update` structure converted to JSON by
the mark conversion functions we wrote previously.
1. A [`useState()`] call that creates an update object field as part of the
main component's state:
```javascript
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
handleUpdate = (upd) => {
const { entries, drafts, results, latestUpdate } = this.state;
if (upd.time !== latestUpdate) {
if ("entries" in upd) {
this.setState({ entries: entries.concat(upd.entries) });
} else if ("add" in upd) {
const { time, add } = upd;
const eInd = this.spot(add.id, entries);
const rInd = this.spot(add.id, results);
const toE =
entries.length === 0 || add.id > entries[entries.length - 1].id;
const toR = this.inSearch(add.id, time);
The key piece of this architecture is the [`useEffect()`] trigger, which is
called whenever an event comes in on the subscription wire (achieved by
including the subscription object `subEvent` as a re-invocation trigger in
[`useEffect()`]'s second argument). In our application, this hook is also
triggered by calls to [`getEntries()`](#getting-entries) and
[`getUpdates()`](#error-handling), which will be described in greater detail
later.
The trigger code is a bit complex, but in broad brushstrokes it just checks the
header of the incoming JSON object (i.e. one of `add`, `edit`, `delete`, or
`entries`) and then updates the state appropriately. The object it's receiving
is just the `$update` structure converted to JSON by the mark conversion
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);
toR && results.splice(rInd, 0, add);
this.setState({
...(toE && { entries: entries }),
...(toR && { results: results }),
latestUpdate: time,
});
} else if ("edit" in upd) {
const { time, edit } = upd;
toE && setEntries([...entries]);
toR && setResults([...results]);
setLatestUpdate(time);
} else if ("edit" in subEvent) {
const { time, edit } = subEvent;
const eInd = entries.findIndex((e) => e.id === edit.id);
const rInd = results.findIndex((e) => e.id === edit.id);
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 (toR) results[rInd] = edit;
(toE || toR) && delete drafts[edit.id];
this.setState({
...(toE && { entries: entries }),
...(toR && { results: results }),
...((toE || toR) && { drafts: drafts }),
latestUpdate: time,
});
} else if ("del" in upd) {
const { time, del } = upd;
toE && setEntries([...entries]);
toR && setResults([...results]);
(toE || toR) && setDrafts({...drafts});
setLatestUpdate(time);
} else if ("del" in subEvent) {
const { time, del } = subEvent;
const eInd = entries.findIndex((e) => e.id === del.id);
const rInd = results.findIndex((e) => e.id === del.id);
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);
toR && results.splice(rInd, 1);
(toE || toR) && delete drafts[del.id];
this.setState({
...(toE && { entries: entries }),
...(toR && { results: results }),
...((toE || toR) && { drafts: drafts }),
latestUpdate: time,
});
toE && setEntries([...entries]);
toR && setResults([...results]);
(toE || toR) && setDrafts({...drafts});
setLatestUpdate(time);
}
}
};
}, [subEvent]);
```
## Add, edit, delete
![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
is called. It uses the `Urbit.poke` method to poke our `%journal` agent.
When a user writes a new journal entry and hits submit, the `createEntry()`
function is called. It uses the `Urbit.poke` method to poke our `%journal`
agent.
```js
submitNew = (id, txt) => {
```javascript
const createEntry = (id, txt) => {
window.urbit.poke({
app: "journal",
mark: "journal-action",
json: { add: { id: id, txt: txt } },
onSuccess: () => this.setState({ newDraft: {} }),
onError: () => this.setErrorMsg("New entry rejected"),
onSuccess: () => setDraft({}),
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
these cases.
The `delete` and `submitEdit` functions are similar to `submitNew`, but for the
`%del` and `%edit` actions rather than `%add`:
The `deleteEntry()` and `editEntry()` functions are similar to `createEntry()`,
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)
```js
submitEdit = (id, txt) => {
if (txt !== null) {
```javascript
const editEntry = (id, txt) => {
if (txt === null) {
delete drafts[id];
setDrafts({...drafts});
} else {
window.urbit.poke({
app: "journal",
mark: "journal-action",
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)
```js
delete = (id) => {
```javascript
const deleteEntry = (id) => {
window.urbit.poke({
app: "journal",
mark: "journal-action",
json: {"del": {"id": id}},
onError: ()=>this.setErrorMsg("Deletion rejected")
})
this.setState({rmModalShow: false, entryToDelete: null})
json: { del: { id: id } },
onError: () => setError("Deletion rejected"),
});
setDeleteId(null);
};
```
@ -279,73 +341,66 @@ our agent.
![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
fashion to [`getEntries`](#getting-entries), but using the
`/x/entries/between/[start]/[end]` endpoint.
```js
getSearch = async () => {
const { searchStart: ss, searchEnd: se, latestUpdate: lu } = this.state;
if (lu !== null && ss !== null && se !== null) {
let start = ss.getTime();
let end = se.getTime();
if (start < 0) start = 0;
if (end < 0) end = 0;
const path = `/entries/between/${start}/${end}`;
window.urbit
.scry({
app: "journal",
path: path,
})
.then(
(result) => {
this.setState({
searchTime: result.time,
searchStart: null,
searchEnd: null,
resultStart: ss,
resultEnd: se,
results: result.entries,
});
},
(err) => {
this.setErrorMsg("Search failed");
}
);
} else {
lu !== null && this.setErrorMsg("Searh failed");
}
```javascript
const searchEntries = async () => {
const start = Math.max(inputStart.getTime(), 0);
const end = Math.max(inputEnd.getTime(), 0);
window.urbit.scry({
app: "journal",
path: `/entries/between/${start}/${end}`,
}).then(
(result) => {
setInputStart(null);
setInputEnd(null);
setResults(result.entries);
setSearchMeta({
time: result.time,
start: inputStart,
end: inputEnd
});
},
(err) => {
setError("Search failed");
}
);
};
```
## 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)
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)
When clicked, the following function is called:
```js
reconnect = () => {
```javascript
const reconnect = () => {
window.urbit.reset();
const latest = this.state.latestUpdate;
if (latest === null) {
this.init();
if (latestUpdate === null) {
init();
} else {
this.getUpdates().then(
getUpdates().then(
(result) => {
result.logs.map((e) => this.handleUpdate(e));
this.subscribe();
result.logs.map(setSubEvent);
subscribe();
},
(err) => {
this.setErrorMsg("Connection failed");
this.setState({ status: "err" });
addError("Connection failed");
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
to get any missing update:
```js
getUpdates = async () => {
const { latestUpdate: latest } = this.state;
const since = latest === null ? Date.now() : latest;
```javascript
const getUpdates = async () => {
const since = latestUpdate === null ? Date.now() : latestUpdate;
const path = `/updates/since/${since}`;
return window.urbit.scry({
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`
function above which called it. The `reconnect` function handles it in a `.then`
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
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,
kicks, scry failures, etc differently than just printing an error, it depends on
the needs of your app.
`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 the needs of your app.
![search failed screenshot](https://media.urbit.org/guides/core/app-school-full-stack-guide/search-failed.png)
## 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
`@urbit/http-api`.
@ -403,3 +460,7 @@ the needs of your app.
- [`@urbit/http-api` source
code](https://github.com/urbit/urbit/tree/master/pkg/npm/http-api) - The
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:
```
``` {% copy=true %}
:~ %journal
==
```
Likewise, `sys.kelvin` just contains:
```
[%zuse 417]
``` {% copy=true %}
[%zuse 414]
```
The `desk.docket-0` file is slightly more complicated:
```
``` {% copy=true %}
:~
title+'Journal'
info+'Dear diary...'
@ -107,24 +107,21 @@ Once created, we can mount it to the unix filesystem.
In the dojo of a fake ship:
```
> |merge %journal our %webterm
>=
> |mount %journal
>=
``` {% copy=true %}
|new-desk %journal
|mount %journal
```
Now we can browse to it in the unix terminal:
```sh
cd ~/zod/journal
```sh {% copy=true %}
cd /path/to/zod/journal
```
Currently it has the same files as the `%webterm` desk, so we need to delete
those:
Currently it just contains some skeleton files, so we need to delete those:
```sh
rm -r .
```sh {% copy=true %}
rm -rI /path/to/zod/journal/*
```
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
`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
`%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/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
cd ~/urbit-git/pkg
```sh {% copy=true %}
cd /path/to/urbit-git/pkg
```
...we can combine the `base-dev` and `garden-dev` desks with the included
`symbolic-merge.sh` script:
...we can combine the `base-dev` and Landscape `desk-dev` desks with the
included `symbolic-merge.sh` script:
```sh
```sh {% copy=true %}
./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:
```sh
cp -rL journal/* ~/zod/journal/
```sh {% copy=true %}
cp -rL journal/* /path/to/zod/journal/
```
Note we've used the `L` flag to resolve symbolic links, because the dev-desks
contain symlinks to files in the actual `arvo` and `garden` folders.
Note we've used the `L` flag to resolve symbolic links.
We can copy across all of our own files too:
```sh
cp -r ~/ourfiles/* ~/zod/journal/
```sh {% copy=true %}
cp -r /path/to/ourfiles/* /path/to/zod/journal/
```
Finally, in the dojo, we can commit the whole lot:
```
``` {% copy=true %}
|commit %journal
```
## Glob
The next step is to build our front-end and upload the files to our ship. In the
`journal-ui` folder containing our React app, we can run:
The next step is to build our front-end and upload the files to our ship. If
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
```
@ -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
dojo:
```
``` {% copy=true %}
|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
from our ship. To do that, we just run the following command in the dojo:
```
``` {% copy=true %}
: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.
#### Types
### Types
- [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
@ -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
`mop`s, and is well commented.
#### Agent
### Agent
- [App School I](/guides/core/app-school/intro) - App School I covers all
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
functions which making writing Gall agents easier.
#### JSON
### JSON
- [The JSON Guide](/guides/additional/json-guide) - The stand-alone JSON guide
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
documentation goes over the basic features of the Eyre vane.
#### 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.
@ -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
files to convert to/from JSON.
#### Eyre
### Eyre
- [The Eyre vane documentation](/reference/arvo/eyre/eyre) - This section of the vane
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
`curl`).
#### React App Setup and Logic
### React App Setup and Logic
- [HTTP API Guide](/guides/additional/http-api-guide) - Reference documentation for
`@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
source code for the `@urbit/http-api` NPM package.
#### Desk and Glob
### Desk and Glob
- [App publishing/distribution docs](/guides/additional/software-distribution) -
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
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
the appropriate vane. When you scry a Gall agent you actually scry Gall itself.
Gall interprets the request, runs it on the specified agent, and then returns
the result. Scries are performed with the
[dotket](/reference/hoon/rune/dot#-dotket) (`.^`) rune. Here's a summary of
their format:
Gall itself defines some special vane-level endpoints [as described in its scry
reference](/reference/arvo/gall/scry), but most scries to Gall are routed to
particular agents and handled by them instead. Agent scries are what we'll
focus on here.
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)
A note on `care`s: Cares are most carefully implemented by Clay, where they specify
submodules and have tightly defined behaviors. For Gall agents, most of these
don't have any special behavior, and are just used to indicate the general kind
of data produced by the endpoint. There are a handful of exceptions to this:
`%d`, `%e`, `%u` and `%x`.
#### `%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
```
A note on `care`s: Cares are most carefully implemented by Clay, where they
specify submodules and have tightly defined behaviors. For Gall agents, most of
these don't have any special behavior, and are just used to indicate the
general kind of data produced by the endpoint, with the exception of the `%x`
care:
#### `%x`
A scry to Gall with a `%x` `care` will be passed to the agent for handling. Gall
handles `%x` specially, and expects an extra field at the end of the `path` that
specifies the `mark` to return. Gall will take the data produced by the
specified endpoint and try to convert it to the given mark, crashing if the mark
conversion fails. The extra field specifying the mark is not passed through to
the agent itself. Here's a couple of examples:
Gall handles `%x` specially, and expects an extra field at the end of the
`path` that specifies the `mark` to return. Gall will take the data produced by
the specified endpoint and try to convert it to the given mark, crashing if the
mark conversion fails. The extra field specifying the mark is not passed
through to the agent itself. Here's a couple of examples:
```
> =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
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.
- Gall scries with an agent name in the `desk` field will be passed to that
agent's `on-peek` arm for handling.
- Gall scries with an agent name in the `desk` field and without an extra empty
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`,
telling Gall to convert the data returned by the scry endpoint to the mark
specified.
@ -352,6 +311,7 @@ crash!
## Exercises
- 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
documentation](/reference/hoon/rune/dot#-dotket).
- 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
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
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
> perfect-number
38
28
```
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.
```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
^- @ud
?: (gth a 1)
%.n
%.y
1
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.

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` |
| `@sw` | signed base64 | `--0wbnC.8haTg` |
| `@sx` | signed hexadecimal | `-0x5f5.e138` |
| `@t` | UTF-8 text (cord) | `'howdy'` |
| `@ta` | ASCII text (knot) | `~.howdy` |
| `@tas` | ASCII text symbol (term) | `%howdy` |
| `@t` | UTF-8 text (`cord`) | `'howdy'` |
| `@ta` | ASCII text (subset) (`knot`) | `~.howdy` |
| `@tas` | ASCII text symbol (subset) (`term`) | `%howdy` |
| `@u` | unsigned integer | no literal |
| `@ub` | unsigned binary | `0b11.1000` |
| `@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).)
`knot` and `term` values each use a URL-safe subset of ASCII, omitting characters like spaces.
### 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:

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.
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:
@ -779,12 +779,12 @@ and verify that our program correctly produces the sequence of numbers 1, 1, 2,
=/ q 1
=/ r *(list @ud)
|- ^- (list @ud)
?: =(i n) r
?: =(index n) r
%= $
i +(i)
p q
q (add p q)
r [q r]
index +(index)
p q
q (add p q)
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}
{% /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.

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.
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
> +vat %base
> +vats %base
%base
/sys/kelvin: [%zuse 417]
base hash: ~
%cz hash: 0v2.r1lbp.i9jr2.hosbi.rvg16.pqe7u.i3hnp.j7k27.9jsgv.8k7rp.oi98q
/sys/kelvin: [%zuse 413]
base hash ends in: hih5c
%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
force on: ~
force off: ~
publishing ship: ~
updates: local
source ship: ~
source desk: ~
source aeon: ~
updates: remote
source ship: ~marnec-dozzod-marzod
source desk: %kids
source aeon: 43
kids desk: %kids
pending updates: ~
```

View File

@ -312,8 +312,6 @@ A `fish-loop` arises when using a recursive mold definition like `list`. (The r
fish-loop
```
although a promised `?#` wuthax rune should match it once implemented.
### `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).

View File

@ -32,7 +32,7 @@ The second way of making a function call involves an expression that _produces_
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?
@ -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`?
`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
> (~(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).
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 %}
|= [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:
```hoon
> +add 12 14
> +sum [12 14]
26
> +add 22
> +sum 22
nest-fail
-need.[a=@ b=@]
-have.@ud

View File

@ -28,7 +28,7 @@ has three folders inside:
1. `bare-desk`: just the hoon files created here without any dependencies.
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`.
3. `react-frontend`: the React front-end files.
3. `ui`: the React front-end files.
Let's get started.
@ -53,7 +53,7 @@ curl -L https://urbit.org/install/linux-aarch64/latest | tar xzk --transform='s/
#### macOS (`x86_64`)
```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`)
@ -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.
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
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
^- (quip card _this)
[~ 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.
::
++ 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
::
%watch-ack
:: if there's no error message it succceeded,
:: if there's no error message it succeeded,
:: do nothing further
::
?~ 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
::
?+ -.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
:: if there's no error message the subscription succeeded,
:: no nothing
@ -1117,7 +1117,7 @@ in `hut/mar/hut/do.hoon` and `hut/mar/hut/did.hoon` respectively.
::
++ 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
:: 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)]
==
==
:: 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
::
++ 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
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
`npm i` in `chat-app/react-frontend`. Additional commentary on the code is in
the [additional commentary](#additional-commentary) section below.
`npm i` in `chat-app/ui`. Additional commentary on the code is in the
[additional commentary](#additional-commentary) section below.
#### 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 %}
npx create-react-app hut-ui
cd hut-ui
```shell
npx @urbit/create-landscape-app
✔ 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:
```
npm i @urbit/http-api
```shell
cd hut/ui
npm i
```
`http-api` handles most of the tricky parts of communicating with our ship for
us, and has a simple set of methods for doing things like pokes, subscriptions,
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`:
We can now open `src/app.jsx`, wipe its contents, and start writing our own
app. The first thing is to import the `Urbit` class from `@urbit/http-api`:
```javascript
import React, {Component} from "react";
import React, {useEffect, useState} from "react";
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 also add some connection state callbacks. Our app is simple and will just
display the connection status in the top-right corner.
We'll create an `App` component that will create a new `Urbit` instance on load
to monitor our front-end's connection with our ship. Our app is simple and will
just display the connection status in the top-left corner:
```javascript
constructor(props) {
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"});
// ......
};
export function App() {
const [status, setStatus] = useState("try");
useEffect(() => {
window.urbit = new Urbit("");
window.urbit.ship = window.ship;
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
constructor(props) {
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 and view the
resulting files in the `dist` directory:
After we've finished writing our React app, we can build it:
```shell {% copy=true %}
```shell
npm run build
ls dist
```
#### Additional commentary
There are a fair few functions our front-end uses, so we'll just look at a
handful. The first is `doPoke`, which (as the name suggests) sends a poke to a
ship. It takes the poke in JSON form. It then calls the `poke` method of our
`Urbit` object to perform the poke.
There are a fair few functions in the
[complete front-end source for `%hut`](https://github.com/urbit/docs-examples);
we'll just look at a handful to cover the basics. The first is the `appPoke`
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
doPoke = jon => {
window.urbit.poke({
export function appPoke(jon) {
return api.poke({
app: "hut",
mark: "hut-do",
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
joinGid = () => {
const joinSelect = this.state.joinSelect
if (joinSelect === "def") return;
const [host, name] = joinSelect.split("/");
this.doPoke(
{"join": {
"gid" : {"host": host, "name": name},
"who" : this.our
}}
);
this.setState({joinSelect: "def"})
const handleJoin = () => {
if (joinSelect !== "def") {
const [host, name] = joinSelect.split("/");
appPoke({
"join": {
"gid" : {"host": host, "name": name},
"who" : OUR
}
});
}
};
```
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`
object with the `path` to subscribe to and an `event` callback to handle each
update it receives. Our agent publishes all updates on the local-only `/all`
path.
object (aliased to `api` in our example) with the `path` to subscribe to and an
`event` callback to handle each update it receives. Our agent publishes all
updates on the local-only `/all` path. Here's the source in the `src/app.jsx`
file:
```javascript
subscribe = () => {
window.urbit.subscribe({
app: "hut",
path: "/all",
event: this.handleUpdate
});
};
const subscription = api.subscribe({
app: "hut",
path: "/all",
event: setSubEvent,
});
```
Here's the `handleUpdate` function we gave as a callback. The update will be one
of our `hut-upd` types in JSON form, so we just switch on the type and handle it
as appropriate.
Notice that the above call to `subscribe` passes the `setSubEvent` function.
This is part of a common pattern for Urbit React applications wherein a state
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" %}
handleUpdate = upd => {
const {huts, msgJar, joined, currentGid, currentHut} = this.state;
if ("initAll" in upd) {
upd.initAll.huts.forEach(obj =>
huts.set(this.gidToStr(obj.gid), new Set(obj.names))
);
this.setState({
huts: huts,
msgJar: new Map(
upd.initAll.msgJar.map(obj => [this.hutToStr(obj.hut), obj.msgs])
),
joined: new Map(
upd.initAll.joined.map(obj =>
[this.gidToStr(obj.gid), new Set(obj.ppl)]
)
)
})
} else if ("init" in upd) {
upd.init.msgJar.forEach(obj =>
msgJar.set(this.hutToStr(obj.hut), obj.msgs)
);
this.setState({
msgJar: msgJar,
huts: huts.set(
this.gidToStr(upd.init.huts[0].gid),
new Set(upd.init.huts[0].names)
),
joined: joined.set(
this.gidToStr(upd.init.joined[0].gid),
new Set(upd.init.joined[0].ppl)
)
})
} else if ("new" in upd) {
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();
useEffect(() => {
const updateFuns = {
"initAll": (update) => {
update.huts.forEach(obj =>
huts.set(gidToStr(obj.gid), new Set(obj.names))
);
setHuts(new Map(huts));
setChatContents(new Map(
update.msgJar.map(o => [hutToStr(o.hut), o.msgs])
));
setChatMembers(new Map(
update.joined.map(o => [gidToStr(o.gid), new Set(o.ppl)])
));
}, "init": (update) => {
setChatContents(new Map(update.msgJar.reduce(
(a, n) => a.set(hutToStr(n.hut), n.msgs)
, chatContents)));
setHuts(new Map(huts.set(
gidToStr(update.huts[0].gid),
new Set(update.huts[0].names)
)));
setChatMembers(new Map(chatMembers.set(
gidToStr(update.joined[0].gid),
new Set(update.joined[0].ppl)
)));
}, "new": (update) => {
const gidStr = gidToStr(update.hut.gid);
const hutStr = hutToStr(update.hut);
if (huts.has(gidStr)) {
huts.get(gidStr).add(update.hut.name);
} else {
huts.set(gidStr, new Set(update.hut.name));
}
)
} else if ("join" in upd) {
const gidStr = this.gidToStr(upd.join.gid);
(joined.has(gidStr))
? joined.get(gidStr).add(upd.join.who)
: joined.set(gidStr, new Set([upd.join.who]));
this.setState({joined: joined})
} else if ("quit" in upd) {
const gidStr = this.gidToStr(upd.quit.gid);
if ("~" + window.ship === upd.quit.who) {
(huts.has(gidStr)) &&
huts.get(gidStr).forEach(name =>
msgJar.delete(gidStr + "/" + name)
setHuts(new Map(huts));
setChatMembers(new Map(chatMembers.set(hutStr, update.msgs)));
}, "post": (update) => {
const newHut = hutToStr(update.hut);
if (chatContents.has(newHut)) {
chatContents.set(newHut, [...chatContents.get(newHut), update.msg]);
} else {
chatContents.set(newHut, [update.msg]);
}
setChatContents(new Map(chatContents));
}, "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);
joined.delete(gidStr);
this.setState({
msgJar: msgJar,
huts: huts,
joined: joined,
currentGid: (currentGid === gidStr)
? null : currentGid,
currentHut: (currentHut === null) ? null :
(
currentHut.split("/")[0] + "/" + currentHut.split("/")[1]
=== gidStr
)
? null : currentHut,
make: (currentGid === gidStr) ? "" : this.state.make
})
} else {
(joined.has(gidStr)) &&
joined.get(gidStr).delete(upd.quit.who);
this.setState({joined: joined})
}
} else if ("del" in upd) {
const gidStr = this.gidToStr(upd.del.hut.gid);
const hutStr = this.hutToStr(upd.del.hut);
(huts.has(gidStr)) &&
huts.get(gidStr).delete(upd.del.hut.name);
msgJar.delete(hutStr);
this.setState({
huts: huts,
msgJar: msgJar,
currentHut: (currentHut === hutStr) ? null : currentHut
})
setViewSelect("def");
setHutInput((currGid === gidStr) ? "" : hutInput);
} else {
if (chatMembers.has(gidStr)) {
chatMembers.get(gidStr).delete(update.who);
}
setChatMembers(new Map(chatMembers));
}
}, "del": (update) => {
const gidStr = gidToStr(update.hut.gid);
const hutStr = hutToStr(update.hut);
if (huts.has(gidStr)) {
huts.get(gidStr).delete(update.hut.name);
}
chatContents.delete(hutStr);
setHuts(new Map(huts));
setChatContents(new Map(chatContents));
setCurrHut((currHut === hutStr) ? null : currHut);
},
};
const eventTypes = Object.keys(subEvent);
if (eventTypes.length > 0) {
const eventType = eventTypes[0];
updateFuns[eventType](subEvent[eventType]);
}
};
}, [subEvent]);
```
## Desk config
@ -1587,7 +1595,7 @@ this by adding a `sys.kelvin` file to the root of our `hut` directory:
```shell {% copy=true %}
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
@ -1627,10 +1635,10 @@ the moment.
## Put it together
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 %}
|merge %hut our %webterm
|new-desk %hut
```
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
```
Currently its contents are the same as the `%webterm` desk, so we'll need to
delete those files and copy in our own instead. In the normal shell, do the
It'll have a handful of skeleton files in it, but we can just delete those and
add our own instead. In the normal shell, do the
following:
```shell {% copy=true %}
rm -r dev-comet/hut/*
rm -rI 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
`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
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.
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.
7. Make a private squad public.
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.
@ -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
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).
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,
:: so we just automaticall try to resubscribe
:: so we just automatically try to resubscribe
::
%kick
?. (~(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
::
?: =(our.bol ship.upd) `this
:: otherwise, update the member list and and let local
:: otherwise, update the member list and let local
:: subscribers know
::
:- ~[(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
::
/- *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
:: 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
==
:: 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 /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 %}
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
@ -1967,10 +1967,10 @@ squad
```
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 %}
|merge %squad our %webterm
|new-desk %squad
```
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
```
Currently its contents are the same as the `%webterm` desk, so we'll need to
delete those files and copy in our own instead. In the normal shell, do the
following:
Currently it just contains some skeleton files, but we can just delete those
and add our own instead. In the normal shell, do the following:
```shell {% copy=true %}
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}
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/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/garden/lib/mip.hoon tally/lib/
```
@ -146,10 +146,10 @@ be sent or received in *pokes* (one-off messages):
- `%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:
- `%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.
- `%new`: someone has created a new 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
`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,
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.
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
`/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
@ -428,7 +428,7 @@ Gall agents live in the `/app` directory of a desk, so save this code in
%1 `this(state old)
%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
:: a handle-action function, and if it's a %handle-http-request from the
:: 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
:_ state(section %subs)
(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
::
?+ 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
::
=/ 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])
(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
:: retreive the target poll from state
:: retrieve the target poll from state
::
=/ [=poll =votes] (~(got bi by-group) gid.act pid.act)
:: 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)
&(=(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
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]))
==
:: 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
::
++ 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
::
=/ 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 %}
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
@ -1933,10 +1933,10 @@ following:
## Put it together
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 %}
|merge %tally our %webterm
|new-desk %tally
```
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
```
Currently its contents are the same as the `%webterm` desk, so we'll need to
delete those files and copy in our own instead. In the normal shell, do the
following:
Currently it just contains some skeleton files, but we can delete those and add
our own instead. In the normal shell, do the following:
```shell {% copy=true %}
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:

View File

@ -1,6 +1,6 @@
+++
title = "Hoon School Live Spring '23"
image = "https://storage.googleapis.com/media.urbit.org/site/featured/hoon-school-march-23.png"
url="https://developers.urbit.org/courses/hsl"
description = "The next session of Hoon School Live begins in March. Sign up to learn the fundamentals of programming on Urbit."
title = "Assembly Hackathon 2023"
image = "https://storage.googleapis.com/media.urbit.org/developers/hackathon/hackathon_logo.svg"
url="/hackathon"
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.
* [Hoon School](/guides/core/hoon-school/): A collection of tutorials
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.
* [Reference](/reference/hoon/): Reference material primarily
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
involved in cryptography.
### Types of keys
## Types of keys
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
cryptography.
#### Azimuth keys
### Azimuth keys
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.
@ -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
the [Azimuth documentation](/reference/azimuth/azimuth).
#### Networking keys
### Networking keys
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
@ -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
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 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
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
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.
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.

View File

@ -65,6 +65,15 @@ Application-level message, as a `%pass`.
- `path` - Internal route on the receiving ship.
- `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`
```hoon
@ -167,6 +176,7 @@ All Ames knows about a peer.
$: messages=(list [=duct =plea])
packets=(set =blob)
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.
- `packets` - Packets we've tried to send.
- `heeds` - Local tracking requests; passed through into [$peer-state](#peer-state).
- `keens` - Subscribers to remote scry paths.
## `$peer-state`
@ -182,6 +193,7 @@ What to do when Ames learns a peer's life and keys.
+$ peer-state
$: $: =symmetric-key
=life
=rift
=public-key
sponsor=ship
==
@ -192,6 +204,9 @@ What to do when Ames learns a peer's life and keys.
rcv=(map bone message-sink-state)
nax=(set [=bone =message-num])
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.
- `nax` - Unprocessed nacks (negative acknowledgments).
- `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`

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
`+on-hear:event-core` arm.
Ames `task`s can be naturally divided into two categories: messaging tasks and
system/lifecycle tasks.
Ames `task`s can be naturally divided into three categories: messaging tasks,
system/lifecycle tasks, and remote scry 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.
---
### `%heed`
```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
unresponsive peer's urbit address.
---
### `%jilt`
```hoon
@ -68,6 +72,8 @@ The `ship` field specifies the peer we want to stop tracking.
This `task` returns no `gift`s.
---
### `%plea`
```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.
---
## System Tasks
### `%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
response to `%born`.
---
### `%init`
```hoon
@ -134,6 +144,8 @@ contained by Jael.
`%init` sends two moves that subscribe to `%turf` and `%private-keys` in Jael.
---
### `%sift`
```hoon
@ -148,6 +160,41 @@ The `ships` field specifies the ships for which debug output is desired.
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`
```hoon
@ -164,6 +211,8 @@ Sets verbosity toggles on debug output. This `task` is used internally when the
This `task` returns no `gift`s.
---
### `%stir`
```hoon
@ -178,6 +227,8 @@ The `arg` field is unused.
This `task` returns no `gift`s.
---
### `%vega`
```hoon
@ -190,3 +241,67 @@ anything in response to this.
#### Returns
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:
it supports typed data and is referentially transparent.
### Revision Control
## Revision Control
Every urbit has one or more desks, which are independently
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
global and referentially transparent.
### A Typed Filesystem
## A Typed Filesystem
Since Clay is a general filesystem for storing data of arbitrary
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
of this system in some detail.
### Marks
## Marks
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)

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,
foreign ships.
### User documentation
## User documentation
[Filesystem](https://urbit.org/using/os/filesystem)
How to interact with the Clay filesystem via Dojo. This includes basics such as
mounting to Unix, changing directory, merging, and listing files.
### Developer Documentation
## Developer Documentation
[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.
## `/lib/csv/hoon`
## `/lib/csv-utils/hoon`
```hoon
```hoon {% mode="collapse" copy="true" %}
|%
++ validate :: All rows same length?
|= 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`
```hoon
/+ *csv
```hoon {% mode="collapse" copy="true" %}
/+ *csv-utils
|_ csv=(list (list @t))
++ grab
|%
@ -232,7 +232,7 @@ These are the files used in the [Writing Marks](/reference/arvo/clay/marks/writi
## `/mar/csv-diff/hoon`
```hoon
```hoon {% copy="true" %}
|_ dif=(urge:clay (list @t))
++ grab
|%

View File

@ -5,7 +5,7 @@ weight = 2
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:
@ -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.
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:
@ -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:
```
> =csv -build-file %/lib/csv/hoon
> =csv -build-file %/lib/csv-utils/hoon
```
...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|>]
```
...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'
```
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
/+ *csv
/+ *csv-utils
|_ csv=(list (list @t))
++ 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.
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:
```hoon
/+ *csv
/+ *csv-utils
|_ csv=(list (list @t))
++ grab
|%

View File

@ -49,7 +49,7 @@ Example:
### `/rang` - Get `rang`
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:
@ -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
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.
Example:
@ -109,7 +149,7 @@ Example:
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
[`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
versions) of any queued updates.
@ -158,7 +198,7 @@ Example:
## `%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`.
Example:
@ -169,7 +209,7 @@ Example:
## %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
`/txt/noun`.
@ -188,10 +228,14 @@ Example:
A scry with a `care` of `%d` will return a `(set desk)` of the `desk`s that
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:
```
> .^((set desk) %cd %)
> .^((set desk) %cd /=//=)
{%bitcoin %base %landscape %webterm %garden %kids}
```
@ -199,7 +243,7 @@ Example:
## `%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
`(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`.
@ -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
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.
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
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
[tako:clay](/reference/arvo/clay/data-types#takoclay).
[tako:clay](/reference/arvo/clay/data-types#tako).
Example:
Here we scry the [dome:clay](/reference/arvo/clay/data-types#domeclay) for `%`,
get the latest `tako:clay` and the do a `%s` scry for the `yaki:clay` in
Here we scry the [dome:clay](/reference/arvo/clay/data-types#dome) for `%`,
get the latest `tako` and the do a `%s` scry for the `yaki` in
question.
```
@ -316,13 +360,13 @@ question.
### `%blob` - File blob
This will return the [page:clay](/reference/arvo/clay/data-types#pageclay) of
some file. It takes a [lobe:clay](/reference/arvo/clay/data-types#lobeclay).
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#lobe).
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 `blob:clay` of the file.
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` of the file.
```
> =/ =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
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:
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
[`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.
```
@ -360,11 +404,11 @@ with a `%v` scry, get the latest
### `%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:
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)
@ -394,10 +438,10 @@ documentaton at a later date.
This will return the most recent revision number of a `desk` that has been fully
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
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]`.
Example:
@ -498,7 +542,7 @@ Examples:
## `%v` - Desk state
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:
@ -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
`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.
Example:
@ -530,19 +574,20 @@ Example:
## `%x` - Read file
A scry with a `care` of `%x` will return the raw data of a file as an `@` or
crash if it's a directory.
A scry with a `care` of `%x` will return the data of a file or crash if it's a
directory. The type returned will be whatever is defined by the `mark` of the
file.
Examples:
```
> .^(@ %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
> .^(@t %cx %/gen/hood/hi/hoon)
':: 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)
':: 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'
> .^(waft:clay %cx %/sys/kelvin)
[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.
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
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.
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.
- `%next` - Subscribe for the next change to a file or directory.
@ -48,7 +51,12 @@ A `%wris` `gift` looks like:
[%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.
@ -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.
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
@ -76,7 +91,9 @@ The [case](/reference/arvo/clay/data-types#case-specifying-a-commit) specifies t
[%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`.
@ -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.
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.
@ -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`.
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.
- `%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
[`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
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.
@ -228,7 +265,7 @@ A `rock:tire` is a:
+$ 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
kernel-compatible update (`%held`). The `wic` set contains the `weft`s (kernel
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
[`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.
- `%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
`%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:
```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
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,
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,
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
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
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`.
@ -358,7 +395,7 @@ make your changes.
A `%tomb` `task` with a `%worn` `clue` is like
[`%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.
---
@ -371,10 +408,10 @@ denotes the commit to apply the policy.
A `%tomb` `task` with a `%seek` `clue` will attempt to retrieve missing,
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
[`tako:clay`](/reference/arvo/clay/data-types#takoclay) or a
[`case:clay`](/reference/arvo/clay/data-types#caseclay).
[`tako`](/reference/arvo/clay/data-types#tako) or a
[`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 `beam:clay` is the following structure:
A `beam` is the following structure:
```hoon
+$ 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.
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
@ -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.
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
+$ 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:
- `%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.
- `%x` - Get data of file.
- `%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.
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.
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
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
given file. A `%many` request asks to be notified on every
change in a `desk` for a range of changes.

View File

@ -3,17 +3,19 @@ title = "Scries"
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.
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.
### How do I scry?
## How do I scry?
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).
### What can I scry?
## What can I scry?
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
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.
#### 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 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.
@ -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)
```
### 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).
### Further reading
## Further reading
[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`
Terminal dimension.
```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`
```hoon
+$ belt
$? bolt
$% [%mod mod=?(%ctl %met %hyp) key=bolt]
[%txt p=(list @c)]
[%ctl p=@c]
[%met p=@c]
== ==
```
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:
- `%mod` - Modifier (Ctrl, Meta or Hyper) plus [key].
- `%txt` - A series of characters
- `%ctl` - Ctrl+[key], deprecated in favour of `%mod`.
- `%met` - Meta+[key], deprecated in favour of `%mod`.
- `%mod` - Modifier (Ctrl, Meta or Hyper) plus a key (see
[`$bolt`](#bolt).
- `%txt` - A series of UTF-32 characters.
--
## `$bolt`
```hoon
+$ bolt
$@ @c
$% [%aro p=?(%d %l %r %u)]
[%bac ~]
[%del ~]
[%hit r=@ud c=@ud]
[%ret ~]
==
```
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.
- `%bac` - Backspace 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.
---
## `$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.
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:
- `%bel` - Ring the terminal bell.
- `%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.
- `%lin` - Set current line, `p` contains the text.
- `%mor` - 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.
- `%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`.
- `%mor` - multiple `$blit`s.
- `%nel` - a newline.
- `%put` - put text (as a list of UTF-32 characters) at the current
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.
- `%wyp` - clear the cursor line.
---
## `$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.
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.
- `%bac` - Backspace key.
- `%cru` - Echo error, `p` is an error `@tas` and `q` is a traceback.
- `%ctl` - Ctrl+[key].
- `%del` - Delete key.
a `$dill-belt` is either [`$belt`](#belt) or one of:
- `%cru` - Echo error, `p` is some error tag and `q` is a stack trace.
- `%hey` - Refresh.
- `%met` - Meta+[key].
- `%ret` - Return key (Enter).
- `%rez` - Terminal resized, `p` is columns and `q` is rows.
- `%txt` - Text input.
- `%yow` - Connect to app.
---
## `$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.
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.
- `%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`
```hoon
+$ flog
$% [%crop p=@ud]
[%crud p=@tas q=(list tank)]
[%heft ~]
[%meld ~]
[%pack ~]
[%text p=tape]
[%verb ~]
==
```
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.
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
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
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 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 client is an external input source and output sink; a terminal with
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)
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
[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.
[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
+++
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
```
> .^(blit:dill %dx /=//=/sessions//line)
[ %lin
p
~[
~-~~
~-z
~-o
~-d
~-~3a.
~-d
~-o
~-j
~-o
...truncated for brevity...
]
]
> .^((set @tas) %dy /=//=/sessions)
{%$}
```
## /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
```
> .^(@ud %dx /=//=/sessions//cursor)
44
> .^(? %du /=//=/sessions/$)
%.y
> .^(? %du /=//=/sessions/foo)
%.n
```
---

View File

@ -3,339 +3,478 @@ title = "API Reference"
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
[%belt p=belt]
```
## System & Misc. Tasks
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.
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`
### `%boot`
```hoon
[%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.
#### 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
[%crop p=@ud]
```
Trim kernel state.
This `task` is the same as the [%trim](#trim) `task`. Like `%trim`, Dill does nothing with it.
This `task` is the same as the `%trim` vane `task`. Like `%trim`, Dill
does nothing with it.
#### Returns
Dill returns no `gift` in response to a `%crop` `task`.
## `%crud`
---
```hoon
[%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`
### `%flog`
```hoon
[%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
Dill does not return a `gift` in response to a `%flog` `task`.
## `%flow`
---
```hoon
[%flow p=@tas q=(list gill:gall)]
```
### `%heft`
Terminal config.
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`
Produce memory report.
```hoon
[%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
Dill does not return a `gift` in response to a `%heft` `task`.
## `%hook`
---
### `%logs`
Watch system output.
```hoon
[%hook ~]
[%logs p=(unit ~)]
```
This terminal hung up.
This task is not used.
## `%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.
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
as it occurs.
#### 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]
```
```
[%meld ~]
```
A `$told` is either a [`%crud`](#crud), [`%talk`](#talk) or
[`%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.
```hoon
[%meld ~]
```
Dill asks the runtime to perform the memory deduplication.
#### Returns
Dill does not return a `gift` in response to a `%meld` `task`.
## `%noop`
---
```hoon
[%noop ~]
```
### `%pack`
No operation.
A `%noop` `task` does nothing, as the name implies.
#### Returns
Dill does not return a `gift` in response to a `%noop` `task`.
## `%pack`
Defragment persistent state.
```hoon
[%pack ~]
```
Defragment persistent state.
Dill asks the runtime to perform the defragmentation.
#### Returns
Dill does not return a `gift` in response to a `%meld` `task`.
## `%talk`
---
### `%seat`
Install desk.
```hoon
[%talk p=tank]
[%seat =desk]
```
This `task` is not used.
## `%text`
```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).
This indirectly pokes `%hood` with a `%kiln-install` `mark` to install
the specified `desk`. You should just poke `%hood` directly rather than
using this.
#### 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
[%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.
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`
Toggle Arvo verbose mode.
```hoon
[%verb ~]
```
This `task` toggles verbose mode for all of Arvo, which is located here since
Dill is the vane that prints errors. To be precise, `%verb` toggles the laconic
bit `lac` in the [Arvo state](/reference/arvo/overview#the-state) by passing a `%verb` `waif` to Arvo.
This `task` toggles verbose mode for all of Arvo, which is located here
since Dill is the vane that prints errors. To be precise, `%verb`
toggles the laconic bit `lac` in the [Arvo
state](/reference/arvo/overview#the-state) by passing a `%verb` `waif`
to Arvo.
#### Returns
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
[%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:
- `%hush` - Completely silent, print nothing.
- `%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`.
This `task` should be wrapped in a [`%shot`](#shot) `task` to specify
the session. Without the `%shot` wrapper, it will use the default
session (`%$`).
#### 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
### `$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`
```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).
---
### `$cors-registry`
```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.
---
### `$outstanding-connection`
```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.
---
### `$authentication-state`
```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.
---
### `$session`
```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.
---
### `$channel-state`
```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.
---
### `$timer`
```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.
---
### `$channel-event`
```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.
---
### `$channel`
```hoon
+$ channel
$: state=(each timer duct)
next-id=@ud
last-ack=@da
events=(qeu [id=@ud request-id=@ud =channel-event])
unacked=(map @ud @ud)
subscriptions=(map @ud [ship=@p app=term =path duc=duct])
heartbeat=(unit timer)
==
+$ channel
$: mode=?(%json %jam)
state=(each timer duct)
next-id=@ud
last-ack=@da
events=(qeu [id=@ud request-id=@ud =channel-event])
unacked=(map @ud @ud)
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`
@ -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.
---
### `$action`
```hoon
+$ action
$% [%gen =generator] :: dispatch to a generator
[%app app=term] :: dispatch to an application
[%authentication ~] :: internal authentication page
[%logout ~] :: internal logout page
[%channel ~] :: gall channel system
[%scry ~] :: gall scry endpoint
[%four-oh-four ~] :: respond with the default file not found page
==
+$ action
$% [%gen =generator]
[%app app=term]
[%authentication ~]
[%logout ~]
[%channel ~]
[%scry ~]
[%name ~]
[%four-oh-four ~]
==
```
The action to take when a [$binding](#binding) matches an incoming HTTP request.
---
### `$generator`
```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.
---
### `$http-config`
```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`.
---
### `$http-rule`
```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.
---
### `$address`
```hoon
@ -188,6 +252,8 @@ This is for updating the server configuration. In the case of `%cert`, a `cert`
A client IP address.
---
### `$inbound-request`
```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.
---
## 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`.
---
### `$method:http`
```hoon
@ -228,6 +298,8 @@ An ordered list of HTTP headers. The `key` is the header name e.g `'Content-Type
An HTTP method.
---
### `$request:http`
```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.
---
### `$response-header:http`
```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.
---
### `$http-event:http`
```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.
---
### `$simple-payload:http`
```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.
---

View File

@ -3,9 +3,12 @@ title = "Scry Reference"
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.
@ -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.
### Example {% #example-1 %}
#### Example {% #example-1 %}
```
> .^(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`
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={~~http~3a.~2f.~2f.baz~.example}
```
---
## `/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)`.
### Example {% #example-3 %}
#### Example {% #example-3 %}
```
> .^(approved=(set origin:eyre) %ex /=//=/cors/approved)
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'))
@ -63,24 +79,32 @@ The origin URL is a `@t`, but since `@t` may not be valid in a path, it must be
%.n
```
---
## `/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={~~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'))
@ -92,13 +116,18 @@ The origin URL must be a cord-encoded `@t` rather than just the plain `@t`, so y
%.n
```
---
## `/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'))
@ -110,11 +139,27 @@ The cookie must be the full cookie including the `urbauth-{SHIP}=` part. The coo
%.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`
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=)
@ -133,20 +178,29 @@ A scry with `bindings` in place of the `desk` in the `beak` will return Eyre's U
## `%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=)
{}
```
---
## `%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=)
@ -157,11 +211,15 @@ A scry with `authentication-state` in place of the `desk` in the `beak` will ret
}
```
---
## `%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=)
@ -182,13 +240,18 @@ A scry with `channel-state` in place of the `desk` in the `beak` will return det
]
```
---
## `%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=)
[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`.
---
## `%rule`
```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`.
---
## `%request`
```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.
---
## `%request-local`
```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.
---
## `%cancel-request`
```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.
---
## `%connect`
```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.
---
## `%serve`
```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.
---
## `%disconnect`
```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`.
---
## `%code-changed`
```hoon
@ -210,3 +226,47 @@ Eyre returns no `gift` in response to a `%reject-origin` `task`.
#### 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"
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`.
## `bitt`
Incoming subscriptions.
```hoon
+$ 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`
```hoon
+$ boat
%+ map [=wire =ship =term]
[acked=? =path]
```
Outgoing subscriptions.
This is the structure Gall uses to keep track of subscriptions our agent has
initiated. The `wex` field of a [`bowl`](#bowl) contails the `boat` for that
agent.
```hoon
+$ boat (map [=wire =ship =term] [acked=? =path])
```
The `wire` field is the `wire` which [`sign:agent`](#signagent)s will come in on.
The `ship` and `term` fields are the ship and the name of the agent to which our
agent has subscribed.
This is the structure Gall uses to keep track of subscriptions our agent
has initiated. The `wex` field of a [`bowl`](#bowl) contails the `boat`
for that agent.
The `acked` field is `%.y` if they have acknowledged our subscription request,
and `%.n` if they have not. The `path` field is the `path` on the other agent to
which our agent has subscribed.
The `wire` field is the `wire` which [`sign:agent`](#signagent)s will
come in on. The `ship` and `term` fields are the ship and the name of
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`
Additional agent state.
```hoon
+$ bowl :: standard app state
$: $: our=ship :: host
src=ship :: guest
dap=term :: agent
== ::
$: wex=boat :: outgoing subs
sup=bitt :: incoming subs
== ::
$: act=@ud :: change number
eny=@uvJ :: entropy
now=@da :: current time
byk=beak :: load source
== == ::
$: $: our=ship :: host
src=ship :: guest
dap=term :: agent
== ::
$: wex=boat :: outgoing subs
sup=bitt :: incoming subs
$= sky :: scry bindings
%+ map path ::
((mop @ud (pair @da (each page @uvI))) lte) ::
== ::
$: 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
as follows:
@ -68,6 +88,9 @@ as follows:
See the [`boat`](#boat) section for details of the type.
- `sup`: Incoming subscriptions. That is, subscriptions others have made to our
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
processed so far.
- `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
`beak` is a triple of `[ship desk case]`.
---
## `dude`
Agent name.
```hoon
+$ dude term
```
Agent name.
---
## `gill`
A general contact.
```hoon
+$ 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`
Opaque duct - used internally.
```hoon
+$ scar
$: p=@ud
@ -102,40 +150,30 @@ A general contact: A pair of the ship and agent name.
==
```
Opaque duct - used internally.
---
## `suss`
Configuration report.
```hoon
+$ suss (trel dude @tas @da)
```
Configuration report.
---
## `well`
Desk and agent.
```hoon
+$ 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`
An agent task or raw poke.
```hoon
+$ deal
$% [%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.
This is used for messages that come in over the network and would not be used
manually.
The additional `%raw-poke` is for pokes which haven't yet been converted
to an ordinary `%poke` by molding the `noun` with the specified `mark`
core. This structure is passed around on the kernel level, it would not
be used in userspace.
---
## `unto`
An agent gift or a raw fact.
```hoon
+$ unto
$% [%raw-fact =mark =noun]
@ -156,9 +199,24 @@ manually.
==
```
Like a [`sign:agent`](#signagent) but with the additional case of a raw fact.
This is used for messages that come in over the network and would not be used
manually.
The additional `%raw-fact` is for facts which haven't yet been converted
to an ordinary `%fact` by molding the `noun` it with the specified
`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`
@ -169,10 +227,10 @@ manually.
```
Container for Gall agent types. The most significant arm is
[`form:agent`](#formagent), which specifies the structure of the agent itself.
There are also some additional structures defined here, mostly defining the
kinds of messages agents can send. The different arms of the core in `agent`
are considered separately below.
[`form:agent`](#formagent), which specifies the structure of the agent
itself. There are also some additional structures defined here, mostly
defining the kinds of messages agents can send. The different arms of
the core in `agent` are considered separately below.
### `step:agent`
@ -180,8 +238,12 @@ are considered separately below.
+$ step (quip card form)
```
A cell of [`card:agent`](#cardagent)s to be sent and a new agent state. 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.
A cell of [`card:agent`](#cardagent)s to be sent and a new agent state.
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`
@ -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)
```
An effect - typically a message to be sent to another agent or vane. A list of
these are returned by most agent arms along with a new state in a
[`step:agent`](#stepagent). A `wind` is the following:
An effect - typically a message to be sent to another agent or vane. A
list of these are returned by most agent arms along with a new state in
a [`step:agent`](#stepagent). A `wind` is the following:
```hoon
++ wind
@ -207,9 +269,11 @@ Gall will not allow a `%slip`, so in practice a `card` will be one of:
- `[%pass path note]`
- `[%give gift]`
For `%pass`, `p` specifies the `wire` on which a response should be returned.
See [`note:agent`](#noteagent) and [`gift:agent`](#giftagent) below for details of their
types.
For `%pass`, `p` specifies the `wire` on which a response should be
returned. See [`note:agent`](#noteagent) and [`gift:agent`](#giftagent)
below for details of their types.
---
### `note:agent`
@ -218,17 +282,23 @@ types.
$% [%agent [=ship name=term] =task]
[%arvo note-arvo]
[%pyre =tang]
::
[%grow =spur =page]
[%tomb =case =spur]
[%cull =case =spur]
==
```
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
vanes, or for sending out updates to subscribers. The three cases are:
[`gift:agent`](#giftagent), which is the type for responding to other
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
subscription to another agent. The `ship` and `name` fields are the ship and
agent to which the `task` should be sent. The `task` is the request itself,
see [`task:agent`](#taskagent) below for its possible types.
subscription to another agent. The `ship` and `name` fields are the
ship and agent to which the `task` should be sent. The `task` is the
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:
```hoon
+$ note-arvo
@ -246,16 +316,23 @@ vanes, or for sending out updates to subscribers. The three cases are:
[@tas %meta vase]
==
```
You can refer to the `/sys/lull.hoon` source code for all the possible vane
tasks, or see each vane's API Reference section in the [Arvo
You can refer to the `/sys/lull.hoon` source code for all the possible
vane tasks, or see each vane's API Reference section in the [Arvo
documentation](/reference/arvo/overview)
- `%pyre`: This is for aborting side-effects initiated during agent
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).
---
### `task:agent`
The types of messages initiated by our agent and sent to another agent.
```hoon
+$ task
$% [%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
in contrast to [`gift:agent`](#giftagent)s, which are responses to incoming
messages from agents or updates to agents already subscribed. The five kinds
are:
This is in contrast to [`gift:agent`](#giftagent)s, which are responses
to incoming messages from agents or updates to agents already
subscribed. The five kinds of `task:agent` are:
- `%watch`: Subscribe to `path` on the target ship and agent.
- `%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).
---
### `gift:agent`
The types of messages our agent can either send in response to messages
from other agents, or send to subscribed agents.
```hoon
+$ gift
$% [%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
other agents, or send to subscribed agents. This is in contrast to
[`task:agent`](#taskagent)s, which are messages to other agents our agent
initiates rather than sends in response. The four kinds are:
This is in contrast to [`task:agent`](#taskagent)s, which are messages
to other agents our agent initiates rather than sends in response. The
four kinds of `gift:agent` are:
- `%fact`: An update to existing subscribers. The `paths` field specifies which
subscription paths the update should go out to. The `cage` is the data, and is
a `[mark vase]`.
- `%fact`: An update to existing subscribers. The `paths` field
specifies which subscription paths the update should go out to. The
`cage` is the data, and is a `[mark vase]`.
- `%kick`: Kick subscriber, ending their subscription. The `paths` field
specifies which paths the subscriber should be kicked from, and the `ship`
field specifies the ship to kick. If the `ship` field is null, all subscribers
on the specified paths are kicked. Gall will automatically remove the
subscription from our agent's [`bitt`](#bitt) (inbound subscription `map`),
and subscriber will no longer receive updates on the `path`s in question.
- `%watch-ack`: Acknowledge a subscription request. If `p` is null, it's an ack
(positive acknowledgement), and if `p` is non-null, it's a nack (negative
acknowledgement). Simply crashing will caused Gall to nack a subscription
request, and not crashing but not explicitly producing a `%watch-ack` `gift`
will cause Gall to ack a subscription request. Therefore, you'd typically only
explicitly produce a `%watch-ack` `gift` if you wanted to nack a subscription
request with a custom error in the `tang`.
- `%poke-ack`: Acknowledge a poke. If `p` is null, it's an ack, and if `p` is
non-null, it's a nack. Simply crashing will cause Gall to nack 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`.
specifies which paths the subscriber should be kicked from, and the
`ship` field specifies the ship to kick. If the `ship` field is null,
all subscribers on the specified paths are kicked. Gall will
automatically remove the subscription from our agent's [`bitt`](#bitt)
(inbound subscription `map`), and subscriber will no longer receive
updates on the `path`s in question.
- `%watch-ack`: Acknowledge a subscription request. If `p` is null, it's
an ack (positive acknowledgement), and if `p` is non-null, it's a nack
(negative acknowledgement). Simply crashing will caused Gall to nack a
subscription request, and not crashing but not explicitly producing a
`%watch-ack` `gift` will cause Gall to ack a subscription request.
Therefore, you'd typically only explicitly produce a `%watch-ack`
`gift` if you wanted to nack a subscription request with a custom
error in the `tang`.
- `%poke-ack`: Acknowledge a poke. If `p` is null, it's an ack, and if
`p` is non-null, it's a nack. Simply crashing will cause Gall to nack
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`
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
+$ sign
$% [%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
_in_ to our agent from another agent rather than something we send out. The
possible types are:
The possible types are:
- `%poke-ack`: Another agent has acked (positively acknowledged) or nacked
(negatively acknowledged) a `%poke` [`task:agent`](#taskagent) we previously
sent. It's an ack if `p` is null and a nack if `p` is non-null. The `tang`
contains an error or traceback if it's a nack.
- `%poke-ack`: Another agent has acked (positively acknowledged) or
nacked (negatively acknowledged) a `%poke` [`task:agent`](#taskagent)
we previously sent. It's an ack if `p` is null and a nack if `p` is
non-null. The `tang` contains an error or traceback if it's a nack.
- `%watch-ack`: Another agent has acked or nacked a `%watch`
[`task:agent`](#taskagent) (subscription request) we previously sent. It's an
ack if `p` is null and a nack if `p` is non-null. The `tang` contains an error
or traceback if it's a nack. If it's a nack, Gall will automatically remove
the subscription from our agent's [`boat`](#boat) (outbound subscription
map).
- `%fact`: An update from another agent to which we've previously subscribed
with a `%watch` [`task:agent`](#taskagent) (subscription 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
receive updates. A `%kick` may be intentional, but it may also happen due to
certain network conditions or other factors. As a result, it's best to try and
resubscribe with another `%watch` [`task:agent`](#taskagent), and if they
nack the `%watch`, we can conclude it was intentional and give up.
[`task:agent`](#taskagent) (subscription request) we previously sent.
It's an ack if `p` is null and a nack if `p` is non-null. The `tang`
contains an error or traceback if it's a nack. If it's a nack, Gall
will automatically remove the subscription from our agent's
[`boat`](#boat) (outbound subscription map).
- `%fact`: An update from another agent to which we've previously
subscribed with a `%watch` [`task:agent`](#taskagent) (subscription
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 receive updates. A `%kick` may be intentional, but it may
also happen due to certain network conditions or other factors. As a
result, it's best to try and resubscribe with another `%watch`
[`task:agent`](#taskagent), and if they nack the `%watch`, we can
conclude it was intentional and give up.
---
### `form:agent`
This defines the structure of the agent itself.
```hoon
++ 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`
@ -434,16 +528,16 @@ This arm is called when another agent subscribes to our agent.
- Accepts: `path`
- Produces: [`step:agent`](#stepagent)
This arm is called when another agent unsubscribes from a subscription path on
our agent.
This arm is called when another agent unsubscribes from a
subscription path on our agent.
#### `on-peek`
- Accepts: `path`
- Produces: `(unit (unit cage))`
This arm is called when a [scry](/reference/arvo/concepts/scry) is performed on our
agent.
This arm is called when a [scry](/reference/arvo/concepts/scry) is
performed on our agent.
#### `on-agent`
@ -458,7 +552,8 @@ This arm is called when another agent give our agent a
- Accepts: `[wire sign-arvo]`
- 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
+$ 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
vane in the [Arvo documentation](/reference/arvo/overview).
You can refer to the `/sys/lull.hoon` source code, or the API Reference
of each vane in the [Arvo documentation](/reference/arvo/overview).
#### `on-fail`
- Accepts: `[term tang]`
- Produces: [`step:agent`](#stepagent)
This arm is called if certain errors occur in Gall, such as if our agent tries
to create a duplicate subscription.
This arm is called if certain errors occur in Gall, such as if our agent
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
An agent is defined as a [core](/reference/glossary/core/) with a set of [arms](/reference/glossary/arm/) to handle various
events. These handlers usually produce a list of effects and the next
state of the agent. The interface definition can be found in
`sys/lull.hoon`, which at the time of writing is:
An agent is defined as a [core](/reference/glossary/core/) with a set of
[arms](/reference/glossary/arm/) to handle various events. These
handlers usually produce a list of effects and the next state of the
agent. The interface definition can be found in `sys/lull.hoon`, which
at the time of writing is:
```hoon
++ agent
@ -71,8 +72,13 @@ state of the agent. The interface definition can be found in
+$ step (quip card form)
+$ card (wind note gift)
+$ 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
$% [%watch =path]
@ -82,8 +88,8 @@ state of the agent. The interface definition can be found in
[%poke-as =mark =cage]
==
+$ gift
$% [%fact path=(unit path) =cage]
[%kick path=(unit path) ship=(unit ship)]
$% [%fact paths=(list path) =cage]
[%kick paths=(list path) ship=(unit ship)]
[%watch-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]
*(quip card _^|(..on-init))
--
--
```
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
```
## `%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`
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
+++
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
[%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 vanes for the first time. Upon receipt of a `%dawn` `task`, Jael will:
`%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:
- record the Ethereum block the public key is registered to,
- record the URL of the Ethereum node used,
- save the signature of the parent planet (if the ship is a moon),
- 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,
- set Jael to subscribe to `%azimuth-tracker`,
- `%slip` a `%init` `task` to Ames, Clay, Gall, Dill, and Eyre, and `%give` an `%init`
`gift` to Arvo, which then informs Unix that the initialization process has concluded.
- `%slip` a `%init` task to Ames, Clay, Gall, Dill, and Eyre, and
`%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
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
[%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 `%.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.
`%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.
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
Jael `%give`s a `%init` `gift` to Unix.
Jael `%give`s a `%init` gift to Unix.
## `%listen`
---
### `%listen`
Set Ethereum source.
```hoon
[%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
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
[%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
[%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 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.
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.
#### 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
[%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
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
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
[%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` to cancel it.
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.
#### 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
[%private-keys =life vein=(map life ring)]
```
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`.
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
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
[%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 specified in the `ships` field. The subscription will continue until Jael receives a [%nuke](#nuke) `task` to cancel it.
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.
#### 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
[%public-keys =public-keys-result]
```
The [$public-keys-result](/reference/arvo/jael/data-types#public-keys-result) contains whatever changes have occurred.
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.
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`](/reference/arvo/jael/data-types#public-keys-result)
each time a change occurs for any of the `ship`s to which you're
subscribed.
#### 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
[%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 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 `@`.
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 `@`.
#### 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
[%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 `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.
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.
#### Returns
Jael will respond to a `%turf` `task` with a `%turf` `gift`, which looks like:
```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' ~] ~]`.
Jael will respond to a `%turf` task with a [`%turf` gift](#turf-1).
#### 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
[%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` 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.
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.
#### 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
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.
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
ergonomic than Spider's, so is usually preferable.
poke [Spider](/reference/arvo/threads/reference) and subscribe for the result.
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`.
Only `%fard` is currently documented in
the [API Reference](/reference/arvo/khan/tasks) section, and a practical example
is given in the [Example](/reference/arvo/khan/example) section.
There are two `task`s for running threads from userspace:
[`%fard`](/reference/arvo/khan/tasks#fard) and
[`%lard`](/reference/arvo/khan/tasks#lard). The former is for running a thread
from file and the latter is for running an "in-line" thread, where you pass
Khan the thread directly.
## Sections

View File

@ -3,31 +3,101 @@ title = "API Reference"
weight = 2
+++
Khan's external interface is still experimental, so there's only one `task` that
is currently useful:
These are the `task`s Khan can be passed and the `gift`s it can give.
## `%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
[%fard p=(fyrd cage)]
```
Run a thread from within Arvo
`p` contains the thread location, name, and start arguments. See the
[`fyrd`](/reference/arvo/khan/types#fyrd) data type reference entry for details.
#### Returns
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
[%arow p=(avow cage)]
```
`p` either contains the result in a `cage`, or an error and stack trace if it
failed. See the [`avow`](/reference/arvo/khan/types#avow) data type reference
entry for details.
This gift contains the result of a finished thread if successful, or an
error and stack trace if it failed. It's given for threads run from
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
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`
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
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
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.

View File

@ -9,9 +9,17 @@ insert_anchor_links = "right"
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/)

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:
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.
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`.
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.
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)
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,
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`,
where the empty span `//` represents Unix and `term/1` represents the terminal
in Unix. Here we have a `wire` instead of a `duct` (i.e. a list of `wire`s)
since Unix I/O events are always the beginning and end of the Arvo event loop,
thus only a single `wire` is ever required at this initial stage.
where the empty path element `//` represents Unix and `term/1`
represents the terminal in Unix. Here we have a `wire` instead of a
`duct` (i.e. a list of `wire`s) since Unix I/O events are always the
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
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
`_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
[`continuityNumber`](/reference/azimuth/azimuth-eth#points) of `_point`, and usually
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).
### Gall agents
## Gall agents
#### `%azimuth` {% #azimuth %}
### `%azimuth` {% #azimuth %}
`%azimuth`, located at `/app/azimuth.hoon`, is a Gall agent and thread handler
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
`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
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)
#### `%roller` {% #roller %}
### `%roller` {% #roller %}
`%roller`, stored at `/app/roller.hoon`, is a Gall agent responsible for
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
`%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
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
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`,
`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
their own individual Ethereum key-pairs.
### Master Ticket
## 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,
@ -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
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
are on-chain actions described and implemented in
[Ecliptic](/reference/glossary/ecliptic), Azimuth's suite of governing
smart-contracts.
### Proxies
## Proxies
Each permanent Urbit ID can designate one or more
[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
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
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
generated.
### ERC-721
## ERC-721
Most Ethereum tokens use the ERC-20 standard for smart contracts. Urbit
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
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
ship to the address `0x1111111111111111111111111111111111111111`. The easiest
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.
### Dominion
## Dominion
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
@ -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
detail what this means in each case in the following.
### Planets
## Planets
*Permitted dominions:* `%l1`, `%l2`.
#### `%l1` planets
### `%l1` planets
*Permitted layer 2 actions:*
- owner: `%escape`, `%cancel-escape`
@ -54,7 +54,7 @@ sponsorship actions.
Layer 1 planets may also move to dominion `%l2` by depositing their ownership to
the layer 2 deposit address.
#### `%l2` planets
### `%l2` planets
*Permitted layer 2 actions:*
- 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
cannot move to layer 1.
### Stars
## Stars
*Permitted dominions:* `%l1`, `%spawn`, `%l2`.
#### `%l1` stars
### `%l1` stars
*Permitted layer 2 actions:*
- 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
to the layer 2 deposit address. Both actions are irreversible.
#### `%spawn` stars
### `%spawn` stars
*Permitted layer 2 actions:*
- 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
irreversible - the only further change to dominion permitted is moving to `%l2`.
#### `%l2` stars
### `%l2` stars
*Permitted layer 2 actions:*
- 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.
### Galaxies
## Galaxies
*Permitted dominions:* `%l1`, `%spawn`.
#### `%l1` galaxies
### `%l1` galaxies
*Permitted layer 2 actions:*
- 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
on sponsorship status.
#### `%spawn` galaxies
### `%spawn` galaxies
*Permitted layer 2 actions:*
- 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`,
- 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
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"
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.
@ -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.
### 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
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
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
rate at which transactions are submitted and manually submitting batches. These

View File

@ -1,5 +1,5 @@
+++
title = "Kelvin versioning"
title = "Kernel"
[extra]
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
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
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).
### Table of Auras
## Table of Auras
```
Aura Meaning Example Literal Syntax
@ -48,7 +48,7 @@ Aura Meaning Example Literal Syntax
@ux unsigned hexadecimal 0x5f5.e138
```
### Bitwidth
## Bitwidth
Capital letters at the end of auras indicate the bitwidth in binary powers of
two, starting from A.
@ -61,7 +61,7 @@ two, starting from A.
@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
given aura:
@ -94,7 +94,7 @@ This is implicitly done by the irregular form of `^-`.
7.303.014
```
### Bunting
## Bunting
The bunt value for all auras is 0 except for `@da`.

View File

@ -3,6 +3,17 @@ title = "Irregular forms"
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
| Form | Regular Form |
@ -366,6 +377,82 @@ See [%sand](/reference/hoon/rune/constants#warm) for other irregular definitions
"~[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
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.
### 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.
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.
@ -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.
### 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`.
@ -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
through" an overriding label.
### Examples
## Examples
The Dojo prompt gives you a subject with a decent namespace.
Try:

View File

@ -6,15 +6,15 @@ weight = 2
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.
### 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.
### Discussion
## Discussion
Intuitively, Hoon wings are written in the opposite order
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 the same limb as `+`, `..b` is the same wing as `+1.foo`.
### Examples
## Examples
```
~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
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/)
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.
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
- 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
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
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:
@ -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
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
analog of C is not too far off in several ways. Almost all code
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
programming language.
@ -215,7 +215,7 @@ Hoon and Nock have several unusual properties:
calmness of working with such inert building blocks is addictive, as
many Hoon programmers will attest.
### Why is Hoon the way it is?
## Why is Hoon the way it is?
Minimalism, mostly.

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