Merge pull request #347 from urbit/app-workbook-cli

Add CLI apps to App Workbook.
This commit is contained in:
Gordon Cieplak 2023-04-28 14:01:16 -07:00 committed by GitHub
commit f20137b3b3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 378 additions and 20 deletions

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.

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.

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