mirror of
https://github.com/urbit/shrub.git
synced 2024-09-11 20:27:19 +03:00
Implement changes to Counter tutorial (#74)
* update counter example to ensure it compiles * fix formatting * make shrub at a meaningful path * update success case, add todo * modify to use cull instead of tomb, per existing diary.hoon * Add comment on counter's +kids arm * Delete old counter files that snuck back in * Improve pith and card section * Implement TODO about verifying poke worked --------- Co-authored-by: Josh Lehman <josh@urbit.org>
This commit is contained in:
parent
1b6e964700
commit
2fa9eff190
@ -1,21 +0,0 @@
|
|||||||
/@ node :: manx
|
|
||||||
/@ counter-diff :: [%inc ~]
|
|
||||||
:: import /lib/manx-utils, which helps us work with XML
|
|
||||||
/- manx-utils
|
|
||||||
:: declare this is a conversion from node to counter-diff
|
|
||||||
:- [%node %$ %counter-diff]
|
|
||||||
|= =node
|
|
||||||
^- counter-diff
|
|
||||||
:: initiate the manx-utils door with node
|
|
||||||
=/ mu ~(. manx-utils node)
|
|
||||||
::
|
|
||||||
:: got:mu gets an attribute from the manx by its name
|
|
||||||
:: in this case, the =head specified in /con/number-htmx
|
|
||||||
:: we expect the head from the manx to be %inc,
|
|
||||||
:: but we could add more terms to that type union...
|
|
||||||
=/ head (?(%inc) (got:mu %head))
|
|
||||||
::
|
|
||||||
:: return the [%inc ~] poke
|
|
||||||
:: if we wanted to handle multiple pokes,
|
|
||||||
:: we'd switch on the type of head here
|
|
||||||
[head ~]
|
|
@ -1,62 +0,0 @@
|
|||||||
/@ number :: @ud
|
|
||||||
:: import /lib/feather-icons
|
|
||||||
/- feather-icons
|
|
||||||
:: declare that this is a conversion from number to HTMX
|
|
||||||
:- [%number %$ %htmx]
|
|
||||||
::
|
|
||||||
:: this gate accepts a number and a bowl:neo;
|
|
||||||
:: we'll access bowl:neo in the UI to access the
|
|
||||||
:: here.bowl of the shrub that's using this /con file
|
|
||||||
|= =number
|
|
||||||
|= =bowl:neo
|
|
||||||
::
|
|
||||||
:: this gate returns a manx, which is what Hoon uses
|
|
||||||
:: to store dynamic XML nodes; in this case we'll use
|
|
||||||
:: Sail to specify a manx that expects the HTMX library
|
|
||||||
:: to be available on the frontend
|
|
||||||
^- manx
|
|
||||||
::
|
|
||||||
:: open a <div class="p3 fc g2 ac br2">
|
|
||||||
:: these utility classes are specified in feather.css,
|
|
||||||
:: which this /con file expects on the frontend
|
|
||||||
;div.p3.fc.g2.ac.br2
|
|
||||||
:: <h1>Counter</h1>
|
|
||||||
;h1: Counter
|
|
||||||
:: <p>{number}</p>
|
|
||||||
;p: {<number>}
|
|
||||||
:: open a <form> with HTMX attributes
|
|
||||||
;form
|
|
||||||
::
|
|
||||||
:: hx-post will issue a POST request to the provided
|
|
||||||
:: url and swap response into the DOM
|
|
||||||
=hx-post "/neo/hawk{(en-tape:pith:neo here.bowl)}?stud=counter-diff"
|
|
||||||
::
|
|
||||||
:: hx-target specifies the target for hx-post's DOM
|
|
||||||
:: swap: the element with class "loading"
|
|
||||||
=hx-target "find .loading"
|
|
||||||
::
|
|
||||||
:: hx-swap specifies how the response to hx-post's
|
|
||||||
:: request will be swapped in relative to the target
|
|
||||||
=hx-swap "outerHTML"
|
|
||||||
::
|
|
||||||
:: here, the head attribute specifies the poke that
|
|
||||||
:: hx-post will send to the target shrub; look at
|
|
||||||
:: /con/node-counter-diff.hoon for more on =head
|
|
||||||
=head "inc"
|
|
||||||
::
|
|
||||||
:: below, the classes "loaded", "loader", and
|
|
||||||
:: "loading" provide loading spinner behavior on
|
|
||||||
:: sending and receiving this form's POST request
|
|
||||||
::
|
|
||||||
:: <button class="bd1 br1 pr b1 hover loader">
|
|
||||||
;button.bd1.br1.p2.b1.hover.loader
|
|
||||||
:: <span class="loaded">Increment</span>
|
|
||||||
;span.loaded: Increment
|
|
||||||
:: <span class="loading">
|
|
||||||
;span.loading
|
|
||||||
:: import +loading sail from /lib/feather-icons
|
|
||||||
;+ loading.feather-icons
|
|
||||||
== :: </span>
|
|
||||||
== :: </button>
|
|
||||||
== :: </form>
|
|
||||||
== :: </div>
|
|
@ -92,6 +92,9 @@ Here's the same thing in Shrubery.
|
|||||||
++ poke
|
++ poke
|
||||||
^- (set stud:neo)
|
^- (set stud:neo)
|
||||||
(sy %counter-diff ~)
|
(sy %counter-diff ~)
|
||||||
|
++ kids
|
||||||
|
^- kids:neo
|
||||||
|
*kids:neo
|
||||||
++ deps
|
++ deps
|
||||||
^- deps:neo
|
^- deps:neo
|
||||||
*deps:neo
|
*deps:neo
|
||||||
@ -101,11 +104,12 @@ Here's the same thing in Shrubery.
|
|||||||
+* state !<(number state-vase)
|
+* state !<(number state-vase)
|
||||||
++ init
|
++ init
|
||||||
|= old=(unit pail:neo)
|
|= old=(unit pail:neo)
|
||||||
^- ((list card:neo) pail:neo)
|
^- (quip card:neo pail:neo)
|
||||||
[~ (need old)]
|
[~ (need old)]
|
||||||
++ poke
|
++ poke
|
||||||
|= [=stud:neo vaz=vase]
|
|= [=stud:neo vaz=vase]
|
||||||
^- ((list card:neo) pail:neo)
|
^- (quip card:neo pail:neo)
|
||||||
|
?> =(%counter-diff stud)
|
||||||
=/ act
|
=/ act
|
||||||
!<(counter-diff vaz)
|
!<(counter-diff vaz)
|
||||||
?> =(-.act %inc)
|
?> =(-.act %inc)
|
||||||
@ -170,13 +174,13 @@ The `form` is where the Gall agent-like application logic lives. We only need tw
|
|||||||
:: unlike +on-init, potentially accept some injected initial state
|
:: unlike +on-init, potentially accept some injected initial state
|
||||||
++ init
|
++ init
|
||||||
|= old=(unit pail:neo)
|
|= old=(unit pail:neo)
|
||||||
^- ((list card:neo) pail:neo)
|
^- (quip card:neo pail:neo)
|
||||||
!!
|
!!
|
||||||
::
|
::
|
||||||
:: like +on-poke, run some logic when this shrub is poked
|
:: like +on-poke, run some logic when this shrub is poked
|
||||||
++ poke
|
++ poke
|
||||||
|= [=stud:neo vaz=vase]
|
|= [=stud:neo vaz=vase]
|
||||||
^- ((list card:neo) pail:neo)
|
^- (quip card:neo pail:neo)
|
||||||
!!
|
!!
|
||||||
--
|
--
|
||||||
```
|
```
|
||||||
@ -220,6 +224,12 @@ There are lots of new types here which are flagged with the `:neo` suffix in cod
|
|||||||
^- (set stud:neo)
|
^- (set stud:neo)
|
||||||
(sy %counter-diff ~)
|
(sy %counter-diff ~)
|
||||||
::
|
::
|
||||||
|
:: counter does not constrain the state/pokes of shrubs
|
||||||
|
:: underneath it in the namespace
|
||||||
|
++ kids
|
||||||
|
^- kids:neo
|
||||||
|
*kids:neo
|
||||||
|
::
|
||||||
:: counter has no dependencies
|
:: counter has no dependencies
|
||||||
++ deps
|
++ deps
|
||||||
^- deps:neo
|
^- deps:neo
|
||||||
@ -229,8 +239,8 @@ There are lots of new types here which are flagged with the `:neo` suffix in cod
|
|||||||
++ form
|
++ form
|
||||||
^- form:neo
|
^- form:neo
|
||||||
::
|
::
|
||||||
:: the sample is populated with context like bowl, version number, and
|
:: the sample is populated with context like bowl,
|
||||||
:: counter's current state
|
:: version number, and counter's current state
|
||||||
|_ [=bowl:neo =aeon:neo =stud:neo state-vase=vase]
|
|_ [=bowl:neo =aeon:neo =stud:neo state-vase=vase]
|
||||||
::
|
::
|
||||||
:: de-vase counter's state
|
:: de-vase counter's state
|
||||||
@ -243,13 +253,16 @@ There are lots of new types here which are flagged with the `:neo` suffix in cod
|
|||||||
:: pail:neo is a (pair stud:neo vase),
|
:: pail:neo is a (pair stud:neo vase),
|
||||||
:: like a cell of a mark and data
|
:: like a cell of a mark and data
|
||||||
|= old=(unit pail:neo)
|
|= old=(unit pail:neo)
|
||||||
^- ((list card:neo) pail:neo)
|
^- (quip card:neo pail:neo)
|
||||||
[~ (need old)]
|
[~ (need old)]
|
||||||
::
|
::
|
||||||
:: +poke, like +on-poke
|
:: +poke, like +on-poke
|
||||||
++ poke
|
++ poke
|
||||||
|= [=stud:neo vaz=vase]
|
|= [=stud:neo vaz=vase]
|
||||||
^- ((list card:neo) pail:neo)
|
^- (quip card:neo pail:neo)
|
||||||
|
::
|
||||||
|
:: assert that the poke's stud is %counter-diff
|
||||||
|
?> =(%counter-diff stud)
|
||||||
::
|
::
|
||||||
:: de-vase the poke
|
:: de-vase the poke
|
||||||
=/ act
|
=/ act
|
||||||
@ -269,16 +282,17 @@ Once you've saved `/imp/counter.hoon` and the `/pro` files, run `|commit %base`
|
|||||||
|
|
||||||
## Poking the shrub
|
## Poking the shrub
|
||||||
|
|
||||||
A `card:neo` is a `(pair pith note)`.
|
A `card:neo` is a `(pair pith note:neo)`. Data in Shrubbery is stored at its `pith`.
|
||||||
|
|
||||||
A `pith` is a `(list iota)`, and an `iota` is either a `term` or a head-tagged noun. For instance:
|
A `pith` is a `(list iota)`, and an `iota` is either a `term` or a head-tagged noun.
|
||||||
* `/examples/counter/one` would be represented as `~[%examples %counter %one]`.
|
|
||||||
* `/~sampel/examples/counter/one` would be represented as `~[[%p ~sampel] %examples %counter %one]`.
|
|
||||||
* `/~sampel/examples/counter/1` would be represented as `~[[%p ~sampel] %examples %counter [%ud 1]]`.
|
|
||||||
|
|
||||||
(You might also see a `pith` written in this irregular form `#/[p/our.bowl]/examples/counter/one`.)
|
For instance:
|
||||||
|
|
||||||
Data in Shrubbery is stored by `pith`.
|
- `/path/to/counter/one` would be represented as `~[%path %to %counter %one]`.
|
||||||
|
- `/~sampel/path/to/counter/one` would be represented as `~[[%p ~sampel] %path %to %counter %one]`.
|
||||||
|
- `/~sampel/path/to/counter/1` would be represented as `~[[%p ~sampel] %path %to %counter [%ud 1]]`.
|
||||||
|
|
||||||
|
(You might also see a `pith` written in this irregular form `#/[p/our.bowl]/path/to/counter/one`.)
|
||||||
|
|
||||||
A `note` is one of the four types of command any shrub will accept.
|
A `note` is one of the four types of command any shrub will accept.
|
||||||
|
|
||||||
@ -291,20 +305,22 @@ A `note` is one of the four types of command any shrub will accept.
|
|||||||
==
|
==
|
||||||
```
|
```
|
||||||
|
|
||||||
Let’s `%make` a shrub at path `/foo/bar` from the Dojo, giving it an initial state of `0`. We’ll explain the structure of the `%make` note in more detail in the Diary tutorial.
|
Let’s `%make` a shrub at an arbitrary path `/path/to/counter` from the Dojo, giving it an initial state of `0`. We’ll explain the structure of the `%make` note in more detail in the Diary tutorial.
|
||||||
|
|
||||||
```
|
```
|
||||||
:neo &neo-card [~[[%p our] %foo %bar] [%make %counter `[%number !>(0)] ~]]
|
:neo &neo-card [~[[%p our] %path %to %counter] [%make %counter `[%number !>(0)] ~]]
|
||||||
```
|
```
|
||||||
|
|
||||||
You should see `>> %make /foo/bar` in the Dojo if successful.
|
You should see `>> /~zod/~/sys -> /~zod/path/to/counter: make` in the Dojo if successful.
|
||||||
|
|
||||||
Now we can now send a `%poke` to the counter shrub at this path.
|
Now we can now send a `%poke` to the counter shrub at this path.
|
||||||
|
|
||||||
```
|
```
|
||||||
:neo &neo-card [~[[%p our] %foo %bar] [%poke [%counter-diff !>([%inc ~])]]]
|
:neo &neo-card [~[[%p our] %path %to %counter] [%poke [%counter-diff !>([%inc ~])]]]
|
||||||
```
|
```
|
||||||
|
|
||||||
|
You'll know the poke worked if you see `>> /~zod/~/sys -> /~zod/counter: %poke %counter-diff` in the Dojo. In the next section, we'll build a simple frontend that will enable us to view and modify the state of the shrub.
|
||||||
|
|
||||||
## Counter frontend in Sky
|
## Counter frontend in Sky
|
||||||
|
|
||||||
Shrubbery aims to be interface-agnostic. One part of that vision is `/con` files, which make it possible to convert data from one type to another. Here are Counter’s `/con` files.
|
Shrubbery aims to be interface-agnostic. One part of that vision is `/con` files, which make it possible to convert data from one type to another. Here are Counter’s `/con` files.
|
||||||
|
@ -39,13 +39,13 @@ Diary is an app that lets you write timestamped diary entries that the frontend
|
|||||||
~[[(welp here.bowl ~[da/id.act]) [%make %txt `txt/!>(txt.act) ~]]]
|
~[[(welp here.bowl ~[da/id.act]) [%make %txt `txt/!>(txt.act) ~]]]
|
||||||
%del-entry
|
%del-entry
|
||||||
:_ diary/!>(state)
|
:_ diary/!>(state)
|
||||||
~[[(welp here.bowl ~[da/id.act]) [%tomb ~]]]
|
~[[(welp here.bowl ~[da/id.act]) [%cull ~]]]
|
||||||
==
|
==
|
||||||
--
|
--
|
||||||
--
|
--
|
||||||
```
|
```
|
||||||
|
|
||||||
Most of this should be legible after the first tutorial. The only new ideas are the `+kids` arm, the use of `bowl`, and `%tomb`.
|
Most of this should be legible after the first tutorial. The only new ideas are the `+kids` arm, the use of `bowl`, and `%cull`.
|
||||||
|
|
||||||
## +kids
|
## +kids
|
||||||
|
|
||||||
@ -115,9 +115,9 @@ Notice that the `src` in `bowl:neo` differs from `bowl:gall`. Here’s the new t
|
|||||||
==
|
==
|
||||||
```
|
```
|
||||||
|
|
||||||
## Generating cards, tombstoning shrubs
|
## Generating cards, culling shrubs
|
||||||
|
|
||||||
We covered `card:neo` in the Counter tutorial, but this is the first time we’re seeing one generated within a shrub. Diary takes two pokes: `%put-entry`, to create a new diary entry, and `%del-entry` to tombstone one.
|
We covered `card:neo` in the Counter tutorial, but this is the first time we’re seeing one generated within a shrub. Diary takes two pokes: `%put-entry`, to create a new diary entry, and `%del-entry` to cull one.
|
||||||
|
|
||||||
Here’s the `+poke` arm of the Diary shrub, expanded with comments.
|
Here’s the `+poke` arm of the Diary shrub, expanded with comments.
|
||||||
|
|
||||||
@ -177,14 +177,14 @@ Here’s the `+poke` arm of the Diary shrub, expanded with comments.
|
|||||||
:: return unchanged state
|
:: return unchanged state
|
||||||
:_ [%diary !>(state)]
|
:_ [%diary !>(state)]
|
||||||
::
|
::
|
||||||
:: send a %tomb note to /path/to/diary/<id>
|
:: send a %cull note to /path/to/diary/<id>
|
||||||
:: this will tombstone the diary entry,
|
:: this will cull the diary entry,
|
||||||
:: effectively deleting it from the namespace
|
:: effectively deleting it from the namespace
|
||||||
:~ :- %+ welp
|
:~ :- %+ welp
|
||||||
here.bowl
|
here.bowl
|
||||||
~[[%da id.act]]
|
~[[%da id.act]]
|
||||||
^- note:neo
|
^- note:neo
|
||||||
[%tomb ~]
|
[%cull ~]
|
||||||
==
|
==
|
||||||
==
|
==
|
||||||
```
|
```
|
||||||
@ -264,7 +264,7 @@ Like Counter, the Diary shrub just has two `/con` files to convert to and from a
|
|||||||
==
|
==
|
||||||
==
|
==
|
||||||
;button.p2.br1.fr.g2.b1.hover.fc.ac.jc.loader
|
;button.p2.br1.fr.g2.b1.hover.fc.ac.jc.loader
|
||||||
=onclick "alert('not yet implemented. no tombstoning?')"
|
=onclick "alert('not yet implemented. no culling?')"
|
||||||
;span.loaded
|
;span.loaded
|
||||||
;+ close.feather-icons
|
;+ close.feather-icons
|
||||||
==
|
==
|
||||||
|
@ -1,80 +0,0 @@
|
|||||||
/@ number :: @ud
|
|
||||||
/@ counter-diff :: [%inc ~]
|
|
||||||
::
|
|
||||||
:: outer core of a shrub: define state, pokes,
|
|
||||||
:: dependencies, and kids
|
|
||||||
^- kook:neo
|
|
||||||
|%
|
|
||||||
::
|
|
||||||
:: the state of the counter is a %number, just a @ud
|
|
||||||
:: a curb:neo is a constraint imposed on a shrub
|
|
||||||
++ state
|
|
||||||
^- curb:neo
|
|
||||||
[%pro %number]
|
|
||||||
::
|
|
||||||
:: the set of pokes that counter
|
|
||||||
:: takes only contains %counter-diff
|
|
||||||
++ poke
|
|
||||||
^- (set stud:neo)
|
|
||||||
(sy %counter-diff ~)
|
|
||||||
::
|
|
||||||
::
|
|
||||||
:: counter does not constrain the type and behaviour of
|
|
||||||
:: its children; any shrub can be made below this shrub
|
|
||||||
:: in the tree, they can have any state, kids, or pokes
|
|
||||||
++ kids
|
|
||||||
^- kids:neo
|
|
||||||
*kids:neo
|
|
||||||
::
|
|
||||||
:: counter has no other shrubs as dependencies
|
|
||||||
++ deps
|
|
||||||
^- deps:neo
|
|
||||||
*deps:neo
|
|
||||||
::
|
|
||||||
:: inner core of a shrub: business logic
|
|
||||||
++ form
|
|
||||||
^- form:neo
|
|
||||||
:: treat this door's sample as boilerplate
|
|
||||||
|_ [=bowl:neo =aeon:neo =pail:neo]
|
|
||||||
::
|
|
||||||
:: +init, like +on-init
|
|
||||||
++ init
|
|
||||||
::
|
|
||||||
:: minimal +init, just returns the
|
|
||||||
:: initial state passed in on %make
|
|
||||||
|= old=(unit pail:neo)
|
|
||||||
^- (quip card:neo pail:neo)
|
|
||||||
[~ (need old)]
|
|
||||||
::
|
|
||||||
:: +poke, like +on-poke
|
|
||||||
++ poke
|
|
||||||
::
|
|
||||||
:: a stud (e.g. %number or %counter-diff) is
|
|
||||||
:: like a mark
|
|
||||||
|= [=stud:neo vaz=vase]
|
|
||||||
::
|
|
||||||
:: return a (list card:neo) and a
|
|
||||||
:: pail, which is a (pair stud vase)
|
|
||||||
^- (quip card:neo pail:neo)
|
|
||||||
::
|
|
||||||
:: assert the stud of the pail (pair stud vase),
|
|
||||||
:: which is the shrub's state given in the sample
|
|
||||||
:: (technically unnecessary in this case, but good
|
|
||||||
:: hygiene)
|
|
||||||
?> =(p.pail %number)
|
|
||||||
::
|
|
||||||
:: de-vase the payload's vase through the number
|
|
||||||
:: type; note the difference between the number
|
|
||||||
:: type and the %number stud
|
|
||||||
=/ state !<(number q.pail)
|
|
||||||
::
|
|
||||||
:: assert that the poke's stud is %counter-diff
|
|
||||||
?> =(%counter-diff stud)
|
|
||||||
=/ act
|
|
||||||
!<(counter-diff vaz)
|
|
||||||
?> =(-.act %inc)
|
|
||||||
::
|
|
||||||
:: return no cards and a pail
|
|
||||||
[~ [%number !>(+(state))]]
|
|
||||||
--
|
|
||||||
--
|
|
@ -1 +0,0 @@
|
|||||||
,[%inc ~]
|
|
@ -1 +0,0 @@
|
|||||||
,@ud
|
|
Loading…
Reference in New Issue
Block a user