on 3f88acd help sign in
urbit.org/tutorials/hoon/cli-tutorial.md
417 lines 16 kB history raw
1+++
2title = "CLI apps"
3weight = 2
4template = "doc.html"
5+++
7## Introduction
9In this walkthrough we will go in-depth on how to build command line interface (CLI)
10applications in Urbit using the `shoe` library.
12There are three CLI apps that currently ship with urbit - `%dojo`, `%chat-cli`,
13and `%shoe`. You should be familiar with the former two, the latter is an example
14app that shows off how the `shoe` library works that we will be looking at
15closely. These are all Gall apps, and as such their source can be found in the `app/` folder of your `%home` desk.
17In [the `shoe` library](#the-shoe-library) we take a closer look at the `shoe` library and its
18cores and how they are utilized in CLI apps. Then in [the `sole`
19library](#the-sole-library) we look at what `shoe` effects ultimately break down
20into. Finally in [`%shoe` app walkthrough](#shoe-app-walkthrough) we explore
21the functionality of the `%shoe` app and then go through the code line-by-line.
23This tutorial can be
24considered to be an application equivalent of the [Hoon school
25lesson](@/docs/tutorials/hoon/hoon-school/generators.md#ask) on `sole` and `%ask`
26generators, which only covers the bare minimum necessary to write generators
27that take user input.
30## The `shoe` library {#the-shoe-library}
32Here we describe how sessions are identified, the specialized `card`s that Gall agents
33with the `shoe` library are able to utilize, and the different cores of `/lib/shoe.hoon` and their purpose.
35### Session identifiers
37An app using the `shoe` library will automatically track sessions by their `sole-id=@ta`.
38These are opaque identifiers generated by the connecting client. An app using the `shoe`
39library may be connected to by a local or remote ship in order to send commands,
40and each of these connections is assigned a unique `@ta` that identifies the
41ship and which session on that ship if there are multiple.
43### `%shoe` `card`s
45Gall agents with the `shoe` library are able to utilize `%shoe` `card`s. These
46additions to the standard set of `cards` have the following shape:
47```hoon
48[%shoe sole-ids=(list @ta) effect=shoe-effect]`
49```
50`sole-ids` is the `list` of session ids that the following `effect` is
51emitted to. An empty `sole-ids` sends the effect to all connected sessions.
52`shoe-effect`s, for now, are always of the shape `[%sole effect=sole-effect]`, where
53`sole-effect`s are basic console events such as displaying text, changing the
54prompt, beeping, etc. These are described in the section on the [`sole` library](#the-sole-library).
56For example, a `%shoe` `card` that causes all connected sessions to beep would
57be `[%shoe ~ %sole %bel ~]`.
59### `shoe` core
61An iron (contravariant) door that defines an interface for Gall agents utilizing
62the `shoe` library. Use this core whenever you want to receive input from the user and run a command. The input will get
63put through the parser (`+command-parser`) and results in a noun of
64`command-type` that the underlying application specifies, which shoe then feeds back into the underlying app as an `+on-command` callback.
66In addition to the ten arms that all Gall core apps possess, `+shoe` defines and expects a few
67more, tailored to common CLI logic. Thus you will need to wrap the `shoe:shoe` core using the `agent:shoe` function to obtain a standard
6810-arm Gall agent core. See the [shoe example app
69walkthrough](#shoe-example-app-walkthrough) for how to do this.
71The additional arms are described below. The Hoon code shows their expected type signature. As we'll see [later](#shoe-app-walkthrough), the `command-type` can differ per application. Note also that most of these take a session identifier as an argument. This lets applications provide different users (at potentially different "places" within the application) with different affordances.
73#### `+command-parser`
75```hoon
76 ++ command-parser
77 |~ sole-id=@ta
78 |~(nail *(like [? command-type]))
79```
81Input parser for a specific command-line session. Will be run on whatever the user tries to input into the command prompt, and won't let them type anything that doesn't parse. If the head of the result is true,
82instantly run the command. If it's false, require the user to press return.
84#### `+tab-list`
86```hoon
87 ++ tab-list
88 |~ sole-id=@ta
89 *(list (option:auto tank))
90```
92Autocomplete options for the command-line session (to match `+command-parser`).
94#### `+on-command`
96```hoon
97 ++ on-command
98 |~ [sole-id=@ta command=command-type]
99 *(quip card _this)
100```
102Called when a valid command is run.
104#### `+can-connect`
106```hoon
107 ++ can-connect
108 |~ sole-id=@ta
109 *?
110```
112Called to determine whether a session may be opened or connected to. For example, you
113may only want the local ship to be able to connect.
115#### `+on-connect`
117```hoon
118 ++ on-connect
119 |~ sole-id=@ta
120 *(quip card _^|(..on-init))
121```
123Called when a session is opened or connected to.
125#### `+on-disconnect`
127```hoon
128 ++ on-disconnect
129 |~ sole-id=@ta
130 *(quip card _^|(..on-init))
131```
133Called when a previously made session gets disconnected from.
135### `default` core
137This core contains the bare minimum implementation of the additional `shoe` arms
138beyond the 10 standard Gall app ams. It is used
139analogously to how the `default-agent` core is used for regular Gall apps.
141### `agent` core
143This is a function for wrapping a `shoe` core, which has too many
144arms to be a valid Gall agent core. This turns it into a standard Gall agent core by
145integrating the additional arms into the standard ones.
148## The `sole` library {#the-sole-library}
150`shoe` apps may create specialized `card`s of the `[%shoe
151(list @ta) shoe-effect]` shape, where `shoe-effect` currently just wrap `sole-effect`s, i.e. instructions for displaying text and producing other effects in the console.
153The list of possible `sole-effects` can be found in `/sur/sole.hoon`. A few
154commonly used ones are as follows.
155- `[%txt tape]` is used to display a line of text.
156- `[%bel ~]` is used to emit a beep.
157- `[%pro sole-prompt]` is used to set the prompt.
158- `[%mor (list sole-effect)]` is used to emit multiple effects.
160For example, a `sole-effect` that beeps and displays `This is some text.` would
161be structured as
162```hoon
163[%mor [%txt "This is some text."] [%bel ~] ~]
164```
166## `%shoe` app walkthrough {#shoe-app-walkthrough}
168Here we explore the capabilities of the `%shoe` example app and then go through
169the code, explaining what each line does.
171### Playing with `%shoe`
173First let's test the functionality of `%shoe` so we know what we're getting
174into.
176Start two fake ships, one named `~zod` and the other can have any name - we will
177go with `~nus`. Fake ships run locally are able to see each other, and our
178intention is to connect their `%shoe` apps.
180On each fake ship start `%shoe` by entering `|start %shoe` into dojo. This will
181automatically
182change the prompt to `~zod:shoe>` and `~nus:shoe>`. Type `demo` and watch the following appear:
183```
184~zod ran the command
185~zod:shoe>
186```
187`~zod ran the command` should be displayed in bold green text, signifying that
188the command originated locally.
190Now we will connect the sessions. Switch `~zod` back to dojo with `Ctrl-X` and enter `|link
191~nus %shoe`. If this succeeds you will see the following.
192```
193>=
194; ~nus is your neighbor
195[linked to [p=~nus q=%shoe]]
196```
197Now `~zod` will have two `%shoe` sessions running - one local one on `~zod` and
198one remote one on `~nus`, which you can access by pressing `Ctrl-X` until you see
199`~nus:shoe>` from `~zod`'s console. On the other hand, you should not see
200`~zod:shoe>` on `~nus`'s side, since you have not connected `~nus` to `~zod`'s
201`%shoe` app. When you enter `demo` from `~nus:shoe>` on
202`~zod`'s console you will again see `~zod ran the command`, but this time it
203should be in the ordinary font used by the console, signifying that the command
204is originating from a remote session. Contrast this with entering `demo` from
205`~nus:shoe>` in `~nus`'s console, which will display `~nus ran the command` in
206bold green text.
208Now try to link to `~zod`'s `%shoe` session from `~nus` by switching to the dojo
209on `~nus` and entering `|link ~zod %shoe`. You should see
210```
211>=
212[unlinked from [p=~zod q=%shoe]]
213```
214and if you press `Ctrl-X` you will not get a `~zod:shoe>` prompt. This is
215because the example app is set up to always allow `~zod` to connect (as well as
216subject moons if the ship happens to be a planet) but not `~nus`, so this
217message means that `~nus` failed to connect to `~zod`'s `%shoe` session.
219### `%shoe`'s code
221```hoon
222:: shoe: example usage of /lib/shoe
223::
224:: the app supports one command: "demo".
225:: running this command renders some text on all sole clients.
226::
227/+ shoe, verb, dbug, default-agent
228```
230`/+` is the Ford rune which imports libraries from the `/lib` directory into
231the subject.
232 * `shoe` is the `shoe` library.
233 * `verb` is a library used to print what a Gall agent is doing.
234 * `dbug` is a library of debugging tools.
235 * `default-agent` contains a Gall agent core with minimal implementations of
236 required Gall arms.
238```hoon
239|%
240+$ state-0 [%0 ~]
241+$ command ~
242::
243+$ card card:shoe
244--
245```
246The types used by the app.
248`state-0` stores the state of the app, which is null as there is no state to
249keep track of. It is good practice to include a version number anyways,
250in case the app is made stateful at a later time.
252`command` is typically a set of tagged union types that represent the possible
253commands that can be entered by the user. Since this app only supports one
254command, it is unnecessary for it to have any associated data, thus the command
255is represented by `~`.
257In a non-trivial context, a `command` is commonly given by `[%name
258data]`, where `%name` is the identifier for the type of command and `data` is
259a type or list of types that contain data needed to execute the command. See
260`app/chat-cli.hoon` for examples of commands, such as `[%say letter:store]` and
261`[%delete path]`. This is not required though, and you could use something like
262`[chat-room=@t =action]`.
264`card` is either an ordinary Gall agent `card` or a `%shoe` `card`, which takes
265the shape `[%shoe sole-ids=(list @ta) effect=shoe-effect]`. A `%shoe` `card` is
266sent to all `sole`s listed in `sole-ids`, imaking them run the `sole-effect`
267specified by `effect` (i.e. printing some text). Here we can
268reference `card:shoe` because of `/+ shoe` at the beginning of the app.
270```hoon
271=| state-0
272=* state -
273::
274```
275Add the bunt value of `state-0` to the head of the subject, then give it the
276macro `state`. The `-` here is a lark expression referring to the head of the
277subject. This allows us to use `state` to refer to the state elsewhere in the
278code no matter what version we're using, while also getting direct access to the
279contents of `state` (if it had any).
281```hoon
282%+ verb |
283%- agent:dbug
284^- agent:gall
285%- (agent:shoe command)
286^- (shoe:shoe command)
287```
289The casts here are just reminders of what is being produced. So let's focus on
290what the `%` runes are doing, from bottom to top. We call `(agent:shoe command)`
291on what follows (i.e. the rest of the app),
292producing a standard Gall agent core. Then we call wrap the Gall agent core with
293`agent:dbug`, endowing it with additional arms useful for debugging, and then
294wrap again with `verb`.
297```hoon
298|_ =bowl:gall
299+* this .
300 def ~(. (default-agent this %|) bowl)
301 des ~(. (default:shoe this command) bowl)
302```
303This is boilerplate Gall agent core code. We set `this` to be a macro for the
304subject, which is the Gall agent core itself. We set `def` and `des` to be
305macros for initialized `default-agent` and `default:shoe` doors respectively.
307Next we implement all of the arms required for a `shoe` agent. Starting with
308the standard Gall arms:
310```hoon
311++ on-init on-init:def
312++ on-save !>(state)
313++ on-load
314 |= old=vase
315 ^- (quip card _this)
316 [~ this]
317::
318++ on-poke on-poke:def
319++ on-watch on-watch:def
320++ on-leave on-leave:def
321++ on-peek on-peek:def
322++ on-agent on-agent:def
323++ on-arvo on-arvo:def
324++ on-fail on-fail:def
325```
326These are minimalist Gall app arm implementations using the default behavior found in `def`.
328Here begins the implementation of the additional arms required by the
329`(shoe:shoe command)` interface.
330```hoon
331++ command-parser
332 |= sole-id=@ta
333 ^+ |~(nail *(like [? command]))
334 (cold [& ~] (jest 'demo'))
335```
336`+command-parser` is of central importance - it is what is used to parse user
337input and transform it into `command`s for the app to execute. Writing a proper
338command parser requires understanding of the Hoon parsing functions found in the
339standard library. How to do so may be found in the [parsing tutorial](@/docs/tutorials/hoon/parsing.md). For now, it is sufficient to know that this arm matches the text "demo" and
340produces a `[? command]`-shaped noun in response. Note how the `&` signifies that the command will be run as soon as it has been entered, without waiting for the user to press return.
343```hoon
344++ tab-list
345 |= sole-id=@ta
346 ^- (list [@t tank])
347 :~ ['demo' leaf+"run example command"]
348 ==
349```
350`+tab-list` is pretty much plug-n-play. For each command you want to be tab
351completed, add an entry to the `list` begun by `:~` of the form `[%command
352leaf+"description"]`. Now whenever the user types a partial command and presses
353tab, the console will display the list of commmands that match the partial
354command as well as the descriptions given here.
356Thus here we have that starting to type `demo` and pressing tab will result in
357the following output in the console:
358```
359demo run example command
360~zod:shoe> demo
361```
362with the remainder of `demo` now added to the input line.
364Next we have `+on-command`, which is called whenever `+command-parser`
365recognizes that `demo` has been entered by a user.
366```hoon
367++ on-command
368 |= [sole-id=@ta =command]
369 ^- (quip card _this)
370```
371This is a gate that takes in the `sole-id` corresponding to the session and the
372`command` noun parsed by `+command-parser` and returns a `list` of `card`s and
373`_this`, which is our shoe agent core including its state.
374```hoon
375 =- [[%shoe ~ %sole -]~ this]
376```
377This creates a cell of a `%shoe` card that triggers a `sole-effect` given by the head of
378the subject `-`, then the Gall agent core `this` - i.e. the return result of
379this gate. The use of the `=-` rune means that what follows this
380expression is actually run first, which puts the desired `sole-effect` into the
381head of the subject.
382```hoon
383 =/ =tape "{(scow %p src.bowl)} ran the command"
384```
385We define the `tape` that we want to be printed.
386```hoon
387 ?. =(src our):bowl
388 [%txt tape]
389 [%klr [[`%br ~ `%g] [(crip tape)]~]~]
390```
391We cannot just produce the `tape` we want printed, - it needs to fit the
392`sole-effect` type. This tells us that if the
393origin of the command is not our ship to just print it normally with the `%txt`
394`sole-effect`. Otherwise we use `%klr`, which prints it stylistically (here it
395makes the text green and bold).
397The following allows either `~zod`, or the host ship and its moons, to connect to
398this app's command line interface using `|link`.
399```hoon
400++ can-connect
401 |= sole-id=@ta
402 ^- ?
403 ?| =(~zod src.bowl)
404 (team:title [our src]:bowl)
405 ==
406```
408We use the minimal implementations for the final two `shoe` arms, since we don't
409want to do anything special when users connect or disconnect.
410```hoon
411++ on-connect on-connect:des
412++ on-disconnect on-disconnect:des
413--
414```
416This concludes our review of the code of the `%shoe` app. To continue learning
417how to build your own CLI app, we recommend checking out `/app/chat-cli.hoon`.